diff options
Diffstat (limited to 'apps/settings/src')
4 files changed, 73 insertions, 1 deletions
diff --git a/apps/settings/src/components/AdminSettingsSharingForm.vue b/apps/settings/src/components/AdminSettingsSharingForm.vue index c582e9febee..b0e142d8480 100644 --- a/apps/settings/src/components/AdminSettingsSharingForm.vue +++ b/apps/settings/src/components/AdminSettingsSharingForm.vue @@ -164,7 +164,7 @@ </NcCheckboxRadioSwitch> <fieldset v-show="settings.allowLinks && settings.defaultExpireDate" id="settings-sharing-api-api-expiration" class="sharing__sub-section"> <NcCheckboxRadioSwitch :checked.sync="settings.enforceExpireDate"> - {{ t('settings', 'Enforce expiration date for remote shares') }} + {{ t('settings', 'Enforce expiration date for link or mail shares') }} </NcCheckboxRadioSwitch> <NcTextField type="number" class="sharing__input" 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/constants/AccountPropertyConstants.ts b/apps/settings/src/constants/AccountPropertyConstants.ts index 455c210976f..575a2744cc6 100644 --- a/apps/settings/src/constants/AccountPropertyConstants.ts +++ b/apps/settings/src/constants/AccountPropertyConstants.ts @@ -28,6 +28,7 @@ export const ACCOUNT_PROPERTY_ENUM = Object.freeze({ PRONOUNS: 'pronouns', ROLE: 'role', TWITTER: 'twitter', + BLUESKY: 'bluesky', WEBSITE: 'website', }) @@ -48,6 +49,7 @@ export const ACCOUNT_PROPERTY_READABLE_ENUM = Object.freeze({ PRONOUNS: t('settings', 'Pronouns'), ROLE: t('settings', 'Role'), TWITTER: t('settings', 'X (formerly Twitter)'), + BLUESKY: t('settings', 'Bluesky'), WEBSITE: t('settings', 'Website'), }) @@ -64,6 +66,7 @@ export const NAME_READABLE_ENUM = Object.freeze({ [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.BLUESKY]: ACCOUNT_PROPERTY_READABLE_ENUM.BLUESKY, [ACCOUNT_PROPERTY_ENUM.FEDIVERSE]: ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE, [ACCOUNT_PROPERTY_ENUM.WEBSITE]: ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE, [ACCOUNT_PROPERTY_ENUM.BIRTHDATE]: ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE, @@ -89,6 +92,7 @@ export const PROPERTY_READABLE_KEYS_ENUM = Object.freeze({ [ACCOUNT_PROPERTY_READABLE_ENUM.PROFILE_ENABLED]: ACCOUNT_PROPERTY_ENUM.PROFILE_ENABLED, [ACCOUNT_PROPERTY_READABLE_ENUM.ROLE]: ACCOUNT_PROPERTY_ENUM.ROLE, [ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER]: ACCOUNT_PROPERTY_ENUM.TWITTER, + [ACCOUNT_PROPERTY_READABLE_ENUM.BLUESKY]: ACCOUNT_PROPERTY_ENUM.BLUESKY, [ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE]: ACCOUNT_PROPERTY_ENUM.FEDIVERSE, [ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: ACCOUNT_PROPERTY_ENUM.WEBSITE, [ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE]: ACCOUNT_PROPERTY_ENUM.BIRTHDATE, @@ -135,6 +139,7 @@ export const PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM = Object.freeze({ [ACCOUNT_PROPERTY_READABLE_ENUM.PROFILE_ENABLED]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], [ACCOUNT_PROPERTY_READABLE_ENUM.ROLE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], [ACCOUNT_PROPERTY_READABLE_ENUM.TWITTER]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], + [ACCOUNT_PROPERTY_READABLE_ENUM.BLUESKY]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], [ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], [ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], [ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE], diff --git a/apps/settings/src/main-personal-info.js b/apps/settings/src/main-personal-info.js index c28f14ee477..5ccfc9848c0 100644 --- a/apps/settings/src/main-personal-info.js +++ b/apps/settings/src/main-personal-info.js @@ -27,6 +27,7 @@ import ProfileVisibilitySection from './components/PersonalInfo/ProfileVisibilit import PronounsSection from './components/PersonalInfo/PronounsSection.vue' import RoleSection from './components/PersonalInfo/RoleSection.vue' import TwitterSection from './components/PersonalInfo/TwitterSection.vue' +import BlueskySection from './components/PersonalInfo/BlueskySection.vue' import WebsiteSection from './components/PersonalInfo/WebsiteSection.vue' __webpack_nonce__ = getCSPNonce() @@ -52,6 +53,7 @@ const LocationView = Vue.extend(LocationSection) const PhoneView = Vue.extend(PhoneSection) const PronounsView = Vue.extend(PronounsSection) const TwitterView = Vue.extend(TwitterSection) +const BlueskyView = Vue.extend(BlueskySection) const WebsiteView = Vue.extend(WebsiteSection) new AvatarView().$mount('#vue-avatar-section') @@ -62,6 +64,7 @@ new PhoneView().$mount('#vue-phone-section') new LocationView().$mount('#vue-location-section') new WebsiteView().$mount('#vue-website-section') new TwitterView().$mount('#vue-twitter-section') +new BlueskyView().$mount('#vue-bluesky-section') new FediverseView().$mount('#vue-fediverse-section') new LanguageView().$mount('#vue-language-section') new LocaleView().$mount('#vue-locale-section') |