]> source.dussan.org Git - nextcloud-server.git/commitdiff
Make emails Vuetiful
authorChristopher Ng <chrng8@gmail.com>
Tue, 29 Jun 2021 18:46:37 +0000 (18:46 +0000)
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Thu, 15 Jul 2021 08:16:06 +0000 (10:16 +0200)
Signed-off-by: Christopher Ng <chrng8@gmail.com>
apps/settings/js/federationsettingsview.js
apps/settings/src/components/PersonalInfo/EmailSection/AddButton.vue [new file with mode: 0644]
apps/settings/src/components/PersonalInfo/EmailSection/Email.vue [new file with mode: 0644]
apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue [new file with mode: 0644]
apps/settings/src/components/PersonalInfo/EmailSection/FederationControl.vue [new file with mode: 0644]
apps/settings/src/components/PersonalInfo/EmailSection/HeaderBar.vue [new file with mode: 0644]
apps/settings/src/main-personal-info.js [new file with mode: 0644]
apps/settings/templates/settings/personal/personal.info.php
apps/settings/webpack.js

index cf2865be1b0a19c5ccdacba261bbcd72a47bbd8d..602acab5c8deb49e44a2861a1f585f37df4e22e0 100644 (file)
                _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);
diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/AddButton.vue b/apps/settings/src/components/PersonalInfo/EmailSection/AddButton.vue
new file mode 100644 (file)
index 0000000..83a293e
--- /dev/null
@@ -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>
diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue b/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
new file mode 100644 (file)
index 0000000..7ae0190
--- /dev/null
@@ -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>
diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue b/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue
new file mode 100644 (file)
index 0000000..7000368
--- /dev/null
@@ -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>
diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/FederationControl.vue b/apps/settings/src/components/PersonalInfo/EmailSection/FederationControl.vue
new file mode 100644 (file)
index 0000000..87496a8
--- /dev/null
@@ -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>
diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/HeaderBar.vue b/apps/settings/src/components/PersonalInfo/EmailSection/HeaderBar.vue
new file mode 100644 (file)
index 0000000..7d2b1ab
--- /dev/null
@@ -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>
diff --git a/apps/settings/src/main-personal-info.js b/apps/settings/src/main-personal-info.js
new file mode 100644 (file)
index 0000000..8edbd29
--- /dev/null
@@ -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',
+})
index 6f8516e6437f90e0644d1de111d08c8c40c0ddf1..d3c666d8385ba3a9cb67ac3d642cd102cd9a72cf 100644 (file)
@@ -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']) {
index 756a748ae1b4b369628f3c83f52ea91e10433228..8c6aacc3c947a792b613156c0250023784fc3a20 100644 (file)
@@ -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>
 
 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'),