mirror of
https://github.com/nextcloud/server.git
synced 2026-06-13 10:40:40 -04:00
Vuetify
- abstract shared components - rewrite email section Signed-off-by: Christopher Ng <chrng8@gmail.com>
This commit is contained in:
parent
db182d6517
commit
a8ad5a3b6e
9 changed files with 477 additions and 161 deletions
|
|
@ -0,0 +1,167 @@
|
|||
<!--
|
||||
- @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 class="displayname">
|
||||
<input
|
||||
id="displayname"
|
||||
ref="displayName"
|
||||
type="text"
|
||||
name="displayname"
|
||||
:placeholder="t('settings', 'Your full name')"
|
||||
:value="displayName"
|
||||
autocapitalize="none"
|
||||
autocomplete="on"
|
||||
autocorrect="off"
|
||||
required="true"
|
||||
@input="onDisplayNameChange">
|
||||
|
||||
<div class="displayname__actions-container">
|
||||
<transition name="fade">
|
||||
<span v-if="showCheckmarkIcon" class="icon-checkmark" />
|
||||
<span v-else-if="showErrorIcon" class="icon-error" />
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import debounce from 'debounce'
|
||||
|
||||
import { savePrimaryDisplayName } from '../../../service/PersonalInfo/DisplayNameService'
|
||||
|
||||
// TODO Global avatar updating on events (e.g. updating the displayname) is currently being handled by global js, investigate using https://github.com/nextcloud/nextcloud-event-bus for global avatar updating
|
||||
|
||||
export default {
|
||||
name: 'DisplayName',
|
||||
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
initialDisplayName: this.displayName,
|
||||
localScope: this.scope,
|
||||
showCheckmarkIcon: false,
|
||||
showErrorIcon: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onDisplayNameChange(e) {
|
||||
this.$emit('update:display-name', e.target.value.trim())
|
||||
// $nextTick() ensures that references to this.dipslayName further down the chain give the correct non-outdated value
|
||||
this.$nextTick(() => this.debounceDisplayNameChange())
|
||||
},
|
||||
|
||||
debounceDisplayNameChange: debounce(async function() {
|
||||
if (this.$refs.displayName?.checkValidity() && this.isValid()) {
|
||||
await this.updatePrimaryDisplayName()
|
||||
}
|
||||
}, 500),
|
||||
|
||||
async updatePrimaryDisplayName() {
|
||||
try {
|
||||
const responseData = await savePrimaryDisplayName(this.displayName)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update full name', e)
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse(status, errorMessage, error) {
|
||||
if (status === 'ok') {
|
||||
// Ensure that local initialDiplayName state reflects server state
|
||||
this.initialDisplayName = this.displayName
|
||||
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)
|
||||
}
|
||||
},
|
||||
|
||||
isValid() {
|
||||
return this.displayName !== ''
|
||||
},
|
||||
|
||||
onScopeChange(scope) {
|
||||
this.$emit('update:scope', scope)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.displayname {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
input[type=text] {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
|
||||
.displayname__actions-container {
|
||||
grid-area: 1 / 1;
|
||||
justify-self: flex-end;
|
||||
height: 30px;
|
||||
|
||||
display: flex;
|
||||
gap: 0 2px;
|
||||
margin-right: 5px;
|
||||
|
||||
.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,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
transition: opacity 200ms ease-out;
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
transition: opacity 300ms ease-out;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<!--
|
||||
- @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
|
||||
:account-property="accountProperty"
|
||||
label-for="displayname"
|
||||
:is-editable="displayNameChangeSupported"
|
||||
:is-valid-form="isValidForm"
|
||||
:handle-scope-change="savePrimaryDisplayNameScope"
|
||||
:scope.sync="primaryDisplayName.scope" />
|
||||
|
||||
<template v-if="displayNameChangeSupported">
|
||||
<DisplayName
|
||||
:scope.sync="primaryDisplayName.scope"
|
||||
:display-name.sync="primaryDisplayName.value"
|
||||
@update:display-name="onUpdateDisplayName" />
|
||||
</template>
|
||||
|
||||
<span v-else>
|
||||
{{ primaryDisplayName.value || t('settings', 'No full name set') }}
|
||||
</span>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
|
||||
import DisplayName from './DisplayName'
|
||||
import HeaderBar from '../shared/HeaderBar'
|
||||
|
||||
import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants'
|
||||
import { savePrimaryDisplayNameScope } from '../../../service/PersonalInfo/DisplayNameService'
|
||||
|
||||
const { displayNames: { primaryDisplayName } } = loadState('settings', 'personalInfoParameters', {})
|
||||
const { displayNameChangeSupported } = loadState('settings', 'accountParameters', {})
|
||||
|
||||
export default {
|
||||
name: 'DisplayNameSection',
|
||||
|
||||
components: {
|
||||
DisplayName,
|
||||
HeaderBar,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.DISPLAYNAME,
|
||||
displayNameChangeSupported,
|
||||
isValidForm: true,
|
||||
primaryDisplayName,
|
||||
savePrimaryDisplayNameScope,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => this.updateFormValidity())
|
||||
},
|
||||
|
||||
methods: {
|
||||
onUpdateDisplayName() {
|
||||
this.$nextTick(() => this.updateFormValidity())
|
||||
},
|
||||
|
||||
updateFormValidity() {
|
||||
this.isValidForm = this.$refs.form?.checkValidity()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
form::v-deep button {
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<div class="email-container">
|
||||
<div class="email">
|
||||
<input
|
||||
ref="email"
|
||||
type="email"
|
||||
|
|
@ -34,21 +34,25 @@
|
|||
required="true"
|
||||
@input="onEmailChange">
|
||||
|
||||
<div class="email-actions-container">
|
||||
<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" />
|
||||
<template v-if="!primary">
|
||||
<FederationControl
|
||||
:account-property="accountProperty"
|
||||
:additional="true"
|
||||
:additional-value="email"
|
||||
:disabled="federationDisabled"
|
||||
:handle-scope-change="saveAdditionalEmailScope"
|
||||
:scope.sync="localScope"
|
||||
@update:scope="onScopeChange" />
|
||||
</template>
|
||||
|
||||
<Actions
|
||||
class="actions-email"
|
||||
class="email__actions"
|
||||
:aria-label="t('settings', 'Email options')"
|
||||
:disabled="deleteDisabled"
|
||||
:force-menu="true">
|
||||
|
|
@ -75,8 +79,10 @@ 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'
|
||||
import FederationControl from '../shared/FederationControl'
|
||||
|
||||
import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants'
|
||||
import { savePrimaryEmail, saveAdditionalEmail, saveAdditionalEmailScope, updateAdditionalEmail, removeAdditionalEmail } from '../../../service/PersonalInfo/EmailService'
|
||||
|
||||
export default {
|
||||
name: 'Email',
|
||||
|
|
@ -92,48 +98,32 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
primary: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL,
|
||||
initialEmail: this.email,
|
||||
localScope: this.scope,
|
||||
saveAdditionalEmailScope,
|
||||
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() {
|
||||
if (this.primary) {
|
||||
return this.email === ''
|
||||
|
|
@ -147,6 +137,24 @@ export default {
|
|||
}
|
||||
return t('settings', 'Delete email')
|
||||
},
|
||||
|
||||
federationDisabled() {
|
||||
return !this.initialEmail
|
||||
},
|
||||
|
||||
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 })
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
|
@ -157,7 +165,7 @@ export default {
|
|||
|
||||
methods: {
|
||||
onEmailChange(e) {
|
||||
this.$emit('update:email', e.target.value)
|
||||
this.$emit('update:email', e.target.value.trim())
|
||||
// $nextTick() ensures that references to this.email further down the chain give the correct non-outdated value
|
||||
this.$nextTick(() => this.debounceEmailChange())
|
||||
},
|
||||
|
|
@ -227,13 +235,9 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
isValid() {
|
||||
return /^\S+$/.test(this.email)
|
||||
},
|
||||
|
||||
handleDeleteAdditionalEmail(status) {
|
||||
if (status === 'ok') {
|
||||
this.$emit('deleteAdditionalEmail')
|
||||
this.$emit('delete-additional-email')
|
||||
} else {
|
||||
this.handleResponse('error', 'Unable to delete additional email address', {})
|
||||
}
|
||||
|
|
@ -253,6 +257,10 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
isValid() {
|
||||
return /^\S+$/.test(this.email)
|
||||
},
|
||||
|
||||
onScopeChange(scope) {
|
||||
this.$emit('update:scope', scope)
|
||||
},
|
||||
|
|
@ -261,7 +269,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.email-container {
|
||||
.email {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
|
|
@ -269,7 +277,7 @@ export default {
|
|||
grid-area: 1 / 1;
|
||||
}
|
||||
|
||||
.email-actions-container {
|
||||
.email__actions-container {
|
||||
grid-area: 1 / 1;
|
||||
justify-self: flex-end;
|
||||
height: 30px;
|
||||
|
|
@ -278,7 +286,7 @@ export default {
|
|||
gap: 0 2px;
|
||||
margin-right: 5px;
|
||||
|
||||
.actions-email {
|
||||
.email__actions {
|
||||
opacity: 0.4 !important;
|
||||
|
||||
&:hover {
|
||||
|
|
@ -293,17 +301,6 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
|
@ -317,6 +314,11 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
transition: opacity 200ms ease-out;
|
||||
}
|
||||
|
|
@ -324,9 +326,4 @@ export default {
|
|||
.fade-leave-active {
|
||||
transition: opacity 300ms ease-out;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -25,10 +25,14 @@
|
|||
class="section"
|
||||
@submit.stop.prevent="() => {}">
|
||||
<HeaderBar
|
||||
:can-edit-emails="displayNameChangeSupported"
|
||||
:account-property="accountProperty"
|
||||
label-for="email"
|
||||
:handle-scope-change="savePrimaryEmailScope"
|
||||
:is-editable="displayNameChangeSupported"
|
||||
:is-multi-value-supported="true"
|
||||
:is-valid-form="isValidForm"
|
||||
:scope.sync="primaryEmail.scope"
|
||||
@addAdditionalEmail="onAddAdditionalEmail" />
|
||||
@add-additional="onAddAdditionalEmail" />
|
||||
|
||||
<template v-if="displayNameChangeSupported">
|
||||
<Email
|
||||
|
|
@ -42,7 +46,7 @@
|
|||
:scope.sync="additionalEmail.scope"
|
||||
:email.sync="additionalEmail.value"
|
||||
@update:email="onUpdateEmail"
|
||||
@deleteAdditionalEmail="onDeleteAdditionalEmail(index)" />
|
||||
@delete-additional-email="onDeleteAdditionalEmail(index)" />
|
||||
</template>
|
||||
|
||||
<span v-else>
|
||||
|
|
@ -54,14 +58,14 @@
|
|||
<script>
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import '@nextcloud/dialogs/styles/toast.scss'
|
||||
|
||||
import HeaderBar from './HeaderBar'
|
||||
import Email from './Email'
|
||||
import { savePrimaryEmail, removeAdditionalEmail } from '../../../service/PersonalInfoService'
|
||||
import { DEFAULT_ADDITIONAL_EMAIL_SCOPE } from '../../../constants/AccountPropertyConstants'
|
||||
import HeaderBar from '../shared/HeaderBar'
|
||||
|
||||
const { additionalEmails, primaryEmail } = loadState('settings', 'emails', {})
|
||||
import { ACCOUNT_PROPERTY_READABLE_ENUM, DEFAULT_ADDITIONAL_EMAIL_SCOPE } from '../../../constants/AccountPropertyConstants'
|
||||
import { savePrimaryEmail, savePrimaryEmailScope, removeAdditionalEmail } from '../../../service/PersonalInfo/EmailService'
|
||||
|
||||
const { emails: { additionalEmails, primaryEmail } } = loadState('settings', 'personalInfoParameters', {})
|
||||
const { displayNameChangeSupported } = loadState('settings', 'accountParameters', {})
|
||||
|
||||
export default {
|
||||
|
|
@ -74,10 +78,12 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL,
|
||||
additionalEmails,
|
||||
displayNameChangeSupported,
|
||||
primaryEmail,
|
||||
isValidForm: true,
|
||||
primaryEmail,
|
||||
savePrimaryEmailScope,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ export default {
|
|||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(127, 127, 127, .15);
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
|
|
@ -66,13 +66,13 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(127, 127, 127, .15);
|
||||
}
|
||||
|
||||
&:enabled:hover {
|
||||
background-color: rgba(127, 127, 127, .25);
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -21,15 +21,15 @@
|
|||
|
||||
<template>
|
||||
<Actions
|
||||
class="actions-federation"
|
||||
:aria-label="t('settings', 'Change privacy level of email')"
|
||||
:class="{ 'federation-actions': !additional, 'federation-actions--additional': additional }"
|
||||
:aria-label="ariaLabel"
|
||||
: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"
|
||||
class="federation-actions__btn"
|
||||
:class="{ 'federation-actions__btn--active': scope === federationScope.name }"
|
||||
:close-after-click="true"
|
||||
:icon="federationScope.iconClass"
|
||||
:title="federationScope.displayName"
|
||||
|
|
@ -45,14 +45,10 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
|||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
import { SCOPE_ENUM, SCOPE_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants'
|
||||
import { savePrimaryEmailScope, saveAdditionalEmailScope } from '../../../service/PersonalInfoService'
|
||||
import { ACCOUNT_PROPERTY_READABLE_ENUM, PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM, SCOPE_ENUM, SCOPE_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants'
|
||||
|
||||
const { lookupServerUploadEnabled } = loadState('settings', 'accountParameters', {})
|
||||
|
||||
// TODO hardcoded for email, should abstract this for other sections
|
||||
const excludedScopes = [SCOPE_ENUM.PRIVATE]
|
||||
|
||||
export default {
|
||||
name: 'FederationControl',
|
||||
|
||||
|
|
@ -62,49 +58,63 @@ export default {
|
|||
},
|
||||
|
||||
props: {
|
||||
primary: {
|
||||
accountProperty: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value) => Object.values(ACCOUNT_PROPERTY_READABLE_ENUM).includes(value),
|
||||
},
|
||||
additional: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
email: {
|
||||
additionalValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
handleScopeChange: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
accountPropertyLowerCase: this.accountProperty.toLowerCase(),
|
||||
initialScope: this.scope,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
federationScopes() {
|
||||
return Object.values(SCOPE_PROPERTY_ENUM).filter(({ name }) => !this.unsupportedScopes.includes(name))
|
||||
ariaLabel() {
|
||||
return t('settings', 'Change privacy level of {accountProperty}', { accountProperty: this.accountPropertyLowerCase })
|
||||
},
|
||||
|
||||
unsupportedScopes() {
|
||||
if (!lookupServerUploadEnabled) {
|
||||
federationScopes() {
|
||||
return Object.values(SCOPE_PROPERTY_ENUM).filter(({ name }) => this.supportedScopes.includes(name))
|
||||
},
|
||||
|
||||
scopeIcon() {
|
||||
return SCOPE_PROPERTY_ENUM[this.scope].iconClass
|
||||
},
|
||||
|
||||
supportedScopes() {
|
||||
if (lookupServerUploadEnabled) {
|
||||
return [
|
||||
...excludedScopes,
|
||||
...PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM[this.accountProperty],
|
||||
SCOPE_ENUM.FEDERATED,
|
||||
SCOPE_ENUM.PUBLISHED,
|
||||
]
|
||||
}
|
||||
|
||||
return excludedScopes
|
||||
},
|
||||
|
||||
scopeIcon() {
|
||||
return SCOPE_PROPERTY_ENUM[this.scope].iconClass
|
||||
return PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM[this.accountProperty]
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -113,29 +123,45 @@ export default {
|
|||
this.$emit('update:scope', scope)
|
||||
|
||||
this.$nextTick(async() => {
|
||||
if (this.primary) {
|
||||
await this.updatePrimaryEmailScope()
|
||||
if (!this.additional) {
|
||||
await this.updatePrimaryScope()
|
||||
} else {
|
||||
await this.updateAdditionalEmailScope()
|
||||
await this.updateAdditionalScope()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async updatePrimaryEmailScope() {
|
||||
async updatePrimaryScope() {
|
||||
try {
|
||||
const responseData = await savePrimaryEmailScope(this.scope)
|
||||
const responseData = await this.handleScopeChange(this.scope)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update federation scope of the primary email', e)
|
||||
this.handleResponse(
|
||||
'error',
|
||||
t(
|
||||
'settings',
|
||||
'Unable to update federation scope of the primary {accountProperty}',
|
||||
{ accountProperty: this.accountPropertyLowerCase }
|
||||
),
|
||||
e,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
async updateAdditionalEmailScope() {
|
||||
async updateAdditionalScope() {
|
||||
try {
|
||||
const responseData = await saveAdditionalEmailScope(this.email, this.scope)
|
||||
const responseData = await this.handleScopeChange(this.additionalValue, this.scope)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update federation scope of additional email', e)
|
||||
this.handleResponse(
|
||||
'error',
|
||||
t(
|
||||
'settings',
|
||||
'Unable to update federation scope of additional {accountProperty}',
|
||||
{ accountProperty: this.accountPropertyLowerCase }
|
||||
),
|
||||
e,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -144,7 +170,7 @@ export default {
|
|||
this.initialScope = this.scope
|
||||
} else {
|
||||
this.$emit('update:scope', this.initialScope)
|
||||
showError(t('settings', errorMessage))
|
||||
showError(errorMessage)
|
||||
this.logger.error(errorMessage, error)
|
||||
}
|
||||
},
|
||||
|
|
@ -153,7 +179,8 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.actions-federation {
|
||||
.federation-actions,
|
||||
.federation-actions--additional {
|
||||
opacity: 0.4 !important;
|
||||
|
||||
&:hover {
|
||||
|
|
@ -161,12 +188,18 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.forced-active {
|
||||
background-color: var(--color-primary-light) !important;
|
||||
box-shadow: inset 2px 0 var(--color-primary) !important;
|
||||
.federation-actions--additional {
|
||||
&::v-deep button {
|
||||
// TODO remove this hack
|
||||
padding-bottom: 7px;
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
width: 30px !important;
|
||||
min-width: 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.forced-action {
|
||||
.federation-actions__btn {
|
||||
&::v-deep p {
|
||||
width: 150px !important;
|
||||
padding: 8px 0 !important;
|
||||
|
|
@ -175,4 +208,9 @@ export default {
|
|||
line-height: 1.5em !important;
|
||||
}
|
||||
}
|
||||
|
||||
.federation-actions__btn--active {
|
||||
background-color: var(--color-primary-light) !important;
|
||||
box-shadow: inset 2px 0 var(--color-primary) !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -21,44 +21,66 @@
|
|||
|
||||
<template>
|
||||
<h3>
|
||||
<label for="email">
|
||||
{{ t('settings', 'Email') }}
|
||||
<label :for="labelFor">
|
||||
{{ t('settings', accountProperty) }}
|
||||
</label>
|
||||
|
||||
<FederationControl
|
||||
class="federation-control"
|
||||
:primary="true"
|
||||
:account-property="accountProperty"
|
||||
:handle-scope-change="handleScopeChange"
|
||||
:scope.sync="localScope"
|
||||
@update:scope="onScopeChange" />
|
||||
|
||||
<AddButton v-if="canEditEmails"
|
||||
class="add-button"
|
||||
:disabled="!isValidForm"
|
||||
@click.stop.prevent="addAdditionalEmail" />
|
||||
<template v-if="isEditable && isMultiValueSupported">
|
||||
<AddButton
|
||||
class="add-button"
|
||||
:disabled="!isValidForm"
|
||||
@click.stop.prevent="onAddAdditional" />
|
||||
</template>
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FederationControl from './FederationControl'
|
||||
import AddButton from './AddButton'
|
||||
import FederationControl from './FederationControl'
|
||||
|
||||
import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants'
|
||||
|
||||
export default {
|
||||
name: 'HeaderBar',
|
||||
|
||||
components: {
|
||||
FederationControl,
|
||||
AddButton,
|
||||
FederationControl,
|
||||
},
|
||||
|
||||
props: {
|
||||
canEditEmails: {
|
||||
accountProperty: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value) => Object.values(ACCOUNT_PROPERTY_READABLE_ENUM).includes(value),
|
||||
},
|
||||
handleScopeChange: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
isEditable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: true,
|
||||
},
|
||||
isMultiValueSupported: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isValidForm: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
labelFor: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -72,8 +94,8 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
addAdditionalEmail() {
|
||||
this.$emit('addAdditionalEmail')
|
||||
onAddAdditional() {
|
||||
this.$emit('add-additional')
|
||||
},
|
||||
|
||||
onScopeChange(scope) {
|
||||
|
|
@ -21,18 +21,29 @@
|
|||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import '@nextcloud/dialogs/styles/toast.scss'
|
||||
|
||||
import logger from './logger'
|
||||
|
||||
import DisplayNameSection from './components/PersonalInfo/DisplayNameSection/DisplayNameSection'
|
||||
import EmailSection from './components/PersonalInfo/EmailSection/EmailSection'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_nonce__ = btoa(OC.requestToken)
|
||||
__webpack_nonce__ = btoa(getRequestToken())
|
||||
|
||||
Vue.prototype.t = t
|
||||
Vue.prototype.logger = logger
|
||||
|
||||
const View = Vue.extend(EmailSection)
|
||||
export default new View({
|
||||
el: '#vue-emailsection',
|
||||
Vue.mixin({
|
||||
props: {
|
||||
logger,
|
||||
},
|
||||
methods: {
|
||||
t,
|
||||
},
|
||||
})
|
||||
|
||||
const DisplayNameView = Vue.extend(DisplayNameSection)
|
||||
const EmailView = Vue.extend(EmailSection)
|
||||
|
||||
new DisplayNameView().$mount('#vue-displaynamesection')
|
||||
new EmailView().$mount('#vue-emailsection')
|
||||
|
|
|
|||
|
|
@ -100,32 +100,7 @@ script('settings', [
|
|||
|
||||
<div class="personal-settings-container">
|
||||
<div class="personal-settings-setting-box">
|
||||
<form id="displaynameform" class="section">
|
||||
<h3>
|
||||
<label for="displayname"><?php p($l->t('Full name')); ?></label>
|
||||
<a href="#" class="federation-menu" aria-label="<?php p($l->t('Change privacy level of full name')); ?>">
|
||||
<span class="icon-federation-menu icon-password">
|
||||
<span class="icon-triangle-s"></span>
|
||||
</span>
|
||||
</a>
|
||||
</h3>
|
||||
<input type="text" id="displayname" name="displayname"
|
||||
<?php if (!$_['displayNameChangeSupported']) {
|
||||
print_unescaped('class="hidden"');
|
||||
} ?>
|
||||
value="<?php p($_['displayName']) ?>"
|
||||
autocomplete="on" autocapitalize="none" autocorrect="off" />
|
||||
<?php if (!$_['displayNameChangeSupported']) { ?>
|
||||
<span><?php if (isset($_['displayName']) && !empty($_['displayName'])) {
|
||||
p($_['displayName']);
|
||||
} else {
|
||||
p($l->t('No display name set'));
|
||||
} ?></span>
|
||||
<?php } ?>
|
||||
<span class="icon-checkmark hidden"></span>
|
||||
<span class="icon-error hidden" ></span>
|
||||
<input type="hidden" id="displaynamescope" value="<?php p($_['displayNameScope']) ?>">
|
||||
</form>
|
||||
<div id="vue-displaynamesection" class="section"></div>
|
||||
</div>
|
||||
<div class="personal-settings-setting-box">
|
||||
<div id="vue-emailsection" class="section"></div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue