keycloakify 11.4.0-rc.0 → 11.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/account/i18n/noJsx/getI18n.js +5 -7
- package/account/i18n/noJsx/getI18n.js.map +1 -1
- package/account/i18n/withJsx/useI18n.js +15 -4
- package/account/i18n/withJsx/useI18n.js.map +1 -1
- package/bin/20.index.js +0 -85
- package/bin/33.index.js +55 -212
- package/bin/356.index.js +106 -45
- package/bin/36.index.js +1 -2922
- package/bin/{526.index.js → 392.index.js} +107 -3
- package/bin/40.index.js +1 -86
- package/bin/453.index.js +85 -22
- package/bin/573.index.js +5 -86
- package/bin/653.index.js +752 -0
- package/bin/658.index.js +197 -0
- package/bin/735.index.js +343 -116
- package/bin/786.index.js +78 -14
- package/bin/805.index.js +15 -18
- package/bin/921.index.js +79 -18
- package/bin/97.index.js +64 -13
- package/bin/main.js +165 -86
- package/bin/shared/constants.d.ts +2 -0
- package/bin/shared/constants.js +3 -1
- package/bin/shared/constants.js.map +1 -1
- package/bin/start-keycloak/startViteDevServer.d.ts +8 -0
- package/bin/tools/crawlAsync.d.ts +1 -1
- package/bin/tools/isTrackedByGit.d.ts +3 -0
- package/bin/tools/runPrettier.d.ts +5 -5
- package/bin/tools/untrackFromGit.d.ts +3 -0
- package/lib/getKcClsx.js +1 -2
- package/lib/getKcClsx.js.map +1 -1
- package/login/KcContext/KcContext.d.ts +2 -2
- package/login/KcContext/kcContextMocks.d.ts +1 -1
- package/login/TemplateProps.d.ts +0 -1
- package/login/i18n/noJsx/getI18n.js +5 -7
- package/login/i18n/noJsx/getI18n.js.map +1 -1
- package/login/i18n/withJsx/useI18n.js +15 -4
- package/login/i18n/withJsx/useI18n.js.map +1 -1
- package/login/pages/LoginConfigTotp.js +1 -1
- package/login/pages/LoginConfigTotp.js.map +1 -1
- package/login/pages/LoginPasskeysConditionalAuthenticate.js +16 -18
- package/login/pages/LoginPasskeysConditionalAuthenticate.js.map +1 -1
- package/package.json +17 -7
- package/src/account/i18n/noJsx/getI18n.tsx +5 -7
- package/src/account/i18n/withJsx/useI18n.tsx +18 -4
- package/src/bin/eject-page.ts +28 -10
- package/src/bin/initialize-account-theme/initializeAccountTheme_singlePage.ts +3 -18
- package/src/bin/initialize-account-theme/updateAccountThemeImplementationInConfig.ts +1 -2
- package/src/bin/keycloakify/generateFtl/generateFtl.ts +6 -1
- package/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl +39 -0
- package/src/bin/keycloakify/generateResources/generateResources.ts +6 -2
- package/src/bin/main.ts +51 -4
- package/src/bin/postinstall/getUiModuleFileSourceCodeReadyToBeCopied.ts +30 -19
- package/src/bin/postinstall/installUiModulesPeerDependencies.ts +3 -3
- package/src/bin/postinstall/managedGitignoreFile.ts +4 -3
- package/src/bin/postinstall/postinstall.ts +24 -2
- package/src/bin/postinstall/uiModuleMeta.ts +14 -9
- package/src/bin/shared/buildContext.ts +1 -2
- package/src/bin/shared/constants.ts +4 -1
- package/src/bin/start-keycloak/myrealm-realm-25.json +21 -34
- package/src/bin/start-keycloak/myrealm-realm-26.json +32 -35
- package/src/bin/start-keycloak/start-keycloak.ts +94 -1
- package/src/bin/start-keycloak/startViteDevServer.ts +66 -0
- package/src/bin/tools/crawlAsync.ts +6 -6
- package/src/bin/tools/isTrackedByGit.ts +29 -0
- package/src/bin/tools/listInstalledModules.ts +1 -2
- package/src/bin/tools/npmInstall.ts +396 -1
- package/src/bin/tools/runPrettier.ts +63 -14
- package/src/bin/tools/untrackFromGit.ts +24 -0
- package/src/bin/tsconfig.json +1 -1
- package/src/bin/update-kc-gen.ts +17 -2
- package/src/lib/getKcClsx.ts +1 -2
- package/src/login/KcContext/KcContext.ts +2 -2
- package/src/login/KcContext/kcContextMocks.ts +1 -1
- package/src/login/TemplateProps.ts +0 -1
- package/src/login/i18n/noJsx/getI18n.tsx +5 -7
- package/src/login/i18n/withJsx/useI18n.tsx +18 -4
- package/src/login/pages/LoginConfigTotp.tsx +18 -20
- package/src/login/pages/LoginPasskeysConditionalAuthenticate.tsx +51 -52
- package/src/tools/StatefulObservable/StatefulObservable.ts +1 -2
- package/src/tools/deepAssign.ts +1 -2
- package/src/tools/extractLastParenthesisContent.ts +43 -0
- package/src/vite-plugin/vite-plugin.ts +67 -0
- package/stories/login/pages/Info.stories.tsx +3 -39
- package/tools/StatefulObservable/StatefulObservable.js +1 -2
- package/tools/StatefulObservable/StatefulObservable.js.map +1 -1
- package/tools/deepAssign.js +1 -2
- package/tools/deepAssign.js.map +1 -1
- package/tools/extractLastParenthesisContent.d.ts +6 -0
- package/tools/extractLastParenthesisContent.js +36 -0
- package/tools/extractLastParenthesisContent.js.map +1 -0
- package/vite-plugin/index.js +326 -186
- package/bin/124.index.js +0 -348
- package/stories/login/pages/LoginDeviceVerifyUserCode.stories.tsx +0 -18
@@ -47,11 +47,25 @@ export function createUseI18n<
|
|
47
47
|
|
48
48
|
function renderHtmlString(params: { htmlString: string; msgKey: string }): JSX.Element {
|
49
49
|
const { htmlString, msgKey } = params;
|
50
|
+
|
51
|
+
const htmlString_sanitized = kcSanitize(htmlString);
|
52
|
+
|
53
|
+
const Element = (() => {
|
54
|
+
if (htmlString_sanitized.includes("<") && htmlString_sanitized.includes(">")) {
|
55
|
+
for (const tagName of ["div", "section", "article", "ul", "ol"]) {
|
56
|
+
if (htmlString_sanitized.includes(`<${tagName}`)) {
|
57
|
+
return "div";
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
return "span";
|
62
|
+
})();
|
63
|
+
|
50
64
|
return (
|
51
|
-
<
|
65
|
+
<Element
|
52
66
|
data-kc-msg={msgKey}
|
53
67
|
dangerouslySetInnerHTML={{
|
54
|
-
__html:
|
68
|
+
__html: htmlString_sanitized
|
55
69
|
}}
|
56
70
|
/>
|
57
71
|
);
|
@@ -83,7 +97,7 @@ export function createUseI18n<
|
|
83
97
|
})();
|
84
98
|
|
85
99
|
add_style: {
|
86
|
-
const attributeName = "data-kc-
|
100
|
+
const attributeName = "data-kc-msg";
|
87
101
|
|
88
102
|
// Check if already exists in head
|
89
103
|
if (document.querySelector(`style[${attributeName}]`) !== null) {
|
@@ -92,7 +106,7 @@ export function createUseI18n<
|
|
92
106
|
|
93
107
|
const styleElement = document.createElement("style");
|
94
108
|
styleElement.attributes.setNamedItem(document.createAttribute(attributeName));
|
95
|
-
styleElement.textContent = `[
|
109
|
+
styleElement.textContent = `div[${attributeName}] { display: inline-block; }`;
|
96
110
|
document.head.prepend(styleElement);
|
97
111
|
}
|
98
112
|
|
@@ -52,28 +52,26 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
|
52
52
|
</li>
|
53
53
|
<li>
|
54
54
|
<p>{msg("loginTotpManualStep3")}</p>
|
55
|
-
<
|
56
|
-
<
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
55
|
+
<ul>
|
56
|
+
<li id="kc-totp-type">
|
57
|
+
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
|
58
|
+
</li>
|
59
|
+
<li id="kc-totp-algorithm">
|
60
|
+
{msg("loginTotpAlgorithm")}: {totp.policy.getAlgorithmKey()}
|
61
|
+
</li>
|
62
|
+
<li id="kc-totp-digits">
|
63
|
+
{msg("loginTotpDigits")}: {totp.policy.digits}
|
64
|
+
</li>
|
65
|
+
{totp.policy.type === "totp" ? (
|
66
|
+
<li id="kc-totp-period">
|
67
|
+
{msg("loginTotpInterval")}: {totp.policy.period}
|
62
68
|
</li>
|
63
|
-
|
64
|
-
|
69
|
+
) : (
|
70
|
+
<li id="kc-totp-counter">
|
71
|
+
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
|
65
72
|
</li>
|
66
|
-
|
67
|
-
|
68
|
-
{msg("loginTotpInterval")}: {totp.policy.period}
|
69
|
-
</li>
|
70
|
-
) : (
|
71
|
-
<li id="kc-totp-counter">
|
72
|
-
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
|
73
|
-
</li>
|
74
|
-
)}
|
75
|
-
</ul>
|
76
|
-
</p>
|
73
|
+
)}
|
74
|
+
</ul>
|
77
75
|
</li>
|
78
76
|
</>
|
79
77
|
) : (
|
@@ -115,62 +115,61 @@ export default function LoginPasskeysConditionalAuthenticate(
|
|
115
115
|
</div>
|
116
116
|
</>
|
117
117
|
)}
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
118
|
+
</>
|
119
|
+
)}
|
120
|
+
<div id="kc-form">
|
121
|
+
<div id="kc-form-wrapper">
|
122
|
+
{realm.password && (
|
123
|
+
<form
|
124
|
+
id="kc-form-login"
|
125
|
+
action={url.loginAction}
|
126
|
+
method="post"
|
127
|
+
style={{ display: "none" }}
|
128
|
+
onSubmit={event => {
|
129
|
+
try {
|
130
|
+
// @ts-expect-error
|
131
|
+
event.target.login.disabled = true;
|
132
|
+
} catch {}
|
131
133
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
</span>
|
156
|
-
)}
|
157
|
-
</div>
|
134
|
+
return true;
|
135
|
+
}}
|
136
|
+
>
|
137
|
+
{!usernameHidden && (
|
138
|
+
<div className={kcClsx("kcFormGroupClass")}>
|
139
|
+
<label htmlFor="username" className={kcClsx("kcLabelClass")}>
|
140
|
+
{msg("passkey-autofill-select")}
|
141
|
+
</label>
|
142
|
+
<input
|
143
|
+
tabIndex={1}
|
144
|
+
id="username"
|
145
|
+
aria-invalid={messagesPerField.existsError("username")}
|
146
|
+
className={kcClsx("kcInputClass")}
|
147
|
+
name="username"
|
148
|
+
defaultValue={login.username ?? ""}
|
149
|
+
autoComplete="username webauthn"
|
150
|
+
type="text"
|
151
|
+
autoFocus
|
152
|
+
/>
|
153
|
+
{messagesPerField.existsError("username") && (
|
154
|
+
<span id="input-error-username" className={kcClsx("kcInputErrorMessageClass")} aria-live="polite">
|
155
|
+
{messagesPerField.get("username")}
|
156
|
+
</span>
|
158
157
|
)}
|
159
|
-
</
|
158
|
+
</div>
|
160
159
|
)}
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
160
|
+
</form>
|
161
|
+
)}
|
162
|
+
<div id="kc-form-passkey-button" className={kcClsx("kcFormButtonsClass")} style={{ display: "none" }}>
|
163
|
+
<input
|
164
|
+
id={authButtonId}
|
165
|
+
type="button"
|
166
|
+
autoFocus
|
167
|
+
value={msgStr("passkey-doAuthenticate")}
|
168
|
+
className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonBlockClass", "kcButtonLargeClass")}
|
169
|
+
/>
|
171
170
|
</div>
|
172
|
-
|
173
|
-
|
171
|
+
</div>
|
172
|
+
</div>
|
174
173
|
</div>
|
175
174
|
</Template>
|
176
175
|
);
|
package/src/tools/deepAssign.ts
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
/**
|
2
|
+
* "Hello (world)" => "world"
|
3
|
+
* "Hello (world) (foo)" => "foo"
|
4
|
+
* "Hello (world (foo))" => "world (foo)"
|
5
|
+
*/
|
6
|
+
export function extractLastParenthesisContent(str: string): string | undefined {
|
7
|
+
const chars: string[] = [];
|
8
|
+
|
9
|
+
for (const char of str) {
|
10
|
+
chars.push(char);
|
11
|
+
}
|
12
|
+
|
13
|
+
const extractedChars: string[] = [];
|
14
|
+
let openingCount = 0;
|
15
|
+
|
16
|
+
loop_through_char: for (let i = chars.length - 1; i >= 0; i--) {
|
17
|
+
const char = chars[i];
|
18
|
+
|
19
|
+
if (i === chars.length - 1) {
|
20
|
+
if (char !== ")") {
|
21
|
+
return undefined;
|
22
|
+
}
|
23
|
+
|
24
|
+
continue;
|
25
|
+
}
|
26
|
+
|
27
|
+
switch (char) {
|
28
|
+
case ")":
|
29
|
+
openingCount++;
|
30
|
+
break;
|
31
|
+
case "(":
|
32
|
+
if (openingCount === 0) {
|
33
|
+
return extractedChars.join("");
|
34
|
+
}
|
35
|
+
openingCount--;
|
36
|
+
break;
|
37
|
+
}
|
38
|
+
|
39
|
+
extractedChars.unshift(char);
|
40
|
+
}
|
41
|
+
|
42
|
+
return undefined;
|
43
|
+
}
|
@@ -212,6 +212,73 @@ export function keycloakify(params: keycloakify.Params) {
|
|
212
212
|
force: true
|
213
213
|
}
|
214
214
|
);
|
215
|
+
},
|
216
|
+
transformIndexHtml: html => {
|
217
|
+
const doReadKcContextFromUrl =
|
218
|
+
process.env.NODE_ENV === "development" &&
|
219
|
+
process.env[
|
220
|
+
VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.READ_KC_CONTEXT_FROM_URL
|
221
|
+
] === "true";
|
222
|
+
|
223
|
+
if (!doReadKcContextFromUrl) {
|
224
|
+
return html;
|
225
|
+
}
|
226
|
+
|
227
|
+
const scriptContent = `
|
228
|
+
(()=>{
|
229
|
+
|
230
|
+
const kcContext = (()=>{
|
231
|
+
|
232
|
+
const paramName= "kcContext";
|
233
|
+
|
234
|
+
read_from_url_case: {
|
235
|
+
|
236
|
+
const url = new URL(window.location.href);
|
237
|
+
|
238
|
+
const paramValue = url.searchParams.get(paramName);
|
239
|
+
|
240
|
+
if( paramValue === null ){
|
241
|
+
break read_from_url_case;
|
242
|
+
}
|
243
|
+
|
244
|
+
url.searchParams.delete(paramName);
|
245
|
+
|
246
|
+
window.history.replaceState({}, "", url);
|
247
|
+
|
248
|
+
const kcContext = JSON.parse(decodeURIComponent(paramValue));
|
249
|
+
|
250
|
+
sessionStorage.setItem(paramName, JSON.stringify(kcContext));
|
251
|
+
|
252
|
+
return kcContext;
|
253
|
+
|
254
|
+
}
|
255
|
+
|
256
|
+
read_from_session_storage_case: {
|
257
|
+
|
258
|
+
const paramValue = sessionStorage.getItem(paramName);
|
259
|
+
|
260
|
+
if( paramValue === null ){
|
261
|
+
break read_from_session_storage_case;
|
262
|
+
}
|
263
|
+
|
264
|
+
return JSON.parse(paramValue);
|
265
|
+
|
266
|
+
}
|
267
|
+
|
268
|
+
return undefined;
|
269
|
+
|
270
|
+
})();
|
271
|
+
|
272
|
+
if( kcContext === undefined ){
|
273
|
+
return;
|
274
|
+
}
|
275
|
+
|
276
|
+
window.kcContext = kcContext;
|
277
|
+
|
278
|
+
})();
|
279
|
+
`;
|
280
|
+
|
281
|
+
return html.replace(/<head>/, `<head><script>${scriptContent}</script>`);
|
215
282
|
}
|
216
283
|
} satisfies Plugin;
|
217
284
|
|
@@ -17,6 +17,7 @@ export const Default: Story = {
|
|
17
17
|
render: () => (
|
18
18
|
<KcPageStory
|
19
19
|
kcContext={{
|
20
|
+
messageHeader: "Message header",
|
20
21
|
message: {
|
21
22
|
summary: "Server info message"
|
22
23
|
}
|
@@ -29,6 +30,7 @@ export const WithLinkBack: Story = {
|
|
29
30
|
render: () => (
|
30
31
|
<KcPageStory
|
31
32
|
kcContext={{
|
33
|
+
messageHeader: "Message header",
|
32
34
|
message: {
|
33
35
|
summary: "Server message"
|
34
36
|
},
|
@@ -42,6 +44,7 @@ export const WithRequiredActions: Story = {
|
|
42
44
|
render: () => (
|
43
45
|
<KcPageStory
|
44
46
|
kcContext={{
|
47
|
+
messageHeader: "Message header",
|
45
48
|
message: {
|
46
49
|
summary: "Required actions: "
|
47
50
|
},
|
@@ -55,42 +58,3 @@ export const WithRequiredActions: Story = {
|
|
55
58
|
/>
|
56
59
|
)
|
57
60
|
};
|
58
|
-
export const WithPageRedirect: Story = {
|
59
|
-
render: () => (
|
60
|
-
<KcPageStory
|
61
|
-
kcContext={{
|
62
|
-
message: { summary: "You will be redirected shortly." },
|
63
|
-
pageRedirectUri: "https://example.com"
|
64
|
-
}}
|
65
|
-
/>
|
66
|
-
)
|
67
|
-
};
|
68
|
-
export const WithoutClientBaseUrl: Story = {
|
69
|
-
render: () => (
|
70
|
-
<KcPageStory
|
71
|
-
kcContext={{
|
72
|
-
message: { summary: "No client base URL defined." },
|
73
|
-
client: { baseUrl: undefined }
|
74
|
-
}}
|
75
|
-
/>
|
76
|
-
)
|
77
|
-
};
|
78
|
-
export const WithMessageHeader: Story = {
|
79
|
-
render: () => (
|
80
|
-
<KcPageStory
|
81
|
-
kcContext={{
|
82
|
-
messageHeader: "Important Notice",
|
83
|
-
message: { summary: "This is an important message." }
|
84
|
-
}}
|
85
|
-
/>
|
86
|
-
)
|
87
|
-
};
|
88
|
-
export const WithAdvancedMessage: Story = {
|
89
|
-
render: () => (
|
90
|
-
<KcPageStory
|
91
|
-
kcContext={{
|
92
|
-
message: { summary: "Please take note of this <strong>important</strong> information." }
|
93
|
-
}}
|
94
|
-
/>
|
95
|
-
)
|
96
|
-
};
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"StatefulObservable.js","sourceRoot":"","sources":["../../src/tools/StatefulObservable/StatefulObservable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,
|
1
|
+
{"version":3,"file":"StatefulObservable.js","sourceRoot":"","sources":["../../src/tools/StatefulObservable/StatefulObservable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAW1C,MAAM,UAAU,wBAAwB,CACpC,eAAwB;IAExB,MAAM,aAAa,GAA0B,EAAE,CAAC;IAEhD,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE;QACvB,IAAI,YAAY,GAAoB,SAAS,CAAC;QAE9C,SAAS,GAAG,CAAC,IAAO;YAChB,YAAY,GAAG,CAAC,IAAI,CAAC,CAAC;YAEtB,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,CAAC;QAED,OAAO;YACH,GAAG,EAAE,GAAG,EAAE;gBACN,IAAI,YAAY,KAAK,SAAS,EAAE;oBAC5B,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC;oBACvB,MAAM,CAAC,CAAC,EAAE,CAAY,YAAY,CAAC,CAAC,CAAC;iBACxC;gBACD,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC;YAC3B,CAAC;YACD,GAAG;SACN,CAAC;IACN,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,MAAM,CAAC,cAAc,CACxB;QACI,OAAO,EAAE,IAAgB;QACzB,SAAS,EAAE,CAAC,IAAuB,EAAE,EAAE;YACnC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEzB,OAAO;gBACH,WAAW,EAAE,GAAG,EAAE,CACd,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;aAC3D,CAAC;QACN,CAAC;KACJ,EACD,SAAS,EACT;QACI,UAAU,EAAE,IAAI;QAChB,GAAG;QACH,GAAG;KACN,CACJ,CAAC;AACN,CAAC"}
|
package/tools/deepAssign.js
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
import { assert } from "tsafe/assert";
|
2
|
-
import { is } from "tsafe/is";
|
1
|
+
import { assert, is } from "tsafe/assert";
|
3
2
|
import { structuredCloneButFunctions } from "./structuredCloneButFunctions";
|
4
3
|
/** NOTE: Array a copied over, not merged. */
|
5
4
|
export function deepAssign(params) {
|
package/tools/deepAssign.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"deepAssign.js","sourceRoot":"","sources":["../src/tools/deepAssign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,
|
1
|
+
{"version":3,"file":"deepAssign.js","sourceRoot":"","sources":["../src/tools/deepAssign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAE5E,6CAA6C;AAC7C,MAAM,UAAU,UAAU,CAAC,MAG1B;IACG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAElC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QAC9B,IAAI,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAErC,IAAI,kBAAkB,KAAK,SAAS,EAAE;YAClC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;YACnB,OAAO;SACV;QAED,IAAI,kBAAkB,YAAY,IAAI,EAAE;YACpC,MAAM,CAAC;gBACH,MAAM;gBACN,GAAG;gBACH,KAAK,EAAE,IAAI,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE,CAAC;aAChD,CAAC,CAAC;YAEH,OAAO;SACV;QAED,IAAI,kBAAkB,YAAY,KAAK,EAAE;YACrC,MAAM,CAAC;gBACH,MAAM;gBACN,GAAG;gBACH,KAAK,EAAE,2BAA2B,CAAC,kBAAkB,CAAC;aACzD,CAAC,CAAC;YAEH,OAAO;SACV;QAED,IACI,kBAAkB,YAAY,QAAQ;YACtC,CAAC,CAAC,kBAAkB,YAAY,MAAM,CAAC,EACzC;YACE,MAAM,CAAC;gBACH,MAAM;gBACN,GAAG;gBACH,KAAK,EAAE,kBAAkB;aAC5B,CAAC,CAAC;YAEH,OAAO;SACV;QAED,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,EAAE;YAClC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;SACpB;QAED,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAEvC,MAAM,CAAC,EAAE,CAA0B,kBAAkB,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,EAAE,CAA0B,kBAAkB,CAAC,CAAC,CAAC;QAExD,UAAU,CAAC;YACP,MAAM,EAAE,kBAAkB;YAC1B,MAAM,EAAE,kBAAkB;SAC7B,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,MAAM,CAAC,MAIf;IACG,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAEtC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;QAC/B,UAAU,EAAE,IAAI;QAChB,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,IAAI;QAClB,KAAK;KACR,CAAC,CAAC;AACP,CAAC"}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
/**
|
2
|
+
* "Hello (world)" => "world"
|
3
|
+
* "Hello (world) (foo)" => "foo"
|
4
|
+
* "Hello (world (foo))" => "world (foo)"
|
5
|
+
*/
|
6
|
+
export function extractLastParenthesisContent(str) {
|
7
|
+
const chars = [];
|
8
|
+
for (const char of str) {
|
9
|
+
chars.push(char);
|
10
|
+
}
|
11
|
+
const extractedChars = [];
|
12
|
+
let openingCount = 0;
|
13
|
+
loop_through_char: for (let i = chars.length - 1; i >= 0; i--) {
|
14
|
+
const char = chars[i];
|
15
|
+
if (i === chars.length - 1) {
|
16
|
+
if (char !== ")") {
|
17
|
+
return undefined;
|
18
|
+
}
|
19
|
+
continue;
|
20
|
+
}
|
21
|
+
switch (char) {
|
22
|
+
case ")":
|
23
|
+
openingCount++;
|
24
|
+
break;
|
25
|
+
case "(":
|
26
|
+
if (openingCount === 0) {
|
27
|
+
return extractedChars.join("");
|
28
|
+
}
|
29
|
+
openingCount--;
|
30
|
+
break;
|
31
|
+
}
|
32
|
+
extractedChars.unshift(char);
|
33
|
+
}
|
34
|
+
return undefined;
|
35
|
+
}
|
36
|
+
//# sourceMappingURL=extractLastParenthesisContent.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"extractLastParenthesisContent.js","sourceRoot":"","sources":["../src/tools/extractLastParenthesisContent.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,GAAW;IACrD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE;QACpB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACpB;IAED,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,iBAAiB,EAAE,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;QAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAEtB,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;YACxB,IAAI,IAAI,KAAK,GAAG,EAAE;gBACd,OAAO,SAAS,CAAC;aACpB;YAED,SAAS;SACZ;QAED,QAAQ,IAAI,EAAE;YACV,KAAK,GAAG;gBACJ,YAAY,EAAE,CAAC;gBACf,MAAM;YACV,KAAK,GAAG;gBACJ,IAAI,YAAY,KAAK,CAAC,EAAE;oBACpB,OAAO,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;iBAClC;gBACD,YAAY,EAAE,CAAC;gBACf,MAAM;SACb;QAED,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;KAChC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC"}
|