Compare commits

...

6 commits
25.0.6 ... main

11 changed files with 319 additions and 241 deletions

View file

@ -21,8 +21,10 @@ html {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
margin: 0; margin: 0;
font-size: 20px; } font-size: 16px; }
@media screen and (min-width: 1200px) {
html {
font-size: 20px; } }
*:focus-visible { *:focus-visible {
outline: 0.2rem solid var(--accent); } outline: 0.2rem solid var(--accent); }
@ -57,23 +59,26 @@ html {
padding: 0rem 2rem; } padding: 0rem 2rem; }
.ps-button { .ps-button {
padding: 0.5rem 1rem;
font-size: 1rem; font-size: 1rem;
line-height: 1.2rem; padding: 0.5em 1em;
border: 2px solid var(--foreground); line-height: 1.2em;
border-radius: 1.5rem; border: 0.125em solid var(--foreground);
border-radius: 1.5em;
background-color: var(--background-darker-2); background-color: var(--background-darker-2);
cursor: pointer; } cursor: pointer; }
.ps-button:hover, .ps-button:focus { .ps-button:hover, .ps-button:focus {
border-color: var(--accent); } border-color: var(--accent); }
.ps-button_primary { .ps-button_primary {
border: 4px solid var(--foreground); border: 0.25em solid var(--foreground);
background-color: var(--background); background-color: var(--background);
color: var(--foreground); color: var(--foreground);
font-weight: bold; } font-weight: bold; }
.ps-button_primary:focus, .ps-button_primary:hover { .ps-button_primary:focus, .ps-button_primary:hover {
background-color: var(--foreground); background-color: var(--foreground);
color: var(--background); } color: var(--background); }
.ps-button_small {
font-size: 0.8rem;
padding: 0.25em 0.7em; }
.ps-input { .ps-input {
padding: 0.5rem 0.5rem; padding: 0.5rem 0.5rem;
@ -116,18 +121,20 @@ html {
display: flex; display: flex;
flex-direction: column; } flex-direction: column; }
.ps-form-group--label { .ps-form-group--label {
margin-bottom: 0.5rem; margin-bottom: 0.25rem;
display: flex; display: inline-block;
font-weight: bold; } font-weight: bold;
margin-top: 0.5rem; }
.ps-form-group .ps-button { .ps-form-group .ps-button {
align-self: flex-start; } align-self: flex-start; }
.ps-form-group--error { .ps-form-group--error {
margin-top: 0.25rem; margin-top: 0.25rem;
color: var(--accent); color: var(--accent);
font-weight: bold; } font-weight: bold; }
.ps-form-group--buttons {
margin: 0.5rem 0; }
.ps-homelink { .ps-homelink {
z-index: 100;
pointer-events: all; pointer-events: all;
color: var(--foreground); color: var(--foreground);
background: white; background: white;
@ -197,7 +204,9 @@ html {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 0; padding: 0;
margin: 0; } margin: 0;
overflow-x: auto;
z-index: 100; }
.ps-header--title { .ps-header--title {
font-size: 1.5rem; font-size: 1.5rem;
padding: 0 1rem; padding: 0 1rem;
@ -206,8 +215,9 @@ html {
background-color: var(--background); background-color: var(--background);
border-right: 0.5rem solid var(--foreground); border-right: 0.5rem solid var(--foreground);
pointer-events: all; } pointer-events: all; }
.ps-header--i18n {
margin-left: auto; }
.ps-header--nav { .ps-header--nav {
margin-left: auto;
display: flex; display: flex;
border-bottom: 0.5rem solid var(--foreground); border-bottom: 0.5rem solid var(--foreground);
border-left: 0.5rem solid var(--foreground); border-left: 0.5rem solid var(--foreground);
@ -260,6 +270,9 @@ html {
border-bottom: 0.5rem solid var(--foreground); border-bottom: 0.5rem solid var(--foreground);
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
margin: 2rem; } margin: 2rem; }
.ps-page--subtitle {
padding-bottom: 0.25rem;
margin: 2rem; }
.ps-page--section { .ps-page--section {
border: 12px solid black; border: 12px solid black;
margin-top: 2rem; margin-top: 2rem;
@ -357,7 +370,8 @@ html {
left: 0; left: 0;
width: 100%; width: 100%;
background-color: var(--background); background-color: var(--background);
border-bottom: 2px solid var(--foreground); } border-bottom: 2px solid var(--foreground);
overflow-x: auto; }
.ps-section-nav--list { .ps-section-nav--list {
list-style: none; list-style: none;
display: flex; display: flex;

View file

@ -3,9 +3,10 @@
flex-direction: column; flex-direction: column;
&--label { &--label {
margin-bottom: 0.5rem; margin-bottom: 0.25rem;
display: flex; display: inline-block;
font-weight: bold; font-weight: bold;
margin-top: 0.5rem;
} }
.ps-button { .ps-button {
@ -18,4 +19,8 @@
font-weight: bold; font-weight: bold;
// font-family: monospace; // font-family: monospace;
} }
&--buttons {
margin: 0.5rem 0;
};
} }

View file

@ -36,6 +36,11 @@
margin: 2rem; margin: 2rem;
} }
&--subtitle {
padding-bottom: 0.25rem;
margin: 2rem;
};
&--section { &--section {
border: 12px solid black; border: 12px solid black;
margin-top: 2rem; margin-top: 2rem;

View file

@ -1,83 +1,12 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','email','firstName','lastName'); section> <#import "user-profile-commons.ftl" as userProfileCommons>
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
<#if section = "header"> <#if section = "header">
${msg("loginProfileTitle")} ${msg("loginProfileTitle")}
<#elseif section = "form"> <#elseif section = "form">
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post"> <form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<#if user.editUsernameAllowed>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="username" name="username" value="${(user.username!'')}"
class="${properties.kcInputClass!}"
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
/>
<#if messagesPerField.existsError('username')> <@userProfileCommons.userProfileFormFields/>
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('username'))?no_esc}
</span>
</#if>
</div>
</div>
</#if>
<#if user.editEmailAllowed>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="email" name="email" value="${(user.email!'')}"
class="${properties.kcInputClass!}"
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
/>
<#if messagesPerField.existsError('email')>
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('email'))?no_esc}
</span>
</#if>
</div>
</div>
</#if>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="firstName" name="firstName" value="${(user.firstName!'')}"
class="${properties.kcInputClass!}"
aria-invalid="<#if messagesPerField.existsError('firstName')>true</#if>"
/>
<#if messagesPerField.existsError('firstName')>
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('firstName'))?no_esc}
</span>
</#if>
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="lastName" name="lastName" value="${(user.lastName!'')}"
class="${properties.kcInputClass!}"
aria-invalid="<#if messagesPerField.existsError('lastName')>true</#if>"
/>
<#if messagesPerField.existsError('lastName')>
<span id="input-error-lastname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('lastName'))?no_esc}
</span>
</#if>
</div>
</div>
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}"> <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
@ -88,7 +17,7 @@
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}"> <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<#if isAppInitiatedAction??> <#if isAppInitiatedAction??>
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" /> <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" />${msg("doCancel")}</button> <button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" formnovalidate/>${msg("doCancel")}</button>
<#else> <#else>
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" /> <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
</#if> </#if>

View file

@ -1,74 +1,31 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section> <#import "user-profile-commons.ftl" as userProfileCommons>
<#import "register-commons.ftl" as registerCommons>
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
<#if section = "header"> <#if section = "header">
<#if messageHeader??>
${kcSanitize(msg("${messageHeader}"))?no_esc}
<#else>
${msg("registerTitle")} ${msg("registerTitle")}
</#if>
<#elseif section = "form"> <#elseif section = "form">
<form id="kc-register-form" class="ps-container" action="${url.registrationAction}" method="post"> <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
<!--div class="${properties.kcFormGroupClass!}">
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName"
value="${(register.formData.firstName!'')}"
aria-invalid="<#if messagesPerField.existsError('firstName')>true</#if>"
/>
<#if messagesPerField.existsError('firstName')> <@userProfileCommons.userProfileFormFields; callback, attribute>
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <#if callback = "afterField">
${kcSanitize(messagesPerField.get('firstName'))?no_esc} <#-- render password fields just under the username or email (if used as username) -->
</span> <#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))>
</#if> <div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> *
</div> </div>
<div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcInputGroup!}">
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName"
value="${(register.formData.lastName!'')}"
aria-invalid="<#if messagesPerField.existsError('lastName')>true</#if>"
/>
<#if messagesPerField.existsError('lastName')>
<span id="input-error-lastname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('lastName'))?no_esc}
</span>
</#if>
</div-->
<#if !realm.registrationEmailAsUsername>
<div class="${properties.kcFormGroupClass!}">
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
<input type="text" id="username" class="${properties.kcInputClass!}" name="username"
value="${(register.formData.username!'')}" autocomplete="username"
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
/>
<#if messagesPerField.existsError('username')>
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('username'))?no_esc}
</span>
</#if>
</div>
</#if>
<div class="${properties.kcFormGroupClass!}">
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
<input type="text" id="email" class="${properties.kcInputClass!}" name="email"
value="${(register.formData.email!'')}" autocomplete="email"
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
/>
<#if messagesPerField.existsError('email')>
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('email'))?no_esc}
</span>
</#if>
</div>
<#if passwordRequired??>
<div class="${properties.kcFormGroupClass!}">
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
<input type="password" id="password" class="${properties.kcInputClass!}" name="password" <input type="password" id="password" class="${properties.kcInputClass!}" name="password"
autocomplete="new-password" autocomplete="new-password"
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
/> />
</div>
<#if messagesPerField.existsError('password')> <#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -76,14 +33,20 @@
</span> </span>
</#if> </#if>
</div> </div>
</div>
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="password-confirm" <label for="password-confirm"
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> *
</div>
<div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcInputGroup!}">
<input type="password" id="password-confirm" class="${properties.kcInputClass!}" <input type="password" id="password-confirm" class="${properties.kcInputClass!}"
name="password-confirm" name="password-confirm"
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
/> />
</div>
<#if messagesPerField.existsError('password-confirm')> <#if messagesPerField.existsError('password-confirm')>
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -91,22 +54,46 @@
</span> </span>
</#if> </#if>
</div> </div>
</div>
</#if> </#if>
</#if>
</@userProfileCommons.userProfileFormFields>
<#if recaptchaRequired??> <@registerCommons.termsAcceptance/>
<div class="ps-form-group">
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div> <#if recaptchaRequired?? && (recaptchaVisible!false)>
<div class="form-group">
<div class="${properties.kcInputWrapperClass!}">
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}" data-action="${recaptchaAction}"></div>
</div>
</div> </div>
</#if> </#if>
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<button
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" <#if recaptchaRequired?? && !(recaptchaVisible!false)>
>${msg("doRegister")}</button> <script>
function onSubmitRecaptcha(token) {
document.getElementById("kc-register-form").submit();
}
</script>
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<button class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!} g-recaptcha"
data-sitekey="${recaptchaSiteKey}" data-callback='onSubmitRecaptcha' data-action='${recaptchaAction}' type="submit">
${msg("doRegister")}
</button>
</div> </div>
<div class="${properties.kcFormGroupClass!}"> <#else>
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
</div>
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<div class="${properties.kcFormOptionsWrapperClass!}">
<span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span> <span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
</div> </div>
</div>
</#if>
</div>
</form> </form>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -0,0 +1,115 @@
// for embedded scripts, quoted and modified from https://github.com/swansontec/rfc4648.js by William Swanson
'use strict';
var base64url = base64url || {};
(function(base64url) {
function parse (string, encoding, opts = {}) {
// Build the character lookup table:
if (!encoding.codes) {
encoding.codes = {};
for (let i = 0; i < encoding.chars.length; ++i) {
encoding.codes[encoding.chars[i]] = i;
}
}
// The string must have a whole number of bytes:
if (!opts.loose && (string.length * encoding.bits) & 7) {
throw new SyntaxError('Invalid padding');
}
// Count the padding bytes:
let end = string.length;
while (string[end - 1] === '=') {
--end;
// If we get a whole number of bytes, there is too much padding:
if (!opts.loose && !(((string.length - end) * encoding.bits) & 7)) {
throw new SyntaxError('Invalid padding');
}
}
// Allocate the output:
const out = new (opts.out || Uint8Array)(((end * encoding.bits) / 8) | 0);
// Parse the data:
let bits = 0; // Number of bits currently in the buffer
let buffer = 0; // Bits waiting to be written out, MSB first
let written = 0; // Next byte to write
for (let i = 0; i < end; ++i) {
// Read one character from the string:
const value = encoding.codes[string[i]];
if (value === void 0) {
throw new SyntaxError('Invalid character ' + string[i]);
}
// Append the bits to the buffer:
buffer = (buffer << encoding.bits) | value;
bits += encoding.bits;
// Write out some bits if the buffer has a byte's worth:
if (bits >= 8) {
bits -= 8;
out[written++] = 0xff & (buffer >> bits);
}
}
// Verify that we have received just enough bits:
if (bits >= encoding.bits || 0xff & (buffer << (8 - bits))) {
throw new SyntaxError('Unexpected end of data');
}
return out
}
function stringify (data, encoding, opts = {}) {
const { pad = true } = opts;
const mask = (1 << encoding.bits) - 1;
let out = '';
let bits = 0; // Number of bits currently in the buffer
let buffer = 0; // Bits waiting to be written out, MSB first
for (let i = 0; i < data.length; ++i) {
// Slurp data into the buffer:
buffer = (buffer << 8) | (0xff & data[i]);
bits += 8;
// Write out as much as we can:
while (bits > encoding.bits) {
bits -= encoding.bits;
out += encoding.chars[mask & (buffer >> bits)];
}
}
// Partial character:
if (bits) {
out += encoding.chars[mask & (buffer << (encoding.bits - bits))];
}
// Add padding characters until we hit a byte boundary:
if (pad) {
while ((out.length * encoding.bits) & 7) {
out += '=';
}
}
return out
}
const encoding = {
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
bits: 6
}
base64url.decode = function (string, opts) {
return parse(string, encoding, opts);
}
base64url.encode = function (data, opts) {
return stringify(data, encoding, opts)
}
return base64url;
}(base64url));

View file

@ -0,0 +1,15 @@
const toggle = (button) => {
const passwordElement = document.getElementById(button.getAttribute('aria-controls'));
if (passwordElement.type === "password") {
passwordElement.type = "text";
button.children.item(0).className = button.dataset.iconHide;
button.setAttribute("aria-label", button.dataset.labelHide);
} else if(passwordElement.type === "text") {
passwordElement.type = "password";
button.children.item(0).className = button.dataset.iconShow;
button.setAttribute("aria-label", button.dataset.labelShow);
}
}
document.querySelectorAll('[data-password-toggle]')
.forEach(button => button.onclick = () => toggle(button));

View file

@ -85,11 +85,13 @@
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())> <#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
<h1 class="ps-page--title"><#nested "header"></h1> <h1 class="ps-page--title"><#nested "header"></h1>
<div class="ps-page--subtitle">
<#if displayRequiredFields> <#if displayRequiredFields>
<div class="${properties.kcLabelWrapperClass!} subtitle"> <div class="${properties.kcLabelWrapperClass!} subtitle">
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span> <span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
</div> </div>
</#if> </#if>
</div>
<#else> <#else>
<#if displayRequiredFields> <#if displayRequiredFields>
<div class="${properties.kcContentWrapperClass!}"> <div class="${properties.kcContentWrapperClass!}">

View file

@ -12,6 +12,9 @@ kcButtonLargeClass=ps-button_large
kcFormGroupClass=ps-form-group kcFormGroupClass=ps-form-group
kcLabelClass=ps-form-group--label kcLabelClass=ps-form-group--label
kcInputErrorMessageClass=ps-form-group--error kcInputErrorMessageClass=ps-form-group--error
kcFormOptionsClass=ps-form-group--options
kcFormOptionsWrapperClass=ps-form-group--options-wrapper
kcFormButtonsClass=ps-form-group--buttons
kcInputClass=ps-input kcInputClass=ps-input

View file

@ -1,27 +1,12 @@
<#import "template.ftl" as layout> <#import "template.ftl" as layout>
<#import "password-commons.ftl" as passwordCommons> <#import "password-commons.ftl" as passwordCommons>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('email'); section> <#import "user-profile-commons.ftl" as userProfileCommons>
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
<#if section = "header"> <#if section = "header">
${msg("updateEmailTitle")} ${msg("updateEmailTitle")}
<#elseif section = "form"> <#elseif section = "form">
<form id="kc-update-email-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post"> <form id="kc-update-email-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div class="${properties.kcFormGroupClass!}"> <@userProfileCommons.userProfileFormFields/>
<div class="${properties.kcLabelWrapperClass!}">
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="email" name="email" value="${(email.value!'')}"
class="${properties.kcInputClass!}"
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
/>
<#if messagesPerField.existsError('email')>
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('email'))?no_esc}
</span>
</#if>
</div>
</div>
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}"> <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">

View file

@ -3,27 +3,31 @@
<#list profile.attributes as attribute> <#list profile.attributes as attribute>
<#assign groupName = attribute.group!""> <#assign group = (attribute.group)!"">
<#if groupName != currentGroup> <#if group != currentGroup>
<#assign currentGroup=groupName> <#assign currentGroup=group>
<#if currentGroup != ""> <#if currentGroup != "">
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}"
<#list group.html5DataAnnotations as key, value>
data-${key}="${value}"
</#list>
>
<#assign groupDisplayHeader=attribute.groupDisplayHeader!""> <#assign groupDisplayHeader=group.displayHeader!"">
<#if groupDisplayHeader != ""> <#if groupDisplayHeader != "">
<#assign groupHeaderText=advancedMsg(attribute.groupDisplayHeader)!groupName> <#assign groupHeaderText=advancedMsg(groupDisplayHeader)!group>
<#else> <#else>
<#assign groupHeaderText=groupName> <#assign groupHeaderText=group.name!"">
</#if> </#if>
<div class="${properties.kcContentWrapperClass!}"> <div class="${properties.kcContentWrapperClass!}">
<label id="header-${groupName}" class="${kcFormGroupHeader!}">${groupHeaderText}</label> <label id="header-${attribute.group.name}" class="${kcFormGroupHeader!}">${groupHeaderText}</label>
</div> </div>
<#assign groupDisplayDescription=attribute.groupDisplayDescription!""> <#assign groupDisplayDescription=group.displayDescription!"">
<#if groupDisplayDescription != ""> <#if groupDisplayDescription != "">
<#assign groupDescriptionText=advancedMsg(attribute.groupDisplayDescription)!""> <#assign groupDescriptionText=advancedMsg(groupDisplayDescription)!"">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label id="description-${groupName}" class="${properties.kcLabelClass!}">${groupDescriptionText}</label> <label id="description-${group.name}" class="${properties.kcLabelClass!}">${groupDescriptionText}</label>
</div> </div>
</#if> </#if>
</div> </div>
@ -53,6 +57,10 @@
</div> </div>
<#nested "afterField" attribute> <#nested "afterField" attribute>
</#list> </#list>
<#list profile.html5DataAnnotations?keys as key>
<script type="module" src="${url.resourcesPath}/js/${key}.js"></script>
</#list>
</#macro> </#macro>
<#macro inputFieldByType attribute> <#macro inputFieldByType attribute>
@ -69,16 +77,22 @@
<@inputTagSelects attribute=attribute/> <@inputTagSelects attribute=attribute/>
<#break> <#break>
<#default> <#default>
<@inputTag attribute=attribute/> <#if attribute.multivalued && attribute.values?has_content>
<#list attribute.values as value>
<@inputTag attribute=attribute value=value!''/>
</#list>
<#else>
<@inputTag attribute=attribute value=attribute.value!''/>
</#if>
</#switch> </#switch>
</#macro> </#macro>
<#macro inputTag attribute> <#macro inputTag attribute value>
<input type="<@inputTagType attribute=attribute/>" id="${attribute.name}" name="${attribute.name}" value="${(attribute.value!'')}" class="${properties.kcInputClass!}" <input type="<@inputTagType attribute=attribute/>" id="${attribute.name}" name="${attribute.name}" value="${(value!'')}" class="${properties.kcInputClass!}"
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>" aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
<#if attribute.readOnly>disabled</#if> <#if attribute.readOnly>disabled</#if>
<#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}"</#if> <#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}"</#if>
<#if attribute.annotations.inputTypePlaceholder??>placeholder="${attribute.annotations.inputTypePlaceholder}"</#if> <#if attribute.annotations.inputTypePlaceholder??>placeholder="${advancedMsg(attribute.annotations.inputTypePlaceholder)}"</#if>
<#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}"</#if> <#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}"</#if>
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if> <#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
<#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if> <#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if>
@ -86,6 +100,10 @@
<#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}"</#if> <#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}"</#if>
<#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}"</#if> <#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}"</#if>
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if> <#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
<#list attribute.html5DataAnnotations as key, value>
data-${key}="${value}"
</#list>
/> />
</#macro> </#macro>
@ -128,13 +146,14 @@
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options> <#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
<#elseif attribute.validators.options?? && attribute.validators.options.options??> <#elseif attribute.validators.options?? && attribute.validators.options.options??>
<#assign options=attribute.validators.options.options> <#assign options=attribute.validators.options.options>
<#else>
<#assign options=[]>
</#if> </#if>
<#if options??>
<#list options as option> <#list options as option>
<option value="${option}" <#if attribute.values?seq_contains(option)>selected</#if>><@selectOptionLabelText attribute=attribute option=option/></option> <option value="${option}" <#if attribute.values?seq_contains(option)>selected</#if>><@selectOptionLabelText attribute=attribute option=option/></option>
</#list> </#list>
</#if>
</select> </select>
</#macro> </#macro>
@ -155,9 +174,10 @@
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options> <#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
<#elseif attribute.validators.options?? && attribute.validators.options.options??> <#elseif attribute.validators.options?? && attribute.validators.options.options??>
<#assign options=attribute.validators.options.options> <#assign options=attribute.validators.options.options>
<#else>
<#assign options=[]>
</#if> </#if>
<#if options??>
<#list options as option> <#list options as option>
<div class="${classDiv}"> <div class="${classDiv}">
<input type="${inputType}" id="${attribute.name}-${option}" name="${attribute.name}" value="${option}" class="${classInput}" <input type="${inputType}" id="${attribute.name}-${option}" name="${attribute.name}" value="${option}" class="${classInput}"
@ -168,8 +188,6 @@
<label for="${attribute.name}-${option}" class="${classLabel}<#if attribute.readOnly> ${properties.kcInputClassRadioCheckboxLabelDisabled!}</#if>"><@selectOptionLabelText attribute=attribute option=option/></label> <label for="${attribute.name}-${option}" class="${classLabel}<#if attribute.readOnly> ${properties.kcInputClassRadioCheckboxLabelDisabled!}</#if>"><@selectOptionLabelText attribute=attribute option=option/></label>
</div> </div>
</#list> </#list>
</#if>
</select>
</#macro> </#macro>
<#macro selectOptionLabelText attribute option> <#macro selectOptionLabelText attribute option>