Преглед изворни кода

Merge pull request #25613 from overleaf/td-bs5-sp-login

Migrate SP/CE login page to Bootstrap 5

GitOrigin-RevId: 37fc7cbb453bfef93abde2080faaa0a88116d1f4
Tim Down пре 1 година
родитељ
комит
d0010217cd

+ 21 - 16
services/web/app/views/_mixins/formMessages.pug

@@ -4,11 +4,12 @@ mixin formMessages()
 		role="alert"
 	)
 
-mixin formMessagesNewStyle()
+mixin formMessagesNewStyle(extraClass = 'form-messages-bottom-margin')
+	- const attrs = extraClass ? { 'class': extraClass }  : {}
 	div(
 		data-ol-form-messages-new-style='',
 		role="alert"
-	)
+	)&attributes(attrs)
 
 mixin customFormMessage(key, kind)
 	if kind === 'success'
@@ -36,20 +37,23 @@ mixin customFormMessage(key, kind)
 		)
 			block
 
-mixin customFormMessageNewStyle(key, kind)
+mixin customFormMessageNewStyle(key, kind, extraClass = 'mb-3')
+	- extraClass = extraClass ? ' ' + extraClass : ''
 	if kind === 'success'
-		div.notification.notification-type-success(
-				hidden,
-				data-ol-custom-form-message=key,
-				role="alert"
-				aria-live="polite"
-			)
+		div(
+			class="notification notification-type-success" + extraClass,
+			hidden,
+			data-ol-custom-form-message=key,
+			role="alert"
+			aria-live="polite"
+		)
 			div.notification-icon
 				span.material-symbols(aria-hidden="true") check_circle
 			div.notification-content.text-left
 				block
 	else if kind === 'danger'
-		div.notification.notification-type-error(
+		div(
+			class="notification notification-type-error" + extraClass,
 			hidden,
 			data-ol-custom-form-message=key,
 			role="alert"
@@ -60,12 +64,13 @@ mixin customFormMessageNewStyle(key, kind)
 			div.notification-content.text-left
 				block
 	else
-		div.notification.notification-type-warning(
-				hidden,
-				data-ol-custom-form-message=key,
-				role="alert"
-				aria-live="polite"
-			)
+		div(
+			class="notification notification-type-warning" + extraClass,
+			hidden,
+			data-ol-custom-form-message=key,
+			role="alert"
+			aria-live="polite"
+		)
 			div.notification-icon
 				span.material-symbols(aria-hidden="true") warning
 			div.notification-content.text-left

+ 43 - 45
services/web/app/views/user/login.pug

@@ -1,52 +1,50 @@
 extends ../layout-marketing
 
-block vars
-	- bootstrap5PageStatus = 'disabled'
-
 block content
 	main.content.content-alt#main-content
 		.container
 			.row
-				.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
+				.col-lg-6.offset-lg-3.col-xl-4.offset-xl-4
 					.card
-						.page-header
-							if login_support_title
-								h1 !{login_support_title}
-							else
-								h1 #{translate("log_in")}					
-						form(data-ol-async-form, name="loginForm", action='/login', method="POST")
-							input(name='_csrf', type='hidden', value=csrfToken)
-							+formMessages()
-							+customFormMessage('invalid-password-retry-or-reset', 'danger')
-								| !{translate('email_or_password_wrong_try_again_or_reset', {}, [{ name: 'a', attrs: { href: '/user/password/reset', 'aria-describedby': 'resetPasswordDescription' } }])}
-								span.sr-only(id='resetPasswordDescription')
-									| #{translate('reset_password_link')}
-							+customValidationMessage('password-compromised')
-								| !{translate('password_compromised_try_again_or_use_known_device_or_reset', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}, {name: 'a', attrs: {href: '/user/password/reset', target: '_blank'}}])}.
-							.form-group
-								input.form-control(
-									type='email',
-									name='email',
-									required,
-									placeholder='email@example.com',
-									autofocus="true"
-								)
-							.form-group
-								input.form-control(
-									type='password',
-									name='password',
-									required,
-									placeholder='********',
-								)
-							.actions
-								button.btn-primary.btn(
-									type='submit',
-									data-ol-disabled-inflight
-								)
-									span(data-ol-inflight="idle") #{translate("login")}
-									span(hidden data-ol-inflight="pending") #{translate("logging_in")}…
-								a.pull-right(href='/user/password/reset') #{translate("forgot_your_password")}?
-							if login_support_text
-								hr
-								p.text-center !{login_support_text}	
-	
+						.card-body
+							.page-header
+								if login_support_title
+									h1 !{login_support_title}
+								else
+									h1 #{translate("log_in")}					
+							form(data-ol-async-form, name="loginForm", action='/login', method="POST")
+								input(name='_csrf', type='hidden', value=csrfToken)
+								+formMessagesNewStyle()
+								+customFormMessageNewStyle('invalid-password-retry-or-reset', 'danger')
+									| !{translate('email_or_password_wrong_try_again_or_reset', {}, [{ name: 'a', attrs: { href: '/user/password/reset', 'aria-describedby': 'resetPasswordDescription' } }])}
+									span.visually-hidden(id='resetPasswordDescription')
+										| #{translate('reset_password_link')}
+								+customFormMessageNewStyle('password-compromised')
+									| !{translate('password_compromised_try_again_or_use_known_device_or_reset', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}, {name: 'a', attrs: {href: '/user/password/reset', target: '_blank'}}])}.
+								.form-group
+									input.form-control(
+										type='email',
+										name='email',
+										required,
+										placeholder='email@example.com',
+										autofocus="true"
+									)
+								.form-group
+									input.form-control(
+										type='password',
+										name='password',
+										required,
+										placeholder='********',
+									)
+								.actions
+									button.btn-primary.btn(
+										type='submit',
+										data-ol-disabled-inflight
+									)
+										span(data-ol-inflight="idle") #{translate("login")}
+										span(hidden data-ol-inflight="pending") #{translate("logging_in")}…
+									a.float-end(href='/user/password/reset') #{translate("forgot_your_password")}?
+								if login_support_text
+									hr
+									p.text-center !{login_support_text}	
+		

+ 7 - 0
services/web/frontend/js/features/form-helpers/create-icon.js

@@ -0,0 +1,7 @@
+export default function createIcon(type) {
+  const icon = document.createElement('span')
+  icon.className = 'material-symbols'
+  icon.setAttribute('aria-hidden', 'true')
+  icon.textContent = type
+  return icon
+}

+ 5 - 8
services/web/frontend/js/features/form-helpers/hydrate-form.js

@@ -4,6 +4,7 @@ import { canSkipCaptcha, validateCaptchaV2 } from './captcha'
 import inputValidator from './input-validator'
 import { disableElement, enableElement } from '../utils/disableElement'
 import { isBootstrap5 } from '@/features/utils/bootstrap-5'
+import createIcon from '@/features/form-helpers/create-icon'
 
 // Form helper(s) to handle:
 // - Attaching to the relevant form elements
@@ -164,10 +165,7 @@ function createNotificationFromMessageBS5(message) {
   if (materialIcon) {
     const iconEl = document.createElement('div')
     iconEl.className = 'notification-icon'
-    const iconSpan = document.createElement('span')
-    iconSpan.className = 'material-symbols'
-    iconSpan.setAttribute('aria-hidden', 'true')
-    iconSpan.textContent = materialIcon
+    const iconSpan = createIcon(materialIcon)
     iconEl.append(iconSpan)
     messageEl.append(iconEl)
   }
@@ -315,10 +313,9 @@ function showMessagesNewStyle(formEl, messageBag) {
       }
 
       // create the left icon
-      const icon = document.createElement('span')
-      icon.className = 'material-symbols'
-      icon.setAttribute('aria-hidden', 'true')
-      icon.innerText = message.type === 'error' ? 'error' : 'check_circle'
+      const icon = createIcon(
+        message.type === 'error' ? 'error' : 'check_circle'
+      )
       const messageIcon = document.createElement('div')
       messageIcon.className = 'notification-icon'
       messageIcon.appendChild(icon)

+ 18 - 2
services/web/frontend/js/features/form-helpers/input-validator.js

@@ -1,9 +1,25 @@
+import { isBootstrap5 } from '@/features/utils/bootstrap-5'
+import createIcon from '@/features/form-helpers/create-icon'
+
 export default function inputValidator(inputEl) {
   const messageEl = document.createElement('div')
   messageEl.className =
     inputEl.getAttribute('data-ol-validation-message-classes') ||
-    'small text-danger mt-2'
+    'small text-danger mt-2 form-text'
   messageEl.hidden = true
+
+  const messageInnerEl = messageEl.appendChild(document.createElement('span'))
+  messageInnerEl.className = 'form-text-inner'
+
+  const messageTextNode = document.createTextNode('')
+
+  // In Bootstrap 5, add an icon
+  if (isBootstrap5()) {
+    const iconEl = createIcon('error')
+    messageInnerEl.append(iconEl)
+  }
+  messageInnerEl.append(messageTextNode)
+
   inputEl.insertAdjacentElement('afterend', messageEl)
 
   // Hide messages until the user leaves the input field or submits the form.
@@ -54,7 +70,7 @@ export default function inputValidator(inputEl) {
       // Require another blur before displaying errors again.
       canDisplayErrorMessages = false
     } else {
-      messageEl.textContent = inputEl.validationMessage
+      messageTextNode.data = inputEl.validationMessage
       messageEl.hidden = false
     }
   }

+ 5 - 0
services/web/frontend/stylesheets/bootstrap-5/components/notifications.scss

@@ -257,3 +257,8 @@
     }
   }
 }
+
+// Only apply bottom margin to form messages when it is non-empty
+.form-messages-bottom-margin > :last-child {
+  margin-bottom: var(--spacing-06);
+}