Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
cfcc48259c | |||
785ce7a8ab | |||
ad5de216b0 | |||
26b80d6af7 | |||
a8623d8066 | |||
86ab9f72a5 | |||
b3892dab8d | |||
57a5d034dd | |||
cee9569581 | |||
159429da6e | |||
a292cb0b4b | |||
d70985d8d2 | |||
484f95f5d2 | |||
6e0553af9b | |||
cb18d3d765 | |||
f316f38ae5 | |||
5f07cb374b | |||
96d31e07c3 | |||
99a5efe36c | |||
5c46ecc0ed | |||
cf93b68816 | |||
457421b8d6 | |||
d36ea9539a | |||
5a5337dc63 | |||
443081cc28 | |||
ac8503f8c8 | |||
1cc1fd0a5a | |||
34314aa4ca | |||
0d8dcf4829 | |||
47c6d0dd62 | |||
84937e3eec | |||
303e270b56 | |||
bb1ada6e14 | |||
4a422cc796 | |||
be0f244c02 | |||
78a8dc8458 | |||
38062af889 | |||
f2eadf5441 | |||
a42931384f | |||
8116ce697b | |||
4964b86d67 | |||
2b331e7655 | |||
c1468b688e | |||
4f7837c88e | |||
fd8e06f1dd | |||
b01a351eaa | |||
604655c02d | |||
6603ac4389 | |||
cca6f952ee | |||
df94a6322d | |||
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 |
36
.github/workflows/ci.yaml
vendored
36
.github/workflows/ci.yaml
vendored
@ -22,7 +22,6 @@ jobs:
|
|||||||
PACKAGE_MANAGER=yarn
|
PACKAGE_MANAGER=yarn
|
||||||
fi
|
fi
|
||||||
$PACKAGE_MANAGER run format:check
|
$PACKAGE_MANAGER run format:check
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: macos-10.15
|
runs-on: macos-10.15
|
||||||
needs: test_formatting
|
needs: test_formatting
|
||||||
@ -33,7 +32,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Tell if project is using npm or yarn
|
- name: Tell if project is using npm or yarn
|
||||||
id: step1
|
id: step1
|
||||||
uses: garronej/github_actions_toolkit@v2.2
|
uses: garronej/ts-ci@v1.1.3
|
||||||
with:
|
with:
|
||||||
action_name: tell_if_project_uses_npm_or_yarn
|
action_name: tell_if_project_uses_npm_or_yarn
|
||||||
- uses: actions/checkout@v2.3.4
|
- uses: actions/checkout@v2.3.4
|
||||||
@ -51,28 +50,35 @@ jobs:
|
|||||||
npm test
|
npm test
|
||||||
check_if_version_upgraded:
|
check_if_version_upgraded:
|
||||||
name: Check if version upgrade
|
name: Check if version upgrade
|
||||||
if: github.event_name == 'push'
|
# We run this only if it's a push on the default branch or if it's a PR from a
|
||||||
|
# branch (meaning not a PR from a fork). It would be more straightforward to test if secrets.NPM_TOKEN is
|
||||||
|
# defined but GitHub Action don't allow it yet.
|
||||||
|
if: |
|
||||||
|
github.event_name == 'push' ||
|
||||||
|
github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: test
|
needs: test
|
||||||
outputs:
|
outputs:
|
||||||
from_version: ${{ steps.step1.outputs.from_version }}
|
from_version: ${{ steps.step1.outputs.from_version }}
|
||||||
to_version: ${{ steps.step1.outputs.to_version }}
|
to_version: ${{ steps.step1.outputs.to_version }}
|
||||||
is_upgraded_version: ${{steps.step1.outputs.is_upgraded_version }}
|
is_upgraded_version: ${{ steps.step1.outputs.is_upgraded_version }}
|
||||||
|
is_release_beta: ${{steps.step1.outputs.is_release_beta }}
|
||||||
steps:
|
steps:
|
||||||
- uses: garronej/github_actions_toolkit@v2.2
|
- uses: garronej/ts-ci@v1.1.3
|
||||||
id: step1
|
id: step1
|
||||||
with:
|
with:
|
||||||
action_name: is_package_json_version_upgraded
|
action_name: is_package_json_version_upgraded
|
||||||
|
branch: ${{ github.head_ref || github.ref }}
|
||||||
|
|
||||||
update_changelog:
|
update_changelog:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: check_if_version_upgraded
|
needs: check_if_version_upgraded
|
||||||
if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true'
|
if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: garronej/github_actions_toolkit@v2.4
|
- uses: garronej/ts-ci@v1.1.3
|
||||||
with:
|
with:
|
||||||
action_name: update_changelog
|
action_name: update_changelog
|
||||||
branch: ${{ github.ref }}
|
branch: ${{ github.head_ref || github.ref }}
|
||||||
|
|
||||||
create_github_release:
|
create_github_release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -80,9 +86,6 @@ jobs:
|
|||||||
- update_changelog
|
- update_changelog
|
||||||
- check_if_version_upgraded
|
- check_if_version_upgraded
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
- name: Build GitHub release body
|
- name: Build GitHub release body
|
||||||
id: step1
|
id: step1
|
||||||
run: |
|
run: |
|
||||||
@ -98,10 +101,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
|
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
|
||||||
tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
|
tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
|
||||||
target_commitish: ${{ github.ref }}
|
target_commitish: ${{ github.head_ref || github.ref }}
|
||||||
body: ${{ steps.step1.outputs.body }}
|
body: ${{ steps.step1.outputs.body }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: ${{ needs.check_if_version_upgraded.outputs.is_release_beta == 'true' }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -138,7 +141,12 @@ jobs:
|
|||||||
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
|
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
|
||||||
false
|
false
|
||||||
fi
|
fi
|
||||||
npm publish
|
EXTRA_ARGS=""
|
||||||
|
if [ "$IS_BETA" = "true" ]; then
|
||||||
|
EXTRA_ARGS="--tag beta"
|
||||||
|
fi
|
||||||
|
npm publish $EXTRA_ARGS
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||||
VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
|
VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
|
||||||
|
IS_BETA: ${{ needs.check_if_version_upgraded.outputs.is_release_beta }}
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -44,3 +44,5 @@ jspm_packages
|
|||||||
|
|
||||||
/sample_react_project/
|
/sample_react_project/
|
||||||
/.yarn_home/
|
/.yarn_home/
|
||||||
|
|
||||||
|
.idea
|
||||||
|
80
CHANGELOG.md
80
CHANGELOG.md
@ -1,3 +1,83 @@
|
|||||||
|
### **4.2.14** (2021-12-12)
|
||||||
|
|
||||||
|
- Merge pull request #65 from InseeFrLab/doge_ftl_errors
|
||||||
|
|
||||||
|
Prevent ftl errors in Keycloak log
|
||||||
|
- Encourage users to report errors in logs
|
||||||
|
- Fix ftl error related to url.loginAction in saml-post-form.ftl
|
||||||
|
- Ftl prevent error with updateProfileCtx
|
||||||
|
- Ftl prevent error with auth.attemptedUsername
|
||||||
|
- Fix ftl error as comment formatting
|
||||||
|
- Merge remote-tracking branch 'origin/main' into doge_ftl_errors
|
||||||
|
- Update README, remove all instruction about errors in logs
|
||||||
|
- Avoid error in Keycloak logs, fix long template loading time
|
||||||
|
- Add missing collon in README sample code
|
||||||
|
|
||||||
|
Add miss ','
|
||||||
|
|
||||||
|
### **4.2.13** (2021-12-08)
|
||||||
|
|
||||||
|
- Fix broken link about how to import fonts #62
|
||||||
|
- Add a video to show how to test the theme in a local container
|
||||||
|
|
||||||
|
### **4.2.12** (2021-12-08)
|
||||||
|
|
||||||
|
- Update post build instructions
|
||||||
|
|
||||||
|
### **4.2.11** (2021-12-07)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### **4.2.10** (2021-11-12)
|
||||||
|
|
||||||
|
- Export an exaustive list of KcLanguageTag
|
||||||
|
|
||||||
|
### **4.2.9** (2021-11-11)
|
||||||
|
|
||||||
|
- Fix useAdvancedMsg
|
||||||
|
|
||||||
|
### **4.2.8** (2021-11-10)
|
||||||
|
|
||||||
|
- Update doc about pattern that can be used for user attributes #50
|
||||||
|
- Bring back Safari compat
|
||||||
|
|
||||||
|
### **4.2.7** (2021-11-09)
|
||||||
|
|
||||||
|
- Fix useFormValidationSlice
|
||||||
|
|
||||||
|
### **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
|
||||||
|
149
README.md
149
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 4.2.14**
|
||||||
|
|
||||||
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.
|
- No more error in Keycloak logs 🍾
|
||||||
It's important to avoid problem when using `keycloakify` alongside [`mui`](https://mui.com) and
|
- Templates now load in fraction of a second 🏎
|
||||||
[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,22 +65,30 @@ 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)
|
||||||
- [Limitations](#limitations)
|
- [Limitations](#limitations)
|
||||||
- [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported)
|
- [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported)
|
||||||
- [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-thesrc-dir)
|
- [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-the-src-dir)
|
||||||
- [Example of setup that **won't** work](#example-of-setup-that-wont-work)
|
- [Example of setup that **won't** work](#example-of-setup-that-wont-work)
|
||||||
- [Possible workarounds](#possible-workarounds)
|
- [Possible workarounds](#possible-workarounds)
|
||||||
- [Implement context persistence (optional)](#implement-context-persistence-optional)
|
- [Implement context persistence (optional)](#implement-context-persistence-optional)
|
||||||
- [Kickstart video](#kickstart-video)
|
- [Kickstart video](#kickstart-video)
|
||||||
- [About the errors related to `objectToJson` in Keycloak logs.](#about-the-errors-related-to-objecttojson-in-keycloak-logs)
|
- [FTL errors related to `ftl_object_to_js_code_declaring_an_object` in Keycloak logs.](#ftl-errors-related-to-ftl_object_to_js_code_declaring_an_object-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
|
||||||
|
|
||||||
|
On Windows OS you'll have to use [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10). More info [here](https://github.com/InseeFrLab/keycloakify/issues/54%23issuecomment-984834217)
|
||||||
|
|
||||||
Tested with the following Keycloak versions:
|
Tested with the following Keycloak versions:
|
||||||
|
|
||||||
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
|
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
|
||||||
@ -120,8 +108,6 @@ For more information see [this issue](https://github.com/InseeFrLab/keycloakify/
|
|||||||
- `mvn` ([Maven](https://maven.apache.org/)), `rm`, `mkdir`, `curl`, `unzip` are assumed to be available.
|
- `mvn` ([Maven](https://maven.apache.org/)), `rm`, `mkdir`, `curl`, `unzip` are assumed to be available.
|
||||||
- `docker` must be up and running when running `yarn keycloak`.
|
- `docker` must be up and running when running `yarn keycloak`.
|
||||||
|
|
||||||
On Windows you'll have to use [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10).
|
|
||||||
|
|
||||||
## My framework doesn’t seem to be supported, what can I do?
|
## My framework doesn’t seem to be supported, what can I do?
|
||||||
|
|
||||||
Currently Keycloakify is only compatible with `create-react-app` apps.
|
Currently Keycloakify is only compatible with `create-react-app` apps.
|
||||||
@ -165,27 +151,23 @@ your index should look something like:
|
|||||||
`src/index.tsx`
|
`src/index.tsx`
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { App } from "./<wherever>/App";
|
import { App } from "./<wherever>/App";
|
||||||
import {
|
import { KcApp, defaultKcProps, getKcContext } from "keycloakify";
|
||||||
KcApp,
|
import { css } from "tss-react/@emotion/css";
|
||||||
defaultKcProps,
|
|
||||||
getKcContext
|
|
||||||
} from "keycloakify";
|
|
||||||
import { css } from "tss-react/@emotion/css";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext();
|
const { kcContext } = getKcContext();
|
||||||
|
|
||||||
const myClassName = css({ "color": "red" });
|
const myClassName = css({ "color": "red" });
|
||||||
|
|
||||||
reactDom.render(
|
reactDom.render(
|
||||||
<KcApp
|
<KcApp
|
||||||
kcContext={kcContext}
|
kcContext={kcContext}
|
||||||
{...{
|
{...{
|
||||||
...defaultKcProps,
|
...defaultKcProps,
|
||||||
"kcHeaderWrapperClass": myClassName
|
"kcHeaderWrapperClass": myClassName,
|
||||||
}}
|
}}
|
||||||
/>
|
/>,
|
||||||
document.getElementById("root")
|
document.getElementById("root"),
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -313,6 +295,34 @@ 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>
|
||||||
|
|
||||||
|
NOTE: In reality the regexp used in this gif doesn't work server side, the regexp pattern should be `^[^@]@gmail\.com$` 😬.
|
||||||
|
|
||||||
|
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).
|
||||||
@ -343,7 +353,7 @@ the building and publishing of the theme (the .jar file).
|
|||||||
You won't be able to [import things from your public directory **in your JavaScript code**](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system).
|
You won't be able to [import things from your public directory **in your JavaScript code**](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system).
|
||||||
(This isn't recommended anyway).
|
(This isn't recommended anyway).
|
||||||
|
|
||||||
## `@font-face` importing fonts from the `src/` dir
|
## `@font-face` importing fonts from the `src/` dir
|
||||||
|
|
||||||
If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-)
|
If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-)
|
||||||
this limitation doesn't apply, you can import fonts however you see fit.
|
this limitation doesn't apply, you can import fonts however you see fit.
|
||||||
@ -359,7 +369,8 @@ this limitation doesn't apply, you can import fonts however you see fit.
|
|||||||
- If it is possible, use Google Fonts or any other font provider.
|
- If it is possible, use Google Fonts or any other font provider.
|
||||||
- If you want to host your font recommended approach is to move your fonts into the `public`
|
- If you want to host your font recommended approach is to move your fonts into the `public`
|
||||||
directory and to place your `@font-face` statements in the `public/index.html`.
|
directory and to place your `@font-face` statements in the `public/index.html`.
|
||||||
Example [here](https://github.com/InseeFrLab/onyxia-ui/blob/0e3a04610cfe872ca71dad59e05ced8f785dee4b/public/index.html#L6-L51).
|
Example [here](https://github.com/garronej/keycloakify-demo-app/blob/9aa2dbaec28a7786d6b2983c9a59d393dec1b2d6/public/index.html#L27-L73)
|
||||||
|
(and the font are [here](https://github.com/garronej/keycloakify-demo-app/tree/main/public/fonts/WorkSans)).
|
||||||
- You can also [use non relative url](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/src/fonts.scss#L16) but don't forget [`Access-Control-Allow-Origin`](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/nginx.conf#L17-L19).
|
- You can also [use non relative url](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/src/fonts.scss#L16) but don't forget [`Access-Control-Allow-Origin`](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/nginx.conf#L17-L19).
|
||||||
|
|
||||||
# Implement context persistence (optional)
|
# Implement context persistence (optional)
|
||||||
@ -426,24 +437,22 @@ flash of the blank html before the js bundle have been evaluated
|
|||||||
_NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded_
|
_NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded_
|
||||||
[](https://youtu.be/xTz0Rj7i2v8)
|
[](https://youtu.be/xTz0Rj7i2v8)
|
||||||
|
|
||||||
# About the errors related to `objectToJson` in Keycloak logs.
|
# FTL errors related to `ftl_object_to_js_code_declaring_an_object` in Keycloak logs.
|
||||||
|
|
||||||
The logs of your keycloak server will always show this kind of errors every time a client request a page:
|
If you ever encounter one of these errors:
|
||||||
|
|
||||||
```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 "ftl_object_to_js_code_declaring_an_object" 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 "ftl_object_to_js_code_declaring_an_object" 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: @ftl_object_to_js_code_declaring_an_object object=value depth=(dep... [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" 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 "ftl_object_to_js_code_declaring_an_object" at line 36, column 5]
|
||||||
- Reached through: @objectToJson object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
|
- Reached through: @ftl_object_to_js_code_declaring_an_object object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
|
||||||
```
|
```
|
||||||
|
|
||||||
Theses are expected and can be safely ignored.
|
It's just noise, they can be safely ignored.
|
||||||
|
You can, however, and are encouraged to, report any that you would spot.
|
||||||
To [converts the `.ftl` values into a JavaScript object](https://github.com/InseeFrLab/keycloakify/blob/main/src/bin/build-keycloak-theme/generateFtl/common.ftl)
|
Just open an issue about it and I will release a patched version of Keycloakify in the better delays.
|
||||||
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.
|
|
||||||
|
|
||||||
# Adding custom message (to `i18n/useKcMessage.tsx`)
|
# Adding custom message (to `i18n/useKcMessage.tsx`)
|
||||||
|
|
||||||
@ -453,5 +462,33 @@ This approach is a bit hacky as it doesn't provide type safety but it works.
|
|||||||
|
|
||||||
# Email domain whitelist
|
# Email domain whitelist
|
||||||
|
|
||||||
|
NOTE: This have been kind of deprecated by [user attribute](#user-profile-and-frontend-form-validation) you could
|
||||||
|
use a pattern [like this one](https://github.com/InseeFrLab/onyxia-web/blob/f1206e0329b3b8d401ca7bffa95ca9c213cb190a/src/app/components/KcApp/kcContext.ts#L106) to whitelist email domains.
|
||||||
|
|
||||||
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`).
|
||||||
|
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "4.0.0",
|
"version": "4.2.14",
|
||||||
"description": "Keycloak theme generator for Reacts app",
|
"description": "Keycloak theme generator for Reacts app",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -56,9 +56,9 @@
|
|||||||
"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 || ^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
@ -72,14 +72,14 @@
|
|||||||
"properties-parser": "^0.3.1",
|
"properties-parser": "^0.3.1",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"tss-react": "^1.1.0",
|
"tss-react": "^3.0.0",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"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"
|
||||||
|
@ -121,11 +121,14 @@ export function main() {
|
|||||||
"",
|
"",
|
||||||
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))} 👈`,
|
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))} 👈`,
|
||||||
"",
|
"",
|
||||||
'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),',
|
"Once your container is up and running: ",
|
||||||
`go to your realm settings, click on the theme tab then select ${themeName}.`,
|
"- Log into the admin console 👉 http://localhost:8080 username: admin, password: admin 👈",
|
||||||
`More details: https://www.keycloak.org/getting-started/getting-started-docker`,
|
'- Create a realm named "myrealm"',
|
||||||
|
'- Create a client with id "myclient" and root url: "https://www.keycloak.org/app/"',
|
||||||
|
`- Select Login Theme: ${themeName} (don't forget to save at the bottom of the page)`,
|
||||||
|
`- Go to 👉 https://www.keycloak.org/app/ 👈 Click "Save" then "Sign in". You should see your login page`,
|
||||||
"",
|
"",
|
||||||
"Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈",
|
"Video demoing this process: https://youtu.be/N3wlBoH4hKg",
|
||||||
"",
|
"",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
);
|
);
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
|
|
||||||
Object.defineProperty(
|
|
||||||
Object,
|
|
||||||
"deepAssign",
|
|
||||||
{
|
|
||||||
"value": function callee(target, source) {
|
|
||||||
Object.keys(source).forEach(function (key) {
|
|
||||||
var value = source[key];
|
|
||||||
if (target[key] === undefined) {
|
|
||||||
target[key] = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof Object) {
|
|
||||||
if (value instanceof Array) {
|
|
||||||
value.forEach(function (entry) {
|
|
||||||
target[key].push(entry);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callee(target[key], value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
target[key] = value;
|
|
||||||
});
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,208 +0,0 @@
|
|||||||
<script>const _=
|
|
||||||
<#macro objectToJson object depth>
|
|
||||||
<@compress>
|
|
||||||
|
|
||||||
<#local isHash = false>
|
|
||||||
<#attempt>
|
|
||||||
<#local isHash = object?is_hash || object?is_hash_ex>
|
|
||||||
<#recover>
|
|
||||||
/* can't evaluate if object is hash */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
<#if isHash>
|
|
||||||
|
|
||||||
<#local keys = "">
|
|
||||||
|
|
||||||
<#attempt>
|
|
||||||
<#local keys = object?keys>
|
|
||||||
<#recover>
|
|
||||||
/* can't list keys of object */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
{${'\n'}
|
|
||||||
|
|
||||||
<#list keys as key>
|
|
||||||
|
|
||||||
<#if key == "class">
|
|
||||||
/* skipping "class" property of object */
|
|
||||||
<#continue>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
<#local value = "">
|
|
||||||
|
|
||||||
<#attempt>
|
|
||||||
<#local value = object[key]>
|
|
||||||
<#recover>
|
|
||||||
/* couldn't dereference ${key} of object */
|
|
||||||
<#continue>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
<#if depth gt 7>
|
|
||||||
/* Avoid calling recustively too many times depth: ${depth}, key: ${key} */
|
|
||||||
<#continue>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
"${key}": <@objectToJson object=value depth=depth+1/>,
|
|
||||||
|
|
||||||
</#list>
|
|
||||||
|
|
||||||
}${'\n'}
|
|
||||||
|
|
||||||
<#return>
|
|
||||||
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
<#local isMethod = "">
|
|
||||||
<#attempt>
|
|
||||||
<#local isMethod = object?is_method>
|
|
||||||
<#recover>
|
|
||||||
/* can't test if object is a method */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
<#if isMethod>
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<#local isBoolean = "">
|
|
||||||
<#attempt>
|
|
||||||
<#local isBoolean = object?is_boolean>
|
|
||||||
<#recover>
|
|
||||||
/* can't test if object is a boolean */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
<#if isBoolean>
|
|
||||||
${object?c}
|
|
||||||
<#return>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
<#local isEnumerable = "">
|
|
||||||
<#attempt>
|
|
||||||
<#local isEnumerable = object?is_enumerable>
|
|
||||||
<#recover>
|
|
||||||
/* can't test if object is enumerable */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
<#if isEnumerable>
|
|
||||||
|
|
||||||
[${'\n'}
|
|
||||||
|
|
||||||
<#list object as item>
|
|
||||||
|
|
||||||
<@objectToJson object=item depth=depth+1/>,
|
|
||||||
|
|
||||||
</#list>
|
|
||||||
|
|
||||||
]${'\n'}
|
|
||||||
|
|
||||||
<#return>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
<#attempt>
|
|
||||||
"${object?replace('"', '\\"')?no_esc}"
|
|
||||||
<#recover>
|
|
||||||
/* couldn't convert into string non hash, non method, non boolean, non enumerable object */
|
|
||||||
undefined;
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
|
|
||||||
</@compress>
|
|
||||||
</#macro>
|
|
||||||
|
|
||||||
(()=>{
|
|
||||||
|
|
||||||
const nonAutomaticallyConvertible = {
|
|
||||||
"messagesPerField": {
|
|
||||||
|
|
||||||
<#assign fieldNames = ["global", "userLabel", "username", "email", "firstName", "lastName", "password", "password-confirm"]>
|
|
||||||
|
|
||||||
<#attempt>
|
|
||||||
<#list profile.attributes as attribute>
|
|
||||||
<#assign fieldNames += [attribute.name]>
|
|
||||||
</#list>
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
"printIfExists": function (fieldName, x) {
|
|
||||||
<#list fieldNames as fieldName>
|
|
||||||
if(fieldName === "${fieldName}" ){
|
|
||||||
<#attempt>
|
|
||||||
return "${messagesPerField.printIfExists(fieldName,'1')}" ? x : undefined;
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
}
|
|
||||||
</#list>
|
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
},
|
|
||||||
"existsError": function (fieldName) {
|
|
||||||
<#list fieldNames as fieldName>
|
|
||||||
if(fieldName === "${fieldName}" ){
|
|
||||||
<#attempt>
|
|
||||||
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
}
|
|
||||||
</#list>
|
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
},
|
|
||||||
"get": function (fieldName) {
|
|
||||||
<#list fieldNames as fieldName>
|
|
||||||
if(fieldName === "${fieldName}" ){
|
|
||||||
<#attempt>
|
|
||||||
<#if messagesPerField.existsError('${fieldName}')>
|
|
||||||
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
|
||||||
</#if>
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
}
|
|
||||||
</#list>
|
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
},
|
|
||||||
"exists": function (fieldName) {
|
|
||||||
<#list fieldNames as fieldName>
|
|
||||||
if(fieldName === "${fieldName}" ){
|
|
||||||
<#attempt>
|
|
||||||
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
}
|
|
||||||
</#list>
|
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"msg": function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); },
|
|
||||||
"advancedMsg": function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); }
|
|
||||||
};
|
|
||||||
|
|
||||||
const out = {};
|
|
||||||
|
|
||||||
Object.deepAssign(
|
|
||||||
out,
|
|
||||||
//Removing all the undefined
|
|
||||||
JSON.parse(JSON.stringify(<@objectToJson object=.data_model depth=0 />))
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.deepAssign(
|
|
||||||
out,
|
|
||||||
nonAutomaticallyConvertible
|
|
||||||
);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
|
|
||||||
})()
|
|
||||||
</script>
|
|
@ -0,0 +1,290 @@
|
|||||||
|
<script>const _=
|
||||||
|
<#assign pageId="PAGE_ID_xIgLsPgGId9D8e">
|
||||||
|
(()=>{
|
||||||
|
|
||||||
|
const out =
|
||||||
|
${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||||
|
|
||||||
|
out["msg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
|
||||||
|
out["advancedMsg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
|
||||||
|
|
||||||
|
out["messagesPerField"]= {
|
||||||
|
<#assign fieldNames = [
|
||||||
|
"global", "userLabel", "username", "email", "firstName", "lastName", "password", "password-confirm",
|
||||||
|
"totp", "totpSecret", "SAMLRequest", "SAMLResponse", "relayState", "device_user_code", "code",
|
||||||
|
"password-new", "rememberMe", "login", "authenticationExecution", "cancel-aia", "clientDataJSON",
|
||||||
|
"authenticatorData", "signature", "credentialId", "userHandle", "error", "authn_use_chk", "authenticationExecution",
|
||||||
|
"isSetRetry", "try-again", "attestationObject", "publicKeyCredentialId", "authenticatorLabel"
|
||||||
|
]>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#if profile?? && profile.attributes?? && profile.attributes?is_enumerable>
|
||||||
|
<#list profile.attributes as attribute>
|
||||||
|
<#if fieldNames?seq_contains(attribute.name)>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
<#assign fieldNames += [attribute.name]>
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
"printIfExists": function (fieldName, x) {
|
||||||
|
<#list fieldNames as fieldName>
|
||||||
|
if(fieldName === "${fieldName}" ){
|
||||||
|
<#attempt>
|
||||||
|
return "${messagesPerField.printIfExists(fieldName,'1')}" ? x : undefined;
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
}
|
||||||
|
</#list>
|
||||||
|
throw new Error("There is no " + fieldName + " field");
|
||||||
|
},
|
||||||
|
"existsError": function (fieldName) {
|
||||||
|
<#list fieldNames as fieldName>
|
||||||
|
if(fieldName === "${fieldName}" ){
|
||||||
|
<#attempt>
|
||||||
|
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
}
|
||||||
|
</#list>
|
||||||
|
throw new Error("There is no " + fieldName + " field");
|
||||||
|
},
|
||||||
|
"get": function (fieldName) {
|
||||||
|
<#list fieldNames as fieldName>
|
||||||
|
if(fieldName === "${fieldName}" ){
|
||||||
|
<#attempt>
|
||||||
|
<#if messagesPerField.existsError('${fieldName}')>
|
||||||
|
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||||
|
</#if>
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
}
|
||||||
|
</#list>
|
||||||
|
throw new Error("There is no " + fieldName + " field");
|
||||||
|
},
|
||||||
|
"exists": function (fieldName) {
|
||||||
|
<#list fieldNames as fieldName>
|
||||||
|
if(fieldName === "${fieldName}" ){
|
||||||
|
<#attempt>
|
||||||
|
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
}
|
||||||
|
</#list>
|
||||||
|
throw new Error("There is no " + fieldName + " field");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
out["pageId"] = "${pageId}";
|
||||||
|
|
||||||
|
return out;
|
||||||
|
|
||||||
|
})()
|
||||||
|
<#function ftl_object_to_js_code_declaring_an_object object path>
|
||||||
|
|
||||||
|
<#local isHash = "">
|
||||||
|
<#attempt>
|
||||||
|
<#local isHash = object?is_hash || object?is_hash_ex>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: Can't evaluate if " + path?join(".") + " is hash">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#if isHash>
|
||||||
|
|
||||||
|
<#if path?size gt 10>
|
||||||
|
<#return "ABORT: Too many recursive calls">
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local keys = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#local keys = object?keys>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: We can't list keys on this object">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
|
||||||
|
<#local out_seq = []>
|
||||||
|
|
||||||
|
<#list keys as key>
|
||||||
|
|
||||||
|
<#if ["class","declaredConstructors","superclass","declaringClass" ]?seq_contains(key) >
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if
|
||||||
|
(
|
||||||
|
["loginUpdatePasswordUrl", "loginUpdateProfileUrl", "loginUsernameReminderUrl", "loginUpdateTotpUrl"]?seq_contains(key) &&
|
||||||
|
are_same_path(path, ["url"])
|
||||||
|
) || (
|
||||||
|
key == "updateProfileCtx" &&
|
||||||
|
are_same_path(path, [])
|
||||||
|
) || (
|
||||||
|
<#-- https://github.com/InseeFrLab/keycloakify/pull/65#issuecomment-991896344 -->
|
||||||
|
key == "loginAction" &&
|
||||||
|
are_same_path(path, ["url"]) &&
|
||||||
|
pageId == "saml-post-form.ftl"
|
||||||
|
)
|
||||||
|
>
|
||||||
|
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if 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 -->
|
||||||
|
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#if !object[key]??>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
<#recover>
|
||||||
|
<#local out_seq += ["/*Couldn't test if '" + key + "' is available on this object*/"]>
|
||||||
|
<#continue>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#local propertyValue = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#local propertyValue = object[key]>
|
||||||
|
<#recover>
|
||||||
|
<#local out_seq += ["/*Couldn't dereference '" + key + "' on this object*/"]>
|
||||||
|
<#continue>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#local rec_out = ftl_object_to_js_code_declaring_an_object(propertyValue, path + [ key ])>
|
||||||
|
|
||||||
|
<#if rec_out?starts_with("ABORT:")>
|
||||||
|
|
||||||
|
<#local errorMessage = rec_out?remove_beginning("ABORT:")>
|
||||||
|
|
||||||
|
<#if errorMessage != " It's a method" >
|
||||||
|
<#local out_seq += ["/*" + key + ": " + errorMessage + "*/"]>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local out_seq += ['"' + key + '": ' + rec_out + ","]>
|
||||||
|
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
<#return (["{"] + out_seq?map(str -> ""?right_pad(4 * (path?size + 1)) + str) + [ ""?right_pad(4 * path?size) + "}"])?join("\n")>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local isMethod = "">
|
||||||
|
<#attempt>
|
||||||
|
<#local isMethod = object?is_method>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: Can't test if it'sa method.">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#if isMethod>
|
||||||
|
<#return "ABORT: It's a method">
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local isBoolean = "">
|
||||||
|
<#attempt>
|
||||||
|
<#local isBoolean = object?is_boolean>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: Can't test if it's a boolean">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#if isBoolean>
|
||||||
|
<#return object?c>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local isEnumerable = "">
|
||||||
|
<#attempt>
|
||||||
|
<#local isEnumerable = object?is_enumerable>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: Can't test if it's an enumerable">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
|
||||||
|
<#if isEnumerable>
|
||||||
|
|
||||||
|
<#local out_seq = []>
|
||||||
|
|
||||||
|
<#local i = 0>
|
||||||
|
|
||||||
|
<#list object as array_item>
|
||||||
|
|
||||||
|
<#local rec_out = ftl_object_to_js_code_declaring_an_object(array_item, path + [ i ])>
|
||||||
|
|
||||||
|
<#local i = i + 1>
|
||||||
|
|
||||||
|
<#if rec_out?starts_with("ABORT:")>
|
||||||
|
|
||||||
|
<#local errorMessage = rec_out?remove_beginning("ABORT:")>
|
||||||
|
|
||||||
|
<#if errorMessage != " It's a method" >
|
||||||
|
<#local out_seq += ["/*" + i?string + ": " + errorMessage + "*/"]>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local out_seq += [rec_out + ","]>
|
||||||
|
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
<#return (["["] + out_seq?map(str -> ""?right_pad(4 * (path?size + 1)) + str) + [ ""?right_pad(4 * path?size) + "]"])?join("\n")>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#return '"' + object?js_string + '"'>;
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#return "ABORT: Couldn't convert into string non hash, non method, non boolean, non enumerable object">
|
||||||
|
|
||||||
|
</#function>
|
||||||
|
<#function are_same_path path searchedPath>
|
||||||
|
|
||||||
|
<#if path?size != path?size>
|
||||||
|
<#return false>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local i=0>
|
||||||
|
|
||||||
|
<#list path as property>
|
||||||
|
|
||||||
|
<#local searchedProperty=searchedPath[i]>
|
||||||
|
|
||||||
|
<#if searchedProperty?is_string && searchedProperty == "*">
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if searchedProperty?is_string && !property?is_string>
|
||||||
|
<#return false>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if searchedProperty?is_number && !property?is_number>
|
||||||
|
<#return false>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if searchedProperty?string != property?string>
|
||||||
|
<#return false>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local i+= 1>
|
||||||
|
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
<#return true>
|
||||||
|
|
||||||
|
</#function>
|
||||||
|
</script>
|
@ -21,10 +21,6 @@ export const pageIds = [
|
|||||||
|
|
||||||
export type PageId = typeof pageIds[number];
|
export type PageId = typeof pageIds[number];
|
||||||
|
|
||||||
function loadAdjacentFile(fileBasename: string) {
|
|
||||||
return fs.readFileSync(pathJoin(__dirname, fileBasename)).toString("utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateFtlFilesCodeFactory(params: {
|
export function generateFtlFilesCodeFactory(params: {
|
||||||
cssGlobalsToDefine: Record<string, string>;
|
cssGlobalsToDefine: Record<string, string>;
|
||||||
indexHtmlCode: string;
|
indexHtmlCode: string;
|
||||||
@ -77,8 +73,11 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
|
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
|
||||||
const ftlPlaceholders = {
|
const replaceValueBySearchValue = {
|
||||||
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadAdjacentFile("common.ftl").match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1],
|
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': fs
|
||||||
|
.readFileSync(pathJoin(__dirname, "ftl_object_to_js_code_declaring_an_object.ftl"))
|
||||||
|
.toString("utf8")
|
||||||
|
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1],
|
||||||
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
|
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
|
||||||
"<#if scripts??>",
|
"<#if scripts??>",
|
||||||
" <#list scripts as script>",
|
" <#list scripts as script>",
|
||||||
@ -88,8 +87,6 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
].join("\n"),
|
].join("\n"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageSpecificCodePlaceholder = "<!-- dIddLqMeOedErIdLsPdNdI9dSl42sw -->";
|
|
||||||
|
|
||||||
$("head").prepend(
|
$("head").prepend(
|
||||||
[
|
[
|
||||||
...(Object.keys(cssGlobalsToDefine).length === 0
|
...(Object.keys(cssGlobalsToDefine).length === 0
|
||||||
@ -105,18 +102,10 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
"",
|
"",
|
||||||
]),
|
]),
|
||||||
"<script>",
|
"<script>",
|
||||||
loadAdjacentFile("Object.deepAssign.js"),
|
` window.${ftlValuesGlobalName}= ${objectKeys(replaceValueBySearchValue)[0]};`,
|
||||||
"</script>",
|
|
||||||
"<script>",
|
|
||||||
` window.${ftlValuesGlobalName}= Object.assign(`,
|
|
||||||
` {},`,
|
|
||||||
` ${objectKeys(ftlPlaceholders)[0]}`,
|
|
||||||
" );",
|
|
||||||
"</script>",
|
"</script>",
|
||||||
"",
|
"",
|
||||||
pageSpecificCodePlaceholder,
|
objectKeys(replaceValueBySearchValue)[1],
|
||||||
"",
|
|
||||||
objectKeys(ftlPlaceholders)[1],
|
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -129,19 +118,13 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
|
|
||||||
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
|
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
|
||||||
|
|
||||||
let ftlCode = $.html().replace(
|
let ftlCode = $.html();
|
||||||
pageSpecificCodePlaceholder,
|
|
||||||
[
|
|
||||||
"<script>",
|
|
||||||
` Object.deepAssign(`,
|
|
||||||
` window.${ftlValuesGlobalName},`,
|
|
||||||
` { "pageId": "${pageId}" }`,
|
|
||||||
" );",
|
|
||||||
"</script>",
|
|
||||||
].join("\n"),
|
|
||||||
);
|
|
||||||
|
|
||||||
objectKeys(ftlPlaceholders).forEach(id => (ftlCode = ftlCode.replace(id, ftlPlaceholders[id])));
|
Object.entries({
|
||||||
|
...replaceValueBySearchValue,
|
||||||
|
//If updated, don't forget to change in the ftl script as well.
|
||||||
|
"PAGE_ID_xIgLsPgGId9D8e": pageId,
|
||||||
|
}).map(([searchValue, replaceValue]) => (ftlCode = ftlCode.replace(searchValue, replaceValue)));
|
||||||
|
|
||||||
return { ftlCode };
|
return { ftlCode };
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -34,7 +34,7 @@ export function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel {
|
|||||||
return kcLanguageByTagLabel[language] ?? language;
|
return kcLanguageByTagLabel[language] ?? language;
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableLanguages = objectKeys(kcMessages);
|
export const kcLanguageTags = objectKeys(kcMessages);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
|
* Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
|
||||||
@ -45,7 +45,7 @@ const availableLanguages = objectKeys(kcMessages);
|
|||||||
export function getBestMatchAmongKcLanguageTag(languageLike: string): KcLanguageTag {
|
export function getBestMatchAmongKcLanguageTag(languageLike: string): KcLanguageTag {
|
||||||
const iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
|
const iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
|
||||||
|
|
||||||
const kcLanguageTag = availableLanguages.find(
|
const kcLanguageTag = kcLanguageTags.find(
|
||||||
language =>
|
language =>
|
||||||
language.toLowerCase().includes(iso2LanguageLike) ||
|
language.toLowerCase().includes(iso2LanguageLike) ||
|
||||||
getKcLanguageTagLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase(),
|
getKcLanguageTagLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase(),
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -27,8 +27,9 @@ function resolveMsg<Key extends string, DoRenderMarkdown extends boolean>(props:
|
|||||||
|
|
||||||
str = (() => {
|
str = (() => {
|
||||||
const startIndex = str
|
const startIndex = str
|
||||||
.match(/(?<={)[0-9]+(?=})/g)
|
.match(/{[0-9]+}/g)
|
||||||
?.map(g => parseInt(g))
|
?.map(g => g.match(/{([0-9]+)}/)![1])
|
||||||
|
.map(indexStr => parseInt(indexStr))
|
||||||
.sort((a, b) => a - b)[0];
|
.sort((a, b) => a - b)[0];
|
||||||
|
|
||||||
if (startIndex === undefined) {
|
if (startIndex === undefined) {
|
||||||
@ -67,16 +68,16 @@ function resolveMsgAdvanced<Key extends string, DoRenderMarkdown extends boolean
|
|||||||
|
|
||||||
const match = key.match(/^\$\{([^{]+)\}$/);
|
const match = key.match(/^\$\{([^{]+)\}$/);
|
||||||
|
|
||||||
const resolvedKey = match === null ? key : match[1];
|
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
||||||
|
|
||||||
const out = resolveMsg({
|
const out = resolveMsg({
|
||||||
"key": resolvedKey,
|
"key": keyUnwrappedFromCurlyBraces,
|
||||||
args,
|
args,
|
||||||
kcLanguageTag,
|
kcLanguageTag,
|
||||||
doRenderMarkdown,
|
doRenderMarkdown,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (out !== undefined ? out : match === null ? doRenderMarkdown ? <span>{key}</span> : key : undefined) as any;
|
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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 = {
|
||||||
@ -39,8 +38,24 @@ export function useGetErrors(params: {
|
|||||||
|
|
||||||
const { value: defaultValue, validators } = attributes.find(attribute => attribute.name === name)!;
|
const { value: defaultValue, validators } = attributes.find(attribute => attribute.name === name)!;
|
||||||
|
|
||||||
if (defaultValue === value && messagesPerField.existsError(value)) {
|
block: {
|
||||||
const errorMessageStr = messagesPerField.get(value);
|
if (defaultValue !== value) {
|
||||||
|
break block;
|
||||||
|
}
|
||||||
|
|
||||||
|
let doesErrorExist: boolean;
|
||||||
|
|
||||||
|
try {
|
||||||
|
doesErrorExist = messagesPerField.existsError(name);
|
||||||
|
} catch {
|
||||||
|
break block;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doesErrorExist) {
|
||||||
|
break block;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessageStr = messagesPerField.get(name);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -278,6 +293,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 +306,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 +364,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 +443,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";
|
||||||
|
182
yarn.lock
182
yarn.lock
@ -64,7 +64,7 @@
|
|||||||
"@emotion/weak-memoize" "^0.2.5"
|
"@emotion/weak-memoize" "^0.2.5"
|
||||||
hoist-non-react-statics "^3.3.1"
|
hoist-non-react-statics "^3.3.1"
|
||||||
|
|
||||||
"@emotion/serialize@^1.0.2":
|
"@emotion/serialize@*", "@emotion/serialize@^1.0.2":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965"
|
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965"
|
||||||
integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==
|
integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==
|
||||||
@ -75,16 +75,6 @@
|
|||||||
"@emotion/utils" "^1.0.0"
|
"@emotion/utils" "^1.0.0"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@emotion/server@^11.4.0":
|
|
||||||
version "11.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/server/-/server-11.4.0.tgz#3ae1d74cb31c7d013c3c76e88c0c4439076e9f66"
|
|
||||||
integrity sha512-IHovdWA3V0DokzxLtUNDx4+hQI82zUXqQFcVz/om2t44O0YSc+NHB+qifnyAOoQwt3SXcBTgaSntobwUI9gnfA==
|
|
||||||
dependencies:
|
|
||||||
"@emotion/utils" "^1.0.0"
|
|
||||||
html-tokenize "^2.0.0"
|
|
||||||
multipipe "^1.0.2"
|
|
||||||
through "^2.3.8"
|
|
||||||
|
|
||||||
"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.2":
|
"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.2":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.2.tgz#1d9ffde531714ba28e62dac6a996a8b1089719d0"
|
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.2.tgz#1d9ffde531714ba28e62dac6a996a8b1089719d0"
|
||||||
@ -95,7 +85,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
|
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
|
||||||
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
|
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
|
||||||
|
|
||||||
"@emotion/utils@^1.0.0":
|
"@emotion/utils@*", "@emotion/utils@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af"
|
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af"
|
||||||
integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==
|
integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==
|
||||||
@ -220,11 +210,6 @@ braces@^3.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^7.0.1"
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
buffer-from@~0.1.1:
|
|
||||||
version "0.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0"
|
|
||||||
integrity sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==
|
|
||||||
|
|
||||||
callsites@^3.0.0:
|
callsites@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||||
@ -452,7 +437,7 @@ domelementtype@^2.0.1, domelementtype@^2.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
||||||
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
||||||
|
|
||||||
domhandler@4.2.2, domhandler@^4.0, domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.2.2:
|
domhandler@^4.0, domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.2.2:
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
|
||||||
integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==
|
integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==
|
||||||
@ -468,13 +453,6 @@ domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0, domutils@^2.8.0:
|
|||||||
domelementtype "^2.2.0"
|
domelementtype "^2.2.0"
|
||||||
domhandler "^4.2.0"
|
domhandler "^4.2.0"
|
||||||
|
|
||||||
duplexer2@^0.1.2:
|
|
||||||
version "0.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
|
|
||||||
integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=
|
|
||||||
dependencies:
|
|
||||||
readable-stream "^2.0.2"
|
|
||||||
|
|
||||||
emoji-regex@^8.0.0:
|
emoji-regex@^8.0.0:
|
||||||
version "8.0.0"
|
version "8.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
@ -665,24 +643,6 @@ hoist-non-react-statics@^3.3.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react-is "^16.7.0"
|
react-is "^16.7.0"
|
||||||
|
|
||||||
html-dom-parser@1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-1.0.2.tgz#bb5ff844f214657d899aa4fb7b0a9e7d15607e96"
|
|
||||||
integrity sha512-Jq4oVkVSn+10ut3fyc2P/Fs1jqTo0l45cP6Q8d2ef/9jfkYwulO0QXmyLI0VUiZrXF4czpGgMEJRa52CQ6Fk8Q==
|
|
||||||
dependencies:
|
|
||||||
domhandler "4.2.2"
|
|
||||||
htmlparser2 "6.1.0"
|
|
||||||
|
|
||||||
html-react-parser@^1.2.7:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-1.4.0.tgz#bf264f38b9fdf4d94e2120f6a39586c15cb81bd0"
|
|
||||||
integrity sha512-v8Kxy+7L90ZFSM690oJWBNRzZWZOQquYPpQt6kDQPzQyZptXgOJ69kHSi7xdqNdm1mOfsDPwF4K9Bo/dS5gRTQ==
|
|
||||||
dependencies:
|
|
||||||
domhandler "4.2.2"
|
|
||||||
html-dom-parser "1.0.2"
|
|
||||||
react-property "2.0.0"
|
|
||||||
style-to-js "1.1.0"
|
|
||||||
|
|
||||||
html-to-react@^1.3.4:
|
html-to-react@^1.3.4:
|
||||||
version "1.4.7"
|
version "1.4.7"
|
||||||
resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.4.7.tgz#a58129c1b77c6d4e047a647372bd194e25420b89"
|
resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.4.7.tgz#a58129c1b77c6d4e047a647372bd194e25420b89"
|
||||||
@ -693,18 +653,7 @@ html-to-react@^1.3.4:
|
|||||||
lodash.camelcase "^4.3.0"
|
lodash.camelcase "^4.3.0"
|
||||||
ramda "^0.27.1"
|
ramda "^0.27.1"
|
||||||
|
|
||||||
html-tokenize@^2.0.0:
|
htmlparser2@^6.1.0:
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/html-tokenize/-/html-tokenize-2.0.1.tgz#c3b2ea6e2837d4f8c06693393e9d2a12c960be5f"
|
|
||||||
integrity sha512-QY6S+hZ0f5m1WT8WffYN+Hg+xm/w5I8XeUcAq/ZYP5wVC8xbKi4Whhru3FtrAebD5EhBW8rmFzkDI6eCAuFe2w==
|
|
||||||
dependencies:
|
|
||||||
buffer-from "~0.1.1"
|
|
||||||
inherits "~2.0.1"
|
|
||||||
minimist "~1.2.5"
|
|
||||||
readable-stream "~1.0.27-1"
|
|
||||||
through2 "~0.4.1"
|
|
||||||
|
|
||||||
htmlparser2@6.1.0, htmlparser2@^6.1.0:
|
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
||||||
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
||||||
@ -771,16 +720,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:
|
|
||||||
version "0.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
|
|
||||||
integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
|
|
||||||
|
|
||||||
is-alphabetical@^1.0.0:
|
is-alphabetical@^1.0.0:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
|
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
|
||||||
@ -1023,11 +962,6 @@ minimatch@^3.0.3, minimatch@^3.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@~1.2.5:
|
|
||||||
version "1.2.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
|
||||||
|
|
||||||
mkdirp@^1.0.4:
|
mkdirp@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
@ -1038,14 +972,6 @@ ms@2.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
|
|
||||||
multipipe@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-1.0.2.tgz#cc13efd833c9cda99f224f868461b8e1a3fd939d"
|
|
||||||
integrity sha1-zBPv2DPJzamfIk+GhGG44aP9k50=
|
|
||||||
dependencies:
|
|
||||||
duplexer2 "^0.1.2"
|
|
||||||
object-assign "^4.1.0"
|
|
||||||
|
|
||||||
next-tick@1, next-tick@^1.1.0:
|
next-tick@1, next-tick@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
|
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
|
||||||
@ -1083,16 +1009,11 @@ nth-check@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
boolbase "^1.0.0"
|
boolbase "^1.0.0"
|
||||||
|
|
||||||
object-assign@^4.1.0, object-assign@^4.1.1:
|
object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||||
|
|
||||||
object-keys@~0.4.0:
|
|
||||||
version "0.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
|
|
||||||
integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=
|
|
||||||
|
|
||||||
once@^1.3.0:
|
once@^1.3.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
@ -1174,6 +1095,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 +1120,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 +1159,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"
|
||||||
@ -1288,11 +1201,6 @@ react-markdown@^5.0.3:
|
|||||||
unist-util-visit "^2.0.0"
|
unist-util-visit "^2.0.0"
|
||||||
xtend "^4.0.1"
|
xtend "^4.0.1"
|
||||||
|
|
||||||
react-property@2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136"
|
|
||||||
integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
|
|
||||||
|
|
||||||
react@^17.0.1:
|
react@^17.0.1:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||||
@ -1301,7 +1209,17 @@ react@^17.0.1:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
readable-stream@^2.0.2, readable-stream@~2.3.6:
|
readable-stream@~1.0.31:
|
||||||
|
version "1.0.34"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
|
||||||
|
integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
|
||||||
|
dependencies:
|
||||||
|
core-util-is "~1.0.0"
|
||||||
|
inherits "~2.0.1"
|
||||||
|
isarray "0.0.1"
|
||||||
|
string_decoder "~0.10.x"
|
||||||
|
|
||||||
|
readable-stream@~2.3.6:
|
||||||
version "2.3.7"
|
version "2.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||||
@ -1314,16 +1232,6 @@ readable-stream@^2.0.2, readable-stream@~2.3.6:
|
|||||||
string_decoder "~1.1.1"
|
string_decoder "~1.1.1"
|
||||||
util-deprecate "~1.0.1"
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
readable-stream@~1.0.17, readable-stream@~1.0.27-1, readable-stream@~1.0.31:
|
|
||||||
version "1.0.34"
|
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
|
|
||||||
integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
|
|
||||||
dependencies:
|
|
||||||
core-util-is "~1.0.0"
|
|
||||||
inherits "~2.0.1"
|
|
||||||
isarray "0.0.1"
|
|
||||||
string_decoder "~0.10.x"
|
|
||||||
|
|
||||||
regenerator-runtime@^0.13.4:
|
regenerator-runtime@^0.13.4:
|
||||||
version "0.13.9"
|
version "0.13.9"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||||
@ -1492,20 +1400,6 @@ strip-final-newline@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||||
|
|
||||||
style-to-js@1.1.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.0.tgz#631cbb20fce204019b3aa1fcb5b69d951ceac4ac"
|
|
||||||
integrity sha512-1OqefPDxGrlMwcbfpsTVRyzwdhr4W0uxYQzeA2F1CBc8WG04udg2+ybRnvh3XYL4TdHQrCahLtax2jc8xaE6rA==
|
|
||||||
dependencies:
|
|
||||||
style-to-object "0.3.0"
|
|
||||||
|
|
||||||
style-to-object@0.3.0:
|
|
||||||
version "0.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46"
|
|
||||||
integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==
|
|
||||||
dependencies:
|
|
||||||
inline-style-parser "0.1.1"
|
|
||||||
|
|
||||||
stylis@^4.0.3:
|
stylis@^4.0.3:
|
||||||
version "4.0.10"
|
version "4.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240"
|
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240"
|
||||||
@ -1540,14 +1434,6 @@ through2@^2.0.1:
|
|||||||
readable-stream "~2.3.6"
|
readable-stream "~2.3.6"
|
||||||
xtend "~4.0.1"
|
xtend "~4.0.1"
|
||||||
|
|
||||||
through2@~0.4.1:
|
|
||||||
version "0.4.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b"
|
|
||||||
integrity sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=
|
|
||||||
dependencies:
|
|
||||||
readable-stream "~1.0.17"
|
|
||||||
xtend "~2.1.1"
|
|
||||||
|
|
||||||
through@^2.3.8:
|
through@^2.3.8:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||||
@ -1593,13 +1479,13 @@ tslib@^2.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||||
|
|
||||||
tss-react@^1.1.0:
|
tss-react@^3.0.0:
|
||||||
version "1.1.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-1.1.0.tgz#bbbf12b3d30eb02e1a39ac6dba4ca05a9c6bb674"
|
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-3.0.0.tgz#b084e1d10d1ea23c925b6a2141c1e91a0c5e9a2d"
|
||||||
integrity sha512-0UUQaMCbefyXsHesnScLwmoo6lD5sdAVR1h5dgIvCOFTk0i5A5a68K2B9gm89hQFfIKPl7fzolCsJ+G9RE/vpw==
|
integrity sha512-aZ/DZEuUvqki/1TKKBM4OmRx6TI5lcaF4BLMo0D8lyT/5S7zRFaAdVfAlsirHcQNgOAdf5IjLUcEbCYWcY6PJw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@emotion/server" "^11.4.0"
|
"@emotion/serialize" "*"
|
||||||
html-react-parser "^1.2.7"
|
"@emotion/utils" "*"
|
||||||
|
|
||||||
type-fest@^0.21.3:
|
type-fest@^0.21.3:
|
||||||
version "0.21.3"
|
version "0.21.3"
|
||||||
@ -1677,13 +1563,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"
|
||||||
@ -1742,13 +1621,6 @@ xtend@^4.0.1, xtend@~4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
xtend@~2.1.1:
|
|
||||||
version "2.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b"
|
|
||||||
integrity sha1-bv7MKk2tjmlixJAbM3znuoe10os=
|
|
||||||
dependencies:
|
|
||||||
object-keys "~0.4.0"
|
|
||||||
|
|
||||||
y18n@^5.0.5:
|
y18n@^5.0.5:
|
||||||
version "5.0.8"
|
version "5.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||||
|
Reference in New Issue
Block a user