diff options
author | Vincent Petry <vincent@nextcloud.com> | 2022-08-26 00:40:18 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-26 00:40:18 +0300 |
commit | 38d0419f14ad70dbd82ef18946846b8ce3ea1682 (patch) | |
tree | f49f331175d7487f1e33faa972350b1ec459e249 /apps | |
parent | 696a48ae975d7f9cf1ea80d35e6d93c103daa7c3 (diff) | |
parent | 09f6eb5e58b4e0e3880d5e454331eb53e6d43eab (diff) |
Merge pull request #33217 from nextcloud/enh/27869/twitter
Remake Twitter handle saving with Vue and modularize account property components
Diffstat (limited to 'apps')
29 files changed, 553 insertions, 1241 deletions
diff --git a/apps/settings/js/federationsettingsview.js b/apps/settings/js/federationsettingsview.js index eb5d7084206..1a823ce5521 100644 --- a/apps/settings/js/federationsettingsview.js +++ b/apps/settings/js/federationsettingsview.js @@ -130,7 +130,8 @@ if ( field === 'avatar' || field === 'email' || - field === 'displayname' + field === 'displayname' || + field === 'twitter' ) { return; } diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php index fbbee7b81bb..df1608e01d9 100644 --- a/apps/settings/lib/Settings/Personal/PersonalInfo.php +++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php @@ -135,7 +135,6 @@ class PersonalInfo implements ISettings { $totalSpace = \OC_Helper::humanFileSize($storageInfo['total']); } - $languageParameters = $this->getLanguageMap($user); $localeParameters = $this->getLocales($user); $messageParameters = $this->getMessageParameters($account); @@ -148,12 +147,6 @@ class PersonalInfo implements ISettings { 'federationEnabled' => $federationEnabled, 'lookupServerUploadEnabled' => $lookupServerUploadEnabled, 'avatarScope' => $account->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(), - 'displayNameChangeSupported' => $user->canChangeDisplayName(), - 'displayName' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(), - 'displayNameScope' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(), - 'email' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(), - 'emailScope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(), - 'emailVerification' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getVerified(), 'phone' => $account->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(), 'phoneScope' => $account->getProperty(IAccountManager::PROPERTY_PHONE)->getScope(), 'address' => $account->getProperty(IAccountManager::PROPERTY_ADDRESS)->getValue(), @@ -161,17 +154,15 @@ class PersonalInfo implements ISettings { 'website' => $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(), 'websiteScope' => $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getScope(), 'websiteVerification' => $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getVerified(), - 'twitter' => $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(), - 'twitterScope' => $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getScope(), - 'twitterVerification' => $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getVerified(), 'groups' => $this->getGroups($user), 'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(), 'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(), - ] + $messageParameters + $languageParameters + $localeParameters; + ] + $messageParameters + $localeParameters; $personalInfoParameters = [ 'userId' => $uid, 'displayName' => $this->getProperty($account, IAccountManager::PROPERTY_DISPLAYNAME), + 'twitter' => $this->getProperty($account, IAccountManager::PROPERTY_TWITTER), 'emailMap' => $this->getEmailMap($account), 'languageMap' => $this->getLanguageMap($user), 'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(), @@ -213,6 +204,7 @@ class PersonalInfo implements ISettings { */ private function getProperty(IAccount $account, string $property): array { $property = [ + 'name' => $account->getProperty($property)->getName(), 'value' => $account->getProperty($property)->getValue(), 'scope' => $account->getProperty($property)->getScope(), 'verified' => $account->getProperty($property)->getVerified(), @@ -262,6 +254,7 @@ class PersonalInfo implements ISettings { */ private function getEmailMap(IAccount $account): array { $systemEmail = [ + 'name' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getName(), 'value' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(), 'scope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(), 'verified' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getVerified(), @@ -270,6 +263,7 @@ class PersonalInfo implements ISettings { $additionalEmails = array_map( function (IAccountProperty $property) { return [ + 'name' => $property->getName(), 'value' => $property->getValue(), 'scope' => $property->getScope(), 'verified' => $property->getVerified(), diff --git a/apps/settings/src/components/PersonalInfo/BiographySection/BiographySection.vue b/apps/settings/src/components/PersonalInfo/BiographySection.vue index c8464f7b243..30c240dee1a 100644 --- a/apps/settings/src/components/PersonalInfo/BiographySection/BiographySection.vue +++ b/apps/settings/src/components/PersonalInfo/BiographySection.vue @@ -1,9 +1,9 @@ <!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> + - @copyright 2022 Christopher Ng <chrng8@gmail.com> - - @author Christopher Ng <chrng8@gmail.com> - - - @license GNU AGPL version 3 or any later version + - @license AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as @@ -12,7 +12,7 @@ - - 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 + - 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 @@ -21,23 +21,17 @@ --> <template> - <section> - <HeaderBar :account-property="accountProperty" - label-for="biography" - :scope.sync="biography.scope" /> - - <Biography :biography.sync="biography.value" - :scope.sync="biography.scope" /> - </section> + <AccountPropertySection v-bind.sync="biography" + :placeholder="t('settings', 'Your biography')" + :multi-line="true" /> </template> <script> import { loadState } from '@nextcloud/initial-state' -import Biography from './Biography' -import HeaderBar from '../shared/HeaderBar' +import AccountPropertySection from './shared/AccountPropertySection.vue' -import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' +import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js' const { biography } = loadState('settings', 'personalInfoParameters', {}) @@ -45,25 +39,13 @@ export default { name: 'BiographySection', components: { - Biography, - HeaderBar, + AccountPropertySection, }, data() { return { - accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.BIOGRAPHY, - biography, + biography: { ...biography, readable: NAME_READABLE_ENUM[biography.name] }, } }, } </script> - -<style lang="scss" scoped> -section { - padding: 10px 10px; - - &::v-deep button:disabled { - cursor: default; - } -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/BiographySection/Biography.vue b/apps/settings/src/components/PersonalInfo/BiographySection/Biography.vue deleted file mode 100644 index 48c2bf4ff77..00000000000 --- a/apps/settings/src/components/PersonalInfo/BiographySection/Biography.vue +++ /dev/null @@ -1,184 +0,0 @@ -<!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> - - - - @author Christopher Ng <chrng8@gmail.com> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - ---> - -<template> - <div class="biography"> - <textarea id="biography" - :placeholder="t('settings', 'Your biography')" - :value="biography" - rows="8" - autocapitalize="none" - autocomplete="off" - autocorrect="off" - @input="onBiographyChange" /> - - <div class="biography__actions-container"> - <transition name="fade"> - <span v-if="showCheckmarkIcon" class="icon-checkmark" /> - <span v-else-if="showErrorIcon" class="icon-error" /> - </transition> - </div> - </div> -</template> - -<script> -import { showError } from '@nextcloud/dialogs' -import { emit } from '@nextcloud/event-bus' -import debounce from 'debounce' - -import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants' -import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService' -import logger from '../../../logger' - -export default { - name: 'Biography', - - props: { - biography: { - type: String, - required: true, - }, - scope: { - type: String, - required: true, - }, - }, - - data() { - return { - initialBiography: this.biography, - localScope: this.scope, - showCheckmarkIcon: false, - showErrorIcon: false, - } - }, - - methods: { - onBiographyChange(e) { - this.$emit('update:biography', e.target.value) - this.debounceBiographyChange(e.target.value.trim()) - }, - - debounceBiographyChange: debounce(async function(biography) { - await this.updatePrimaryBiography(biography) - }, 500), - - async updatePrimaryBiography(biography) { - try { - const responseData = await savePrimaryAccountProperty(ACCOUNT_PROPERTY_ENUM.BIOGRAPHY, biography) - this.handleResponse({ - biography, - status: responseData.ocs?.meta?.status, - }) - } catch (e) { - this.handleResponse({ - errorMessage: t('settings', 'Unable to update biography'), - error: e, - }) - } - }, - - handleResponse({ biography, status, errorMessage, error }) { - if (status === 'ok') { - // Ensure that local state reflects server state - this.initialBiography = biography - emit('settings:biography:updated', biography) - this.showCheckmarkIcon = true - setTimeout(() => { this.showCheckmarkIcon = false }, 2000) - } else { - showError(errorMessage) - logger.error(errorMessage, error) - this.showErrorIcon = true - setTimeout(() => { this.showErrorIcon = false }, 2000) - } - }, - - onScopeChange(scope) { - this.$emit('update:scope', scope) - }, - }, -} -</script> - -<style lang="scss" scoped> -.biography { - display: grid; - align-items: center; - - textarea { - resize: vertical; - grid-area: 1 / 1; - width: 100%; - margin: 3px 3px 3px 0; - padding: 7px 6px; - color: var(--color-main-text); - border: 1px solid var(--color-border-dark); - border-radius: var(--border-radius); - background-color: var(--color-main-background); - font-family: var(--font-face); - cursor: text; - - &:hover, - &:focus, - &:active { - border-color: var(--color-primary-element) !important; - outline: none !important; - } - } - - .biography__actions-container { - grid-area: 1 / 1; - justify-self: flex-end; - align-self: flex-end; - height: 30px; - - display: flex; - gap: 0 2px; - margin-right: 5px; - margin-bottom: 5px; - - .icon-checkmark, - .icon-error { - height: 30px !important; - min-height: 30px !important; - width: 30px !important; - min-width: 30px !important; - top: 0; - right: 0; - float: none; - } - } -} - -.fade-enter, -.fade-leave-to { - opacity: 0; -} - -.fade-enter-active { - transition: opacity 200ms ease-out; -} - -.fade-leave-active { - transition: opacity 300ms ease-out; -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/DisplayNameSection.vue b/apps/settings/src/components/PersonalInfo/DisplayNameSection.vue new file mode 100644 index 00000000000..dd5f97966ac --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/DisplayNameSection.vue @@ -0,0 +1,66 @@ +<!-- + - @copyright 2022 Christopher Ng <chrng8@gmail.com> + - + - @author Christopher Ng <chrng8@gmail.com> + - + - @license AGPL-3.0-or-later + - + - 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> + <AccountPropertySection v-bind.sync="displayName" + :placeholder="t('settings', 'Your full name')" + :is-editable="displayNameChangeSupported" + :on-validate="onValidate" + :on-save="onSave" /> +</template> + +<script> +import { loadState } from '@nextcloud/initial-state' +import { emit } from '@nextcloud/event-bus' + +import AccountPropertySection from './shared/AccountPropertySection.vue' + +import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js' + +const { displayName } = loadState('settings', 'personalInfoParameters', {}) +const { displayNameChangeSupported } = loadState('settings', 'accountParameters', {}) + +export default { + name: 'DisplayNameSection', + + components: { + AccountPropertySection, + }, + + data() { + return { + displayName: { ...displayName, readable: NAME_READABLE_ENUM[displayName.name] }, + displayNameChangeSupported, + } + }, + + methods: { + onValidate(value) { + return value !== '' + }, + + onSave(value) { + emit('settings:display-name:updated', value) + }, + } +} +</script> diff --git a/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue b/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue deleted file mode 100644 index 58252bfbd63..00000000000 --- a/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue +++ /dev/null @@ -1,180 +0,0 @@ -<!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> - - - - @author Christopher Ng <chrng8@gmail.com> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - ---> - -<template> - <div class="displayname"> - <input id="displayname" - type="text" - :placeholder="t('settings', 'Your full name')" - :value="displayName" - autocapitalize="none" - autocomplete="on" - autocorrect="off" - @input="onDisplayNameChange"> - - <div class="displayname__actions-container"> - <transition name="fade"> - <span v-if="showCheckmarkIcon" class="icon-checkmark" /> - <span v-else-if="showErrorIcon" class="icon-error" /> - </transition> - </div> - </div> -</template> - -<script> -import { showError } from '@nextcloud/dialogs' -import { emit } from '@nextcloud/event-bus' -import debounce from 'debounce' - -import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants' -import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService' -import { validateStringInput } from '../../../utils/validate' -import logger from '../../../logger' - -// TODO Global avatar updating on events (e.g. updating the displayname) is currently being handled by global js, investigate using https://github.com/nextcloud/nextcloud-event-bus for global avatar updating - -export default { - name: 'DisplayName', - - props: { - displayName: { - type: String, - required: true, - }, - scope: { - type: String, - required: true, - }, - }, - - data() { - return { - initialDisplayName: this.displayName, - localScope: this.scope, - showCheckmarkIcon: false, - showErrorIcon: false, - } - }, - - methods: { - onDisplayNameChange(e) { - this.$emit('update:display-name', e.target.value) - this.debounceDisplayNameChange(e.target.value.trim()) - }, - - debounceDisplayNameChange: debounce(async function(displayName) { - if (validateStringInput(displayName)) { - await this.updatePrimaryDisplayName(displayName) - } - }, 500), - - async updatePrimaryDisplayName(displayName) { - try { - const responseData = await savePrimaryAccountProperty(ACCOUNT_PROPERTY_ENUM.DISPLAYNAME, displayName) - this.handleResponse({ - displayName, - status: responseData.ocs?.meta?.status, - }) - } catch (e) { - this.handleResponse({ - errorMessage: t('settings', 'Unable to update full name'), - error: e, - }) - } - }, - - handleResponse({ displayName, status, errorMessage, error }) { - if (status === 'ok') { - // Ensure that local state reflects server state - this.initialDisplayName = displayName - emit('settings:display-name:updated', displayName) - this.showCheckmarkIcon = true - setTimeout(() => { this.showCheckmarkIcon = false }, 2000) - } else { - showError(errorMessage) - logger.error(errorMessage, error) - this.showErrorIcon = true - setTimeout(() => { this.showErrorIcon = false }, 2000) - } - }, - - onScopeChange(scope) { - this.$emit('update:scope', scope) - }, - }, -} -</script> - -<style lang="scss" scoped> -.displayname { - display: grid; - align-items: center; - - input { - grid-area: 1 / 1; - width: 100%; - height: 34px; - margin: 3px 3px 3px 0; - padding: 7px 6px; - color: var(--color-main-text); - border: 1px solid var(--color-border-dark); - border-radius: var(--border-radius); - background-color: var(--color-main-background); - font-family: var(--font-face); - cursor: text; - } - - .displayname__actions-container { - grid-area: 1 / 1; - justify-self: flex-end; - height: 30px; - - display: flex; - gap: 0 2px; - margin-right: 5px; - - .icon-checkmark, - .icon-error { - height: 30px !important; - min-height: 30px !important; - width: 30px !important; - min-width: 30px !important; - top: 0; - right: 0; - float: none; - } - } -} - -.fade-enter, -.fade-leave-to { - opacity: 0; -} - -.fade-enter-active { - transition: opacity 200ms ease-out; -} - -.fade-leave-active { - transition: opacity 300ms ease-out; -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayNameSection.vue b/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayNameSection.vue deleted file mode 100644 index d808369e9fb..00000000000 --- a/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayNameSection.vue +++ /dev/null @@ -1,86 +0,0 @@ -<!-- - - @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> - <section> - <HeaderBar :account-property="accountProperty" - label-for="displayname" - :is-editable="displayNameChangeSupported" - :is-valid-section="isValidSection" - :scope.sync="displayName.scope" /> - - <template v-if="displayNameChangeSupported"> - <DisplayName :display-name.sync="displayName.value" - :scope.sync="displayName.scope" /> - </template> - - <span v-else> - {{ displayName.value || t('settings', 'No full name set') }} - </span> - </section> -</template> - -<script> -import { loadState } from '@nextcloud/initial-state' - -import DisplayName from './DisplayName' -import HeaderBar from '../shared/HeaderBar' - -import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' -import { validateStringInput } from '../../../utils/validate' - -const { displayName } = loadState('settings', 'personalInfoParameters', {}) -const { displayNameChangeSupported } = loadState('settings', 'accountParameters', {}) - -export default { - name: 'DisplayNameSection', - - components: { - DisplayName, - HeaderBar, - }, - - data() { - return { - accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.DISPLAYNAME, - displayNameChangeSupported, - displayName, - } - }, - - computed: { - isValidSection() { - return validateStringInput(this.displayName.value) - }, - }, -} -</script> - -<style lang="scss" scoped> -section { - padding: 10px 10px; - - &::v-deep button:disabled { - cursor: default; - } -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue b/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue index be837baf2bd..bdcc1bdd753 100644 --- a/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue +++ b/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue @@ -40,7 +40,7 @@ </transition> <template v-if="!primary"> - <FederationControl :account-property="accountProperty" + <FederationControl :readable="propertyReadable" :additional="true" :additional-value="email" :disabled="federationDisabled" @@ -85,10 +85,10 @@ import Check from 'vue-material-design-icons/Check' import { showError } from '@nextcloud/dialogs' import debounce from 'debounce' -import FederationControl from '../shared/FederationControl' -import logger from '../../../logger' +import FederationControl from '../shared/FederationControl.vue' +import logger from '../../../logger.js' -import { ACCOUNT_PROPERTY_READABLE_ENUM, VERIFICATION_ENUM } from '../../../constants/AccountPropertyConstants' +import { ACCOUNT_PROPERTY_READABLE_ENUM, VERIFICATION_ENUM } from '../../../constants/AccountPropertyConstants.js' import { removeAdditionalEmail, saveAdditionalEmail, @@ -96,8 +96,8 @@ import { saveNotificationEmail, savePrimaryEmail, updateAdditionalEmail, -} from '../../../service/PersonalInfo/EmailService' -import { validateEmail } from '../../../utils/validate' +} from '../../../service/PersonalInfo/EmailService.js' +import { validateEmail } from '../../../utils/validate.js' export default { name: 'Email', @@ -139,7 +139,7 @@ export default { data() { return { - accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL, + propertyReadable: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL, initialEmail: this.email, localScope: this.scope, saveAdditionalEmailScope, diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue b/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue index 3463daa5755..6bfb8bfee8b 100644 --- a/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue +++ b/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue @@ -22,8 +22,8 @@ <template> <section> - <HeaderBar :account-property="accountProperty" - label-for="email" + <HeaderBar :input-id="inputId" + :readable="primaryEmail.readable" :handle-scope-change="savePrimaryEmailScope" :is-editable="true" :is-multi-value-supported="true" @@ -65,13 +65,13 @@ import { loadState } from '@nextcloud/initial-state' import { showError } from '@nextcloud/dialogs' -import Email from './Email' -import HeaderBar from '../shared/HeaderBar' +import Email from './Email.vue' +import HeaderBar from '../shared/HeaderBar.vue' -import { ACCOUNT_PROPERTY_READABLE_ENUM, DEFAULT_ADDITIONAL_EMAIL_SCOPE } from '../../../constants/AccountPropertyConstants' -import { savePrimaryEmail, savePrimaryEmailScope, removeAdditionalEmail } from '../../../service/PersonalInfo/EmailService' -import { validateEmail } from '../../../utils/validate' -import logger from '../../../logger' +import { ACCOUNT_PROPERTY_READABLE_ENUM, DEFAULT_ADDITIONAL_EMAIL_SCOPE, NAME_READABLE_ENUM } from '../../../constants/AccountPropertyConstants.js' +import { savePrimaryEmail, savePrimaryEmailScope, removeAdditionalEmail } from '../../../service/PersonalInfo/EmailService.js' +import { validateEmail } from '../../../utils/validate.js' +import logger from '../../../logger.js' const { emailMap: { additionalEmails, primaryEmail, notificationEmail } } = loadState('settings', 'personalInfoParameters', {}) const { displayNameChangeSupported } = loadState('settings', 'accountParameters', {}) @@ -89,7 +89,7 @@ export default { accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL, additionalEmails: additionalEmails.map(properties => ({ ...properties, key: this.generateUniqueKey() })), displayNameChangeSupported, - primaryEmail, + primaryEmail: { ...primaryEmail, readable: NAME_READABLE_ENUM[primaryEmail.name] }, savePrimaryEmailScope, notificationEmail, } @@ -103,6 +103,10 @@ export default { return null }, + inputId() { + return `account-property-${this.primaryEmail.name}` + }, + isValidSection() { return validateEmail(this.primaryEmail.value) && this.additionalEmails.map(({ value }) => value).every(validateEmail) diff --git a/apps/settings/src/components/PersonalInfo/HeadlineSection/HeadlineSection.vue b/apps/settings/src/components/PersonalInfo/HeadlineSection.vue index 1a608d2fc02..0275c1df80b 100644 --- a/apps/settings/src/components/PersonalInfo/HeadlineSection/HeadlineSection.vue +++ b/apps/settings/src/components/PersonalInfo/HeadlineSection.vue @@ -1,9 +1,9 @@ <!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> + - @copyright 2022 Christopher Ng <chrng8@gmail.com> - - @author Christopher Ng <chrng8@gmail.com> - - - @license GNU AGPL version 3 or any later version + - @license AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as @@ -12,7 +12,7 @@ - - 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 + - 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 @@ -21,23 +21,16 @@ --> <template> - <section> - <HeaderBar :account-property="accountProperty" - label-for="headline" - :scope.sync="headline.scope" /> - - <Headline :headline.sync="headline.value" - :scope.sync="headline.scope" /> - </section> + <AccountPropertySection v-bind.sync="headline" + :placeholder="t('settings', 'Your headline')" /> </template> <script> import { loadState } from '@nextcloud/initial-state' -import Headline from './Headline' -import HeaderBar from '../shared/HeaderBar' +import AccountPropertySection from './shared/AccountPropertySection.vue' -import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' +import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js' const { headline } = loadState('settings', 'personalInfoParameters', {}) @@ -45,25 +38,13 @@ export default { name: 'HeadlineSection', components: { - Headline, - HeaderBar, + AccountPropertySection, }, data() { return { - accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.HEADLINE, - headline, + headline: { ...headline, readable: NAME_READABLE_ENUM[headline.name] }, } }, } </script> - -<style lang="scss" scoped> -section { - padding: 10px 10px; - - &::v-deep button:disabled { - cursor: default; - } -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/HeadlineSection/Headline.vue b/apps/settings/src/components/PersonalInfo/HeadlineSection/Headline.vue deleted file mode 100644 index 4cb7d63b522..00000000000 --- a/apps/settings/src/components/PersonalInfo/HeadlineSection/Headline.vue +++ /dev/null @@ -1,175 +0,0 @@ -<!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> - - - - @author Christopher Ng <chrng8@gmail.com> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - ---> - -<template> - <div class="headline"> - <input id="headline" - type="text" - :placeholder="t('settings', 'Your headline')" - :value="headline" - autocapitalize="none" - autocomplete="on" - autocorrect="off" - @input="onHeadlineChange"> - - <div class="headline__actions-container"> - <transition name="fade"> - <span v-if="showCheckmarkIcon" class="icon-checkmark" /> - <span v-else-if="showErrorIcon" class="icon-error" /> - </transition> - </div> - </div> -</template> - -<script> -import { showError } from '@nextcloud/dialogs' -import { emit } from '@nextcloud/event-bus' -import debounce from 'debounce' - -import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants' -import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService' -import logger from '../../../logger' - -export default { - name: 'Headline', - - props: { - headline: { - type: String, - required: true, - }, - scope: { - type: String, - required: true, - }, - }, - - data() { - return { - initialHeadline: this.headline, - localScope: this.scope, - showCheckmarkIcon: false, - showErrorIcon: false, - } - }, - - methods: { - onHeadlineChange(e) { - this.$emit('update:headline', e.target.value) - this.debounceHeadlineChange(e.target.value.trim()) - }, - - debounceHeadlineChange: debounce(async function(headline) { - await this.updatePrimaryHeadline(headline) - }, 500), - - async updatePrimaryHeadline(headline) { - try { - const responseData = await savePrimaryAccountProperty(ACCOUNT_PROPERTY_ENUM.HEADLINE, headline) - this.handleResponse({ - headline, - status: responseData.ocs?.meta?.status, - }) - } catch (e) { - this.handleResponse({ - errorMessage: t('settings', 'Unable to update headline'), - error: e, - }) - } - }, - - handleResponse({ headline, status, errorMessage, error }) { - if (status === 'ok') { - // Ensure that local state reflects server state - this.initialHeadline = headline - emit('settings:headline:updated', headline) - this.showCheckmarkIcon = true - setTimeout(() => { this.showCheckmarkIcon = false }, 2000) - } else { - showError(errorMessage) - logger.error(errorMessage, error) - this.showErrorIcon = true - setTimeout(() => { this.showErrorIcon = false }, 2000) - } - }, - - onScopeChange(scope) { - this.$emit('update:scope', scope) - }, - }, -} -</script> - -<style lang="scss" scoped> -.headline { - display: grid; - align-items: center; - - input { - grid-area: 1 / 1; - width: 100%; - height: 34px; - margin: 3px 3px 3px 0; - padding: 7px 6px; - color: var(--color-main-text); - border: 1px solid var(--color-border-dark); - border-radius: var(--border-radius); - background-color: var(--color-main-background); - font-family: var(--font-face); - cursor: text; - } - - .headline__actions-container { - grid-area: 1 / 1; - justify-self: flex-end; - height: 30px; - - display: flex; - gap: 0 2px; - margin-right: 5px; - - .icon-checkmark, - .icon-error { - height: 30px !important; - min-height: 30px !important; - width: 30px !important; - min-width: 30px !important; - top: 0; - right: 0; - float: none; - } - } -} - -.fade-enter, -.fade-leave-to { - opacity: 0; -} - -.fade-enter-active { - transition: opacity 200ms ease-out; -} - -.fade-leave-active { - transition: opacity 300ms ease-out; -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue b/apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue index 99dadd449ce..8b2f8e3d0cc 100644 --- a/apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue +++ b/apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue @@ -22,7 +22,7 @@ <template> <div class="language"> - <select id="language" + <select :id="inputId" :placeholder="t('settings', 'Language')" @change="onLanguageChange"> <option v-for="commonLanguage in commonLanguages" @@ -53,15 +53,19 @@ <script> import { showError } from '@nextcloud/dialogs' -import { ACCOUNT_SETTING_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants' -import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService' -import { validateLanguage } from '../../../utils/validate' -import logger from '../../../logger' +import { ACCOUNT_SETTING_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants.js' +import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService.js' +import { validateLanguage } from '../../../utils/validate.js' +import logger from '../../../logger.js' export default { name: 'Language', props: { + inputId: { + type: String, + default: null, + }, commonLanguages: { type: Array, required: true, diff --git a/apps/settings/src/components/PersonalInfo/LanguageSection/LanguageSection.vue b/apps/settings/src/components/PersonalInfo/LanguageSection/LanguageSection.vue index 90882b23869..fdc1d31d10c 100644 --- a/apps/settings/src/components/PersonalInfo/LanguageSection/LanguageSection.vue +++ b/apps/settings/src/components/PersonalInfo/LanguageSection/LanguageSection.vue @@ -22,11 +22,12 @@ <template> <section> - <HeaderBar :account-property="accountProperty" - label-for="language" /> + <HeaderBar :input-id="inputId" + :readable="propertyReadable" /> <template v-if="isEditable"> - <Language :common-languages="commonLanguages" + <Language :input-id="inputId" + :common-languages="commonLanguages" :other-languages="otherLanguages" :language.sync="language" /> </template> @@ -40,10 +41,10 @@ <script> import { loadState } from '@nextcloud/initial-state' -import Language from './Language' -import HeaderBar from '../shared/HeaderBar' +import Language from './Language.vue' +import HeaderBar from '../shared/HeaderBar.vue' -import { ACCOUNT_SETTING_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' +import { ACCOUNT_SETTING_PROPERTY_ENUM, ACCOUNT_SETTING_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants.js' const { languageMap: { activeLanguage, commonLanguages, otherLanguages } } = loadState('settings', 'personalInfoParameters', {}) @@ -57,7 +58,7 @@ export default { data() { return { - accountProperty: ACCOUNT_SETTING_PROPERTY_READABLE_ENUM.LANGUAGE, + propertyReadable: ACCOUNT_SETTING_PROPERTY_READABLE_ENUM.LANGUAGE, commonLanguages, otherLanguages, language: activeLanguage, @@ -65,6 +66,10 @@ export default { }, computed: { + inputId() { + return `account-setting-${ACCOUNT_SETTING_PROPERTY_ENUM.LANGUAGE}` + }, + isEditable() { return Boolean(this.language) }, diff --git a/apps/settings/src/components/PersonalInfo/OrganisationSection/OrganisationSection.vue b/apps/settings/src/components/PersonalInfo/OrganisationSection.vue index b436b2edd0b..7a85d2fd647 100644 --- a/apps/settings/src/components/PersonalInfo/OrganisationSection/OrganisationSection.vue +++ b/apps/settings/src/components/PersonalInfo/OrganisationSection.vue @@ -1,9 +1,9 @@ <!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> + - @copyright 2022 Christopher Ng <chrng8@gmail.com> - - @author Christopher Ng <chrng8@gmail.com> - - - @license GNU AGPL version 3 or any later version + - @license AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as @@ -12,7 +12,7 @@ - - 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 + - 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 @@ -21,23 +21,16 @@ --> <template> - <section> - <HeaderBar :account-property="accountProperty" - label-for="organisation" - :scope.sync="organisation.scope" /> - - <Organisation :organisation.sync="organisation.value" - :scope.sync="organisation.scope" /> - </section> + <AccountPropertySection v-bind.sync="organisation" + :placeholder="t('settings', 'Your organisation')" /> </template> <script> import { loadState } from '@nextcloud/initial-state' -import Organisation from './Organisation' -import HeaderBar from '../shared/HeaderBar' +import AccountPropertySection from './shared/AccountPropertySection.vue' -import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' +import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js' const { organisation } = loadState('settings', 'personalInfoParameters', {}) @@ -45,25 +38,13 @@ export default { name: 'OrganisationSection', components: { - Organisation, - HeaderBar, + AccountPropertySection, }, data() { return { - accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.ORGANISATION, - organisation, + organisation: { ...organisation, readable: NAME_READABLE_ENUM[organisation.name] }, } }, } </script> - -<style lang="scss" scoped> -section { - padding: 10px 10px; - - &::v-deep button:disabled { - cursor: default; - } -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/OrganisationSection/Organisation.vue b/apps/settings/src/components/PersonalInfo/OrganisationSection/Organisation.vue deleted file mode 100644 index 4f69d7b6b9a..00000000000 --- a/apps/settings/src/components/PersonalInfo/OrganisationSection/Organisation.vue +++ /dev/null @@ -1,175 +0,0 @@ -<!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> - - - - @author Christopher Ng <chrng8@gmail.com> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - ---> - -<template> - <div class="organisation"> - <input id="organisation" - type="text" - :placeholder="t('settings', 'Your organisation')" - :value="organisation" - autocapitalize="none" - autocomplete="on" - autocorrect="off" - @input="onOrganisationChange"> - - <div class="organisation__actions-container"> - <transition name="fade"> - <span v-if="showCheckmarkIcon" class="icon-checkmark" /> - <span v-else-if="showErrorIcon" class="icon-error" /> - </transition> - </div> - </div> -</template> - -<script> -import { showError } from '@nextcloud/dialogs' -import { emit } from '@nextcloud/event-bus' -import debounce from 'debounce' - -import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants' -import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService' -import logger from '../../../logger' - -export default { - name: 'Organisation', - - props: { - organisation: { - type: String, - required: true, - }, - scope: { - type: String, - required: true, - }, - }, - - data() { - return { - initialOrganisation: this.organisation, - localScope: this.scope, - showCheckmarkIcon: false, - showErrorIcon: false, - } - }, - - methods: { - onOrganisationChange(e) { - this.$emit('update:organisation', e.target.value) - this.debounceOrganisationChange(e.target.value.trim()) - }, - - debounceOrganisationChange: debounce(async function(organisation) { - await this.updatePrimaryOrganisation(organisation) - }, 500), - - async updatePrimaryOrganisation(organisation) { - try { - const responseData = await savePrimaryAccountProperty(ACCOUNT_PROPERTY_ENUM.ORGANISATION, organisation) - this.handleResponse({ - organisation, - status: responseData.ocs?.meta?.status, - }) - } catch (e) { - this.handleResponse({ - errorMessage: t('settings', 'Unable to update organisation'), - error: e, - }) - } - }, - - handleResponse({ organisation, status, errorMessage, error }) { - if (status === 'ok') { - // Ensure that local state reflects server state - this.initialOrganisation = organisation - emit('settings:organisation:updated', organisation) - this.showCheckmarkIcon = true - setTimeout(() => { this.showCheckmarkIcon = false }, 2000) - } else { - showError(errorMessage) - logger.error(errorMessage, error) - this.showErrorIcon = true - setTimeout(() => { this.showErrorIcon = false }, 2000) - } - }, - - onScopeChange(scope) { - this.$emit('update:scope', scope) - }, - }, -} -</script> - -<style lang="scss" scoped> -.organisation { - display: grid; - align-items: center; - - input { - grid-area: 1 / 1; - width: 100%; - height: 34px; - margin: 3px 3px 3px 0; - padding: 7px 6px; - color: var(--color-main-text); - border: 1px solid var(--color-border-dark); - border-radius: var(--border-radius); - background-color: var(--color-main-background); - font-family: var(--font-face); - cursor: text; - } - - .organisation__actions-container { - grid-area: 1 / 1; - justify-self: flex-end; - height: 30px; - - display: flex; - gap: 0 2px; - margin-right: 5px; - - .icon-checkmark, - .icon-error { - height: 30px !important; - min-height: 30px !important; - width: 30px !important; - min-width: 30px !important; - top: 0; - right: 0; - float: none; - } - } -} - -.fade-enter, -.fade-leave-to { - opacity: 0; -} - -.fade-enter-active { - transition: opacity 200ms ease-out; -} - -.fade-leave-active { - transition: opacity 300ms ease-out; -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue index da3d509c2f5..0ab9445e1f9 100644 --- a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue +++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue @@ -37,10 +37,10 @@ import { showError } from '@nextcloud/dialogs' import { emit } from '@nextcloud/event-bus' -import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService' -import { validateBoolean } from '../../../utils/validate' -import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants' -import logger from '../../../logger' +import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService.js' +import { validateBoolean } from '../../../utils/validate.js' +import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants.js' +import logger from '../../../logger.js' export default { name: 'ProfileCheckbox', diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue index 0196b3161a7..2f1cc935f22 100644 --- a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue +++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue @@ -22,7 +22,7 @@ <template> <section> - <HeaderBar :account-property="accountProperty" /> + <HeaderBar :readable="propertyReadable" /> <ProfileCheckbox :profile-enabled.sync="profileEnabled" /> @@ -39,12 +39,12 @@ import { loadState } from '@nextcloud/initial-state' import { subscribe, unsubscribe } from '@nextcloud/event-bus' -import EditProfileAnchorLink from './EditProfileAnchorLink' -import HeaderBar from '../shared/HeaderBar' -import ProfileCheckbox from './ProfileCheckbox' -import ProfilePreviewCard from './ProfilePreviewCard' +import EditProfileAnchorLink from './EditProfileAnchorLink.vue' +import HeaderBar from '../shared/HeaderBar.vue' +import ProfileCheckbox from './ProfileCheckbox.vue' +import ProfilePreviewCard from './ProfilePreviewCard.vue' -import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' +import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants.js' const { organisation: { value: organisation }, @@ -65,7 +65,7 @@ export default { data() { return { - accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.PROFILE_ENABLED, + propertyReadable: ACCOUNT_PROPERTY_READABLE_ENUM.PROFILE_ENABLED, organisation, displayName, profileEnabled, diff --git a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue index 16a46fee969..eab49f11f49 100644 --- a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue +++ b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue @@ -24,7 +24,7 @@ <!-- TODO remove this inline margin placeholder once the settings layout is updated --> <section id="profile-visibility" :style="{ marginLeft }"> - <HeaderBar :account-property="heading" /> + <HeaderBar :readable="heading" /> <em :class="{ disabled }"> {{ t('settings', 'The more restrictive setting of either visibility or scope is respected on your Profile. For example, if visibility is set to "Show to everyone" and scope is set to "Private", "Private" is respected.') }} @@ -47,9 +47,9 @@ import { loadState } from '@nextcloud/initial-state' import { subscribe, unsubscribe } from '@nextcloud/event-bus' -import HeaderBar from '../shared/HeaderBar' -import VisibilityDropdown from './VisibilityDropdown' -import { PROFILE_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' +import HeaderBar from '../shared/HeaderBar.vue' +import VisibilityDropdown from './VisibilityDropdown.vue' +import { PROFILE_READABLE_ENUM } from '../../../constants/AccountPropertyConstants.js' const { profileConfig } = loadState('settings', 'profileParameters', {}) const { profileEnabled } = loadState('settings', 'personalInfoParameters', false) diff --git a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue index e5251522b22..b148fa97a4d 100644 --- a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue +++ b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue @@ -43,10 +43,9 @@ import { subscribe, unsubscribe } from '@nextcloud/event-bus' import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect' -import { saveProfileParameterVisibility } from '../../../service/ProfileService' -import { validateStringInput } from '../../../utils/validate' -import { VISIBILITY_PROPERTY_ENUM } from '../../../constants/ProfileConstants' -import logger from '../../../logger' +import { saveProfileParameterVisibility } from '../../../service/ProfileService.js' +import { VISIBILITY_PROPERTY_ENUM } from '../../../constants/ProfileConstants.js' +import logger from '../../../logger.js' const { profileEnabled } = loadState('settings', 'personalInfoParameters', false) @@ -112,7 +111,7 @@ export default { const { name: visibility } = visibilityObject this.$emit('update:visibility', visibility) - if (validateStringInput(visibility)) { + if (visibility !== '') { await this.updateVisibility(visibility) } } diff --git a/apps/settings/src/components/PersonalInfo/RoleSection/RoleSection.vue b/apps/settings/src/components/PersonalInfo/RoleSection.vue index b3e95bc8153..ab9b9c910fe 100644 --- a/apps/settings/src/components/PersonalInfo/RoleSection/RoleSection.vue +++ b/apps/settings/src/components/PersonalInfo/RoleSection.vue @@ -1,9 +1,9 @@ <!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> + - @copyright 2022 Christopher Ng <chrng8@gmail.com> - - @author Christopher Ng <chrng8@gmail.com> - - - @license GNU AGPL version 3 or any later version + - @license AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as @@ -12,7 +12,7 @@ - - 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 + - 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 @@ -21,23 +21,16 @@ --> <template> - <section> - <HeaderBar :account-property="accountProperty" - label-for="role" - :scope.sync="role.scope" /> - - <Role :role.sync="role.value" - :scope.sync="role.scope" /> - </section> + <AccountPropertySection v-bind.sync="role" + :placeholder="t('settings', 'Your role')" /> </template> <script> import { loadState } from '@nextcloud/initial-state' -import Role from './Role' -import HeaderBar from '../shared/HeaderBar' +import AccountPropertySection from './shared/AccountPropertySection.vue' -import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' +import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js' const { role } = loadState('settings', 'personalInfoParameters', {}) @@ -45,25 +38,13 @@ export default { name: 'RoleSection', components: { - Role, - HeaderBar, + AccountPropertySection, }, data() { return { - accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.ROLE, - role, + role: { ...role, readable: NAME_READABLE_ENUM[role.name] }, } }, } </script> - -<style lang="scss" scoped> -section { - padding: 10px 10px; - - &::v-deep button:disabled { - cursor: default; - } -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/RoleSection/Role.vue b/apps/settings/src/components/PersonalInfo/RoleSection/Role.vue deleted file mode 100644 index 4add0d04d0d..00000000000 --- a/apps/settings/src/components/PersonalInfo/RoleSection/Role.vue +++ /dev/null @@ -1,175 +0,0 @@ -<!-- - - @copyright 2021, Christopher Ng <chrng8@gmail.com> - - - - @author Christopher Ng <chrng8@gmail.com> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - ---> - -<template> - <div class="role"> - <input id="role" - type="text" - :placeholder="t('settings', 'Your role')" - :value="role" - autocapitalize="none" - autocomplete="on" - autocorrect="off" - @input="onRoleChange"> - - <div class="role__actions-container"> - <transition name="fade"> - <span v-if="showCheckmarkIcon" class="icon-checkmark" /> - <span v-else-if="showErrorIcon" class="icon-error" /> - </transition> - </div> - </div> -</template> - -<script> -import { showError } from '@nextcloud/dialogs' -import { emit } from '@nextcloud/event-bus' -import debounce from 'debounce' - -import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants' -import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService' -import logger from '../../../logger' - -export default { - name: 'Role', - - props: { - role: { - type: String, - required: true, - }, - scope: { - type: String, - required: true, - }, - }, - - data() { - return { - initialRole: this.role, - localScope: this.scope, - showCheckmarkIcon: false, - showErrorIcon: false, - } - }, - - methods: { - onRoleChange(e) { - this.$emit('update:role', e.target.value) - this.debounceRoleChange(e.target.value.trim()) - }, - - debounceRoleChange: debounce(async function(role) { - await this.updatePrimaryRole(role) - }, 500), - - async updatePrimaryRole(role) { - try { - const responseData = await savePrimaryAccountProperty(ACCOUNT_PROPERTY_ENUM.ROLE, role) - this.handleResponse({ - role, - status: responseData.ocs?.meta?.status, - }) - } catch (e) { - this.handleResponse({ - errorMessage: t('settings', 'Unable to update role'), - error: e, - }) - } - }, - - handleResponse({ role, status, errorMessage, error }) { - if (status === 'ok') { - // Ensure that local state reflects server state - this.initialRole = role - emit('settings:role:updated', role) - this.showCheckmarkIcon = true - setTimeout(() => { this.showCheckmarkIcon = false }, 2000) - } else { - showError(errorMessage) - logger.error(errorMessage, error) - this.showErrorIcon = true - setTimeout(() => { this.showErrorIcon = false }, 2000) - } - }, - - onScopeChange(scope) { - this.$emit('update:scope', scope) - }, - }, -} -</script> - -<style lang="scss" scoped> -.role { - display: grid; - align-items: center; - - input { - grid-area: 1 / 1; - width: 100%; - height: 34px; - margin: 3px 3px 3px 0; - padding: 7px 6px; - color: var(--color-main-text); - border: 1px solid var(--color-border-dark); - border-radius: var(--border-radius); - background-color: var(--color-main-background); - font-family: var(--font-face); - cursor: text; - } - - .role__actions-container { - grid-area: 1 / 1; - justify-self: flex-end; - height: 30px; - - display: flex; - gap: 0 2px; - margin-right: 5px; - - .icon-checkmark, - .icon-error { - height: 30px !important; - min-height: 30px !important; - width: 30px !important; - min-width: 30px !important; - top: 0; - right: 0; - float: none; - } - } -} - -.fade-enter, -.fade-leave-to { - opacity: 0; -} - -.fade-enter-active { - transition: opacity 200ms ease-out; -} - -.fade-leave-active { - transition: opacity 300ms ease-out; -} -</style> diff --git a/apps/settings/src/components/PersonalInfo/TwitterSection.vue b/apps/settings/src/components/PersonalInfo/TwitterSection.vue new file mode 100644 index 00000000000..78a772973fb --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/TwitterSection.vue @@ -0,0 +1,50 @@ +<!-- + - @copyright 2022 Christopher Ng <chrng8@gmail.com> + - + - @author Christopher Ng <chrng8@gmail.com> + - + - @license AGPL-3.0-or-later + - + - 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> + <AccountPropertySection v-bind.sync="twitter" + :placeholder="t('settings', 'Your Twitter handle')" /> +</template> + +<script> +import { loadState } from '@nextcloud/initial-state' + +import AccountPropertySection from './shared/AccountPropertySection.vue' + +import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js' + +const { twitter } = loadState('settings', 'personalInfoParameters', {}) + +export default { + name: 'TwitterSection', + + components: { + AccountPropertySection, + }, + + data() { + return { + twitter: { ...twitter, readable: NAME_READABLE_ENUM[twitter.name] }, + } + }, +} +</script> diff --git a/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue b/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue new file mode 100644 index 00000000000..351af504682 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue @@ -0,0 +1,265 @@ +<!-- + - @copyright 2022 Christopher Ng <chrng8@gmail.com> + - + - @author Christopher Ng <chrng8@gmail.com> + - + - @license AGPL-3.0-or-later + - + - 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> + <section> + <HeaderBar :scope.sync="scope" + :readable.sync="readable" + :input-id="inputId" + :is-editable="isEditable" /> + + <div v-if="isEditable" class="property"> + <textarea v-if="multiLine" + :id="inputId" + :placeholder="placeholder" + :value="value" + rows="8" + autocapitalize="none" + autocomplete="off" + autocorrect="off" + @input="onPropertyChange" /> + <input v-else + :id="inputId" + :placeholder="placeholder" + :type="type" + :value="value" + autocapitalize="none" + autocomplete="on" + autocorrect="off" + @input="onPropertyChange"> + + <div class="property__actions-container"> + <transition name="fade"> + <Check v-if="showCheckmarkIcon" :size="20" /> + <AlertOctagon v-else-if="showErrorIcon" :size="20" /> + </transition> + </div> + </div> + <span v-else> + {{ value || t('settings', 'No {property} set', { property: readable.toLocaleLowerCase() }) }} + </span> + </section> +</template> + +<script> +import debounce from 'debounce' +import { showError } from '@nextcloud/dialogs' + +import Check from 'vue-material-design-icons/Check' +import AlertOctagon from 'vue-material-design-icons/AlertOctagon' + +import HeaderBar from '../shared/HeaderBar.vue' + +import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService.js' +import logger from '../../../logger.js' + +export default { + name: 'AccountPropertySection', + + components: { + AlertOctagon, + Check, + HeaderBar, + }, + + props: { + name: { + type: String, + required: true, + }, + value: { + type: String, + required: true, + }, + scope: { + type: String, + required: true, + }, + readable: { + type: String, + required: true, + }, + placeholder: { + type: String, + required: true, + }, + type: { + type: String, + default: 'text', + }, + isEditable: { + type: Boolean, + default: true, + }, + multiLine: { + type: Boolean, + default: false, + }, + onValidate: { + type: Function, + default: null, + }, + onSave: { + type: Function, + default: null, + }, + }, + + data() { + return { + initialValue: this.value, + showCheckmarkIcon: false, + showErrorIcon: false, + } + }, + + computed: { + inputId() { + return `account-property-${this.name}` + }, + }, + + methods: { + onPropertyChange(e) { + this.$emit('update:value', e.target.value) + this.debouncePropertyChange(e.target.value.trim()) + }, + + debouncePropertyChange: debounce(async function(value) { + if (this.onValidate && !this.onValidate(value)) { + return + } + await this.updateProperty(value) + }, 500), + + async updateProperty(value) { + try { + const responseData = await savePrimaryAccountProperty( + this.name, + value, + ) + this.handleResponse({ + value, + status: responseData.ocs?.meta?.status, + }) + } catch (e) { + this.handleResponse({ + errorMessage: t('settings', 'Unable to update {property}', { property: this.readable.toLocaleLowerCase() }), + error: e, + }) + } + }, + + handleResponse({ value, status, errorMessage, error }) { + if (status === 'ok') { + this.initialValue = value + if (this.onSave) { + this.onSave(value) + } + this.showCheckmarkIcon = true + setTimeout(() => { this.showCheckmarkIcon = false }, 2000) + } else { + this.$emit('update:value', this.initialValue) + showError(errorMessage) + logger.error(errorMessage, error) + this.showErrorIcon = true + setTimeout(() => { this.showErrorIcon = false }, 2000) + } + }, + }, +} +</script> + +<style lang="scss" scoped> +section { + padding: 10px 10px; + + &::v-deep button:disabled { + cursor: default; + } + + .property { + display: grid; + align-items: center; + + textarea { + resize: vertical; + grid-area: 1 / 1; + width: 100%; + margin: 3px 3px 3px 0; + padding: 7px 6px; + color: var(--color-main-text); + border: 1px solid var(--color-border-dark); + border-radius: var(--border-radius); + background-color: var(--color-main-background); + font-family: var(--font-face); + cursor: text; + + &:hover, + &:focus, + &:active { + border-color: var(--color-primary-element) !important; + outline: none !important; + } + } + + input { + grid-area: 1 / 1; + width: 100%; + height: 34px; + margin: 3px 3px 3px 0; + padding: 7px 6px; + color: var(--color-main-text); + border: 1px solid var(--color-border-dark); + border-radius: var(--border-radius); + background-color: var(--color-main-background); + font-family: var(--font-face); + cursor: text; + } + + .property__actions-container { + grid-area: 1 / 1; + justify-self: flex-end; + align-self: flex-end; + height: 30px; + + display: flex; + gap: 0 2px; + margin-right: 5px; + margin-bottom: 5px; + } + } + + .fade-enter, + .fade-leave-to { + opacity: 0; + } + + .fade-enter-active { + transition: opacity 200ms ease-out; + } + + .fade-leave-active { + transition: opacity 300ms ease-out; + } +} +</style> diff --git a/apps/settings/src/components/PersonalInfo/shared/FederationControl.vue b/apps/settings/src/components/PersonalInfo/shared/FederationControl.vue index af2b9670ed1..555b917ad1f 100644 --- a/apps/settings/src/components/PersonalInfo/shared/FederationControl.vue +++ b/apps/settings/src/components/PersonalInfo/shared/FederationControl.vue @@ -43,17 +43,19 @@ import NcActions from '@nextcloud/vue/dist/Components/NcActions' import { loadState } from '@nextcloud/initial-state' import { showError } from '@nextcloud/dialogs' -import FederationControlAction from './FederationControlAction' +import FederationControlAction from './FederationControlAction.vue' import { ACCOUNT_PROPERTY_READABLE_ENUM, + ACCOUNT_SETTING_PROPERTY_READABLE_ENUM, + PROFILE_READABLE_ENUM, PROPERTY_READABLE_KEYS_ENUM, PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM, SCOPE_ENUM, SCOPE_PROPERTY_ENUM, UNPUBLISHED_READABLE_PROPERTIES, -} from '../../../constants/AccountPropertyConstants' -import { savePrimaryAccountPropertyScope } from '../../../service/PersonalInfo/PersonalInfoService' -import logger from '../../../logger' +} from '../../../constants/AccountPropertyConstants.js' +import { savePrimaryAccountPropertyScope } from '../../../service/PersonalInfo/PersonalInfoService.js' +import logger from '../../../logger.js' const { lookupServerUploadEnabled } = loadState('settings', 'accountParameters', {}) @@ -66,10 +68,10 @@ export default { }, props: { - accountProperty: { + readable: { type: String, required: true, - validator: (value) => Object.values(ACCOUNT_PROPERTY_READABLE_ENUM).includes(value), + validator: (value) => Object.values(ACCOUNT_PROPERTY_READABLE_ENUM).includes(value) || Object.values(ACCOUNT_SETTING_PROPERTY_READABLE_ENUM).includes(value) || value === PROFILE_READABLE_ENUM.PROFILE_VISIBILITY, }, additional: { type: Boolean, @@ -95,14 +97,14 @@ export default { data() { return { - accountPropertyLowerCase: this.accountProperty.toLocaleLowerCase(), + readableLowerCase: this.readable.toLocaleLowerCase(), initialScope: this.scope, } }, computed: { ariaLabel() { - return t('settings', 'Change scope level of {accountProperty}, current scope is {scope}', { accountProperty: this.accountPropertyLowerCase, scope: this.scopeDisplayNameLowerCase }) + return t('settings', 'Change scope level of {property}, current scope is {scope}', { property: this.readableLowerCase, scope: this.scopeDisplayNameLowerCase }) }, scopeDisplayNameLowerCase() { @@ -118,15 +120,15 @@ export default { }, supportedScopes() { - if (lookupServerUploadEnabled && !UNPUBLISHED_READABLE_PROPERTIES.includes(this.accountProperty)) { + if (lookupServerUploadEnabled && !UNPUBLISHED_READABLE_PROPERTIES.includes(this.readable)) { return [ - ...PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM[this.accountProperty], + ...PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM[this.readable], SCOPE_ENUM.FEDERATED, SCOPE_ENUM.PUBLISHED, ] } - return PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM[this.accountProperty] + return PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM[this.readable] }, }, @@ -143,14 +145,14 @@ export default { async updatePrimaryScope(scope) { try { - const responseData = await savePrimaryAccountPropertyScope(PROPERTY_READABLE_KEYS_ENUM[this.accountProperty], scope) + const responseData = await savePrimaryAccountPropertyScope(PROPERTY_READABLE_KEYS_ENUM[this.readable], scope) this.handleResponse({ scope, status: responseData.ocs?.meta?.status, }) } catch (e) { this.handleResponse({ - errorMessage: t('settings', 'Unable to update federation scope of the primary {accountProperty}', { accountProperty: this.accountPropertyLowerCase }), + errorMessage: t('settings', 'Unable to update federation scope of the primary {property}', { property: this.readableLowerCase }), error: e, }) } @@ -165,7 +167,7 @@ export default { }) } catch (e) { this.handleResponse({ - errorMessage: t('settings', 'Unable to update federation scope of additional {accountProperty}', { accountProperty: this.accountPropertyLowerCase }), + errorMessage: t('settings', 'Unable to update federation scope of additional {property}', { property: this.readableLowerCase }), error: e, }) } diff --git a/apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue b/apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue index f353e673b81..bd6cc437928 100644 --- a/apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue +++ b/apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue @@ -22,14 +22,14 @@ <template> <h3 :class="{ 'setting-property': isSettingProperty, 'profile-property': isProfileProperty }"> - <label :for="labelFor"> + <label :for="inputId"> <!-- Already translated as required by prop validator --> - {{ accountProperty }} + {{ readable }} </label> <template v-if="scope"> <FederationControl class="federation-control" - :account-property="accountProperty" + :readable="readable" :scope.sync="localScope" @update:scope="onScopeChange" /> </template> @@ -49,10 +49,16 @@ </template> <script> -import FederationControl from './FederationControl' import NcButton from '@nextcloud/vue/dist/Components/NcButton' import Plus from 'vue-material-design-icons/Plus' -import { ACCOUNT_PROPERTY_READABLE_ENUM, ACCOUNT_SETTING_PROPERTY_READABLE_ENUM, PROFILE_READABLE_ENUM } from '../../../constants/AccountPropertyConstants' + +import FederationControl from './FederationControl.vue' + +import { + ACCOUNT_PROPERTY_READABLE_ENUM, + ACCOUNT_SETTING_PROPERTY_READABLE_ENUM, + PROFILE_READABLE_ENUM, +} from '../../../constants/AccountPropertyConstants.js' export default { name: 'HeaderBar', @@ -64,11 +70,19 @@ export default { }, props: { - accountProperty: { + scope: { + type: String, + default: null, + }, + readable: { type: String, required: true, validator: (value) => Object.values(ACCOUNT_PROPERTY_READABLE_ENUM).includes(value) || Object.values(ACCOUNT_SETTING_PROPERTY_READABLE_ENUM).includes(value) || value === PROFILE_READABLE_ENUM.PROFILE_VISIBILITY, }, + inputId: { + type: String, + default: null, + }, isEditable: { type: Boolean, default: true, @@ -79,15 +93,7 @@ export default { }, isValidSection: { type: Boolean, - default: false, - }, - labelFor: { - type: String, - default: '', - }, - scope: { - type: String, - default: null, + default: true, }, }, @@ -99,11 +105,11 @@ export default { computed: { isProfileProperty() { - return this.accountProperty === ACCOUNT_PROPERTY_READABLE_ENUM.PROFILE_ENABLED + return this.readable === ACCOUNT_PROPERTY_READABLE_ENUM.PROFILE_ENABLED }, isSettingProperty() { - return Object.values(ACCOUNT_SETTING_PROPERTY_READABLE_ENUM).includes(this.accountProperty) + return Object.values(ACCOUNT_SETTING_PROPERTY_READABLE_ENUM).includes(this.readable) }, }, diff --git a/apps/settings/src/constants/AccountPropertyConstants.js b/apps/settings/src/constants/AccountPropertyConstants.js index 941e20a912a..17debe27c55 100644 --- a/apps/settings/src/constants/AccountPropertyConstants.js +++ b/apps/settings/src/constants/AccountPropertyConstants.js @@ -61,6 +61,22 @@ export const ACCOUNT_PROPERTY_READABLE_ENUM = Object.freeze({ WEBSITE: t('settings', 'Website'), }) +export const NAME_READABLE_ENUM = Object.freeze({ + [ACCOUNT_PROPERTY_ENUM.ADDRESS]: ACCOUNT_PROPERTY_READABLE_ENUM.ADDRESS, + [ACCOUNT_PROPERTY_ENUM.AVATAR]: ACCOUNT_PROPERTY_READABLE_ENUM.AVATAR, + [ACCOUNT_PROPERTY_ENUM.BIOGRAPHY]: ACCOUNT_PROPERTY_READABLE_ENUM.BIOGRAPHY, + [ACCOUNT_PROPERTY_ENUM.DISPLAYNAME]: ACCOUNT_PROPERTY_READABLE_ENUM.DISPLAYNAME, + [ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION]: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL_COLLECTION, + [ACCOUNT_PROPERTY_ENUM.EMAIL]: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL, + [ACCOUNT_PROPERTY_ENUM.HEADLINE]: ACCOUNT_PROPERTY_READABLE_ENUM.HEADLINE, + [ACCOUNT_PROPERTY_ENUM.ORGANISATION]: ACCOUNT_PROPERTY_READABLE_ENUM.ORGANISATION, + [ACCOUNT_PROPERTY_ENUM.PHONE]: ACCOUNT_PROPERTY_READABLE_ENUM.PHONE, + [ACCOUNT_PROPERTY_ENUM.PROFILE_ENABLED]: ACCOUNT_PROPERTY_READABLE_ENUM.PROFILE_ENABLED, + [ACCOUNT_PROPERTY_ENUM.ROLE]: ACCOUNT_PROPERTY_READABLE_ENUM.ROLE, + [ACCOUNT_PROPERTY_ENUM.TWITTER]: ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER, + [ACCOUNT_PROPERTY_ENUM.WEBSITE]: ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE, +}) + /** Enum of profile specific sections to human readable names */ export const PROFILE_READABLE_ENUM = Object.freeze({ PROFILE_VISIBILITY: t('settings', 'Profile visibility'), diff --git a/apps/settings/src/main-personal-info.js b/apps/settings/src/main-personal-info.js index d3cfd3ec9cc..1ebaae0fc8e 100644 --- a/apps/settings/src/main-personal-info.js +++ b/apps/settings/src/main-personal-info.js @@ -26,15 +26,16 @@ import { loadState } from '@nextcloud/initial-state' import { translate as t } from '@nextcloud/l10n' import '@nextcloud/dialogs/styles/toast.scss' -import DisplayNameSection from './components/PersonalInfo/DisplayNameSection/DisplayNameSection' -import EmailSection from './components/PersonalInfo/EmailSection/EmailSection' -import LanguageSection from './components/PersonalInfo/LanguageSection/LanguageSection' -import ProfileSection from './components/PersonalInfo/ProfileSection/ProfileSection' -import OrganisationSection from './components/PersonalInfo/OrganisationSection/OrganisationSection' -import RoleSection from './components/PersonalInfo/RoleSection/RoleSection' -import HeadlineSection from './components/PersonalInfo/HeadlineSection/HeadlineSection' -import BiographySection from './components/PersonalInfo/BiographySection/BiographySection' -import ProfileVisibilitySection from './components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection' +import DisplayNameSection from './components/PersonalInfo/DisplayNameSection.vue' +import EmailSection from './components/PersonalInfo/EmailSection/EmailSection.vue' +import TwitterSection from './components/PersonalInfo/TwitterSection.vue' +import LanguageSection from './components/PersonalInfo/LanguageSection/LanguageSection.vue' +import ProfileSection from './components/PersonalInfo/ProfileSection/ProfileSection.vue' +import OrganisationSection from './components/PersonalInfo/OrganisationSection.vue' +import RoleSection from './components/PersonalInfo/RoleSection.vue' +import HeadlineSection from './components/PersonalInfo/HeadlineSection.vue' +import BiographySection from './components/PersonalInfo/BiographySection.vue' +import ProfileVisibilitySection from './components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue' __webpack_nonce__ = btoa(getRequestToken()) @@ -48,10 +49,12 @@ Vue.mixin({ const DisplayNameView = Vue.extend(DisplayNameSection) const EmailView = Vue.extend(EmailSection) +const TwitterView = Vue.extend(TwitterSection) const LanguageView = Vue.extend(LanguageSection) new DisplayNameView().$mount('#vue-displayname-section') new EmailView().$mount('#vue-email-section') +new TwitterView().$mount('#vue-twitter-section') new LanguageView().$mount('#vue-language-section') if (profileEnabledGlobally) { diff --git a/apps/settings/src/utils/validate.js b/apps/settings/src/utils/validate.js index 58d42863f4e..83af1d94fbb 100644 --- a/apps/settings/src/utils/validate.js +++ b/apps/settings/src/utils/validate.js @@ -29,18 +29,6 @@ import { VALIDATE_EMAIL_REGEX } from '../constants/AccountPropertyConstants' /** - * Validate the string input - * - * Generic validator just to check that input is not an empty string* - * - * @param {string} input the input - * @return {boolean} - */ -export function validateStringInput(input) { - return input !== '' -} - -/** * Validate the email input * * Compliant with PHP core FILTER_VALIDATE_EMAIL validator* diff --git a/apps/settings/templates/settings/personal/personal.info.php b/apps/settings/templates/settings/personal/personal.info.php index 98009cc52be..973c98521f5 100644 --- a/apps/settings/templates/settings/personal/personal.info.php +++ b/apps/settings/templates/settings/personal/personal.info.php @@ -196,48 +196,7 @@ script('settings', [ </form> </div> <div class="personal-settings-setting-box"> - <form id="twitterform" class="section"> - <h3> - <label for="twitter"><?php p($l->t('Twitter')); ?></label> - <a href="#" class="federation-menu" aria-label="<?php p($l->t('Change privacy level of Twitter profile')); ?>"> - <span class="icon-federation-menu icon-password"> - <span class="icon-triangle-s"></span> - </span> - </a> - </h3> - <?php if ($_['lookupServerUploadEnabled']) { ?> - <div class="verify <?php if ($_['twitter'] === '' || $_['twitterScope'] !== 'public') { - p('hidden'); - } ?>"> - <img id="verify-twitter" title="<?php p($_['twitterMessage']); ?>" data-status="<?php p($_['twitterVerification']) ?>" src=" - <?php - switch ($_['twitterVerification']) { - 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')); - } - ?>" <?php if ($_['twitterVerification'] === \OC\Accounts\AccountManager::VERIFICATION_IN_PROGRESS || $_['twitterVerification'] === \OC\Accounts\AccountManager::NOT_VERIFIED) { - print_unescaped(' class="verify-action"'); - } ?>> - <div class="verification-dialog popovermenu bubble menu"> - <div class="verification-dialog-content"> - <p class="explainVerification"></p> - <p class="verificationCode"></p> - <p><?php p($l->t('It can take up to 24 hours before the account is displayed as verified.')); ?></p> - </div> - </div> - </div> - <?php } ?> - <input type="text" name="twitter" id="twitter" value="<?php p($_['twitter']); ?>" placeholder="<?php p($l->t('Twitter handle @…')); ?>" autocomplete="on" autocapitalize="none" autocorrect="off" /> - <span class="icon-checkmark hidden"></span> - <span class="icon-error hidden"></span> - <input type="hidden" id="twitterscope" value="<?php p($_['twitterScope']) ?>"> - </form> + <div id="vue-twitter-section"></div> </div> <?php if ($_['profileEnabledGlobally']) : ?> <div class="personal-settings-setting-box"> |