diff options
Diffstat (limited to 'apps/settings/src')
6 files changed, 93 insertions, 9 deletions
diff --git a/apps/settings/src/components/AdminAI.vue b/apps/settings/src/components/AdminAI.vue index 044ebd9183e..0d3e9154bb9 100644 --- a/apps/settings/src/components/AdminAI.vue +++ b/apps/settings/src/components/AdminAI.vue @@ -11,16 +11,17 @@ @update:modelValue="saveChanges"> {{ t('settings', 'Allow AI usage for guest users') }} </NcCheckboxRadioSwitch> + <h3>{{ t('settings', 'Provider for Task types') }}</h3> <template v-for="type in taskProcessingTaskTypes"> - <div :key="type"> - <h3>{{ t('settings', 'Task:') }} {{ type.name }}</h3> - <p>{{ type.description }}</p> + <div :key="type" class="tasktype-item"> + <p class="tasktype-name"> + {{ type.name }} + </p> <NcCheckboxRadioSwitch v-model="settings['ai.taskprocessing_type_preferences'][type.id]" type="switch" @update:modelValue="saveChanges"> {{ t('settings', 'Enable') }} - </NcCheckboxRadioSwitch> - <NcSelect v-model="settings['ai.taskprocessing_provider_preferences'][type.id]" + </NcCheckboxRadioSwitch><NcSelect v-model="settings['ai.taskprocessing_provider_preferences'][type.id]" class="provider-select" :clearable="false" :disabled="!settings['ai.taskprocessing_type_preferences'][type.id]" @@ -33,7 +34,6 @@ {{ taskProcessingProviders.find(p => p.id === label)?.name }} </template> </NcSelect> - <p> </p> </div> </template> <template v-if="!hasTaskProcessing"> @@ -244,4 +244,14 @@ export default { .provider-select { min-width: 350px !important; } + +.tasktype-item { + display: flex; + align-items: center; + gap: 8px; + .tasktype-name { + flex: 1; + margin: 0; + } +} </style> 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/constants/AppstoreCategoryIcons.ts b/apps/settings/src/constants/AppstoreCategoryIcons.ts index 24bb0faea6d..989ffe79c22 100644 --- a/apps/settings/src/constants/AppstoreCategoryIcons.ts +++ b/apps/settings/src/constants/AppstoreCategoryIcons.ts @@ -3,14 +3,15 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import { - mdiAccountOutline, mdiAccountMultipleOutline, + mdiAccountOutline, mdiArchiveOutline, mdiCheck, mdiClipboardFlowOutline, mdiClose, mdiCogOutline, mdiControllerClassicOutline, + mdiCreationOutline, mdiDownload, mdiFileDocumentEdit, mdiFolder, @@ -42,7 +43,8 @@ export default Object.freeze({ featured: mdiStar, updates: mdiDownload, - // generic categories + // generic category + ai: mdiCreationOutline, auth: mdiKeyOutline, customization: mdiCogOutline, dashboard: mdiViewColumnOutline, 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') |