mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
Make emails Vuetiful
Signed-off-by: Christopher Ng <chrng8@gmail.com>
This commit is contained in:
parent
de6e55075b
commit
44763576b1
9 changed files with 832 additions and 49 deletions
|
|
@ -119,7 +119,10 @@
|
|||
_registerEvents: function() {
|
||||
var self = this;
|
||||
_.each(this._inputFields, function(field) {
|
||||
if (field === 'avatar') {
|
||||
if (
|
||||
field === 'avatar' ||
|
||||
field === 'email'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.$('#' + field).keyUpDelayedOrEnter(_.bind(self._onInputChanged, self), true);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<button
|
||||
:disabled="disabled"
|
||||
@click.stop.prevent="onClick">
|
||||
<span class="icon icon-add" />
|
||||
{{ t('settings', 'Add') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AddButton',
|
||||
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick(e) {
|
||||
this.$emit('click', e)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
button {
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(127, 127, 127, .15);
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
opacity: 0.4 !important;
|
||||
|
||||
.icon {
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:enabled:hover {
|
||||
background-color: rgba(127, 127, 127, .25);
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
323
apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
Normal file
323
apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="email-container">
|
||||
<input
|
||||
ref="email"
|
||||
type="email"
|
||||
:name="inputName"
|
||||
:placeholder="inputPlaceholder"
|
||||
:value="email"
|
||||
autocapitalize="none"
|
||||
autocomplete="on"
|
||||
autocorrect="off"
|
||||
required="true"
|
||||
@input="onEmailChange">
|
||||
|
||||
<div class="email-actions-container">
|
||||
<transition name="fade">
|
||||
<span v-if="showCheckmarkIcon" class="icon-checkmark" />
|
||||
<span v-else-if="showErrorIcon" class="icon-error" />
|
||||
</transition>
|
||||
|
||||
<FederationControl v-if="!primary"
|
||||
class="federation-control"
|
||||
:disabled="federationDisabled"
|
||||
:email="email"
|
||||
:scope.sync="localScope"
|
||||
@update:scope="onScopeChange" />
|
||||
|
||||
<Actions
|
||||
class="actions-email"
|
||||
:aria-label="t('settings', 'Email options')"
|
||||
:disabled="deleteDisabled"
|
||||
:force-menu="true">
|
||||
<ActionButton
|
||||
:aria-label="deleteEmailLabel"
|
||||
:close-after-click="true"
|
||||
icon="icon-delete"
|
||||
@click.stop.prevent="deleteEmail">
|
||||
{{ deleteEmailLabel }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<em v-if="primary">
|
||||
{{ t('settings', 'Primary email for password reset and notifications') }}
|
||||
</em>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import debounce from 'debounce'
|
||||
|
||||
import FederationControl from './FederationControl'
|
||||
import { savePrimaryEmail, saveAdditionalEmail, updateAdditionalEmail, removeAdditionalEmail } from '../../../service/PersonalInfoService'
|
||||
|
||||
export default {
|
||||
name: 'Email',
|
||||
|
||||
components: {
|
||||
Actions,
|
||||
ActionButton,
|
||||
FederationControl,
|
||||
},
|
||||
|
||||
props: {
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
primary: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
initialEmail: this.email,
|
||||
localScope: this.scope,
|
||||
showCheckmarkIcon: false,
|
||||
showErrorIcon: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
inputName() {
|
||||
if (this.primary) {
|
||||
return 'email'
|
||||
}
|
||||
return 'additionalEmail[]'
|
||||
},
|
||||
|
||||
inputPlaceholder() {
|
||||
if (this.primary) {
|
||||
return t('settings', 'Your email address')
|
||||
}
|
||||
return t('settings', 'Additional email address {index}', { index: this.index + 1 })
|
||||
},
|
||||
|
||||
federationDisabled() {
|
||||
return !this.initialEmail
|
||||
},
|
||||
|
||||
deleteDisabled() {
|
||||
return !this.containsNoWhitespace(this.email)
|
||||
},
|
||||
|
||||
deleteEmailLabel() {
|
||||
if (this.primary) {
|
||||
return t('settings', 'Remove primary email')
|
||||
}
|
||||
return t('settings', 'Delete email')
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onEmailChange(e) {
|
||||
this.$emit('update:email', e.target.value)
|
||||
// $nextTick() ensures that references to this.email further down the chain give the correct non-outdated value
|
||||
this.$nextTick(() => this.debounceEmailChange())
|
||||
},
|
||||
|
||||
debounceEmailChange: debounce(async function() {
|
||||
if ((this.$refs.email?.checkValidity() && this.containsNoWhitespace(this.email)) || this.email === '') {
|
||||
if (this.primary) {
|
||||
await this.updatePrimaryEmail()
|
||||
} else {
|
||||
if (this.initialEmail && this.email === '') {
|
||||
await this.deleteAdditionalEmail()
|
||||
} else if (this.initialEmail === '') {
|
||||
await this.addAdditionalEmail()
|
||||
} else {
|
||||
await this.updateAdditionalEmail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500),
|
||||
|
||||
async deleteEmail() {
|
||||
if (this.primary) {
|
||||
this.$emit('update:email', '')
|
||||
this.$nextTick(async() => await this.updatePrimaryEmail())
|
||||
} else {
|
||||
await this.deleteAdditionalEmail()
|
||||
}
|
||||
},
|
||||
|
||||
async updatePrimaryEmail() {
|
||||
try {
|
||||
const responseData = await savePrimaryEmail(this.email)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
if (this.email === '') {
|
||||
this.handleResponse('error', 'Unable to delete primary email address', e)
|
||||
} else {
|
||||
this.handleResponse('error', 'Unable to update primary email address', e)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async addAdditionalEmail() {
|
||||
try {
|
||||
const responseData = await saveAdditionalEmail(this.email)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to add additional email address', e)
|
||||
}
|
||||
},
|
||||
|
||||
async updateAdditionalEmail() {
|
||||
try {
|
||||
const responseData = await updateAdditionalEmail(this.initialEmail, this.email)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update additional email address', e)
|
||||
}
|
||||
},
|
||||
|
||||
async deleteAdditionalEmail() {
|
||||
try {
|
||||
const responseData = await removeAdditionalEmail(this.initialEmail)
|
||||
this.handleDeleteAdditionalEmail(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to delete additional email address', e)
|
||||
}
|
||||
},
|
||||
|
||||
containsNoWhitespace(string) {
|
||||
return /^\S+$/.test(string)
|
||||
},
|
||||
|
||||
handleDeleteAdditionalEmail(status) {
|
||||
if (status === 'ok') {
|
||||
this.$emit('deleteAdditionalEmail')
|
||||
} else {
|
||||
this.handleResponse('error', 'Unable to delete additional email address', {})
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse(status, errorMessage, error) {
|
||||
if (status === 'ok') {
|
||||
// Ensure that local initialEmail state reflects server state
|
||||
this.initialEmail = this.email
|
||||
this.showCheckmarkIcon = true
|
||||
setTimeout(() => { this.showCheckmarkIcon = false }, 2000)
|
||||
} else {
|
||||
showError(t('settings', errorMessage))
|
||||
this.logger.error(errorMessage, error)
|
||||
this.showErrorIcon = true
|
||||
setTimeout(() => { this.showErrorIcon = false }, 2000)
|
||||
}
|
||||
},
|
||||
|
||||
onScopeChange(scope) {
|
||||
this.$emit('update:scope', scope)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.email-container {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
input[type=email] {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
|
||||
.email-actions-container {
|
||||
grid-area: 1 / 1;
|
||||
justify-self: flex-end;
|
||||
height: 30px;
|
||||
|
||||
display: flex;
|
||||
gap: 0 2px;
|
||||
margin-right: 5px;
|
||||
|
||||
.actions-email {
|
||||
opacity: 0.4 !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
|
||||
&::v-deep button {
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
width: 30px !important;
|
||||
min-width: 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.federation-control {
|
||||
&::v-deep button {
|
||||
// TODO remove this hack
|
||||
padding-bottom: 7px;
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
width: 30px !important;
|
||||
min-width: 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-checkmark,
|
||||
.icon-error {
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
width: 30px !important;
|
||||
min-width: 30px !important;
|
||||
top: 0;
|
||||
right: 0;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
transition: opacity 200ms ease-out;
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
transition: opacity 300ms ease-out;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<form
|
||||
ref="form"
|
||||
class="section"
|
||||
@submit.stop.prevent="() => {}">
|
||||
<HeaderBar
|
||||
:can-edit-emails="isDisplayNameChangeSupported"
|
||||
:is-valid-form="isValidForm"
|
||||
:scope.sync="primaryEmail.scope"
|
||||
@addAdditionalEmail="onAddAdditionalEmail" />
|
||||
|
||||
<template v-if="isDisplayNameChangeSupported">
|
||||
<Email
|
||||
:primary="true"
|
||||
:scope.sync="primaryEmail.scope"
|
||||
:email.sync="primaryEmail.value"
|
||||
@update:email="updateFormValidity" />
|
||||
<Email v-for="(additionalEmail, index) in additionalEmails"
|
||||
:key="index"
|
||||
:index="index"
|
||||
:scope.sync="additionalEmail.scope"
|
||||
:email.sync="additionalEmail.value"
|
||||
@update:email="updateFormValidity"
|
||||
@deleteAdditionalEmail="onDeleteAdditionalEmail(index)" />
|
||||
</template>
|
||||
|
||||
<span v-else>
|
||||
{{ primaryEmail.value || t('settings', 'No email address set') }}
|
||||
</span>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import '@nextcloud/dialogs/styles/toast.scss'
|
||||
|
||||
import HeaderBar from './HeaderBar'
|
||||
import Email from './Email'
|
||||
import { DEFAULT_ADDITIONAL_EMAIL_SCOPE } from '../../../constants/AccountPropertyConstants'
|
||||
|
||||
const { additionalEmails, primaryEmail } = loadState('settings', 'emails', {})
|
||||
const accountParams = loadState('settings', 'accountParameters', {})
|
||||
|
||||
export default {
|
||||
name: 'EmailSection',
|
||||
|
||||
components: {
|
||||
HeaderBar,
|
||||
Email,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
accountParams,
|
||||
additionalEmails,
|
||||
primaryEmail,
|
||||
isValidForm: true,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isDisplayNameChangeSupported() {
|
||||
return this.accountParams.displayNameChangeSupported
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => this.updateFormValidity())
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAddAdditionalEmail() {
|
||||
if (this.$refs.form?.checkValidity()) {
|
||||
this.additionalEmails.push({ value: '', scope: DEFAULT_ADDITIONAL_EMAIL_SCOPE })
|
||||
this.$nextTick(() => this.updateFormValidity())
|
||||
}
|
||||
},
|
||||
|
||||
onDeleteAdditionalEmail(index) {
|
||||
this.$delete(this.additionalEmails, index)
|
||||
},
|
||||
|
||||
updateFormValidity() {
|
||||
this.isValidForm = this.$refs.form?.checkValidity()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
form::v-deep button {
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<Actions
|
||||
class="actions-federation"
|
||||
:aria-label="t('settings', 'Change privacy level of email')"
|
||||
:default-icon="scopeIcon"
|
||||
:disabled="disabled">
|
||||
<ActionButton v-for="federationScope in federationScopes"
|
||||
:key="federationScope.name"
|
||||
class="forced-action"
|
||||
:class="{ 'forced-active': scope === federationScope.name }"
|
||||
:aria-label="federationScope.tooltip"
|
||||
:close-after-click="true"
|
||||
:icon="federationScope.iconClass"
|
||||
:title="federationScope.displayName"
|
||||
@click.stop.prevent="changeScope(federationScope.name)">
|
||||
{{ federationScope.tooltip }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
import { SCOPE_ENUM, SCOPE_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants'
|
||||
import { savePrimaryEmailScope, saveAdditionalEmailScope } from '../../../service/PersonalInfoService'
|
||||
|
||||
// TODO hardcoded for email, should abstract this for other sections
|
||||
const excludedScopes = [SCOPE_ENUM.PRIVATE]
|
||||
|
||||
export default {
|
||||
name: 'FederationControl',
|
||||
|
||||
components: {
|
||||
Actions,
|
||||
ActionButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
primary: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
initialScope: this.scope,
|
||||
federationScopes: Object.values(SCOPE_PROPERTY_ENUM).filter(({ name }) => !excludedScopes.includes(name)),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
scopeIcon() {
|
||||
return SCOPE_PROPERTY_ENUM[this.scope].iconClass
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async changeScope(scope) {
|
||||
this.$emit('update:scope', scope)
|
||||
|
||||
this.$nextTick(async() => {
|
||||
if (this.primary) {
|
||||
await this.updatePrimaryEmailScope()
|
||||
} else {
|
||||
await this.updateAdditionalEmailScope()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async updatePrimaryEmailScope() {
|
||||
try {
|
||||
const responseData = await savePrimaryEmailScope(this.scope)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update federation scope of the primary email', e)
|
||||
}
|
||||
},
|
||||
|
||||
async updateAdditionalEmailScope() {
|
||||
try {
|
||||
const responseData = await saveAdditionalEmailScope(this.email, this.scope)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update federation scope of additional email', e)
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse(status, errorMessage, error) {
|
||||
if (status === 'ok') {
|
||||
this.initialScope = this.scope
|
||||
} else {
|
||||
this.$emit('update:scope', this.initialScope)
|
||||
showError(t('settings', errorMessage))
|
||||
this.logger.error(errorMessage, error)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.actions-federation {
|
||||
opacity: 0.4 !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.forced-active {
|
||||
background-color: var(--color-primary-light) !important;
|
||||
box-shadow: inset 2px 0 var(--color-primary) !important;
|
||||
}
|
||||
|
||||
.forced-action {
|
||||
&::v-deep p {
|
||||
width: 150px !important;
|
||||
padding: 8px 0 !important;
|
||||
color: var(--color-main-text) !important;
|
||||
font-size: 12.8px !important;
|
||||
line-height: 1.5em !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<h3>
|
||||
<label for="email">
|
||||
{{ t('settings', 'Email') }}
|
||||
</label>
|
||||
|
||||
<FederationControl
|
||||
class="federation-control"
|
||||
:primary="true"
|
||||
:scope.sync="localScope"
|
||||
@update:scope="onScopeChange" />
|
||||
|
||||
<AddButton v-if="canEditEmails"
|
||||
class="add-button"
|
||||
:disabled="!isValidForm"
|
||||
@click.stop.prevent="addAdditionalEmail" />
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FederationControl from './FederationControl'
|
||||
import AddButton from './AddButton'
|
||||
|
||||
export default {
|
||||
name: 'HeaderBar',
|
||||
|
||||
components: {
|
||||
FederationControl,
|
||||
AddButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
canEditEmails: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isValidForm: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
localScope: this.scope,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
addAdditionalEmail() {
|
||||
this.$emit('addAdditionalEmail')
|
||||
},
|
||||
|
||||
onScopeChange(scope) {
|
||||
this.$emit('update:scope', scope)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.federation-control {
|
||||
margin: -12px 0 0 8px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
margin: -12px 0 0 auto !important;
|
||||
}
|
||||
</style>
|
||||
38
apps/settings/src/main-personal-info.js
Normal file
38
apps/settings/src/main-personal-info.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
|
||||
import logger from './logger'
|
||||
|
||||
import EmailSection from './components/PersonalInfo/EmailSection/EmailSection'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_nonce__ = btoa(OC.requestToken)
|
||||
|
||||
Vue.prototype.t = t
|
||||
Vue.prototype.logger = logger
|
||||
|
||||
const View = Vue.extend(EmailSection)
|
||||
export default new View({
|
||||
el: '#vue-emailsection',
|
||||
})
|
||||
|
|
@ -31,6 +31,7 @@ script('settings', [
|
|||
'federationsettingsview',
|
||||
'federationscopemenu',
|
||||
'settings/personalInfo',
|
||||
'vue-settings-personal-info',
|
||||
]);
|
||||
?>
|
||||
|
||||
|
|
@ -126,52 +127,7 @@ script('settings', [
|
|||
</form>
|
||||
</div>
|
||||
<div class="personal-settings-setting-box">
|
||||
<form id="emailform" class="section">
|
||||
<h3>
|
||||
<label for="email"><?php p($l->t('Email')); ?></label>
|
||||
<a href="#" class="federation-menu" aria-label="<?php p($l->t('Change privacy level of email')); ?>">
|
||||
<span class="icon-federation-menu icon-password">
|
||||
<span class="icon-triangle-s"></span>
|
||||
</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="verify <?php if ($_['email'] === '' || $_['emailScope'] !== 'public') {
|
||||
p('hidden');
|
||||
} ?>">
|
||||
<img id="verify-email" title="<?php p($_['emailMessage']); ?>" data-status="<?php p($_['emailVerification']) ?>" src="
|
||||
<?php
|
||||
switch ($_['emailVerification']) {
|
||||
case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
|
||||
p(image_path('core', 'actions/verifying.svg'));
|
||||
break;
|
||||
case \OC\Accounts\AccountManager::VERIFIED:
|
||||
p(image_path('core', 'actions/verified.svg'));
|
||||
break;
|
||||
default:
|
||||
p(image_path('core', 'actions/verify.svg'));
|
||||
}
|
||||
?>">
|
||||
</div>
|
||||
<input type="email" name="email" id="email" value="<?php p($_['email']); ?>"
|
||||
<?php if (!$_['displayNameChangeSupported']) {
|
||||
print_unescaped('class="hidden"');
|
||||
} ?>
|
||||
placeholder="<?php p($l->t('Your email address')); ?>"
|
||||
autocomplete="on" autocapitalize="none" autocorrect="off" />
|
||||
<span class="icon-checkmark hidden"></span>
|
||||
<span class="icon-error hidden" ></span>
|
||||
<?php if (!$_['displayNameChangeSupported']) { ?>
|
||||
<span><?php if (isset($_['email']) && !empty($_['email'])) {
|
||||
p($_['email']);
|
||||
} else {
|
||||
p($l->t('No email address set'));
|
||||
}?></span>
|
||||
<?php } ?>
|
||||
<?php if ($_['displayNameChangeSupported']) { ?>
|
||||
<em><?php p($l->t('For password reset and notifications')); ?></em>
|
||||
<?php } ?>
|
||||
<input type="hidden" id="emailscope" value="<?php p($_['emailScope']) ?>">
|
||||
</form>
|
||||
<div id="vue-emailsection" class="section"></div>
|
||||
</div>
|
||||
<div class="personal-settings-setting-box">
|
||||
<form id="phoneform" class="section">
|
||||
|
|
@ -223,8 +179,8 @@ script('settings', [
|
|||
</h3>
|
||||
<?php if ($_['lookupServerUploadEnabled']) { ?>
|
||||
<div class="verify <?php if ($_['website'] === '' || $_['websiteScope'] !== 'public') {
|
||||
p('hidden');
|
||||
} ?>">
|
||||
p('hidden');
|
||||
} ?>">
|
||||
<img id="verify-website" title="<?php p($_['websiteMessage']); ?>" data-status="<?php p($_['websiteVerification']) ?>" src="
|
||||
<?php
|
||||
switch ($_['websiteVerification']) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
* @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
* @author Jan C. Borchardt <hey@jancborchardt.net>
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
|
|
@ -25,13 +26,26 @@
|
|||
|
||||
const path = require('path')
|
||||
|
||||
// TODO use @nextcloud/webpack-vue-config
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf)$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]',
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
entry: {
|
||||
'settings-apps-users-management': path.join(__dirname, 'src', 'main-apps-users-management'),
|
||||
'settings-admin-security': path.join(__dirname, 'src', 'main-admin-security'),
|
||||
'settings-personal-security': path.join(__dirname, 'src', 'main-personal-security'),
|
||||
'settings-personal-webauthn': path.join(__dirname, 'src', 'main-personal-webauth'),
|
||||
'settings-nextcloud-pdf': path.join(__dirname, 'src', 'main-nextcloud-pdf'),
|
||||
'settings-personal-info': path.join(__dirname, 'src', 'main-personal-info'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, './js'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue