mirror of
https://github.com/nextcloud/server.git
synced 2026-06-13 18:50:47 -04:00
Merge pull request #28001 from nextcloud/backport/27379/stable22
This commit is contained in:
commit
0263eea033
37 changed files with 1655 additions and 276 deletions
|
|
@ -119,7 +119,10 @@
|
|||
_registerEvents: function() {
|
||||
var self = this;
|
||||
_.each(this._inputFields, function(field) {
|
||||
if (field === 'avatar') {
|
||||
if (
|
||||
field === 'avatar' ||
|
||||
field === 'email'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.$('#' + field).keyUpDelayedOrEnter(_.bind(self._onInputChanged, self), true);
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
227
apps/settings/js/vue-settings-personal-info.js
Normal file
227
apps/settings/js/vue-settings-personal-info.js
Normal file
File diff suppressed because one or more lines are too long
1
apps/settings/js/vue-settings-personal-info.js.map
Normal file
1
apps/settings/js/vue-settings-personal-info.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -7,6 +7,7 @@ declare(strict_types=1);
|
|||
*
|
||||
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
|
|
@ -48,6 +49,8 @@ use OCP\IUser;
|
|||
use OCP\IUserManager;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Settings\ISettings;
|
||||
use OCP\Accounts\IAccountProperty;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
|
||||
class PersonalInfo implements ISettings {
|
||||
|
||||
|
|
@ -65,6 +68,8 @@ class PersonalInfo implements ISettings {
|
|||
private $l10nFactory;
|
||||
/** @var IL10N */
|
||||
private $l;
|
||||
/** @var IInitialState */
|
||||
private $initialStateService;
|
||||
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
|
|
@ -73,7 +78,8 @@ class PersonalInfo implements ISettings {
|
|||
IAccountManager $accountManager,
|
||||
IAppManager $appManager,
|
||||
IFactory $l10nFactory,
|
||||
IL10N $l
|
||||
IL10N $l,
|
||||
IInitialState $initialStateService
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->userManager = $userManager;
|
||||
|
|
@ -82,6 +88,7 @@ class PersonalInfo implements ISettings {
|
|||
$this->appManager = $appManager;
|
||||
$this->l10nFactory = $l10nFactory;
|
||||
$this->l = $l;
|
||||
$this->initialStateService = $initialStateService;
|
||||
}
|
||||
|
||||
public function getForm(): TemplateResponse {
|
||||
|
|
@ -138,6 +145,14 @@ class PersonalInfo implements ISettings {
|
|||
'groups' => $this->getGroups($user),
|
||||
] + $messageParameters + $languageParameters + $localeParameters;
|
||||
|
||||
$emails = $this->getEmails($account);
|
||||
|
||||
$accountParameters = [
|
||||
'displayNameChangeSupported' => $user->canChangeDisplayName(),
|
||||
];
|
||||
|
||||
$this->initialStateService->provideInitialState('emails', $emails);
|
||||
$this->initialStateService->provideInitialState('accountParameters', $accountParameters);
|
||||
|
||||
return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, '');
|
||||
}
|
||||
|
|
@ -180,6 +195,39 @@ class PersonalInfo implements ISettings {
|
|||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the primary email and additional emails in an
|
||||
* associative array
|
||||
*
|
||||
* @param IAccount $account
|
||||
* @return array
|
||||
*/
|
||||
private function getEmails(IAccount $account): array {
|
||||
$primaryEmail = [
|
||||
'value' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
|
||||
'scope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
|
||||
'verified' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getVerified(),
|
||||
];
|
||||
|
||||
$additionalEmails = array_map(
|
||||
function (IAccountProperty $property) {
|
||||
return [
|
||||
'value' => $property->getValue(),
|
||||
'scope' => $property->getScope(),
|
||||
'verified' => $property->getVerified(),
|
||||
];
|
||||
},
|
||||
$account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties()
|
||||
);
|
||||
|
||||
$emails = [
|
||||
'primaryEmail' => $primaryEmail,
|
||||
'additionalEmails' => $additionalEmails,
|
||||
];
|
||||
|
||||
return $emails;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the user language, common language and other languages in an
|
||||
* associative array
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<button
|
||||
:disabled="disabled"
|
||||
@click.stop.prevent="onClick">
|
||||
<span class="icon icon-add" />
|
||||
{{ t('settings', 'Add') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AddButton',
|
||||
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick(e) {
|
||||
this.$emit('click', e)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
button {
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(127, 127, 127, .15);
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
opacity: 0.4 !important;
|
||||
|
||||
.icon {
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:enabled:hover {
|
||||
background-color: rgba(127, 127, 127, .25);
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
323
apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
Normal file
323
apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="email-container">
|
||||
<input
|
||||
ref="email"
|
||||
type="email"
|
||||
:name="inputName"
|
||||
:placeholder="inputPlaceholder"
|
||||
:value="email"
|
||||
autocapitalize="none"
|
||||
autocomplete="on"
|
||||
autocorrect="off"
|
||||
required="true"
|
||||
@input="onEmailChange">
|
||||
|
||||
<div class="email-actions-container">
|
||||
<transition name="fade">
|
||||
<span v-if="showCheckmarkIcon" class="icon-checkmark" />
|
||||
<span v-else-if="showErrorIcon" class="icon-error" />
|
||||
</transition>
|
||||
|
||||
<FederationControl v-if="!primary"
|
||||
class="federation-control"
|
||||
:disabled="federationDisabled"
|
||||
:email="email"
|
||||
:scope.sync="localScope"
|
||||
@update:scope="onScopeChange" />
|
||||
|
||||
<Actions
|
||||
class="actions-email"
|
||||
:aria-label="t('settings', 'Email options')"
|
||||
:disabled="deleteDisabled"
|
||||
:force-menu="true">
|
||||
<ActionButton
|
||||
:aria-label="deleteEmailLabel"
|
||||
:close-after-click="true"
|
||||
icon="icon-delete"
|
||||
@click.stop.prevent="deleteEmail">
|
||||
{{ deleteEmailLabel }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<em v-if="primary">
|
||||
{{ t('settings', 'Primary email for password reset and notifications') }}
|
||||
</em>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import debounce from 'debounce'
|
||||
|
||||
import FederationControl from './FederationControl'
|
||||
import { savePrimaryEmail, saveAdditionalEmail, updateAdditionalEmail, removeAdditionalEmail } from '../../../service/PersonalInfoService'
|
||||
|
||||
export default {
|
||||
name: 'Email',
|
||||
|
||||
components: {
|
||||
Actions,
|
||||
ActionButton,
|
||||
FederationControl,
|
||||
},
|
||||
|
||||
props: {
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
primary: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
initialEmail: this.email,
|
||||
localScope: this.scope,
|
||||
showCheckmarkIcon: false,
|
||||
showErrorIcon: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
inputName() {
|
||||
if (this.primary) {
|
||||
return 'email'
|
||||
}
|
||||
return 'additionalEmail[]'
|
||||
},
|
||||
|
||||
inputPlaceholder() {
|
||||
if (this.primary) {
|
||||
return t('settings', 'Your email address')
|
||||
}
|
||||
return t('settings', 'Additional email address {index}', { index: this.index + 1 })
|
||||
},
|
||||
|
||||
federationDisabled() {
|
||||
return !this.initialEmail
|
||||
},
|
||||
|
||||
deleteDisabled() {
|
||||
return !this.containsNoWhitespace(this.email)
|
||||
},
|
||||
|
||||
deleteEmailLabel() {
|
||||
if (this.primary) {
|
||||
return t('settings', 'Remove primary email')
|
||||
}
|
||||
return t('settings', 'Delete email')
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onEmailChange(e) {
|
||||
this.$emit('update:email', e.target.value)
|
||||
// $nextTick() ensures that references to this.email further down the chain give the correct non-outdated value
|
||||
this.$nextTick(() => this.debounceEmailChange())
|
||||
},
|
||||
|
||||
debounceEmailChange: debounce(async function() {
|
||||
if ((this.$refs.email?.checkValidity() && this.containsNoWhitespace(this.email)) || this.email === '') {
|
||||
if (this.primary) {
|
||||
await this.updatePrimaryEmail()
|
||||
} else {
|
||||
if (this.initialEmail && this.email === '') {
|
||||
await this.deleteAdditionalEmail()
|
||||
} else if (this.initialEmail === '') {
|
||||
await this.addAdditionalEmail()
|
||||
} else {
|
||||
await this.updateAdditionalEmail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500),
|
||||
|
||||
async deleteEmail() {
|
||||
if (this.primary) {
|
||||
this.$emit('update:email', '')
|
||||
this.$nextTick(async() => await this.updatePrimaryEmail())
|
||||
} else {
|
||||
await this.deleteAdditionalEmail()
|
||||
}
|
||||
},
|
||||
|
||||
async updatePrimaryEmail() {
|
||||
try {
|
||||
const responseData = await savePrimaryEmail(this.email)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
if (this.email === '') {
|
||||
this.handleResponse('error', 'Unable to delete primary email address', e)
|
||||
} else {
|
||||
this.handleResponse('error', 'Unable to update primary email address', e)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async addAdditionalEmail() {
|
||||
try {
|
||||
const responseData = await saveAdditionalEmail(this.email)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to add additional email address', e)
|
||||
}
|
||||
},
|
||||
|
||||
async updateAdditionalEmail() {
|
||||
try {
|
||||
const responseData = await updateAdditionalEmail(this.initialEmail, this.email)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update additional email address', e)
|
||||
}
|
||||
},
|
||||
|
||||
async deleteAdditionalEmail() {
|
||||
try {
|
||||
const responseData = await removeAdditionalEmail(this.initialEmail)
|
||||
this.handleDeleteAdditionalEmail(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to delete additional email address', e)
|
||||
}
|
||||
},
|
||||
|
||||
containsNoWhitespace(string) {
|
||||
return /^\S+$/.test(string)
|
||||
},
|
||||
|
||||
handleDeleteAdditionalEmail(status) {
|
||||
if (status === 'ok') {
|
||||
this.$emit('deleteAdditionalEmail')
|
||||
} else {
|
||||
this.handleResponse('error', 'Unable to delete additional email address', {})
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse(status, errorMessage, error) {
|
||||
if (status === 'ok') {
|
||||
// Ensure that local initialEmail state reflects server state
|
||||
this.initialEmail = this.email
|
||||
this.showCheckmarkIcon = true
|
||||
setTimeout(() => { this.showCheckmarkIcon = false }, 2000)
|
||||
} else {
|
||||
showError(t('settings', errorMessage))
|
||||
this.logger.error(errorMessage, error)
|
||||
this.showErrorIcon = true
|
||||
setTimeout(() => { this.showErrorIcon = false }, 2000)
|
||||
}
|
||||
},
|
||||
|
||||
onScopeChange(scope) {
|
||||
this.$emit('update:scope', scope)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.email-container {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
input[type=email] {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
|
||||
.email-actions-container {
|
||||
grid-area: 1 / 1;
|
||||
justify-self: flex-end;
|
||||
height: 30px;
|
||||
|
||||
display: flex;
|
||||
gap: 0 2px;
|
||||
margin-right: 5px;
|
||||
|
||||
.actions-email {
|
||||
opacity: 0.4 !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
|
||||
&::v-deep button {
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
width: 30px !important;
|
||||
min-width: 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.federation-control {
|
||||
&::v-deep button {
|
||||
// TODO remove this hack
|
||||
padding-bottom: 7px;
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
width: 30px !important;
|
||||
min-width: 30px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-checkmark,
|
||||
.icon-error {
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
width: 30px !important;
|
||||
min-width: 30px !important;
|
||||
top: 0;
|
||||
right: 0;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
transition: opacity 200ms ease-out;
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
transition: opacity 300ms ease-out;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
<!--
|
||||
- @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="onUpdateEmail" />
|
||||
<Email v-for="(additionalEmail, index) in additionalEmails"
|
||||
:key="index"
|
||||
:index="index"
|
||||
:scope.sync="additionalEmail.scope"
|
||||
:email.sync="additionalEmail.value"
|
||||
@update:email="onUpdateEmail"
|
||||
@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 { 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'
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
primaryEmailValue: {
|
||||
get() {
|
||||
return this.primaryEmail.value
|
||||
},
|
||||
set(value) {
|
||||
this.primaryEmail.value = value
|
||||
},
|
||||
},
|
||||
|
||||
firstAdditionalEmail() {
|
||||
if (this.additionalEmails.length) {
|
||||
return this.additionalEmails[0].value
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
|
||||
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)
|
||||
},
|
||||
|
||||
async onUpdateEmail() {
|
||||
this.$nextTick(() => this.updateFormValidity())
|
||||
|
||||
if (this.primaryEmailValue === '' && this.firstAdditionalEmail) {
|
||||
const deletedEmail = this.firstAdditionalEmail
|
||||
await this.deleteFirstAdditionalEmail()
|
||||
this.primaryEmailValue = deletedEmail
|
||||
await this.updatePrimaryEmail()
|
||||
this.$nextTick(() => this.updateFormValidity())
|
||||
}
|
||||
},
|
||||
|
||||
async updatePrimaryEmail() {
|
||||
try {
|
||||
const responseData = await savePrimaryEmail(this.primaryEmailValue)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update primary email address', e)
|
||||
}
|
||||
},
|
||||
|
||||
async deleteFirstAdditionalEmail() {
|
||||
try {
|
||||
const responseData = await removeAdditionalEmail(this.firstAdditionalEmail)
|
||||
this.handleDeleteFirstAdditionalEmail(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to delete additional email address', e)
|
||||
}
|
||||
},
|
||||
|
||||
handleDeleteFirstAdditionalEmail(status) {
|
||||
if (status === 'ok') {
|
||||
this.$delete(this.additionalEmails, 0)
|
||||
} else {
|
||||
this.handleResponse('error', 'Unable to delete additional email address', {})
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse(status, errorMessage, error) {
|
||||
if (status !== 'ok') {
|
||||
showError(t('settings', errorMessage))
|
||||
this.logger.error(errorMessage, error)
|
||||
}
|
||||
},
|
||||
|
||||
updateFormValidity() {
|
||||
this.isValidForm = this.$refs.form?.checkValidity()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
form::v-deep button {
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<Actions
|
||||
class="actions-federation"
|
||||
:aria-label="t('settings', 'Change privacy level of email')"
|
||||
:default-icon="scopeIcon"
|
||||
:disabled="disabled">
|
||||
<ActionButton v-for="federationScope in federationScopes"
|
||||
:key="federationScope.name"
|
||||
class="forced-action"
|
||||
:class="{ 'forced-active': scope === federationScope.name }"
|
||||
:aria-label="federationScope.tooltip"
|
||||
:close-after-click="true"
|
||||
:icon="federationScope.iconClass"
|
||||
:title="federationScope.displayName"
|
||||
@click.stop.prevent="changeScope(federationScope.name)">
|
||||
{{ federationScope.tooltip }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
import { SCOPE_ENUM, SCOPE_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants'
|
||||
import { savePrimaryEmailScope, saveAdditionalEmailScope } from '../../../service/PersonalInfoService'
|
||||
|
||||
// TODO hardcoded for email, should abstract this for other sections
|
||||
const excludedScopes = [SCOPE_ENUM.PRIVATE]
|
||||
|
||||
export default {
|
||||
name: 'FederationControl',
|
||||
|
||||
components: {
|
||||
Actions,
|
||||
ActionButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
primary: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
initialScope: this.scope,
|
||||
federationScopes: Object.values(SCOPE_PROPERTY_ENUM).filter(({ name }) => !excludedScopes.includes(name)),
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
scopeIcon() {
|
||||
return SCOPE_PROPERTY_ENUM[this.scope].iconClass
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async changeScope(scope) {
|
||||
this.$emit('update:scope', scope)
|
||||
|
||||
this.$nextTick(async() => {
|
||||
if (this.primary) {
|
||||
await this.updatePrimaryEmailScope()
|
||||
} else {
|
||||
await this.updateAdditionalEmailScope()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async updatePrimaryEmailScope() {
|
||||
try {
|
||||
const responseData = await savePrimaryEmailScope(this.scope)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update federation scope of the primary email', e)
|
||||
}
|
||||
},
|
||||
|
||||
async updateAdditionalEmailScope() {
|
||||
try {
|
||||
const responseData = await saveAdditionalEmailScope(this.email, this.scope)
|
||||
this.handleResponse(responseData.ocs?.meta?.status)
|
||||
} catch (e) {
|
||||
this.handleResponse('error', 'Unable to update federation scope of additional email', e)
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse(status, errorMessage, error) {
|
||||
if (status === 'ok') {
|
||||
this.initialScope = this.scope
|
||||
} else {
|
||||
this.$emit('update:scope', this.initialScope)
|
||||
showError(t('settings', errorMessage))
|
||||
this.logger.error(errorMessage, error)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.actions-federation {
|
||||
opacity: 0.4 !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.forced-active {
|
||||
background-color: var(--color-primary-light) !important;
|
||||
box-shadow: inset 2px 0 var(--color-primary) !important;
|
||||
}
|
||||
|
||||
.forced-action {
|
||||
&::v-deep p {
|
||||
width: 150px !important;
|
||||
padding: 8px 0 !important;
|
||||
color: var(--color-main-text) !important;
|
||||
font-size: 12.8px !important;
|
||||
line-height: 1.5em !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<!--
|
||||
- @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<h3>
|
||||
<label for="email">
|
||||
{{ t('settings', 'Email') }}
|
||||
</label>
|
||||
|
||||
<FederationControl
|
||||
class="federation-control"
|
||||
:primary="true"
|
||||
:scope.sync="localScope"
|
||||
@update:scope="onScopeChange" />
|
||||
|
||||
<AddButton v-if="canEditEmails"
|
||||
class="add-button"
|
||||
:disabled="!isValidForm"
|
||||
@click.stop.prevent="addAdditionalEmail" />
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FederationControl from './FederationControl'
|
||||
import AddButton from './AddButton'
|
||||
|
||||
export default {
|
||||
name: 'HeaderBar',
|
||||
|
||||
components: {
|
||||
FederationControl,
|
||||
AddButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
canEditEmails: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isValidForm: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
localScope: this.scope,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
addAdditionalEmail() {
|
||||
this.$emit('addAdditionalEmail')
|
||||
},
|
||||
|
||||
onScopeChange(scope) {
|
||||
this.$emit('update:scope', scope)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.federation-control {
|
||||
margin: -12px 0 0 8px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
margin: -12px 0 0 auto !important;
|
||||
}
|
||||
</style>
|
||||
83
apps/settings/src/constants/AccountPropertyConstants.js
Normal file
83
apps/settings/src/constants/AccountPropertyConstants.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* SYNC to be kept in sync with lib/public/Accounts/IAccountManager.php
|
||||
*/
|
||||
|
||||
/** Enum of account properties */
|
||||
export const ACCOUNT_PROPERTY_ENUM = Object.freeze({
|
||||
AVATAR: 'avatar',
|
||||
DISPLAYNAME: 'displayname',
|
||||
PHONE: 'phone',
|
||||
EMAIL: 'email',
|
||||
WEBSITE: 'website',
|
||||
ADDRESS: 'address',
|
||||
TWITTER: 'twitter',
|
||||
EMAIL_COLLECTION: 'additional_mail',
|
||||
})
|
||||
|
||||
/** Enum of scopes */
|
||||
export const SCOPE_ENUM = Object.freeze({
|
||||
PRIVATE: 'v2-private',
|
||||
LOCAL: 'v2-local',
|
||||
FEDERATED: 'v2-federated',
|
||||
PUBLISHED: 'v2-published',
|
||||
})
|
||||
|
||||
/** Scope suffix */
|
||||
export const SCOPE_SUFFIX = 'Scope'
|
||||
|
||||
/** Default additional email scope */
|
||||
export const DEFAULT_ADDITIONAL_EMAIL_SCOPE = SCOPE_ENUM.LOCAL
|
||||
|
||||
/**
|
||||
* Enum of scope names to properties
|
||||
*
|
||||
* *Used for federation control*
|
||||
*/
|
||||
export const SCOPE_PROPERTY_ENUM = Object.freeze({
|
||||
[SCOPE_ENUM.PRIVATE]: {
|
||||
name: SCOPE_ENUM.PRIVATE,
|
||||
displayName: t('settings', 'Private'),
|
||||
tooltip: t('settings', 'Only visible to people matched via phone number integration through Talk on mobile'),
|
||||
iconClass: 'icon-phone',
|
||||
},
|
||||
[SCOPE_ENUM.LOCAL]: {
|
||||
name: SCOPE_ENUM.LOCAL,
|
||||
displayName: t('settings', 'Local'),
|
||||
tooltip: t('settings', 'Only visible to people on this instance and guests'),
|
||||
iconClass: 'icon-password',
|
||||
},
|
||||
[SCOPE_ENUM.FEDERATED]: {
|
||||
name: SCOPE_ENUM.FEDERATED,
|
||||
displayName: t('settings', 'Federated'),
|
||||
tooltip: t('settings', 'Only synchronize to trusted servers'),
|
||||
iconClass: 'icon-contacts-dark',
|
||||
},
|
||||
[SCOPE_ENUM.PUBLISHED]: {
|
||||
name: SCOPE_ENUM.PUBLISHED,
|
||||
displayName: t('settings', 'Published'),
|
||||
tooltip: t('settings', 'Synchronize to trusted servers and the global and public address book'),
|
||||
iconClass: 'icon-link',
|
||||
},
|
||||
})
|
||||
38
apps/settings/src/main-personal-info.js
Normal file
38
apps/settings/src/main-personal-info.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @copyright 2021, Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
|
||||
import logger from './logger'
|
||||
|
||||
import EmailSection from './components/PersonalInfo/EmailSection/EmailSection'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_nonce__ = btoa(OC.requestToken)
|
||||
|
||||
Vue.prototype.t = t
|
||||
Vue.prototype.logger = logger
|
||||
|
||||
const View = Vue.extend(EmailSection)
|
||||
export default new View({
|
||||
el: '#vue-emailsection',
|
||||
})
|
||||
153
apps/settings/src/service/PersonalInfoService.js
Normal file
153
apps/settings/src/service/PersonalInfoService.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* @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 axios from '@nextcloud/axios'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import confirmPassword from '@nextcloud/password-confirmation'
|
||||
|
||||
import { ACCOUNT_PROPERTY_ENUM, SCOPE_SUFFIX } from '../constants/AccountPropertyConstants'
|
||||
|
||||
/**
|
||||
* Save the primary email of the user
|
||||
*
|
||||
* @param {string} email the primary email
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const savePrimaryEmail = async(email) => {
|
||||
const userId = getCurrentUser().uid
|
||||
// TODO upgrade @nextcloud/router to v2.0 so we can remove the .slice() trailing slash hacks (same below)
|
||||
const url = generateOcsUrl(`cloud/users/${userId}`, 2).slice(0, -1)
|
||||
|
||||
await confirmPassword()
|
||||
|
||||
const res = await axios.put(url, {
|
||||
key: ACCOUNT_PROPERTY_ENUM.EMAIL,
|
||||
value: email,
|
||||
})
|
||||
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an additional email of the user
|
||||
*
|
||||
* *Will be appended to the user's additional emails*
|
||||
*
|
||||
* @param {string} email the additional email
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const saveAdditionalEmail = async(email) => {
|
||||
const userId = getCurrentUser().uid
|
||||
const url = generateOcsUrl(`cloud/users/${userId}`, 2).slice(0, -1)
|
||||
|
||||
await confirmPassword()
|
||||
|
||||
const res = await axios.put(url, {
|
||||
key: ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION,
|
||||
value: email,
|
||||
})
|
||||
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an additional email of the user
|
||||
*
|
||||
* @param {string} email the additional email
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const removeAdditionalEmail = async(email) => {
|
||||
const userId = getCurrentUser().uid
|
||||
const url = generateOcsUrl(`cloud/users/${userId}/${ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION}`, 2).slice(0, -1)
|
||||
|
||||
await confirmPassword()
|
||||
|
||||
const res = await axios.put(url, {
|
||||
key: email,
|
||||
value: '',
|
||||
})
|
||||
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an additional email of the user
|
||||
*
|
||||
* @param {string} prevEmail the additional email to be updated
|
||||
* @param {string} newEmail the new additional email
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const updateAdditionalEmail = async(prevEmail, newEmail) => {
|
||||
const userId = getCurrentUser().uid
|
||||
const url = generateOcsUrl(`cloud/users/${userId}/${ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION}`, 2).slice(0, -1)
|
||||
|
||||
await confirmPassword()
|
||||
|
||||
const res = await axios.put(url, {
|
||||
key: prevEmail,
|
||||
value: newEmail,
|
||||
})
|
||||
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the federation scope for the primary email of the user
|
||||
*
|
||||
* @param {string} scope the federation scope
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const savePrimaryEmailScope = async(scope) => {
|
||||
const userId = getCurrentUser().uid
|
||||
const url = generateOcsUrl(`cloud/users/${userId}`, 2).slice(0, -1)
|
||||
|
||||
await confirmPassword()
|
||||
|
||||
const res = await axios.put(url, {
|
||||
key: `${ACCOUNT_PROPERTY_ENUM.EMAIL}${SCOPE_SUFFIX}`,
|
||||
value: scope,
|
||||
})
|
||||
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the federation scope for the additional email of the user
|
||||
*
|
||||
* @param {string} email the additional email
|
||||
* @param {string} scope the federation scope
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const saveAdditionalEmailScope = async(email, scope) => {
|
||||
const userId = getCurrentUser().uid
|
||||
const url = generateOcsUrl(`cloud/users/${userId}/${ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION}${SCOPE_SUFFIX}`, 2).slice(0, -1)
|
||||
|
||||
await confirmPassword()
|
||||
|
||||
const res = await axios.put(url, {
|
||||
key: email,
|
||||
value: scope,
|
||||
})
|
||||
|
||||
return res.data
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ script('settings', [
|
|||
'federationsettingsview',
|
||||
'federationscopemenu',
|
||||
'settings/personalInfo',
|
||||
'vue-settings-personal-info',
|
||||
]);
|
||||
?>
|
||||
|
||||
|
|
@ -126,52 +127,7 @@ script('settings', [
|
|||
</form>
|
||||
</div>
|
||||
<div class="personal-settings-setting-box">
|
||||
<form id="emailform" class="section">
|
||||
<h3>
|
||||
<label for="email"><?php p($l->t('Email')); ?></label>
|
||||
<a href="#" class="federation-menu" aria-label="<?php p($l->t('Change privacy level of email')); ?>">
|
||||
<span class="icon-federation-menu icon-password">
|
||||
<span class="icon-triangle-s"></span>
|
||||
</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="verify <?php if ($_['email'] === '' || $_['emailScope'] !== 'public') {
|
||||
p('hidden');
|
||||
} ?>">
|
||||
<img id="verify-email" title="<?php p($_['emailMessage']); ?>" data-status="<?php p($_['emailVerification']) ?>" src="
|
||||
<?php
|
||||
switch ($_['emailVerification']) {
|
||||
case \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS:
|
||||
p(image_path('core', 'actions/verifying.svg'));
|
||||
break;
|
||||
case \OC\Accounts\AccountManager::VERIFIED:
|
||||
p(image_path('core', 'actions/verified.svg'));
|
||||
break;
|
||||
default:
|
||||
p(image_path('core', 'actions/verify.svg'));
|
||||
}
|
||||
?>">
|
||||
</div>
|
||||
<input type="email" name="email" id="email" value="<?php p($_['email']); ?>"
|
||||
<?php if (!$_['displayNameChangeSupported']) {
|
||||
print_unescaped('class="hidden"');
|
||||
} ?>
|
||||
placeholder="<?php p($l->t('Your email address')); ?>"
|
||||
autocomplete="on" autocapitalize="none" autocorrect="off" />
|
||||
<span class="icon-checkmark hidden"></span>
|
||||
<span class="icon-error hidden" ></span>
|
||||
<?php if (!$_['displayNameChangeSupported']) { ?>
|
||||
<span><?php if (isset($_['email']) && !empty($_['email'])) {
|
||||
p($_['email']);
|
||||
} else {
|
||||
p($l->t('No email address set'));
|
||||
}?></span>
|
||||
<?php } ?>
|
||||
<?php if ($_['displayNameChangeSupported']) { ?>
|
||||
<em><?php p($l->t('For password reset and notifications')); ?></em>
|
||||
<?php } ?>
|
||||
<input type="hidden" id="emailscope" value="<?php p($_['emailScope']) ?>">
|
||||
</form>
|
||||
<div id="vue-emailsection" class="section"></div>
|
||||
</div>
|
||||
<div class="personal-settings-setting-box">
|
||||
<form id="phoneform" class="section">
|
||||
|
|
@ -223,8 +179,8 @@ script('settings', [
|
|||
</h3>
|
||||
<?php if ($_['lookupServerUploadEnabled']) { ?>
|
||||
<div class="verify <?php if ($_['website'] === '' || $_['websiteScope'] !== 'public') {
|
||||
p('hidden');
|
||||
} ?>">
|
||||
p('hidden');
|
||||
} ?>">
|
||||
<img id="verify-website" title="<?php p($_['websiteMessage']); ?>" data-status="<?php p($_['websiteVerification']) ?>" src="
|
||||
<?php
|
||||
switch ($_['websiteVerification']) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
* @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
* @author Jan C. Borchardt <hey@jancborchardt.net>
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
|
|
@ -25,13 +26,26 @@
|
|||
|
||||
const path = require('path')
|
||||
|
||||
// TODO use @nextcloud/webpack-vue-config
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf)$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]',
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
entry: {
|
||||
'settings-apps-users-management': path.join(__dirname, 'src', 'main-apps-users-management'),
|
||||
'settings-admin-security': path.join(__dirname, 'src', 'main-admin-security'),
|
||||
'settings-personal-security': path.join(__dirname, 'src', 'main-personal-security'),
|
||||
'settings-personal-webauthn': path.join(__dirname, 'src', 'main-personal-webauth'),
|
||||
'settings-nextcloud-pdf': path.join(__dirname, 'src', 'main-nextcloud-pdf'),
|
||||
'settings-personal-info': path.join(__dirname, 'src', 'main-personal-info'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, './js'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue