- Improve accessibility - Simply code Signed-off-by: Carl Schwan <carl@carlschwan.eu>tags/v25.0.0beta5
@@ -108,14 +108,6 @@ form { | |||
margin: auto; | |||
padding: 0; | |||
} | |||
form fieldset { | |||
width: 260px; | |||
margin-top: 8px; | |||
-webkit-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
} | |||
form #sqliteInformation { | |||
margin-top: 0px; | |||
margin-bottom: 20px; | |||
@@ -160,9 +152,6 @@ form #datadirField legend { | |||
.wrapper { | |||
margin-top: 0; | |||
} | |||
.alternative-logins { | |||
margin: auto; | |||
} | |||
} | |||
@@ -262,9 +251,6 @@ input[type='email'] { | |||
color: var(--color-text-lighter); | |||
cursor: text; | |||
font-family: inherit; | |||
-webkit-appearance: textfield; | |||
-moz-appearance: textfield; | |||
box-sizing: content-box; | |||
font-weight: normal; | |||
margin-left: 0; | |||
margin-right: 0; | |||
@@ -491,34 +477,6 @@ form .warning input[type='checkbox']+label { | |||
color: var(--color-primary-text); | |||
} | |||
/* Alternative Logins */ | |||
.alternative-logins legend { | |||
margin-bottom: 10px; | |||
} | |||
.alternative-logins li { | |||
height: 40px; | |||
white-space: nowrap; | |||
padding: 05px; | |||
} | |||
.alternative-logins a.button, | |||
.alternative-logins li a { | |||
width: 100%; | |||
display: inline-block; | |||
text-align: center; | |||
box-sizing: border-box; | |||
border: 2px solid var(--color-primary-text); | |||
background-color: var(--color-primary); | |||
color: var(--color-primary-text); | |||
border-radius: 100px; /* --border-radius-pill */ | |||
} | |||
.alternative-logins a.button:focus, | |||
.alternative-logins li a:focus { | |||
border: 2px solid var(--color-primary-hover); | |||
background-image: linear-gradient(40deg, var(--color-primary) 0%, var(--color-primary-light) 100%); | |||
background-position: initial; | |||
} | |||
/* fixes for update page TODO should be fixed some time in a proper way */ | |||
/* this is just for an error while updating the ownCloud instance */ | |||
.updateProgress .error { |
@@ -21,11 +21,12 @@ | |||
<template> | |||
<form ref="loginForm" | |||
class="login-form" | |||
method="post" | |||
name="login" | |||
:action="loginActionUrl" | |||
@submit="submit"> | |||
<fieldset> | |||
<fieldset class="login-form__fieldset"> | |||
<div v-if="apacheAuthFailed" | |||
class="warning"> | |||
{{ t('core', 'Server side authentication failed!') }}<br> | |||
@@ -52,65 +53,36 @@ | |||
<!-- the following div ensures that the spinner is always inside the #message div --> | |||
<div style="clear: both;" /> | |||
</div> | |||
<p class="grouptop" | |||
:class="{shake: invalidPassword}"> | |||
<input id="user" | |||
ref="user" | |||
v-model="user" | |||
type="text" | |||
name="user" | |||
autocapitalize="none" | |||
autocorrect="off" | |||
:autocomplete="autoCompleteAllowed ? 'on' : 'off'" | |||
:placeholder="t('core', 'Username or email')" | |||
:aria-label="t('core', 'Username or email')" | |||
required | |||
@change="updateUsername"> | |||
<label for="user" class="infield">{{ t('core', 'Username or email') }}</label> | |||
</p> | |||
<p class="groupbottom" | |||
:class="{shake: invalidPassword}"> | |||
<input id="password" | |||
ref="password" | |||
:type="passwordInputType" | |||
class="password-with-toggle" | |||
name="password" | |||
autocorrect="off" | |||
autocapitalize="none" | |||
:autocomplete="autoCompleteAllowed ? 'current-password' : 'off'" | |||
:placeholder="t('core', 'Password')" | |||
:aria-label="t('core', 'Password')" | |||
required> | |||
<label for="password" | |||
class="infield">{{ t('core', 'Password') }}</label> | |||
<NcButton class="toggle-password" | |||
type="tertiary-no-background" | |||
:aria-label="isPasswordHidden ? t('core', 'Show password') : t('core', 'Hide password')" | |||
@click.stop.prevent="togglePassword"> | |||
<template #icon> | |||
<Eye v-if="isPasswordHidden" :size="20" /> | |||
<EyeOff v-else :size="20" /> | |||
</template> | |||
</NcButton> | |||
</p> | |||
<NcTextField id="user" | |||
:label="t('core', 'Username or email')" | |||
:labelVisible="true" | |||
ref="user" | |||
name="user" | |||
:class="{shake: invalidPassword}" | |||
:value.sync="user" | |||
autocapitalize="none" | |||
:spellchecking="false" | |||
:autocomplete="autoCompleteAllowed ? 'username' : 'off'" | |||
:aria-label="t('core', 'Username or email')" | |||
required | |||
@change="updateUsername" /> | |||
<NcPasswordField id="password" | |||
ref="password" | |||
name="password" | |||
:labelVisible="true" | |||
:class="{shake: invalidPassword}" | |||
:value.sync="password" | |||
:spellchecking="false" | |||
autocapitalize="none" | |||
:autocomplete="autoCompleteAllowed ? 'current-password' : 'off'" | |||
:label="t('core', 'Password')" | |||
:helperText="errorLabel" | |||
:error="isError" | |||
required /> | |||
<LoginButton :loading="loading" /> | |||
<p v-if="invalidPassword" | |||
class="warning wrongPasswordMsg"> | |||
{{ t('core', 'Wrong username or password.') }} | |||
</p> | |||
<p v-else-if="userDisabled" | |||
class="warning userDisabledMsg"> | |||
{{ t('core', 'User disabled') }} | |||
</p> | |||
<p v-if="throttleDelay && throttleDelay > 5000" | |||
class="warning throttledMsg"> | |||
{{ t('core', 'We have detected multiple invalid login attempts from your IP. Therefore your next login is throttled up to 30 seconds.') }} | |||
</p> | |||
<input v-if="redirectUrl" | |||
type="hidden" | |||
name="redirect_url" | |||
@@ -136,7 +108,9 @@ | |||
import jstz from 'jstimezonedetect' | |||
import { generateUrl, imagePath } from '@nextcloud/router' | |||
import NcButton from '@nextcloud/vue/dist/Components/NcButton' | |||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' | |||
import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js' | |||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' | |||
import Eye from 'vue-material-design-icons/Eye' | |||
import EyeOff from 'vue-material-design-icons/EyeOff' | |||
@@ -150,6 +124,8 @@ export default { | |||
Eye, | |||
EyeOff, | |||
LoginButton, | |||
NcPasswordField, | |||
NcTextField, | |||
}, | |||
props: { | |||
@@ -190,11 +166,26 @@ export default { | |||
timezoneOffset: (-new Date().getTimezoneOffset() / 60), | |||
user: this.username, | |||
password: '', | |||
passwordInputType: 'password', | |||
} | |||
}, | |||
computed: { | |||
isError() { | |||
return this.invalidPassword || this.userDisabled | |||
|| (this.throttleDelay && this.throttleDelay > 5000) | |||
}, | |||
errorLabel() { | |||
if (this.invalidPassword) { | |||
return t('core', 'Wrong username or password.') | |||
} | |||
if (this.userDisabled) { | |||
return t('core', 'User disabled') | |||
} | |||
if (this.throttleDelay && this.throttleDelay > 5000) { | |||
return t('core', 'We have detected multiple invalid login attempts from your IP. Therefore your next login is throttled up to 30 seconds.') | |||
} | |||
return undefined; | |||
}, | |||
apacheAuthFailed() { | |||
return this.errors.indexOf('apacheAuthFailed') !== -1 | |||
}, | |||
@@ -213,9 +204,6 @@ export default { | |||
loginActionUrl() { | |||
return generateUrl('login') | |||
}, | |||
isPasswordHidden() { | |||
return this.passwordInputType === 'password' | |||
}, | |||
}, | |||
mounted() { | |||
@@ -227,13 +215,6 @@ export default { | |||
}, | |||
methods: { | |||
togglePassword() { | |||
if (this.passwordInputType === 'password') { | |||
this.passwordInputType = 'text' | |||
} else { | |||
this.passwordInputType = 'password' | |||
} | |||
}, | |||
updateUsername() { | |||
this.$emit('update:username', this.user) | |||
}, | |||
@@ -246,10 +227,15 @@ export default { | |||
</script> | |||
<style lang="scss" scoped> | |||
.toggle-password { | |||
position: absolute; | |||
top: 2px; | |||
right: 10px; | |||
color: var(--color-text-lighter); | |||
.login-form { | |||
text-align: left; | |||
font-size: 1rem; | |||
&__fieldset { | |||
width: 100%; | |||
display: flex; | |||
flex-direction: column; | |||
gap: 1rem; | |||
} | |||
} | |||
</style> |
@@ -20,39 +20,35 @@ | |||
--> | |||
<template> | |||
<form @submit.prevent="submit"> | |||
<fieldset> | |||
<p> | |||
<input id="user" | |||
v-model="user" | |||
type="text" | |||
name="user" | |||
autocapitalize="off" | |||
:placeholder="t('core', 'Username or email')" | |||
:aria-label="t('core', 'Username or email')" | |||
required | |||
@change="updateUsername"> | |||
<form @submit.prevent="submit" class="login-form"> | |||
<fieldset class="login-form__fieldset"> | |||
<NcTextField id="user" | |||
:value.sync="user" | |||
name="user" | |||
autocapitalize="off" | |||
:label="t('core', 'Username or email')" | |||
:labelVisible="true" | |||
required | |||
@change="updateUsername" /> | |||
<!--<?php p($_['user_autofocus'] ? 'autofocus' : ''); ?> | |||
autocomplete="<?php p($_['login_form_autocomplete']); ?>" autocapitalize="none" autocorrect="off"--> | |||
<label for="user" class="infield">{{ t('core', 'Username or email') }}</label> | |||
</p> | |||
<div id="reset-password-wrapper"> | |||
<LoginButton :value="t('core', 'Reset password')" /> | |||
</div> | |||
<p v-if="message === 'send-success'" | |||
class="notecard success"> | |||
<NcNoteCard v-if="message === 'send-success'" | |||
type="success"> | |||
{{ t('core', 'A password reset message has been sent to the email address of this account. If you do not receive it, check your spam/junk folders or ask your local administrator for help.') }} | |||
<br> | |||
{{ t('core', 'If it is not there ask your local administrator.') }} | |||
</p> | |||
<p v-else-if="message === 'send-error'" | |||
class="notecard error"> | |||
</NcNoteCard> | |||
<NcNoteCard v-else-if="message === 'send-error'" | |||
type="error"> | |||
{{ t('core', 'Couldn\'t send reset email. Please contact your administrator.') }} | |||
</p> | |||
<p v-else-if="message === 'reset-error'" | |||
class="notecard error"> | |||
</NcNoteCard> | |||
<NcNoteCard v-else-if="message === 'reset-error'" | |||
type="error"> | |||
{{ t('core', 'Password cannot be changed. Please contact your administrator.') }} | |||
</p> | |||
</NcNoteCard> | |||
<a href="#" | |||
@click.prevent="$emit('abort')"> | |||
@@ -66,11 +62,15 @@ | |||
import axios from '@nextcloud/axios' | |||
import { generateUrl } from '@nextcloud/router' | |||
import LoginButton from './LoginButton.vue' | |||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' | |||
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' | |||
export default { | |||
name: 'ResetPassword', | |||
components: { | |||
LoginButton, | |||
NcNoteCard, | |||
NcTextField, | |||
}, | |||
props: { | |||
username: { | |||
@@ -131,7 +131,15 @@ export default { | |||
</script> | |||
<style scoped> | |||
.update { | |||
width: auto; | |||
.login-form { | |||
text-align: left; | |||
font-size: 1rem; | |||
&__fieldset { | |||
width: 100%; | |||
display: flex; | |||
flex-direction: column; | |||
gap: 1rem; | |||
} | |||
} | |||
</style> |
@@ -192,6 +192,9 @@ export default { | |||
</script> | |||
<style lang="scss"> | |||
#login { | |||
width: 300px; | |||
} | |||
.fade-enter-active, .fade-leave-active { | |||
transition: opacity .3s; | |||
} | |||
@@ -205,15 +208,13 @@ export default { | |||
border-radius: var(--border-radius); | |||
} | |||
.alternative-logins button { | |||
margin-top: 12px; | |||
margin-bottom: 12px; | |||
&:first-child { | |||
margin-top: 0; | |||
} | |||
.alternative-logins { | |||
display: flex; | |||
flex-direction: column; | |||
gap: 0.75rem; | |||
&:last-child { | |||
margin-bottom: 0; | |||
.button-vue { | |||
box-sizing: border-box; | |||
} | |||
} | |||
</style> |