diff options
Diffstat (limited to 'apps/dav/src')
20 files changed, 910 insertions, 306 deletions
diff --git a/apps/dav/src/components/AbsenceForm.vue b/apps/dav/src/components/AbsenceForm.vue index f43fb889b9e..5350c04a565 100644 --- a/apps/dav/src/components/AbsenceForm.vue +++ b/apps/dav/src/components/AbsenceForm.vue @@ -1,24 +1,7 @@ <!-- - - @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud> - - - - @author Richard Steinmetz <richard@steinmetz.cloud> - - - - @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 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 General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - - --> + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <form class="absence" @submit.prevent="saveForm"> @@ -34,6 +17,20 @@ class="absence__dates__picker" :required="true" /> </div> + <label for="replacement-search-input">{{ $t('dav', 'Out of office replacement (optional)') }}</label> + <NcSelect ref="select" + v-model="replacementUser" + input-id="replacement-search-input" + :loading="searchLoading" + :placeholder="$t('dav', 'Name of the replacement')" + :clear-search-on-blur="() => false" + :user-select="true" + :options="options" + @search="asyncFind"> + <template #no-options="{ search }"> + {{ search ?$t('dav', 'No results.') : $t('dav', 'Start typing.') }} + </template> + </NcSelect> <NcTextField :value.sync="status" :label="$t('dav', 'Short absence status')" :required="true" /> <NcTextArea :value.sync="message" :label="$t('dav', 'Long absence Message')" :required="true" /> @@ -53,19 +50,22 @@ </template> <script> -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' -import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js' -import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js' -import { generateOcsUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' -import axios from '@nextcloud/axios' -import { formatDateAsYMD } from '../utils/date.js' -import { loadState } from '@nextcloud/initial-state' import { showError, showSuccess } from '@nextcloud/dialogs' - +import { loadState } from '@nextcloud/initial-state' +import { generateOcsUrl } from '@nextcloud/router' +import { ShareType } from '@nextcloud/sharing' +import { formatDateAsYMD } from '../utils/date.js' +import axios from '@nextcloud/axios' +import debounce from 'debounce' import logger from '../service/logger.js' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcTextField from '@nextcloud/vue/components/NcTextField' +import NcTextArea from '@nextcloud/vue/components/NcTextArea' +import NcSelect from '@nextcloud/vue/components/NcSelect' +import NcDateTimePickerNative from '@nextcloud/vue/components/NcDateTimePickerNative' + export default { name: 'AbsenceForm', components: { @@ -73,16 +73,20 @@ export default { NcTextField, NcTextArea, NcDateTimePickerNative, + NcSelect, }, data() { - const { firstDay, lastDay, status, message } = loadState('dav', 'absence', {}) - + const { firstDay, lastDay, status, message, replacementUserId, replacementUserDisplayName } = loadState('dav', 'absence', {}) return { loading: false, status: status ?? '', message: message ?? '', firstDay: firstDay ? new Date(firstDay) : new Date(), lastDay: lastDay ? new Date(lastDay) : null, + replacementUserId, + replacementUser: replacementUserId ? { user: replacementUserId, displayName: replacementUserDisplayName } : null, + searchLoading: false, + options: [], } }, computed: { @@ -110,6 +114,99 @@ export default { this.firstDay = new Date() this.lastDay = null }, + + /** + * Format shares for the multiselect options + * + * @param {object} result select entry item + * @return {object} + */ + formatForMultiselect(result) { + return { + user: result.uuid || result.value.shareWith, + displayName: result.name || result.label, + subtitle: result.dsc | '', + } + }, + + async asyncFind(query) { + this.searchLoading = true + await this.debounceGetSuggestions(query.trim()) + }, + /** + * Get suggestions + * + * @param {string} search the search query + */ + async getSuggestions(search) { + + const shareType = [ + ShareType.User, + ] + + let request = null + try { + request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees'), { + params: { + format: 'json', + itemType: 'file', + search, + shareType, + }, + }) + } catch (error) { + console.error('Error fetching suggestions', error) + return + } + + const data = request.data.ocs.data + const exact = request.data.ocs.data.exact + data.exact = [] // removing exact from general results + const rawExactSuggestions = exact.users + const rawSuggestions = data.users + console.info('rawExactSuggestions', rawExactSuggestions) + console.info('rawSuggestions', rawSuggestions) + // remove invalid data and format to user-select layout + const exactSuggestions = rawExactSuggestions + .map(share => this.formatForMultiselect(share)) + const suggestions = rawSuggestions + .map(share => this.formatForMultiselect(share)) + + const allSuggestions = exactSuggestions.concat(suggestions) + + // Count occurrences of display names in order to provide a distinguishable description if needed + const nameCounts = allSuggestions.reduce((nameCounts, result) => { + if (!result.displayName) { + return nameCounts + } + if (!nameCounts[result.displayName]) { + nameCounts[result.displayName] = 0 + } + nameCounts[result.displayName]++ + return nameCounts + }, {}) + + this.options = allSuggestions.map(item => { + // Make sure that items with duplicate displayName get the shareWith applied as a description + if (nameCounts[item.displayName] > 1 && !item.desc) { + return { ...item, desc: item.shareWithDisplayNameUnique } + } + return item + }) + + this.searchLoading = false + console.info('suggestions', this.options) + }, + + /** + * Debounce getSuggestions + * + * @param {...*} args the arguments + */ + debounceGetSuggestions: debounce(function(...args) { + this.getSuggestions(...args) + }, 300), + async saveForm() { if (!this.valid) { return @@ -122,6 +219,7 @@ export default { lastDay: formatDateAsYMD(this.lastDay), status: this.status, message: this.message, + replacementUserId: this.replacementUser?.user ?? null, }) showSuccess(this.$t('dav', 'Absence saved')) } catch (error) { @@ -162,7 +260,7 @@ export default { &__picker { flex: 1 auto; - ::v-deep .native-datetime-picker--input { + :deep(.native-datetime-picker--input) { margin-bottom: 0; } } diff --git a/apps/dav/src/components/AvailabilityForm.vue b/apps/dav/src/components/AvailabilityForm.vue index d2501482673..d53c092be9d 100644 --- a/apps/dav/src/components/AvailabilityForm.vue +++ b/apps/dav/src/components/AvailabilityForm.vue @@ -1,3 +1,7 @@ +<!-- + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <div> <div class="time-zone"> @@ -15,6 +19,7 @@ :l10n-delete-slot="$t('dav', 'Delete slot')" :l10n-empty-day="$t('dav', 'No working hours set')" :l10n-add-slot="$t('dav', 'Add slot')" + :l10n-week-day-list-label="$t('dav', 'Weekdays')" :l10n-monday="$t('dav', 'Monday')" :l10n-tuesday="$t('dav', 'Tuesday')" :l10n-wednesday="$t('dav', 'Wednesday')" @@ -53,9 +58,9 @@ import { enableUserStatusAutomation, disableUserStatusAutomation, } from '../service/PreferenceService.js' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' -import NcTimezonePicker from '@nextcloud/vue/dist/Components/NcTimezonePicker.js' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' +import NcTimezonePicker from '@nextcloud/vue/components/NcTimezonePicker' export default { name: 'AvailabilityForm', @@ -134,41 +139,50 @@ export default { padding: 0 10px 0 10px; position: absolute; } + :deep(.availability-slots) { display: flex; white-space: normal; } + :deep(.availability-slot) { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; } + :deep(.availability-slot-group) { display: flex; flex-direction: column; } + :deep(.mx-input-wrapper) { width: 85px; } + :deep(.mx-datepicker) { width: 97px; } + :deep(.multiselect) { border: 1px solid var(--color-border-dark); width: 120px; } + .time-zone { - padding: 32px 12px 12px 0; - display: flex; - flex-wrap: wrap; + padding-block: 32px 12px; + padding-inline: 0 12px; + display: flex; + flex-wrap: wrap; &__heading { - margin-right: calc(var(--default-grid-baseline) * 2); + margin-inline-end: calc(var(--default-grid-baseline) * 2); line-height: var(--default-clickable-area); font-weight: bold; } } + .grid-table { display: grid; margin-bottom: 32px; @@ -177,9 +191,11 @@ export default { grid-template-columns: min-content auto min-content; max-width: 500px; } + .button { align-self: flex-end; } + :deep(.label-weekday) { position: relative; display: inline-flex; @@ -196,12 +212,12 @@ export default { } .to-text { - padding-right: 12px; + padding-inline-end: 12px; } .empty-content { color: var(--color-text-lighter); - margin-top: 4px; + margin-block-start: var(--default-grid-baseline); align-self: center; } </style> diff --git a/apps/dav/src/components/ExampleContactSettings.vue b/apps/dav/src/components/ExampleContactSettings.vue new file mode 100644 index 00000000000..cdfdc130189 --- /dev/null +++ b/apps/dav/src/components/ExampleContactSettings.vue @@ -0,0 +1,172 @@ +<!-- + - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <div class="example-contact-settings"> + <NcCheckboxRadioSwitch :checked="enableDefaultContact" + type="switch" + @update:model-value="updateEnableDefaultContact"> + {{ $t('dav', "Add example contact to user's address book when they first log in") }} + </NcCheckboxRadioSwitch> + <div v-if="enableDefaultContact" class="example-contact-settings__buttons"> + <ExampleContentDownloadButton :href="downloadUrl"> + <template #icon> + <IconAccount :size="20" /> + </template> + example_contact.vcf + </ExampleContentDownloadButton> + <NcButton type="secondary" + @click="toggleModal"> + <template #icon> + <IconUpload :size="20" /> + </template> + {{ $t('dav', 'Import contact') }} + </NcButton> + <NcButton v-if="hasCustomDefaultContact" + type="tertiary" + @click="resetContact"> + <template #icon> + <IconRestore :size="20" /> + </template> + {{ $t('dav', 'Reset to default') }} + </NcButton> + </div> + <NcDialog :open.sync="isModalOpen" + :name="$t('dav', 'Import contacts')" + :buttons="buttons"> + <div> + <p>{{ $t('dav', 'Importing a new .vcf file will delete the existing default contact and replace it with the new one. Do you want to continue?') }}</p> + </div> + </NcDialog> + <input id="example-contact-import" + ref="exampleContactImportInput" + :disabled="loading" + type="file" + accept=".vcf" + class="hidden-visually" + @change="processFile"> + </div> +</template> +<script> +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +import { loadState } from '@nextcloud/initial-state' +import { NcDialog, NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue' +import { showError, showSuccess } from '@nextcloud/dialogs' +import IconUpload from 'vue-material-design-icons/Upload.vue' +import IconRestore from 'vue-material-design-icons/Restore.vue' +import IconAccount from 'vue-material-design-icons/Account.vue' +import IconCancel from '@mdi/svg/svg/cancel.svg?raw' +import IconCheck from '@mdi/svg/svg/check.svg?raw' +import logger from '../service/logger.js' +import ExampleContentDownloadButton from './ExampleContentDownloadButton.vue' + +const enableDefaultContact = loadState('dav', 'enableDefaultContact') +const hasCustomDefaultContact = loadState('dav', 'hasCustomDefaultContact') + +export default { + name: 'ExampleContactSettings', + components: { + NcDialog, + NcButton, + NcCheckboxRadioSwitch, + IconUpload, + IconRestore, + IconAccount, + ExampleContentDownloadButton, + }, + data() { + return { + enableDefaultContact, + hasCustomDefaultContact, + isModalOpen: false, + loading: false, + buttons: [ + { + label: this.$t('dav', 'Cancel'), + icon: IconCancel, + callback: () => { this.isModalOpen = false }, + }, + { + label: this.$t('dav', 'Import'), + type: 'primary', + icon: IconCheck, + callback: () => { this.clickImportInput() }, + }, + ], + } + }, + computed: { + downloadUrl() { + return generateUrl('/apps/dav/api/defaultcontact/contact') + }, + }, + methods: { + updateEnableDefaultContact() { + axios.put(generateUrl('apps/dav/api/defaultcontact/config'), { + allow: !this.enableDefaultContact, + }).then(() => { + this.enableDefaultContact = !this.enableDefaultContact + }).catch(() => { + showError(this.$t('dav', 'Error while saving settings')) + }) + }, + toggleModal() { + this.isModalOpen = !this.isModalOpen + }, + clickImportInput() { + this.$refs.exampleContactImportInput.click() + }, + resetContact() { + this.loading = true + axios.put(generateUrl('/apps/dav/api/defaultcontact/contact')) + .then(() => { + this.hasCustomDefaultContact = false + showSuccess(this.$t('dav', 'Contact reset successfully')) + }) + .catch((error) => { + logger.error('Error importing contact:', { error }) + showError(this.$t('dav', 'Error while resetting contact')) + }) + .finally(() => { + this.loading = false + }) + }, + processFile(event) { + this.loading = true + + const file = event.target.files[0] + const reader = new FileReader() + + reader.onload = async () => { + this.isModalOpen = false + try { + await axios.put(generateUrl('/apps/dav/api/defaultcontact/contact'), { contactData: reader.result }) + this.hasCustomDefaultContact = true + showSuccess(this.$t('dav', 'Contact imported successfully')) + } catch (error) { + logger.error('Error importing contact:', { error }) + showError(this.$t('dav', 'Error while importing contact')) + } finally { + this.loading = false + event.target.value = '' + } + } + reader.readAsText(file) + }, + }, +} +</script> +<style lang="scss" scoped> +.example-contact-settings { + margin-block-start: 2rem; + + &__buttons { + display: flex; + gap: calc(var(--default-grid-baseline) * 2); + margin-top: calc(var(--default-grid-baseline) * 2); + } +} +</style> diff --git a/apps/dav/src/components/ExampleContentDownloadButton.vue b/apps/dav/src/components/ExampleContentDownloadButton.vue new file mode 100644 index 00000000000..6ee13e057bd --- /dev/null +++ b/apps/dav/src/components/ExampleContentDownloadButton.vue @@ -0,0 +1,57 @@ +<!-- + - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later + --> + +<template> + <NcButton type="tertiary" :href="href"> + <template #icon> + <slot name="icon" /> + </template> + <div class="download-button"> + <span class="download-button__label"> + <slot name="default" /> + </span> + <IconDownload class="download-button__icon" + :size="20" /> + </div> + </NcButton> +</template> + +<script> +import { NcButton } from '@nextcloud/vue' +import IconDownload from 'vue-material-design-icons/Download.vue' + +export default { + name: 'ExampleContentDownloadButton', + components: { + NcButton, + IconDownload, + }, + props: { + href: { + type: String, + required: true, + }, + }, +} +</script> + +<style lang="scss" scoped> +.download-button { + display: flex; + max-width: 200px; + + &__label { + font-weight: initial; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + &__icon { + margin-top: 2px; + margin-inline-start: var(--default-grid-baseline); + } +} +</style> diff --git a/apps/dav/src/components/ExampleEventSettings.vue b/apps/dav/src/components/ExampleEventSettings.vue new file mode 100644 index 00000000000..5d2053def50 --- /dev/null +++ b/apps/dav/src/components/ExampleEventSettings.vue @@ -0,0 +1,217 @@ +<!-- + - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later + --> + +<template> + <div class="example-event-settings"> + <NcCheckboxRadioSwitch :checked="createExampleEvent" + :disabled="savingConfig" + type="switch" + @update:model-value="updateCreateExampleEvent"> + {{ t('dav', "Add example event to user's calendar when they first log in") }} + </NcCheckboxRadioSwitch> + <div v-if="createExampleEvent" + class="example-event-settings__buttons"> + <ExampleContentDownloadButton :href="downloadUrl"> + <template #icon> + <IconCalendarBlank :size="20" /> + </template> + example_event.ics + </ExampleContentDownloadButton> + <NcButton type="secondary" + @click="showImportModal = true"> + <template #icon> + <IconUpload :size="20" /> + </template> + {{ t('dav', 'Import calendar event') }} + </NcButton> + <NcButton v-if="hasCustomEvent" + type="tertiary" + :disabled="deleting" + @click="deleteCustomEvent"> + <template #icon> + <IconRestore :size="20" /> + </template> + {{ t('dav', 'Reset to default') }} + </NcButton> + </div> + <NcDialog :open.sync="showImportModal" + :name="t('dav', 'Import calendar event')"> + <div class="import-event-modal"> + <p> + {{ t('dav', 'Uploading a new event will overwrite the existing one.') }} + </p> + <input ref="event-file" + :disabled="uploading" + type="file" + accept=".ics,text/calendar" + class="import-event-modal__file-picker" + @change="selectFile"> + <div class="import-event-modal__buttons"> + <NcButton :disabled="uploading || !selectedFile" + type="primary" + @click="uploadCustomEvent()"> + <template #icon> + <IconUpload :size="20" /> + </template> + {{ t('dav', 'Upload event') }} + </NcButton> + </div> + </div> + </NcDialog> + </div> +</template> + +<script> +import { NcButton, NcCheckboxRadioSwitch, NcDialog } from '@nextcloud/vue' +import { loadState } from '@nextcloud/initial-state' +import IconCalendarBlank from 'vue-material-design-icons/CalendarBlank.vue' +import IconUpload from 'vue-material-design-icons/Upload.vue' +import IconRestore from 'vue-material-design-icons/Restore.vue' +import * as ExampleEventService from '../service/ExampleEventService.js' +import { showError, showSuccess } from '@nextcloud/dialogs' +import logger from '../service/logger.js' +import { generateUrl } from '@nextcloud/router' +import ExampleContentDownloadButton from './ExampleContentDownloadButton.vue' + +export default { + name: 'ExampleEventSettings', + components: { + NcButton, + NcCheckboxRadioSwitch, + NcDialog, + IconCalendarBlank, + IconUpload, + IconRestore, + ExampleContentDownloadButton, + }, + data() { + return { + createExampleEvent: loadState('dav', 'create_example_event', false), + hasCustomEvent: loadState('dav', 'has_custom_example_event', false), + showImportModal: false, + uploading: false, + deleting: false, + savingConfig: false, + selectedFile: undefined, + } + }, + computed: { + downloadUrl() { + return generateUrl('/apps/dav/api/exampleEvent/event') + }, + }, + methods: { + selectFile() { + this.selectedFile = this.$refs['event-file']?.files[0] + }, + async updateCreateExampleEvent() { + this.savingConfig = true + + const enable = !this.createExampleEvent + try { + await ExampleEventService.setCreateExampleEvent(enable) + } catch (error) { + showError(t('dav', 'Failed to save example event creation setting')) + logger.error('Failed to save example event creation setting', { + error, + enable, + }) + } finally { + this.savingConfig = false + } + + this.createExampleEvent = enable + }, + uploadCustomEvent() { + if (!this.selectedFile) { + return + } + + this.uploading = true + + const reader = new FileReader() + reader.addEventListener('load', async () => { + const ics = reader.result + + try { + await ExampleEventService.uploadExampleEvent(ics) + } catch (error) { + showError(t('dav', 'Failed to upload the example event')) + logger.error('Failed to upload example ICS', { + error, + ics, + }) + return + } finally { + this.uploading = false + } + + showSuccess(t('dav', 'Custom example event was saved successfully')) + this.showImportModal = false + this.hasCustomEvent = true + }) + reader.readAsText(this.selectedFile) + }, + async deleteCustomEvent() { + this.deleting = true + + try { + await ExampleEventService.deleteExampleEvent() + } catch (error) { + showError(t('dav', 'Failed to delete the custom example event')) + logger.error('Failed to delete the custom example event', { + error, + }) + return + } finally { + this.deleting = false + } + + showSuccess(t('dav', 'Custom example event was deleted successfully')) + this.hasCustomEvent = false + }, + }, +} +</script> + +<style lang="scss" scoped> +.example-event-settings { + margin-block: 2rem; + + &__buttons { + display: flex; + gap: calc(var(--default-grid-baseline) * 2); + margin-top: calc(var(--default-grid-baseline) * 2); + + &__download-link { + display: flex; + max-width: 100px; + + &__label { + text-decoration: underline; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } + } +} + +.import-event-modal { + display: flex; + flex-direction: column; + gap: calc(var(--default-grid-baseline) * 2); + padding: calc(var(--default-grid-baseline) * 2); + + &__file-picker { + width: 100%; + } + + &__buttons { + display: flex; + justify-content: flex-end; + } +} +</style> diff --git a/apps/dav/src/dav/client.js b/apps/dav/src/dav/client.js index b053e585ce8..d286f6f48d6 100644 --- a/apps/dav/src/dav/client.js +++ b/apps/dav/src/dav/client.js @@ -1,39 +1,31 @@ /** - * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @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/>. + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ -import * as webdav from 'webdav' -import axios from '@nextcloud/axios' +import { createClient } from 'webdav' import memoize from 'lodash/fp/memoize.js' import { generateRemoteUrl } from '@nextcloud/router' -import { getCurrentUser } from '@nextcloud/auth' +import { getCurrentUser, getRequestToken, onRequestTokenUpdate } from '@nextcloud/auth' export const getClient = memoize((service) => { - // Add this so the server knows it is an request from the browser - axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest' + // init webdav client + const remote = generateRemoteUrl(`dav/${service}/${getCurrentUser().uid}`) + const client = createClient(remote) - // force our axios - const patcher = webdav.getPatcher() - patcher.patch('request', axios) + // set CSRF token header + const setHeaders = (token) => { + client.setHeaders({ + // Add this so the server knows it is an request from the browser + 'X-Requested-With': 'XMLHttpRequest', + // Inject user auth + requesttoken: token ?? '', + }) + } - return webdav.createClient( - generateRemoteUrl(`dav/${service}/${getCurrentUser().uid}`) - ) + // refresh headers when request token changes + onRequestTokenUpdate(setHeaders) + setHeaders(getRequestToken()) + + return client }) diff --git a/apps/dav/src/service/CalendarService.js b/apps/dav/src/service/CalendarService.js index 46c92436d6b..93b36b8e74f 100644 --- a/apps/dav/src/service/CalendarService.js +++ b/apps/dav/src/service/CalendarService.js @@ -1,22 +1,6 @@ /** - * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @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/>. + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { getClient } from '../dav/client.js' import logger from './logger.js' @@ -58,7 +42,7 @@ export async function findScheduleInboxAvailability() { </x0:propfind>`, }) - const xml = await parseXML(response.data) + const xml = await parseXML(await response.text()) if (!xml) { return undefined diff --git a/apps/dav/src/service/ExampleEventService.js b/apps/dav/src/service/ExampleEventService.js new file mode 100644 index 00000000000..a39e3641bd9 --- /dev/null +++ b/apps/dav/src/service/ExampleEventService.js @@ -0,0 +1,43 @@ +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { generateUrl } from '@nextcloud/router' +import axios from '@nextcloud/axios' + +/** + * Configure the creation of example events on a user's first login. + * + * @param {boolean} enable Whether to enable or disable the feature. + * @return {Promise<void>} + */ +export async function setCreateExampleEvent(enable) { + const url = generateUrl('/apps/dav/api/exampleEvent/enable') + await axios.post(url, { + enable, + }) +} + +/** + * Upload a custom example event. + * + * @param {string} ics The ICS data of the event. + * @return {Promise<void>} + */ +export async function uploadExampleEvent(ics) { + const url = generateUrl('/apps/dav/api/exampleEvent/event') + await axios.post(url, { + ics, + }) +} + +/** + * Delete a previously uploaded custom example event. + * + * @return {Promise<void>} + */ +export async function deleteExampleEvent() { + const url = generateUrl('/apps/dav/api/exampleEvent/event') + await axios.delete(url) +} diff --git a/apps/dav/src/service/PreferenceService.js b/apps/dav/src/service/PreferenceService.js index 6b8d29029b5..39b2c067c61 100644 --- a/apps/dav/src/service/PreferenceService.js +++ b/apps/dav/src/service/PreferenceService.js @@ -1,22 +1,6 @@ /** - * @copyright 2022 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.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/>. + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import axios from '@nextcloud/axios' @@ -33,7 +17,7 @@ export async function enableUserStatusAutomation() { }), { configValue: 'yes', - } + }, ) } @@ -45,6 +29,6 @@ export async function disableUserStatusAutomation() { generateOcsUrl('/apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', { appId: 'dav', configKey: 'user_status_automation', - }) + }), ) } diff --git a/apps/dav/src/service/logger.js b/apps/dav/src/service/logger.js index dd6ec9163a6..cb7f1a95103 100644 --- a/apps/dav/src/service/logger.js +++ b/apps/dav/src/service/logger.js @@ -1,22 +1,6 @@ -/* - * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @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/>. +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ import { getLoggerBuilder } from '@nextcloud/logger' diff --git a/apps/dav/src/settings-example-content.js b/apps/dav/src/settings-example-content.js new file mode 100644 index 00000000000..fdeba642a67 --- /dev/null +++ b/apps/dav/src/settings-example-content.js @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import Vue from 'vue' +import { translate } from '@nextcloud/l10n' +import ExampleContentSettingsSection from './views/ExampleContentSettingsSection.vue' + +Vue.mixin({ + methods: { + t: translate, + $t: translate, + }, +}) + +const View = Vue.extend(ExampleContentSettingsSection); + +(new View({})).$mount('#settings-example-content') diff --git a/apps/dav/src/settings-personal-availability.js b/apps/dav/src/settings-personal-availability.js index 8b7bcba0c26..b24144b81f0 100644 --- a/apps/dav/src/settings-personal-availability.js +++ b/apps/dav/src/settings-personal-availability.js @@ -1,3 +1,7 @@ +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ import Vue from 'vue' import { translate } from '@nextcloud/l10n' import Availability from './views/Availability.vue' diff --git a/apps/dav/src/settings.js b/apps/dav/src/settings.js index 56b9c7f3fb9..c69a8b03614 100644 --- a/apps/dav/src/settings.js +++ b/apps/dav/src/settings.js @@ -1,3 +1,7 @@ +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ import Vue from 'vue' import { loadState } from '@nextcloud/initial-state' import { translate } from '@nextcloud/l10n' @@ -13,12 +17,12 @@ const CalDavSettingsView = new View({ sendInvitations: loadState('dav', 'sendInvitations'), generateBirthdayCalendar: loadState( 'dav', - 'generateBirthdayCalendar' + 'generateBirthdayCalendar', ), sendEventReminders: loadState('dav', 'sendEventReminders'), sendEventRemindersToSharedUsers: loadState( 'dav', - 'sendEventRemindersToSharedUsers' + 'sendEventRemindersToSharedUsers', ), sendEventRemindersPush: loadState('dav', 'sendEventRemindersPush'), } diff --git a/apps/dav/src/utils/date.js b/apps/dav/src/utils/date.js index 1b67e05085b..de1d65e310d 100644 --- a/apps/dav/src/utils/date.js +++ b/apps/dav/src/utils/date.js @@ -1,23 +1,6 @@ /** - * @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud> - * - * @author Richard Steinmetz <richard@steinmetz.cloud> - * - * @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 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ /** diff --git a/apps/dav/src/views/Availability.vue b/apps/dav/src/views/Availability.vue index 62778839142..1922f5b706e 100644 --- a/apps/dav/src/views/Availability.vue +++ b/apps/dav/src/views/Availability.vue @@ -1,10 +1,16 @@ +<!-- + - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <div> - <NcSettingsSection :name="$t('dav', 'Availability')" - :description="$t('dav', 'If you configure your working hours, other users will see when you are out of office when they book a meeting.')"> + <NcSettingsSection id="availability" + :name="$t('dav', 'Availability')" + :description="$t('dav', 'If you configure your working hours, other people will see when you are out of office when they book a meeting.')"> <AvailabilityForm /> </NcSettingsSection> <NcSettingsSection v-if="!hideAbsenceSettings" + id="absence" :name="$t('dav', 'Absence')" :description="$t('dav', 'Configure your next absence period.')"> <AbsenceForm /> @@ -13,7 +19,7 @@ </template> <script> -import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js' +import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' import AbsenceForm from '../components/AbsenceForm.vue' import AvailabilityForm from '../components/AvailabilityForm.vue' import { loadState } from '@nextcloud/initial-state' diff --git a/apps/dav/src/views/CalDavSettings.spec.js b/apps/dav/src/views/CalDavSettings.spec.js index 311c8923dc3..7a4345b3ddf 100644 --- a/apps/dav/src/views/CalDavSettings.spec.js +++ b/apps/dav/src/views/CalDavSettings.spec.js @@ -1,38 +1,35 @@ +/** + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ import { render } from '@testing-library/vue' +import { beforeEach, describe, expect, test, vi } from 'vitest' + import CalDavSettings from './CalDavSettings.vue' -// eslint-disable-next-line no-unused-vars -import { generateUrl } from '@nextcloud/router' -jest.mock('@nextcloud/axios') -jest.mock('@nextcloud/router', () => { +vi.mock('@nextcloud/axios') +vi.mock('@nextcloud/router', () => { return { generateUrl(url) { return url }, } }) -jest.mock('@nextcloud/initial-state', () => { +vi.mock('@nextcloud/initial-state', () => { return { - loadState: jest.fn(() => 'https://docs.nextcloud.com/server/23/go.php?to=user-sync-calendars'), + loadState: vi.fn(() => 'https://docs.nextcloud.com/server/23/go.php?to=user-sync-calendars'), } }) describe('CalDavSettings', () => { - const originalOC = global.OC - const originalOCP = global.OCP - beforeEach(() => { - global.OC = { requestToken: 'secret' } - global.OCP = { + window.OC = { requestToken: 'secret' } + window.OCP = { AppConfig: { - setValue: jest.fn(), + setValue: vi.fn(), }, } }) - afterAll(() => { - global.OC = originalOC - global.OCP = originalOCP - }) test('interactions', async () => { const TLUtils = render( @@ -49,28 +46,28 @@ describe('CalDavSettings', () => { }, }, Vue => { - Vue.prototype.$t = jest.fn((app, text) => text) - } + Vue.prototype.$t = vi.fn((app, text) => text) + }, ) expect(TLUtils.container).toMatchSnapshot() const sendInvitations = TLUtils.getByLabelText( - 'Send invitations to attendees' + 'Send invitations to attendees', ) expect(sendInvitations).toBeChecked() const generateBirthdayCalendar = TLUtils.getByLabelText( - 'Automatically generate a birthday calendar' + 'Automatically generate a birthday calendar', ) expect(generateBirthdayCalendar).toBeChecked() const sendEventReminders = TLUtils.getByLabelText( - 'Send notifications for events' + 'Send notifications for events', ) expect(sendEventReminders).toBeChecked() const sendEventRemindersToSharedUsers = TLUtils.getByLabelText( - 'Send reminder notifications to calendar sharees as well' + 'Send reminder notifications to calendar sharees as well', ) expect(sendEventRemindersToSharedUsers).toBeChecked() const sendEventRemindersPush = TLUtils.getByLabelText( - 'Enable notifications for events via push' + 'Enable notifications for events via push', ) expect(sendEventRemindersPush).toBeChecked() diff --git a/apps/dav/src/views/CalDavSettings.vue b/apps/dav/src/views/CalDavSettings.vue index 2bf4fbad561..6be67cf93ff 100644 --- a/apps/dav/src/views/CalDavSettings.vue +++ b/apps/dav/src/views/CalDavSettings.vue @@ -1,3 +1,7 @@ +<!-- + - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <NcSettingsSection :name="$t('dav', 'Calendar server')" :doc-url="userSyncCalendarsDocUrl"> @@ -75,8 +79,8 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' import { loadState } from '@nextcloud/initial-state' -import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js' -import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' +import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' const userSyncCalendarsDocUrl = loadState('dav', 'userSyncCalendarsDocUrl', '#') @@ -124,7 +128,7 @@ export default { OCP.AppConfig.setValue( 'dav', 'sendInvitations', - value ? 'yes' : 'no' + value ? 'yes' : 'no', ) }, sendEventReminders(value) { @@ -134,7 +138,7 @@ export default { OCP.AppConfig.setValue( 'dav', 'sendEventRemindersToSharedUsers', - value ? 'yes' : 'no' + value ? 'yes' : 'no', ) }, sendEventRemindersPush(value) { @@ -146,12 +150,13 @@ export default { <style scoped> .indented { - padding-left: 28px; + padding-inline-start: 28px; } /** Use deep selector to affect v-html */ - * >>> a { + * :deep(a) { text-decoration: underline; } + .settings-hint { margin-top: -.2em; margin-bottom: 1em; diff --git a/apps/dav/src/views/ExampleContentSettingsSection.vue b/apps/dav/src/views/ExampleContentSettingsSection.vue new file mode 100644 index 00000000000..3ee2d9e8648 --- /dev/null +++ b/apps/dav/src/views/ExampleContentSettingsSection.vue @@ -0,0 +1,38 @@ +<!-- + - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <NcSettingsSection id="example-content" + :name="$t('dav', 'Example content')" + class="example-content-setting" + :description="$t('dav', 'Example content serves to showcase the features of Nextcloud. Default content is shipped with Nextcloud, and can be replaced by custom content.')"> + <ExampleContactSettings v-if="hasContactsApp" /> + <ExampleEventSettings v-if="hasCalendarApp" /> + </NcSettingsSection> +</template> + +<script> +import { loadState } from '@nextcloud/initial-state' +import { NcSettingsSection } from '@nextcloud/vue' +import ExampleEventSettings from '../components/ExampleEventSettings.vue' +import ExampleContactSettings from '../components/ExampleContactSettings.vue' + +export default { + name: 'ExampleContentSettingsSection', + components: { + NcSettingsSection, + ExampleContactSettings, + ExampleEventSettings, + }, + computed: { + hasContactsApp() { + return loadState('dav', 'contactsEnabled') + }, + hasCalendarApp() { + return loadState('dav', 'calendarEnabled') + }, + }, +} +</script> diff --git a/apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap b/apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap index cf6954ca0b3..fdbe09f5b5e 100644 --- a/apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap +++ b/apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap @@ -1,19 +1,21 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`CalDavSettings interactions 1`] = ` +exports[`CalDavSettings > interactions 1`] = ` <div> <div class="settings-section settings-section--limit-width" - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > <h2 class="settings-section__name" - data-v-375ea653="" + data-v-6f6953b5="" > Calendar server <a + aria-label="External documentation for Calendar server" class="settings-section__info" - data-v-375ea653="" + data-v-6f6953b5="" href="https://docs.nextcloud.com/server/23/go.php?to=user-sync-calendars" rel="noreferrer nofollow" target="_blank" @@ -22,7 +24,7 @@ exports[`CalDavSettings interactions 1`] = ` <span aria-hidden="true" class="material-design-icon help-circle-icon" - data-v-375ea653="" + data-v-6f6953b5="" role="img" > <svg @@ -44,7 +46,8 @@ exports[`CalDavSettings interactions 1`] = ` <!----> <p class="settings-hint" - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > Also install the <a @@ -63,38 +66,41 @@ exports[`CalDavSettings interactions 1`] = ` </a> . </p> - <p - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > <span class="checkbox-radio-switch checkbox-radio-switch-switch checkbox-radio-switch--checked" - data-v-375ea653="" - data-v-87511acb="" - style="--icon-size: 36px;" + data-v-6b8d4c30="" + data-v-6f6953b5="" + data-v-f275cf53="" + style="--icon-size: 36px; --icon-height: 16px;" > <input + aria-labelledby="caldavSendInvitations-label" class="checkbox-radio-switch__input" - data-v-87511acb="" + data-v-f275cf53="" id="caldavSendInvitations" type="checkbox" value="" /> - <label - class="checkbox-content checkbox-radio-switch__content checkbox-content-switch" - data-v-87511acb="" - data-v-bc42acb7="" - for="caldavSendInvitations" + <span + class="checkbox-content checkbox-radio-switch__content checkbox-content-switch checkbox-content--has-text" + data-v-3714b019="" + data-v-f275cf53="" + id="caldavSendInvitations-label" > <span aria-hidden="true" class="checkbox-content__icon checkbox-content__icon--checked checkbox-radio-switch__icon" - data-v-bc42acb7="" + data-v-3714b019="" + inert="inert" > <span aria-hidden="true" class="material-design-icon toggle-switch-icon" - data-v-bc42acb7="" + data-v-3714b019="" role="img" > <svg @@ -114,17 +120,15 @@ exports[`CalDavSettings interactions 1`] = ` </span> <span class="checkbox-content__text checkbox-radio-switch__text" - data-v-bc42acb7="" + data-v-3714b019="" > - - Send invitations to attendees - + Send invitations to attendees </span> - </label> + </span> </span> - <em - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > Please make sure to properly set up <a @@ -135,38 +139,41 @@ exports[`CalDavSettings interactions 1`] = ` . </em> </p> - <p - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > <span class="checkbox-radio-switch checkbox checkbox-radio-switch-switch checkbox-radio-switch--checked" - data-v-375ea653="" - data-v-87511acb="" - style="--icon-size: 36px;" + data-v-6b8d4c30="" + data-v-6f6953b5="" + data-v-f275cf53="" + style="--icon-size: 36px; --icon-height: 16px;" > <input + aria-labelledby="caldavGenerateBirthdayCalendar-label" class="checkbox-radio-switch__input" - data-v-87511acb="" + data-v-f275cf53="" id="caldavGenerateBirthdayCalendar" type="checkbox" value="" /> - <label - class="checkbox-content checkbox-radio-switch__content checkbox-content-switch" - data-v-87511acb="" - data-v-bc42acb7="" - for="caldavGenerateBirthdayCalendar" + <span + class="checkbox-content checkbox-radio-switch__content checkbox-content-switch checkbox-content--has-text" + data-v-3714b019="" + data-v-f275cf53="" + id="caldavGenerateBirthdayCalendar-label" > <span aria-hidden="true" class="checkbox-content__icon checkbox-content__icon--checked checkbox-radio-switch__icon" - data-v-bc42acb7="" + data-v-3714b019="" + inert="inert" > <span aria-hidden="true" class="material-design-icon toggle-switch-icon" - data-v-bc42acb7="" + data-v-3714b019="" role="img" > <svg @@ -186,67 +193,64 @@ exports[`CalDavSettings interactions 1`] = ` </span> <span class="checkbox-content__text checkbox-radio-switch__text" - data-v-bc42acb7="" + data-v-3714b019="" > - - Automatically generate a birthday calendar - + Automatically generate a birthday calendar </span> - </label> + </span> </span> - <em - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > - - Birthday calendars will be generated by a background job. - + Birthday calendars will be generated by a background job. </em> - <br - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" /> - <em - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > - - Hence they will not be available immediately after enabling but will show up after some time. - + Hence they will not be available immediately after enabling but will show up after some time. </em> </p> - <p - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > <span class="checkbox-radio-switch checkbox-radio-switch-switch checkbox-radio-switch--checked" - data-v-375ea653="" - data-v-87511acb="" - style="--icon-size: 36px;" + data-v-6b8d4c30="" + data-v-6f6953b5="" + data-v-f275cf53="" + style="--icon-size: 36px; --icon-height: 16px;" > <input + aria-labelledby="caldavSendEventReminders-label" class="checkbox-radio-switch__input" - data-v-87511acb="" + data-v-f275cf53="" id="caldavSendEventReminders" type="checkbox" value="" /> - <label - class="checkbox-content checkbox-radio-switch__content checkbox-content-switch" - data-v-87511acb="" - data-v-bc42acb7="" - for="caldavSendEventReminders" + <span + class="checkbox-content checkbox-radio-switch__content checkbox-content-switch checkbox-content--has-text" + data-v-3714b019="" + data-v-f275cf53="" + id="caldavSendEventReminders-label" > <span aria-hidden="true" class="checkbox-content__icon checkbox-content__icon--checked checkbox-radio-switch__icon" - data-v-bc42acb7="" + data-v-3714b019="" + inert="inert" > <span aria-hidden="true" class="material-design-icon toggle-switch-icon" - data-v-bc42acb7="" + data-v-3714b019="" role="img" > <svg @@ -266,17 +270,15 @@ exports[`CalDavSettings interactions 1`] = ` </span> <span class="checkbox-content__text checkbox-radio-switch__text" - data-v-bc42acb7="" + data-v-3714b019="" > - - Send notifications for events - + Send notifications for events </span> - </label> + </span> </span> - <em - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > Please make sure to properly set up <a @@ -286,52 +288,53 @@ exports[`CalDavSettings interactions 1`] = ` </a> . </em> - <br - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" /> - <em - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > - - Notifications are sent via background jobs, so these must occur often enough. - + Notifications are sent via background jobs, so these must occur often enough. </em> </p> - <p class="indented" - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > <span class="checkbox-radio-switch checkbox-radio-switch-switch checkbox-radio-switch--checked" - data-v-375ea653="" - data-v-87511acb="" - style="--icon-size: 36px;" + data-v-6b8d4c30="" + data-v-6f6953b5="" + data-v-f275cf53="" + style="--icon-size: 36px; --icon-height: 16px;" > <input + aria-labelledby="caldavSendEventRemindersToSharedGroupMembers-label" class="checkbox-radio-switch__input" - data-v-87511acb="" + data-v-f275cf53="" id="caldavSendEventRemindersToSharedGroupMembers" type="checkbox" value="" /> - <label - class="checkbox-content checkbox-radio-switch__content checkbox-content-switch" - data-v-87511acb="" - data-v-bc42acb7="" - for="caldavSendEventRemindersToSharedGroupMembers" + <span + class="checkbox-content checkbox-radio-switch__content checkbox-content-switch checkbox-content--has-text" + data-v-3714b019="" + data-v-f275cf53="" + id="caldavSendEventRemindersToSharedGroupMembers-label" > <span aria-hidden="true" class="checkbox-content__icon checkbox-content__icon--checked checkbox-radio-switch__icon" - data-v-bc42acb7="" + data-v-3714b019="" + inert="inert" > <span aria-hidden="true" class="material-design-icon toggle-switch-icon" - data-v-bc42acb7="" + data-v-3714b019="" role="img" > <svg @@ -351,56 +354,55 @@ exports[`CalDavSettings interactions 1`] = ` </span> <span class="checkbox-content__text checkbox-radio-switch__text" - data-v-bc42acb7="" + data-v-3714b019="" > - - Send reminder notifications to calendar sharees as well - + Send reminder notifications to calendar sharees as well </span> - </label> + </span> </span> - <em - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > - - Reminders are always sent to organizers and attendees. - + Reminders are always sent to organizers and attendees. </em> </p> - <p class="indented" - data-v-375ea653="" + data-v-6b8d4c30="" + data-v-6f6953b5="" > <span class="checkbox-radio-switch checkbox-radio-switch-switch checkbox-radio-switch--checked" - data-v-375ea653="" - data-v-87511acb="" - style="--icon-size: 36px;" + data-v-6b8d4c30="" + data-v-6f6953b5="" + data-v-f275cf53="" + style="--icon-size: 36px; --icon-height: 16px;" > <input + aria-labelledby="caldavSendEventRemindersPush-label" class="checkbox-radio-switch__input" - data-v-87511acb="" + data-v-f275cf53="" id="caldavSendEventRemindersPush" type="checkbox" value="" /> - <label - class="checkbox-content checkbox-radio-switch__content checkbox-content-switch" - data-v-87511acb="" - data-v-bc42acb7="" - for="caldavSendEventRemindersPush" + <span + class="checkbox-content checkbox-radio-switch__content checkbox-content-switch checkbox-content--has-text" + data-v-3714b019="" + data-v-f275cf53="" + id="caldavSendEventRemindersPush-label" > <span aria-hidden="true" class="checkbox-content__icon checkbox-content__icon--checked checkbox-radio-switch__icon" - data-v-bc42acb7="" + data-v-3714b019="" + inert="inert" > <span aria-hidden="true" class="material-design-icon toggle-switch-icon" - data-v-bc42acb7="" + data-v-3714b019="" role="img" > <svg @@ -420,13 +422,11 @@ exports[`CalDavSettings interactions 1`] = ` </span> <span class="checkbox-content__text checkbox-radio-switch__text" - data-v-bc42acb7="" + data-v-3714b019="" > - - Enable notifications for events via push - + Enable notifications for events via push </span> - </label> + </span> </span> </p> </div> diff --git a/apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap.license b/apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap.license new file mode 100644 index 00000000000..b8f52265f1f --- /dev/null +++ b/apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors +SPDX-License-Identifier: AGPL-3.0-or-later
\ No newline at end of file |