aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/src/components/PersonalInfo
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/src/components/PersonalInfo')
-rw-r--r--apps/settings/src/components/PersonalInfo/AvatarSection.vue11
-rw-r--r--apps/settings/src/components/PersonalInfo/BiographySection.vue2
-rw-r--r--apps/settings/src/components/PersonalInfo/BirthdaySection.vue32
-rw-r--r--apps/settings/src/components/PersonalInfo/BlueskySection.vue64
-rw-r--r--apps/settings/src/components/PersonalInfo/DetailsSection.vue17
-rw-r--r--apps/settings/src/components/PersonalInfo/EmailSection/Email.vue19
-rw-r--r--apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue6
-rw-r--r--apps/settings/src/components/PersonalInfo/FediverseSection.vue53
-rw-r--r--apps/settings/src/components/PersonalInfo/FirstDayOfWeekSection.vue126
-rw-r--r--apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue2
-rw-r--r--apps/settings/src/components/PersonalInfo/LocaleSection/Locale.vue2
-rw-r--r--apps/settings/src/components/PersonalInfo/PhoneSection.vue4
-rw-r--r--apps/settings/src/components/PersonalInfo/ProfileSection/EditProfileAnchorLink.vue2
-rw-r--r--apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue2
-rw-r--r--apps/settings/src/components/PersonalInfo/ProfileSection/ProfilePreviewCard.vue14
-rw-r--r--apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue2
-rw-r--r--apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue2
-rw-r--r--apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue4
-rw-r--r--apps/settings/src/components/PersonalInfo/PronounsSection.vue47
-rw-r--r--apps/settings/src/components/PersonalInfo/TwitterSection.vue35
-rw-r--r--apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue11
-rw-r--r--apps/settings/src/components/PersonalInfo/shared/FederationControl.vue8
-rw-r--r--apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue13
23 files changed, 374 insertions, 104 deletions
diff --git a/apps/settings/src/components/PersonalInfo/AvatarSection.vue b/apps/settings/src/components/PersonalInfo/AvatarSection.vue
index 3145cc28fe2..a99f228668c 100644
--- a/apps/settings/src/components/PersonalInfo/AvatarSection.vue
+++ b/apps/settings/src/components/PersonalInfo/AvatarSection.vue
@@ -80,15 +80,15 @@ import { getCurrentUser } from '@nextcloud/auth'
import { getFilePickerBuilder, showError } from '@nextcloud/dialogs'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
-import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
-import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcAvatar from '@nextcloud/vue/components/NcAvatar'
+import NcButton from '@nextcloud/vue/components/NcButton'
import VueCropper from 'vue-cropperjs'
// eslint-disable-next-line n/no-extraneous-import
import 'cropperjs/dist/cropper.css'
import Upload from 'vue-material-design-icons/Upload.vue'
import Folder from 'vue-material-design-icons/Folder.vue'
-import Delete from 'vue-material-design-icons/Delete.vue'
+import Delete from 'vue-material-design-icons/DeleteOutline.vue'
import HeaderBar from './shared/HeaderBar.vue'
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
@@ -257,9 +257,10 @@ section {
grid-row: 1/3;
padding: 10px 10px;
}
+
.avatar {
&__container {
- margin: 0 auto;
+ margin: calc(var(--default-grid-baseline) * 2) auto 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
@@ -296,7 +297,7 @@ section {
justify-content: space-between;
}
- &::v-deep .cropper-view-box {
+ :deep(.cropper-view-box) {
border-radius: 50%;
}
}
diff --git a/apps/settings/src/components/PersonalInfo/BiographySection.vue b/apps/settings/src/components/PersonalInfo/BiographySection.vue
index 32af1a03e2d..bbfb25e25cc 100644
--- a/apps/settings/src/components/PersonalInfo/BiographySection.vue
+++ b/apps/settings/src/components/PersonalInfo/BiographySection.vue
@@ -5,7 +5,7 @@
<template>
<AccountPropertySection v-bind.sync="biography"
- :placeholder="t('settings', 'Your biography')"
+ :placeholder="t('settings', 'Your biography. Markdown is supported.')"
:multi-line="true" />
</template>
diff --git a/apps/settings/src/components/PersonalInfo/BirthdaySection.vue b/apps/settings/src/components/PersonalInfo/BirthdaySection.vue
index 7580e958023..f55f09c95e5 100644
--- a/apps/settings/src/components/PersonalInfo/BirthdaySection.vue
+++ b/apps/settings/src/components/PersonalInfo/BirthdaySection.vue
@@ -8,13 +8,11 @@
:input-id="inputId"
:readable="birthdate.readable" />
- <template>
- <NcDateTimePickerNative :id="inputId"
- type="date"
- label=""
- :value="value"
- @input="onInput" />
- </template>
+ <NcDateTimePickerNative :id="inputId"
+ type="date"
+ label=""
+ :value="value"
+ @input="onInput" />
<p class="property__helper-text-message">
{{ t('settings', 'Enter your date of birth') }}
@@ -23,15 +21,15 @@
</template>
<script>
-import HeaderBar from './shared/HeaderBar.vue'
-import AccountPropertySection from './shared/AccountPropertySection.vue'
+import { loadState } from '@nextcloud/initial-state'
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
-import { NcDateTimePickerNative } from '@nextcloud/vue'
-import debounce from 'debounce'
import { savePrimaryAccountProperty } from '../../service/PersonalInfo/PersonalInfoService'
import { handleError } from '../../utils/handlers'
-import AlertCircle from 'vue-material-design-icons/AlertCircleOutline.vue'
-import { loadState } from '@nextcloud/initial-state'
+
+import debounce from 'debounce'
+
+import NcDateTimePickerNative from '@nextcloud/vue/components/NcDateTimePickerNative'
+import HeaderBar from './shared/HeaderBar.vue'
const { birthdate } = loadState('settings', 'personalInfoParameters', {})
@@ -39,8 +37,6 @@ export default {
name: 'BirthdaySection',
components: {
- AlertCircle,
- AccountPropertySection,
NcDateTimePickerNative,
HeaderBar,
},
@@ -68,13 +64,13 @@ export default {
get() {
return new Date(this.birthdate.value)
},
- /** @param {Date} value */
+ /** @param {Date} value The date to set */
set(value) {
const day = value.getDate().toString().padStart(2, '0')
const month = (value.getMonth() + 1).toString().padStart(2, '0')
const year = value.getFullYear()
this.birthdate.value = `${year}-${month}-${day}`
- }
+ },
},
},
@@ -122,7 +118,7 @@ export default {
section {
padding: 10px 10px;
- &::v-deep button:disabled {
+ :deep(button:disabled) {
cursor: default;
}
diff --git a/apps/settings/src/components/PersonalInfo/BlueskySection.vue b/apps/settings/src/components/PersonalInfo/BlueskySection.vue
new file mode 100644
index 00000000000..65223d1ab53
--- /dev/null
+++ b/apps/settings/src/components/PersonalInfo/BlueskySection.vue
@@ -0,0 +1,64 @@
+<!--
+ - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<template>
+ <AccountPropertySection v-bind.sync="value"
+ :readable="readable"
+ :on-validate="onValidate"
+ :placeholder="t('settings', 'Bluesky handle')" />
+</template>
+
+<script setup lang="ts">
+import type { AccountProperties } from '../../constants/AccountPropertyConstants.js'
+
+import { loadState } from '@nextcloud/initial-state'
+import { t } from '@nextcloud/l10n'
+import { ref } from 'vue'
+import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.ts'
+import AccountPropertySection from './shared/AccountPropertySection.vue'
+
+const { bluesky } = loadState<AccountProperties>('settings', 'personalInfoParameters')
+
+const value = ref({ ...bluesky })
+const readable = NAME_READABLE_ENUM[bluesky.name]
+
+/**
+ * Validate that the text might be a bluesky handle
+ * @param text The potential bluesky handle
+ */
+function onValidate(text: string): boolean {
+ if (text === '') return true
+
+ const lowerText = text.toLowerCase()
+
+ if (lowerText === 'bsky.social') {
+ // Standalone bsky.social is invalid
+ return false
+ }
+
+ if (lowerText.endsWith('.bsky.social')) {
+ // Enforce format: exactly one label + '.bsky.social'
+ const parts = lowerText.split('.')
+
+ // Must be in form: [username, 'bsky', 'social']
+ if (parts.length !== 3 || parts[1] !== 'bsky' || parts[2] !== 'social') {
+ return false
+ }
+
+ const username = parts[0]
+ const validateRegex = /^[a-z0-9][a-z0-9-]{2,17}$/
+ return validateRegex.test(username)
+ }
+
+ // Else, treat as a custom domain
+ try {
+ const url = new URL(`https://${text}`)
+ // Ensure the parsed host matches exactly (case-insensitive already)
+ return url.host === lowerText
+ } catch {
+ return false
+ }
+}
+</script>
diff --git a/apps/settings/src/components/PersonalInfo/DetailsSection.vue b/apps/settings/src/components/PersonalInfo/DetailsSection.vue
index 22a0a185db7..d4bb0ce16ec 100644
--- a/apps/settings/src/components/PersonalInfo/DetailsSection.vue
+++ b/apps/settings/src/components/PersonalInfo/DetailsSection.vue
@@ -20,6 +20,7 @@
<div class="details__quota">
<CircleSlice :size="20" />
<div class="details__quota-info">
+ <!-- eslint-disable-next-line vue/no-v-html -->
<p class="details__quota-text" v-html="quotaText" />
<NcProgressBar size="medium"
:value="usageRelative"
@@ -32,9 +33,10 @@
<script>
import { loadState } from '@nextcloud/initial-state'
-import NcProgressBar from '@nextcloud/vue/dist/Components/NcProgressBar.js'
+import { t } from '@nextcloud/l10n'
-import Account from 'vue-material-design-icons/Account.vue'
+import NcProgressBar from '@nextcloud/vue/components/NcProgressBar'
+import Account from 'vue-material-design-icons/AccountOutline.vue'
import CircleSlice from 'vue-material-design-icons/CircleSlice3.vue'
import HeaderBar from './shared/HeaderBar.vue'
@@ -64,12 +66,14 @@ export default {
computed: {
quotaText() {
if (quota === SPACE_UNLIMITED) {
- return t('settings', 'You are using <strong>{usage}</strong>', { usage })
+ return t('settings', 'You are using {s}{usage}{/s}', { usage, s: '<strong>', '/s': '</strong>' }, undefined, { escape: false })
}
return t(
'settings',
- 'You are using <strong>{usage}</strong> of <strong>{totalSpace}</strong> (<strong>{usageRelative}%</strong>)',
- { usage, totalSpace, usageRelative },
+ 'You are using {s}{usage}{/s} of {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})',
+ { usage, totalSpace, usageRelative, s: '<strong>', '/s': '</strong>' },
+ undefined,
+ { escape: false },
)
},
},
@@ -80,7 +84,8 @@ export default {
.details {
display: flex;
flex-direction: column;
- margin: 10px 32px 10px 0;
+ margin-block: 10px;
+ margin-inline: 0 32px;
gap: 16px 0;
color: var(--color-text-maxcontrast);
diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue b/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
index ecc463c7363..6a6baef8817 100644
--- a/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
+++ b/apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
@@ -48,7 +48,7 @@
:disabled="deleteDisabled"
@click="deleteEmail">
<template #icon>
- <NcIconSvgWrapper :path="mdiTrashCan" />
+ <NcIconSvgWrapper :path="mdiTrashCanOutline" />
</template>
{{ deleteEmailLabel }}
</NcActionButton>
@@ -64,14 +64,14 @@
</template>
<script>
-import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
-import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
-import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
-import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
+import NcActions from '@nextcloud/vue/components/NcActions'
+import NcActionButton from '@nextcloud/vue/components/NcActionButton'
+import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
+import NcTextField from '@nextcloud/vue/components/NcTextField'
import debounce from 'debounce'
-import { mdiArrowLeft, mdiLock, mdiStar, mdiStarOutline, mdiTrashCan } from '@mdi/js'
+import { mdiArrowLeft, mdiLockOutline, mdiStar, mdiStarOutline, mdiTrashCanOutline } from '@mdi/js'
import FederationControl from '../shared/FederationControl.vue'
import { handleError } from '../../../utils/handlers.ts'
@@ -133,10 +133,10 @@ export default {
setup() {
return {
mdiArrowLeft,
- mdiLock,
+ mdiLockOutline,
mdiStar,
mdiStarOutline,
- mdiTrashCan,
+ mdiTrashCanOutline,
saveAdditionalEmailScope,
}
},
@@ -356,6 +356,9 @@ export default {
handleDeleteAdditionalEmail(status) {
if (status === 'ok') {
this.$emit('delete-additional-email')
+ if (this.isNotificationEmail) {
+ this.$emit('update:notification-email', '')
+ }
} else {
this.handleResponse({
errorMessage: t('settings', 'Unable to delete additional email address'),
diff --git a/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue b/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue
index 8fd17922724..f9674a3163b 100644
--- a/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue
+++ b/apps/settings/src/components/PersonalInfo/EmailSection/EmailSection.vue
@@ -13,7 +13,7 @@
:scope.sync="primaryEmail.scope"
@add-additional="onAddAdditionalEmail" />
- <template v-if="displayNameChangeSupported">
+ <template v-if="emailChangeSupported">
<Email :input-id="inputId"
:primary="true"
:scope.sync="primaryEmail.scope"
@@ -56,7 +56,7 @@ import { validateEmail } from '../../../utils/validate.js'
import { handleError } from '../../../utils/handlers.ts'
const { emailMap: { additionalEmails, primaryEmail, notificationEmail } } = loadState('settings', 'personalInfoParameters', {})
-const { displayNameChangeSupported } = loadState('settings', 'accountParameters', {})
+const { emailChangeSupported } = loadState('settings', 'accountParameters', {})
export default {
name: 'EmailSection',
@@ -70,7 +70,7 @@ export default {
return {
accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL,
additionalEmails: additionalEmails.map(properties => ({ ...properties, key: this.generateUniqueKey() })),
- displayNameChangeSupported,
+ emailChangeSupported,
primaryEmail: { ...primaryEmail, readable: NAME_READABLE_ENUM[primaryEmail.name] },
notificationEmail,
}
diff --git a/apps/settings/src/components/PersonalInfo/FediverseSection.vue b/apps/settings/src/components/PersonalInfo/FediverseSection.vue
index 9ba9c37ab80..043fa6e64b9 100644
--- a/apps/settings/src/components/PersonalInfo/FediverseSection.vue
+++ b/apps/settings/src/components/PersonalInfo/FediverseSection.vue
@@ -4,30 +4,47 @@
-->
<template>
- <AccountPropertySection v-bind.sync="fediverse"
+ <AccountPropertySection v-bind.sync="value"
+ :readable="readable"
+ :on-validate="onValidate"
:placeholder="t('settings', 'Your handle')" />
</template>
-<script>
+<script setup lang="ts">
+import type { AccountProperties } from '../../constants/AccountPropertyConstants.js'
import { loadState } from '@nextcloud/initial-state'
-
-import AccountPropertySection from './shared/AccountPropertySection.vue'
-
+import { t } from '@nextcloud/l10n'
+import { ref } from 'vue'
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
-const { fediverse } = loadState('settings', 'personalInfoParameters', {})
-
-export default {
- name: 'FediverseSection',
-
- components: {
- AccountPropertySection,
- },
+import AccountPropertySection from './shared/AccountPropertySection.vue'
- data() {
- return {
- fediverse: { ...fediverse, readable: NAME_READABLE_ENUM[fediverse.name] },
- }
- },
+const { fediverse } = loadState<AccountProperties>('settings', 'personalInfoParameters')
+
+const value = ref({ ...fediverse })
+const readable = NAME_READABLE_ENUM[fediverse.name]
+
+/**
+ * Validate a fediverse handle
+ * @param text The potential fediverse handle
+ */
+function onValidate(text: string): boolean {
+ // allow to clear the value
+ if (text === '') {
+ return true
+ }
+
+ // check its in valid format
+ const result = text.match(/^@?([^@/]+)@([^@/]+)$/)
+ if (result === null) {
+ return false
+ }
+
+ // check its a valid URL
+ try {
+ return URL.parse(`https://${result[2]}/`) !== null
+ } catch {
+ return false
+ }
}
</script>
diff --git a/apps/settings/src/components/PersonalInfo/FirstDayOfWeekSection.vue b/apps/settings/src/components/PersonalInfo/FirstDayOfWeekSection.vue
new file mode 100644
index 00000000000..98501db7ccc
--- /dev/null
+++ b/apps/settings/src/components/PersonalInfo/FirstDayOfWeekSection.vue
@@ -0,0 +1,126 @@
+<!--
+ - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+ -->
+
+<template>
+ <section class="fdow-section">
+ <HeaderBar :input-id="inputId"
+ :readable="propertyReadable" />
+
+ <NcSelect :aria-label-listbox="t('settings', 'Day to use as the first day of week')"
+ class="fdow-section__day-select"
+ :clearable="false"
+ :input-id="inputId"
+ label="label"
+ label-outside
+ :options="dayOptions"
+ :value="valueOption"
+ @option:selected="updateFirstDayOfWeek" />
+ </section>
+</template>
+
+<script lang="ts">
+import HeaderBar from './shared/HeaderBar.vue'
+import NcSelect from '@nextcloud/vue/components/NcSelect'
+import {
+ ACCOUNT_SETTING_PROPERTY_ENUM,
+ ACCOUNT_SETTING_PROPERTY_READABLE_ENUM,
+} from '../../constants/AccountPropertyConstants'
+import { getDayNames, getFirstDay } from '@nextcloud/l10n'
+import { savePrimaryAccountProperty } from '../../service/PersonalInfo/PersonalInfoService'
+import { handleError } from '../../utils/handlers.ts'
+import { loadState } from '@nextcloud/initial-state'
+
+interface DayOption {
+ value: number,
+ label: string,
+}
+
+const { firstDayOfWeek } = loadState<{firstDayOfWeek?: string}>(
+ 'settings',
+ 'personalInfoParameters',
+ {},
+)
+
+export default {
+ name: 'FirstDayOfWeekSection',
+ components: {
+ HeaderBar,
+ NcSelect,
+ },
+ data() {
+ let firstDay = -1
+ if (firstDayOfWeek) {
+ firstDay = parseInt(firstDayOfWeek)
+ }
+
+ return {
+ firstDay,
+ }
+ },
+ computed: {
+ inputId(): string {
+ return 'account-property-fdow'
+ },
+ propertyReadable(): string {
+ return ACCOUNT_SETTING_PROPERTY_READABLE_ENUM.FIRST_DAY_OF_WEEK
+ },
+ dayOptions(): DayOption[] {
+ const options = [{
+ value: -1,
+ label: t('settings', 'Derived from your locale ({weekDayName})', {
+ weekDayName: getDayNames()[getFirstDay()],
+ }),
+ }]
+ for (const [index, dayName] of getDayNames().entries()) {
+ options.push({ value: index, label: dayName })
+ }
+ return options
+ },
+ valueOption(): DayOption | undefined {
+ return this.dayOptions.find((option) => option.value === this.firstDay)
+ },
+ },
+ methods: {
+ async updateFirstDayOfWeek(option: DayOption): Promise<void> {
+ try {
+ const responseData = await savePrimaryAccountProperty(
+ ACCOUNT_SETTING_PROPERTY_ENUM.FIRST_DAY_OF_WEEK,
+ option.value.toString(),
+ )
+ this.handleResponse({
+ value: option.value,
+ status: responseData.ocs?.meta?.status,
+ })
+ window.location.reload()
+ } catch (e) {
+ this.handleResponse({
+ errorMessage: t('settings', 'Unable to update first day of week'),
+ error: e,
+ })
+ }
+ },
+
+ handleResponse({ value, status, errorMessage, error }): void {
+ if (status === 'ok') {
+ this.firstDay = value
+ } else {
+ this.$emit('update:value', this.firstDay)
+ handleError(error, errorMessage)
+ }
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.fdow-section {
+ padding: 10px;
+
+ &__day-select {
+ width: 100%;
+ margin-top: 6px; // align with other inputs
+ }
+}
+</style>
diff --git a/apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue b/apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue
index 3e5b9b67bf5..8f42b2771c0 100644
--- a/apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue
+++ b/apps/settings/src/components/PersonalInfo/LanguageSection/Language.vue
@@ -29,7 +29,7 @@ import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/Person
import { validateLanguage } from '../../../utils/validate.js'
import { handleError } from '../../../utils/handlers.ts'
-import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
+import NcSelect from '@nextcloud/vue/components/NcSelect'
export default {
name: 'Language',
diff --git a/apps/settings/src/components/PersonalInfo/LocaleSection/Locale.vue b/apps/settings/src/components/PersonalInfo/LocaleSection/Locale.vue
index 311aa697adb..73300756472 100644
--- a/apps/settings/src/components/PersonalInfo/LocaleSection/Locale.vue
+++ b/apps/settings/src/components/PersonalInfo/LocaleSection/Locale.vue
@@ -32,7 +32,7 @@
<script>
import moment from '@nextcloud/moment'
-import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
+import NcSelect from '@nextcloud/vue/components/NcSelect'
import MapClock from 'vue-material-design-icons/MapClock.vue'
import { ACCOUNT_SETTING_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants.js'
diff --git a/apps/settings/src/components/PersonalInfo/PhoneSection.vue b/apps/settings/src/components/PersonalInfo/PhoneSection.vue
index 13ac7fdca0f..8ddeada960e 100644
--- a/apps/settings/src/components/PersonalInfo/PhoneSection.vue
+++ b/apps/settings/src/components/PersonalInfo/PhoneSection.vue
@@ -39,6 +39,10 @@ export default {
methods: {
onValidate(value) {
+ if (value === '') {
+ return true
+ }
+
if (defaultPhoneRegion) {
return isValidPhoneNumber(value, defaultPhoneRegion)
}
diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/EditProfileAnchorLink.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/EditProfileAnchorLink.vue
index 4514f11f6d8..3deb5340751 100644
--- a/apps/settings/src/components/PersonalInfo/ProfileSection/EditProfileAnchorLink.vue
+++ b/apps/settings/src/components/PersonalInfo/ProfileSection/EditProfileAnchorLink.vue
@@ -66,7 +66,7 @@ a {
display: inline-block;
vertical-align: middle;
margin-top: 6px;
- margin-right: 8px;
+ margin-inline-end: 8px;
}
&:hover,
diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue
index b9d8b1276eb..6eb7cf8c34c 100644
--- a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue
+++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue
@@ -19,7 +19,7 @@ import { emit } from '@nextcloud/event-bus'
import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService.js'
import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants.js'
-import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
+import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import { handleError } from '../../../utils/handlers.ts'
export default {
diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfilePreviewCard.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfilePreviewCard.vue
index 317544ecbc3..47894f64f34 100644
--- a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfilePreviewCard.vue
+++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfilePreviewCard.vue
@@ -27,7 +27,7 @@
import { getCurrentUser } from '@nextcloud/auth'
import { generateUrl } from '@nextcloud/router'
-import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
+import NcAvatar from '@nextcloud/vue/components/NcAvatar'
export default {
name: 'ProfilePreviewCard',
@@ -104,7 +104,7 @@ export default {
box-shadow: 0 0 3px var(--color-box-shadow);
& *,
- &::v-deep * {
+ &:deep(*) {
cursor: default;
}
}
@@ -113,7 +113,7 @@ export default {
// Override Avatar component position to fix positioning on rerender
position: absolute !important;
top: 40px;
- left: 18px;
+ inset-inline-start: 18px;
z-index: 1;
&:not(.avatardiv--unknown) {
@@ -128,7 +128,7 @@ export default {
span {
position: absolute;
- left: 78px;
+ inset-inline-start: 78px;
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: anywhere;
@@ -151,7 +151,8 @@ export default {
color: var(--color-primary-element-text);
font-size: 18px;
font-weight: bold;
- margin: 0 4px 8px 0;
+ margin-block: 0 8px;
+ margin-inline: 0 4px;
}
}
@@ -163,7 +164,8 @@ export default {
color: var(--color-text-maxcontrast);
font-size: 14px;
font-weight: normal;
- margin: 4px 4px 0 0;
+ margin-block: 4px 0;
+ margin-inline: 0 4px;
line-height: 1.3;
}
}
diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue
index f5f71eed075..22c03f72697 100644
--- a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue
+++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue
@@ -82,7 +82,7 @@ export default {
section {
padding: 10px 10px;
- &::v-deep button:disabled {
+ &:deep(button:disabled) {
cursor: default;
}
}
diff --git a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue
index a780883eade..8acec883842 100644
--- a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue
+++ b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue
@@ -118,7 +118,7 @@ section {
pointer-events: none;
& *,
- &::v-deep * {
+ &:deep(*) {
cursor: default;
pointer-events: none;
}
diff --git a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue
index 51c0203eb7f..aaa13e63e92 100644
--- a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue
+++ b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue
@@ -23,7 +23,7 @@
import { loadState } from '@nextcloud/initial-state'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
-import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
+import NcSelect from '@nextcloud/vue/components/NcSelect'
import { saveProfileParameterVisibility } from '../../../service/ProfileService.js'
import { VISIBILITY_PROPERTY_ENUM } from '../../../constants/ProfileConstants.js'
@@ -142,7 +142,7 @@ export default {
pointer-events: none;
& *,
- &::v-deep * {
+ &:deep(*) {
cursor: default;
pointer-events: none;
}
diff --git a/apps/settings/src/components/PersonalInfo/PronounsSection.vue b/apps/settings/src/components/PersonalInfo/PronounsSection.vue
new file mode 100644
index 00000000000..e345cb8e225
--- /dev/null
+++ b/apps/settings/src/components/PersonalInfo/PronounsSection.vue
@@ -0,0 +1,47 @@
+<!--
+ - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<template>
+ <AccountPropertySection v-bind.sync="pronouns"
+ :placeholder="randomPronounsPlaceholder" />
+</template>
+
+<script lang="ts">
+import type { IAccountProperty } from '../../constants/AccountPropertyConstants.ts'
+
+import { loadState } from '@nextcloud/initial-state'
+import { t } from '@nextcloud/l10n'
+import { defineComponent } from 'vue'
+import AccountPropertySection from './shared/AccountPropertySection.vue'
+import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.ts'
+
+const { pronouns } = loadState<{ pronouns: IAccountProperty }>('settings', 'personalInfoParameters')
+
+export default defineComponent({
+ name: 'PronounsSection',
+
+ components: {
+ AccountPropertySection,
+ },
+
+ data() {
+ return {
+ pronouns: { ...pronouns, readable: NAME_READABLE_ENUM[pronouns.name] },
+ }
+ },
+
+ computed: {
+ randomPronounsPlaceholder() {
+ const pronouns = [
+ t('settings', 'she/her'),
+ t('settings', 'he/him'),
+ t('settings', 'they/them'),
+ ]
+ const pronounsExample = pronouns[Math.floor(Math.random() * pronouns.length)]
+ return t('settings', 'Your pronouns. E.g. {pronounsExample}', { pronounsExample })
+ },
+ },
+})
+</script>
diff --git a/apps/settings/src/components/PersonalInfo/TwitterSection.vue b/apps/settings/src/components/PersonalInfo/TwitterSection.vue
index bb809c8d2b7..43d08f81e3f 100644
--- a/apps/settings/src/components/PersonalInfo/TwitterSection.vue
+++ b/apps/settings/src/components/PersonalInfo/TwitterSection.vue
@@ -4,30 +4,31 @@
-->
<template>
- <AccountPropertySection v-bind.sync="twitter"
+ <AccountPropertySection v-bind.sync="value"
+ :readable="readable"
+ :on-validate="onValidate"
:placeholder="t('settings', 'Your X (formerly Twitter) handle')" />
</template>
-<script>
-import { loadState } from '@nextcloud/initial-state'
+<script setup lang="ts">
+import type { AccountProperties } from '../../constants/AccountPropertyConstants.js'
+import { loadState } from '@nextcloud/initial-state'
+import { t } from '@nextcloud/l10n'
+import { ref } from 'vue'
+import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.ts'
import AccountPropertySection from './shared/AccountPropertySection.vue'
-import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
-
-const { twitter } = loadState('settings', 'personalInfoParameters', {})
-
-export default {
- name: 'TwitterSection',
+const { twitter } = loadState<AccountProperties>('settings', 'personalInfoParameters')
- components: {
- AccountPropertySection,
- },
+const value = ref({ ...twitter })
+const readable = NAME_READABLE_ENUM[twitter.name]
- data() {
- return {
- twitter: { ...twitter, readable: NAME_READABLE_ENUM[twitter.name] },
- }
- },
+/**
+ * Validate that the text might be a twitter handle
+ * @param text The potential twitter handle
+ */
+function onValidate(text: string): boolean {
+ return text === '' || text.match(/^@?([a-zA-Z0-9_]{2,15})$/) !== null
}
</script>
diff --git a/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue b/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
index fc7fe7f7984..d039641ec72 100644
--- a/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
+++ b/apps/settings/src/components/PersonalInfo/shared/AccountPropertySection.vue
@@ -46,8 +46,8 @@
<script>
import debounce from 'debounce'
-import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
-import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'
+import NcInputField from '@nextcloud/vue/components/NcInputField'
+import NcTextArea from '@nextcloud/vue/components/NcTextArea'
import HeaderBar from './HeaderBar.vue'
@@ -155,6 +155,7 @@ export default {
methods: {
async updateProperty(value) {
try {
+ this.hasError = false
const responseData = await savePrimaryAccountProperty(
this.name,
value,
@@ -180,10 +181,8 @@ export default {
this.isSuccess = true
setTimeout(() => { this.isSuccess = false }, 2000)
} else {
- this.$emit('update:value', this.initialValue)
handleError(error, errorMessage)
this.hasError = true
- setTimeout(() => { this.hasError = false }, 2000)
}
},
},
@@ -207,7 +206,7 @@ section {
display: flex;
gap: 0 2px;
- margin-right: 5px;
+ margin-inline-end: 5px;
margin-bottom: 5px;
}
}
@@ -218,7 +217,7 @@ section {
align-items: center;
&__icon {
- margin-right: 8px;
+ margin-inline-end: 8px;
align-self: start;
margin-top: 4px;
}
diff --git a/apps/settings/src/components/PersonalInfo/shared/FederationControl.vue b/apps/settings/src/components/PersonalInfo/shared/FederationControl.vue
index dd4eb2b5181..e55a50056d3 100644
--- a/apps/settings/src/components/PersonalInfo/shared/FederationControl.vue
+++ b/apps/settings/src/components/PersonalInfo/shared/FederationControl.vue
@@ -30,9 +30,9 @@
</template>
<script>
-import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
-import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
-import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
+import NcActions from '@nextcloud/vue/components/NcActions'
+import NcActionButton from '@nextcloud/vue/components/NcActionButton'
+import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import { loadState } from '@nextcloud/initial-state'
import {
@@ -146,7 +146,7 @@ export default {
}
// TODO: provide focus method from NcActions
- this.$refs.federationActions.$refs.menuButton.$el.focus()
+ this.$refs.federationActions.$refs?.triggerButton?.$el?.focus?.()
},
async updatePrimaryScope(scope) {
diff --git a/apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue b/apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue
index ca41db2a454..7c95c2b8f4c 100644
--- a/apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue
+++ b/apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue
@@ -5,7 +5,7 @@
<template>
<div class="headerbar-label" :class="{ 'setting-property': isSettingProperty, 'profile-property': isProfileProperty }">
- <h3 v-if="isHeading">
+ <h3 v-if="isHeading" class="headerbar__heading">
<!-- Already translated as required by prop validator -->
{{ readable }}
</h3>
@@ -36,7 +36,7 @@
</template>
<script>
-import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcButton from '@nextcloud/vue/components/NcButton'
import Plus from 'vue-material-design-icons/Plus.vue'
import FederationControl from './FederationControl.vue'
@@ -130,7 +130,7 @@ export default {
}
&.setting-property {
- height: 44px;
+ height: 34px;
}
label {
@@ -138,11 +138,16 @@ export default {
}
}
+ .headerbar__heading {
+ margin: 0;
+ }
+
.federation-control {
margin: 0;
}
.button-vue {
- margin: 0 0 0 auto !important;
+ margin: 0 !important;
+ margin-inline-start: auto !important;
}
</style>