diff options
9 files changed, 832 insertions, 49 deletions
diff --git a/apps/settings/js/federationsettingsview.js b/apps/settings/js/federationsettingsview.js index cf2865be1b0..602acab5c8d 100644 --- a/apps/settings/js/federationsettingsview.js +++ b/apps/settings/js/federationsettingsview.js @@ -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); diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/AddButton.vue b/apps/settings/src/components/PersonalInfo/EmailSection/AddButton.vue new file mode 100644 index 00000000000..83a293ed234 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/EmailSection/AddButton.vue @@ -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 index 00000000000..7ae01908013 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue @@ -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 index 00000000000..700036872b4 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue @@ -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 index 00000000000..87496a81160 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/EmailSection/FederationControl.vue @@ -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 index 00000000000..7d2b1ab76b6 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/EmailSection/HeaderBar.vue @@ -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 index 00000000000..8edbd29669f --- /dev/null +++ b/apps/settings/src/main-personal-info.js @@ -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', +}) diff --git a/apps/settings/templates/settings/personal/personal.info.php b/apps/settings/templates/settings/personal/personal.info.php index 6f8516e6437..d3c666d8385 100644 --- a/apps/settings/templates/settings/personal/personal.info.php +++ b/apps/settings/templates/settings/personal/personal.info.php @@ -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']) { diff --git a/apps/settings/webpack.js b/apps/settings/webpack.js index 756a748ae1b..8c6aacc3c94 100644 --- a/apps/settings/webpack.js +++ b/apps/settings/webpack.js @@ -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'), |