Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
ea22107b9b | |||
8e4a7fed9e | |||
30efd8fcf4 | |||
f4c4e92ca1 | |||
cfda99f5b0 | |||
5063b1c7ab | |||
955b6cac45 | |||
1fa3d6133c |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "11.3.26",
|
||||
"version": "11.3.29",
|
||||
"description": "Framework to create custom Keycloak UIs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -33,7 +33,7 @@ export type KcContext =
|
||||
| KcContext.LoginResetPassword
|
||||
| KcContext.LoginVerifyEmail
|
||||
| KcContext.Terms
|
||||
| KcContext.LoginDeviceVerifyUserCode
|
||||
| KcContext.LoginOauth2DeviceVerifyUserCode
|
||||
| KcContext.LoginOauthGrant
|
||||
| KcContext.LoginOtp
|
||||
| KcContext.LoginUsername
|
||||
@ -277,7 +277,7 @@ export declare namespace KcContext {
|
||||
__localizationRealmOverridesTermsText?: string;
|
||||
};
|
||||
|
||||
export type LoginDeviceVerifyUserCode = Common & {
|
||||
export type LoginOauth2DeviceVerifyUserCode = Common & {
|
||||
pageId: "login-oauth2-device-verify-user-code.ftl";
|
||||
url: {
|
||||
oauth2DeviceVerificationAction: string;
|
||||
|
@ -290,7 +290,7 @@ export const kcContextMocks = [
|
||||
...kcContextCommonMock,
|
||||
pageId: "terms.ftl"
|
||||
}),
|
||||
id<KcContext.LoginDeviceVerifyUserCode>({
|
||||
id<KcContext.LoginOauth2DeviceVerifyUserCode>({
|
||||
...kcContextCommonMock,
|
||||
pageId: "login-oauth2-device-verify-user-code.ftl",
|
||||
url: loginUrl
|
||||
|
@ -11,7 +11,6 @@ export type TemplateProps<KcContext, I18n> = {
|
||||
displayInfo?: boolean;
|
||||
displayMessage?: boolean;
|
||||
displayRequiredFields?: boolean;
|
||||
showAnotherWayIfPresent?: boolean;
|
||||
headerNode: ReactNode;
|
||||
socialProvidersNode?: ReactNode;
|
||||
infoNode?: ReactNode;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import "keycloakify/tools/Object.fromEntries";
|
||||
import { assert, is } from "tsafe/assert";
|
||||
import { extractLastParenthesisContent } from "keycloakify/tools/extractLastParenthesisContent";
|
||||
import messages_defaultSet_fallbackLanguage from "../messages_defaultSet/en";
|
||||
import { fetchMessages_defaultSet } from "../messages_defaultSet";
|
||||
import type { KcContext } from "../../KcContext";
|
||||
@ -168,12 +169,10 @@ export function createGetI18n<
|
||||
break from_server;
|
||||
}
|
||||
|
||||
// cspell: disable-next-line
|
||||
// from "Espagnol (Español)" we want to extract "Español"
|
||||
const match = supportedEntry.label.match(/[^(]+\(([^)]+)\)/);
|
||||
const lastParenthesisContent = extractLastParenthesisContent(supportedEntry.label);
|
||||
|
||||
if (match !== null) {
|
||||
return match[1];
|
||||
if (lastParenthesisContent !== undefined) {
|
||||
return lastParenthesisContent;
|
||||
}
|
||||
|
||||
return supportedEntry.label;
|
||||
|
@ -47,11 +47,25 @@ export function createUseI18n<
|
||||
|
||||
function renderHtmlString(params: { htmlString: string; msgKey: string }): JSX.Element {
|
||||
const { htmlString, msgKey } = params;
|
||||
|
||||
const htmlString_sanitized = kcSanitize(htmlString);
|
||||
|
||||
const Element = (() => {
|
||||
if (htmlString_sanitized.includes("<") && htmlString_sanitized.includes(">")) {
|
||||
for (const tagName of ["div", "section", "article", "ul", "ol"]) {
|
||||
if (htmlString_sanitized.includes(`<${tagName}`)) {
|
||||
return "div";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "span";
|
||||
})();
|
||||
|
||||
return (
|
||||
<div
|
||||
<Element
|
||||
data-kc-msg={msgKey}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(htmlString)
|
||||
__html: htmlString_sanitized
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -83,7 +97,7 @@ export function createUseI18n<
|
||||
})();
|
||||
|
||||
add_style: {
|
||||
const attributeName = "data-kc-i18n";
|
||||
const attributeName = "data-kc-msg";
|
||||
|
||||
// Check if already exists in head
|
||||
if (document.querySelector(`style[${attributeName}]`) !== null) {
|
||||
@ -92,7 +106,7 @@ export function createUseI18n<
|
||||
|
||||
const styleElement = document.createElement("style");
|
||||
styleElement.attributes.setNamedItem(document.createAttribute(attributeName));
|
||||
styleElement.textContent = `[data-kc-msg] { display: inline-block; }`;
|
||||
styleElement.textContent = `div[${attributeName}] { display: inline-block; }`;
|
||||
document.head.prepend(styleElement);
|
||||
}
|
||||
|
||||
|
@ -52,28 +52,26 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
||||
</li>
|
||||
<li>
|
||||
<p>{msg("loginTotpManualStep3")}</p>
|
||||
<p>
|
||||
<ul>
|
||||
<li id="kc-totp-type">
|
||||
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
|
||||
<ul>
|
||||
<li id="kc-totp-type">
|
||||
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
|
||||
</li>
|
||||
<li id="kc-totp-algorithm">
|
||||
{msg("loginTotpAlgorithm")}: {totp.policy.getAlgorithmKey()}
|
||||
</li>
|
||||
<li id="kc-totp-digits">
|
||||
{msg("loginTotpDigits")}: {totp.policy.digits}
|
||||
</li>
|
||||
{totp.policy.type === "totp" ? (
|
||||
<li id="kc-totp-period">
|
||||
{msg("loginTotpInterval")}: {totp.policy.period}
|
||||
</li>
|
||||
<li id="kc-totp-algorithm">
|
||||
{msg("loginTotpAlgorithm")}: {totp.policy.getAlgorithmKey()}
|
||||
) : (
|
||||
<li id="kc-totp-counter">
|
||||
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
|
||||
</li>
|
||||
<li id="kc-totp-digits">
|
||||
{msg("loginTotpDigits")}: {totp.policy.digits}
|
||||
</li>
|
||||
{totp.policy.type === "totp" ? (
|
||||
<li id="kc-totp-period">
|
||||
{msg("loginTotpInterval")}: {totp.policy.period}
|
||||
</li>
|
||||
) : (
|
||||
<li id="kc-totp-counter">
|
||||
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</p>
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
</>
|
||||
) : (
|
||||
|
43
src/tools/extractLastParenthesisContent.ts
Normal file
43
src/tools/extractLastParenthesisContent.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* "Hello (world)" => "world"
|
||||
* "Hello (world) (foo)" => "foo"
|
||||
* "Hello (world (foo))" => "world (foo)"
|
||||
*/
|
||||
export function extractLastParenthesisContent(str: string): string | undefined {
|
||||
const chars: string[] = [];
|
||||
|
||||
for (const char of str) {
|
||||
chars.push(char);
|
||||
}
|
||||
|
||||
const extractedChars: string[] = [];
|
||||
let openingCount = 0;
|
||||
|
||||
loop_through_char: for (let i = chars.length - 1; i >= 0; i--) {
|
||||
const char = chars[i];
|
||||
|
||||
if (i === chars.length - 1) {
|
||||
if (char !== ")") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (char) {
|
||||
case ")":
|
||||
openingCount++;
|
||||
break;
|
||||
case "(":
|
||||
if (openingCount === 0) {
|
||||
return extractedChars.join("");
|
||||
}
|
||||
openingCount--;
|
||||
break;
|
||||
}
|
||||
|
||||
extractedChars.unshift(char);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
@ -17,6 +17,7 @@ export const Default: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
messageHeader: "Message header",
|
||||
message: {
|
||||
summary: "Server info message"
|
||||
}
|
||||
@ -29,6 +30,7 @@ export const WithLinkBack: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
messageHeader: "Message header",
|
||||
message: {
|
||||
summary: "Server message"
|
||||
},
|
||||
@ -42,6 +44,7 @@ export const WithRequiredActions: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
messageHeader: "Message header",
|
||||
message: {
|
||||
summary: "Required actions: "
|
||||
},
|
||||
@ -55,42 +58,3 @@ export const WithRequiredActions: Story = {
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithPageRedirect: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: { summary: "You will be redirected shortly." },
|
||||
pageRedirectUri: "https://example.com"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithoutClientBaseUrl: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: { summary: "No client base URL defined." },
|
||||
client: { baseUrl: undefined }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithMessageHeader: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
messageHeader: "Important Notice",
|
||||
message: { summary: "This is an important message." }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithAdvancedMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: { summary: "Please take note of this <strong>important</strong> information." }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
@ -1,18 +0,0 @@
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "login-device-verify-user-code.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/login-device-verify-user-code.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
Reference in New Issue
Block a user