From d4d59493a294d3031e122d895f1c727ae25d9c57 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Thu, 22 Jul 2021 00:03:33 +0000 Subject: [PATCH 1/6] Provide initial state Signed-off-by: Christopher Ng --- .../lib/Settings/Personal/PersonalInfo.php | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php index 387843c552258..d69d4b33b4c3c 100644 --- a/apps/settings/lib/Settings/Personal/PersonalInfo.php +++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php @@ -145,14 +145,17 @@ public function getForm(): TemplateResponse { 'groups' => $this->getGroups($user), ] + $messageParameters + $languageParameters + $localeParameters; - $emails = $this->getEmails($account); + $personalInfoParameters = [ + 'displayNames' => $this->getDisplayNames($account), + 'emails' => $this->getEmails($account), + ]; $accountParameters = [ 'displayNameChangeSupported' => $user->canChangeDisplayName(), 'lookupServerUploadEnabled' => $lookupServerUploadEnabled, ]; - $this->initialStateService->provideInitialState('emails', $emails); + $this->initialStateService->provideInitialState('personalInfoParameters', $personalInfoParameters); $this->initialStateService->provideInitialState('accountParameters', $accountParameters); return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, ''); @@ -196,6 +199,29 @@ static function (IGroup $group) { return $groups; } + /** + * returns the primary display name in an + * associative array + * + * NOTE may be extended to provide additional display names (i.e. aliases) in the future + * + * @param IAccount $account + * @return array + */ + private function getDisplayNames(IAccount $account): array { + $primaryDisplayName = [ + 'value' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(), + 'scope' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(), + 'verified' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getVerified(), + ]; + + $displayNames = [ + 'primaryDisplayName' => $primaryDisplayName, + ]; + + return $displayNames; + } + /** * returns the primary email and additional emails in an * associative array From db182d6517f6a8086bd7e6bc6950f13f404adde2 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Thu, 22 Jul 2021 00:05:01 +0000 Subject: [PATCH 2/6] Create display name service and update constants Signed-off-by: Christopher Ng --- .../src/constants/AccountPropertyConstants.js | 46 ++++++++++--- .../PersonalInfo/DisplayNameService.js | 68 +++++++++++++++++++ .../EmailService.js} | 14 ++-- 3 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 apps/settings/src/service/PersonalInfo/DisplayNameService.js rename apps/settings/src/service/{PersonalInfoService.js => PersonalInfo/EmailService.js} (94%) diff --git a/apps/settings/src/constants/AccountPropertyConstants.js b/apps/settings/src/constants/AccountPropertyConstants.js index 61e45be9b226f..a1c2b4814caf0 100644 --- a/apps/settings/src/constants/AccountPropertyConstants.js +++ b/apps/settings/src/constants/AccountPropertyConstants.js @@ -26,24 +26,48 @@ /** Enum of account properties */ export const ACCOUNT_PROPERTY_ENUM = Object.freeze({ + ADDRESS: 'address', AVATAR: 'avatar', DISPLAYNAME: 'displayname', - PHONE: 'phone', EMAIL: 'email', - WEBSITE: 'website', - ADDRESS: 'address', - TWITTER: 'twitter', EMAIL_COLLECTION: 'additional_mail', + PHONE: 'phone', + TWITTER: 'twitter', + WEBSITE: 'website', +}) + +/** Enum of account properties to human readable account properties */ +export const ACCOUNT_PROPERTY_READABLE_ENUM = Object.freeze({ + ADDRESS: 'Address', + AVATAR: 'Avatar', + DISPLAYNAME: 'Full name', + EMAIL: 'Email', + EMAIL_COLLECTION: 'Additional Email', + PHONE: 'Phone', + TWITTER: 'Twitter', + WEBSITE: 'Website', }) /** Enum of scopes */ export const SCOPE_ENUM = Object.freeze({ - PRIVATE: 'v2-private', LOCAL: 'v2-local', + PRIVATE: 'v2-private', FEDERATED: 'v2-federated', PUBLISHED: 'v2-published', }) +/** Enum of readable account properties to supported scopes */ +export const PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM = Object.freeze({ + [ACCOUNT_PROPERTY_READABLE_ENUM.ADDRESS]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], + [ACCOUNT_PROPERTY_READABLE_ENUM.AVATAR]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], + [ACCOUNT_PROPERTY_READABLE_ENUM.DISPLAYNAME]: [SCOPE_ENUM.LOCAL], + [ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL]: [SCOPE_ENUM.LOCAL], + [ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL_COLLECTION]: [SCOPE_ENUM.LOCAL], + [ACCOUNT_PROPERTY_READABLE_ENUM.PHONE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], + [ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], + [ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], +}) + /** Scope suffix */ export const SCOPE_SUFFIX = 'Scope' @@ -56,18 +80,18 @@ export const DEFAULT_ADDITIONAL_EMAIL_SCOPE = SCOPE_ENUM.LOCAL * *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.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.FEDERATED]: { name: SCOPE_ENUM.FEDERATED, displayName: t('settings', 'Federated'), diff --git a/apps/settings/src/service/PersonalInfo/DisplayNameService.js b/apps/settings/src/service/PersonalInfo/DisplayNameService.js new file mode 100644 index 0000000000000..f0e98c015de26 --- /dev/null +++ b/apps/settings/src/service/PersonalInfo/DisplayNameService.js @@ -0,0 +1,68 @@ +/** + * @copyright 2021, Christopher Ng + * + * @author Christopher Ng + * + * @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 . + * + */ + +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 display name of the user + * + * @param {string} displayName the primary display name + * @returns {object} + */ +export const savePrimaryDisplayName = async(displayName) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}', { userId }) + + await confirmPassword() + + const res = await axios.put(url, { + key: ACCOUNT_PROPERTY_ENUM.DISPLAYNAME, + value: displayName, + }) + + return res.data +} + +/** + * Save the federation scope for the primary display name of the user + * + * @param {string} scope the federation scope + * @returns {object} + */ +export const savePrimaryDisplayNameScope = async(scope) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}', { userId }) + + await confirmPassword() + + const res = await axios.put(url, { + key: `${ACCOUNT_PROPERTY_ENUM.DISPLAYNAME}${SCOPE_SUFFIX}`, + value: scope, + }) + + return res.data +} diff --git a/apps/settings/src/service/PersonalInfoService.js b/apps/settings/src/service/PersonalInfo/EmailService.js similarity index 94% rename from apps/settings/src/service/PersonalInfoService.js rename to apps/settings/src/service/PersonalInfo/EmailService.js index f5c010f5377cb..00e2373736c76 100644 --- a/apps/settings/src/service/PersonalInfoService.js +++ b/apps/settings/src/service/PersonalInfo/EmailService.js @@ -25,13 +25,13 @@ 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' +import { ACCOUNT_PROPERTY_ENUM, SCOPE_SUFFIX } from '../../constants/AccountPropertyConstants' /** * Save the primary email of the user * * @param {string} email the primary email - * @returns {Object} + * @returns {object} */ export const savePrimaryEmail = async(email) => { const userId = getCurrentUser().uid @@ -53,7 +53,7 @@ export const savePrimaryEmail = async(email) => { * *Will be appended to the user's additional emails* * * @param {string} email the additional email - * @returns {Object} + * @returns {object} */ export const saveAdditionalEmail = async(email) => { const userId = getCurrentUser().uid @@ -73,7 +73,7 @@ export const saveAdditionalEmail = async(email) => { * Remove an additional email of the user * * @param {string} email the additional email - * @returns {Object} + * @returns {object} */ export const removeAdditionalEmail = async(email) => { const userId = getCurrentUser().uid @@ -94,7 +94,7 @@ export const removeAdditionalEmail = async(email) => { * * @param {string} prevEmail the additional email to be updated * @param {string} newEmail the new additional email - * @returns {Object} + * @returns {object} */ export const updateAdditionalEmail = async(prevEmail, newEmail) => { const userId = getCurrentUser().uid @@ -114,7 +114,7 @@ export const updateAdditionalEmail = async(prevEmail, newEmail) => { * Save the federation scope for the primary email of the user * * @param {string} scope the federation scope - * @returns {Object} + * @returns {object} */ export const savePrimaryEmailScope = async(scope) => { const userId = getCurrentUser().uid @@ -135,7 +135,7 @@ export const savePrimaryEmailScope = async(scope) => { * * @param {string} email the additional email * @param {string} scope the federation scope - * @returns {Object} + * @returns {object} */ export const saveAdditionalEmailScope = async(email, scope) => { const userId = getCurrentUser().uid From a8ad5a3b6e40a076f97ac4847fe6acc2b9be4c8a Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Thu, 29 Jul 2021 01:23:04 +0000 Subject: [PATCH 3/6] Vuetify - abstract shared components - rewrite email section Signed-off-by: Christopher Ng --- .../DisplayNameSection/DisplayName.vue | 167 ++++++++++++++++++ .../DisplayNameSection/DisplayNameSection.vue | 100 +++++++++++ .../PersonalInfo/EmailSection/Email.vue | 105 ++++++----- .../EmailSection/EmailSection.vue | 24 ++- .../{EmailSection => shared}/AddButton.vue | 12 +- .../FederationControl.vue | 116 ++++++++---- .../{EmailSection => shared}/HeaderBar.vue | 48 +++-- apps/settings/src/main-personal-info.js | 25 ++- .../settings/personal/personal.info.php | 27 +-- 9 files changed, 470 insertions(+), 154 deletions(-) create mode 100644 apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue create mode 100644 apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayNameSection.vue rename apps/settings/src/components/PersonalInfo/{EmailSection => shared}/AddButton.vue (100%) rename apps/settings/src/components/PersonalInfo/{EmailSection => shared}/FederationControl.vue (58%) rename apps/settings/src/components/PersonalInfo/{EmailSection => shared}/HeaderBar.vue (65%) diff --git a/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue b/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue new file mode 100644 index 0000000000000..1f32f55dcf76e --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue @@ -0,0 +1,167 @@ + + + + + + + diff --git a/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayNameSection.vue b/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayNameSection.vue new file mode 100644 index 0000000000000..100e7fad87607 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayNameSection.vue @@ -0,0 +1,100 @@ + + + + + + + diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue b/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue index faca83821e24d..6c0a98d26f9d8 100644 --- a/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue +++ b/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue @@ -21,7 +21,7 @@