@conduction/nextcloud-vue 2.98.0 → 2.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +12 -0
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +11 -2
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +6 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +2 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +25 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +7 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -8
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +13 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +54 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +16 -2
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +42 -7
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +121 -57
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +136 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +26 -7
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +78 -17
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +68 -7
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +256 -240
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +9 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js +265 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +191 -0
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +9 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +537 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js +99 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +18 -70
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +6 -44
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +16 -2
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +40 -25
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +7 -7
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js +9 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js.map +1 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js +472 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js +231 -0
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWorkingCalendarWidget/index.js +27 -0
- package/dist/esm/components/CnWorkingCalendarWidget/index.js.map +1 -0
- package/dist/esm/composables/useFlowStore.js +2 -1
- package/dist/esm/composables/useFlowStore.js.map +1 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +101 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +115 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +999 -233
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/workingCalendarMonth.js +85 -0
- package/dist/esm/utils/workingCalendarMonth.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +10314 -7778
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +999 -233
- package/l10n/en.json +101 -1
- package/l10n/nl.json +115 -1
- package/package.json +1 -1
- package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +7 -1
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +29 -9
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +13 -4
- package/src/components/CnFactsWidget/CnFactsWidget.vue +65 -3
- package/src/components/CnFlowsPage/CnFlowBoard.vue +77 -2
- package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +136 -7
- package/src/components/CnOrderedListField/CnOrderedListField.vue +81 -9
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +114 -7
- package/src/components/CnSettingsField/CnSettingsField.vue +449 -0
- package/src/components/CnSettingsField/index.js +4 -0
- package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +642 -0
- package/src/components/CnSettingsFieldsSection/index.js +4 -0
- package/src/components/CnSettingsPage/CnSettingsPage.vue +6 -86
- package/src/components/CnSetupWizard/CnSetupWizard.vue +37 -3
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWizardDialog/CnWizardDialog.vue +7 -7
- package/src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue +725 -0
- package/src/components/CnWorkingCalendarWidget/index.js +26 -0
- package/src/components/index.js +3 -0
- package/src/composables/useFlowStore.js +2 -1
- package/src/css/badge.css +32 -16
- package/src/css/detail-page.css +15 -0
- package/src/css/dialog.css +48 -0
- package/src/css/look-board-detail.css +80 -8
- package/src/css/look-board-index.css +52 -8
- package/src/css/look-board.css +301 -1
- package/src/index.js +3 -0
- package/src/schemas/app-manifest-v2.schema.json +25 -1
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/workingCalendarMonth.js +82 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<div class="cn-working-calendar" data-testid="cn-working-calendar">
|
|
7
|
+
<p v-if="error"
|
|
8
|
+
class="cn-working-calendar__error"
|
|
9
|
+
role="alert"
|
|
10
|
+
data-testid="cn-working-calendar-error">
|
|
11
|
+
{{ error }}
|
|
12
|
+
</p>
|
|
13
|
+
<div v-else-if="loading && !calendar" class="cn-working-calendar__loading">
|
|
14
|
+
<NcLoadingIcon :size="20" :name="loadingLabel" />
|
|
15
|
+
</div>
|
|
16
|
+
<template v-else-if="calendar">
|
|
17
|
+
<div class="cn-working-calendar__head">
|
|
18
|
+
<NcButton
|
|
19
|
+
variant="tertiary"
|
|
20
|
+
:aria-label="previousMonthLabel"
|
|
21
|
+
data-testid="cn-working-calendar-previous"
|
|
22
|
+
@click="shiftMonth(-1)">
|
|
23
|
+
<template #icon>
|
|
24
|
+
<ChevronLeft :size="20" />
|
|
25
|
+
</template>
|
|
26
|
+
</NcButton>
|
|
27
|
+
<strong :id="`${uid}-title`" class="cn-working-calendar__title" data-testid="cn-working-calendar-title">{{ monthTitle }}</strong>
|
|
28
|
+
<NcButton
|
|
29
|
+
variant="tertiary"
|
|
30
|
+
:aria-label="nextMonthLabel"
|
|
31
|
+
data-testid="cn-working-calendar-next"
|
|
32
|
+
@click="shiftMonth(1)">
|
|
33
|
+
<template #icon>
|
|
34
|
+
<ChevronRight :size="20" />
|
|
35
|
+
</template>
|
|
36
|
+
</NcButton>
|
|
37
|
+
<NcButton
|
|
38
|
+
v-if="editable"
|
|
39
|
+
class="cn-working-calendar__add"
|
|
40
|
+
variant="secondary"
|
|
41
|
+
data-testid="cn-working-calendar-add"
|
|
42
|
+
@click="openAdd()">
|
|
43
|
+
{{ addLabel }}
|
|
44
|
+
</NcButton>
|
|
45
|
+
</div>
|
|
46
|
+
<p v-if="previewNote" class="cn-working-calendar__note" data-testid="cn-working-calendar-note">
|
|
47
|
+
{{ previewNote }}
|
|
48
|
+
</p>
|
|
49
|
+
<span class="cn-working-calendar__live" aria-live="polite">{{ liveText }}</span>
|
|
50
|
+
|
|
51
|
+
<div
|
|
52
|
+
role="grid"
|
|
53
|
+
class="cn-working-calendar__grid"
|
|
54
|
+
:aria-labelledby="`${uid}-title`"
|
|
55
|
+
data-testid="cn-working-calendar-grid"
|
|
56
|
+
@keydown="onKeydown">
|
|
57
|
+
<div role="row" class="cn-working-calendar__row">
|
|
58
|
+
<span
|
|
59
|
+
v-for="name in weekdayNames"
|
|
60
|
+
:key="name.long"
|
|
61
|
+
role="columnheader"
|
|
62
|
+
class="cn-working-calendar__weekday"
|
|
63
|
+
:aria-label="name.long">{{ name.short }}</span>
|
|
64
|
+
</div>
|
|
65
|
+
<div
|
|
66
|
+
v-for="(week, w) in weeks"
|
|
67
|
+
:key="w"
|
|
68
|
+
role="row"
|
|
69
|
+
class="cn-working-calendar__row">
|
|
70
|
+
<template v-for="(slot, d) in week">
|
|
71
|
+
<span v-if="!slot"
|
|
72
|
+
:key="`empty-${w}-${d}`"
|
|
73
|
+
role="gridcell"
|
|
74
|
+
class="cn-working-calendar__blank" />
|
|
75
|
+
<span
|
|
76
|
+
v-else
|
|
77
|
+
:key="slot.date"
|
|
78
|
+
role="gridcell"
|
|
79
|
+
class="cn-working-calendar__day"
|
|
80
|
+
:class="{
|
|
81
|
+
'cn-working-calendar__day--off': !slot.working && !slot.closure,
|
|
82
|
+
'cn-working-calendar__day--closed': slot.closure,
|
|
83
|
+
'cn-working-calendar__day--today': slot.today,
|
|
84
|
+
}"
|
|
85
|
+
:tabindex="slot.date === focusDate ? 0 : -1"
|
|
86
|
+
:aria-label="dayLabel(slot)"
|
|
87
|
+
:aria-current="slot.today ? 'date' : null"
|
|
88
|
+
:title="slot.closure ? closureName(slot.closure) : null"
|
|
89
|
+
:data-date="slot.date"
|
|
90
|
+
data-testid="cn-working-calendar-day"
|
|
91
|
+
@click="onDayClick(slot)"
|
|
92
|
+
@focus="focusDate = slot.date">{{ slot.day }}</span>
|
|
93
|
+
</template>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<form
|
|
98
|
+
v-if="adding"
|
|
99
|
+
class="cn-working-calendar__form"
|
|
100
|
+
data-testid="cn-working-calendar-form"
|
|
101
|
+
@submit.prevent="saveDay">
|
|
102
|
+
<label class="cn-working-calendar__field">
|
|
103
|
+
{{ dateLabel }}
|
|
104
|
+
<input
|
|
105
|
+
ref="dateInput"
|
|
106
|
+
v-model="newDate"
|
|
107
|
+
type="date"
|
|
108
|
+
required
|
|
109
|
+
data-testid="cn-working-calendar-date">
|
|
110
|
+
</label>
|
|
111
|
+
<label class="cn-working-calendar__field">
|
|
112
|
+
{{ nameLabel }}
|
|
113
|
+
<input
|
|
114
|
+
v-model="newName"
|
|
115
|
+
type="text"
|
|
116
|
+
data-testid="cn-working-calendar-name">
|
|
117
|
+
</label>
|
|
118
|
+
<div class="cn-working-calendar__form-actions">
|
|
119
|
+
<NcButton variant="tertiary" data-testid="cn-working-calendar-cancel" @click="adding = false">
|
|
120
|
+
{{ cancelLabel }}
|
|
121
|
+
</NcButton>
|
|
122
|
+
<NcButton
|
|
123
|
+
variant="primary"
|
|
124
|
+
type="submit"
|
|
125
|
+
:disabled="saving || !newDate"
|
|
126
|
+
data-testid="cn-working-calendar-save">
|
|
127
|
+
{{ saveLabel }}
|
|
128
|
+
</NcButton>
|
|
129
|
+
</div>
|
|
130
|
+
<p v-if="saveError"
|
|
131
|
+
class="cn-working-calendar__error"
|
|
132
|
+
role="alert"
|
|
133
|
+
data-testid="cn-working-calendar-save-error">
|
|
134
|
+
{{ saveError }}
|
|
135
|
+
</p>
|
|
136
|
+
</form>
|
|
137
|
+
</template>
|
|
138
|
+
<p v-else class="cn-working-calendar__note" data-testid="cn-working-calendar-empty">
|
|
139
|
+
{{ emptyLabel }}
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
</template>
|
|
143
|
+
|
|
144
|
+
<script>
|
|
145
|
+
import { getCurrentUser } from '@nextcloud/auth'
|
|
146
|
+
import axios from '@nextcloud/axios'
|
|
147
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
148
|
+
import { generateUrl } from '@nextcloud/router'
|
|
149
|
+
import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
|
|
150
|
+
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
|
|
151
|
+
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
152
|
+
import { fetchReferenceObjects } from '../../utils/boardReferenceColumns.js'
|
|
153
|
+
import { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
154
|
+
import { userLocale } from '../../utils/userLocale.js'
|
|
155
|
+
import { buildMonth, isoDate, normaliseExceptions } from '../../utils/workingCalendarMonth.js'
|
|
156
|
+
|
|
157
|
+
const OBJECT_API = '/apps/openregister/api/objects'
|
|
158
|
+
const PREVIEW_URL = '/apps/openregister/api/flow-timers/calendars/preview'
|
|
159
|
+
|
|
160
|
+
let instanceCounter = 0
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The id of an object, wherever OpenRegister carries it.
|
|
164
|
+
*
|
|
165
|
+
* @param {object|null} object The object.
|
|
166
|
+
* @return {string} The id, or ''.
|
|
167
|
+
*/
|
|
168
|
+
function idOf(object) {
|
|
169
|
+
return String(object?.id ?? object?.['@self']?.id ?? object?.uuid ?? '')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Today as `YYYY-MM-DD`, in local time.
|
|
174
|
+
*
|
|
175
|
+
* @return {string} The date.
|
|
176
|
+
*/
|
|
177
|
+
function todayIso() {
|
|
178
|
+
const now = new Date()
|
|
179
|
+
return isoDate(now.getFullYear(), now.getMonth(), now.getDate())
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* CnWorkingCalendarWidget — the `working-calendar` widget: one month of a
|
|
184
|
+
* working calendar kept as an OpenRegister object (board
|
|
185
|
+
* DqInstellingenAlgemeen, "Werkdagen en feestdagen").
|
|
186
|
+
*
|
|
187
|
+
* The grid runs Monday to Sunday. Weekdays the calendar does not work are
|
|
188
|
+
* grey; the dates it is closed on are red, with their name on hover and in
|
|
189
|
+
* the day's label. The closures are not computed here: the widget asks
|
|
190
|
+
* OpenRegister's calendar preview (`/api/flow-timers/calendars/preview`) for
|
|
191
|
+
* the year shown, which expands the calendar's rules and its exceptions.
|
|
192
|
+
*
|
|
193
|
+
* "Add day" appends `{ date, name }` to the object's `exceptions` with one
|
|
194
|
+
* update through the object API, under the reader's own rights; it shows for
|
|
195
|
+
* administrators, and the server decides. The preview is administrators-only
|
|
196
|
+
* too; for anyone else the widget shows the stored exceptions and says the
|
|
197
|
+
* holidays could not be read.
|
|
198
|
+
*
|
|
199
|
+
* ```json
|
|
200
|
+
* {
|
|
201
|
+
* "type": "working-calendar",
|
|
202
|
+
* "content": {
|
|
203
|
+
* "calendar": { "register": "flow-timers", "schema": "working-calendar", "filter": { "slug": "default" } }
|
|
204
|
+
* }
|
|
205
|
+
* }
|
|
206
|
+
* ```
|
|
207
|
+
*
|
|
208
|
+
* Keyboard: the days form a grid with one tab stop; the arrow keys move a
|
|
209
|
+
* day or a week, Home and End to the start and end of the week, Page Up and
|
|
210
|
+
* Page Down a month. Enter on a day opens Add day for that date.
|
|
211
|
+
*/
|
|
212
|
+
export default {
|
|
213
|
+
name: 'CnWorkingCalendarWidget',
|
|
214
|
+
|
|
215
|
+
components: { ChevronLeft, ChevronRight, NcButton, NcLoadingIcon },
|
|
216
|
+
|
|
217
|
+
inject: {
|
|
218
|
+
/** The page's workspace values, for `@workspace.<key>` tokens in the filter. */
|
|
219
|
+
cnWorkspaceContext: { default: null },
|
|
220
|
+
/** The host's translate, for the manifest's texts. */
|
|
221
|
+
cnTranslate: { default: () => (key) => key },
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
props: {
|
|
225
|
+
/**
|
|
226
|
+
* The widget config: `{ calendar: { register, schema, id? | filter? },
|
|
227
|
+
* previewUrl?, editable?, emptyText? }`.
|
|
228
|
+
*
|
|
229
|
+
* @type {object}
|
|
230
|
+
*/
|
|
231
|
+
content: {
|
|
232
|
+
type: Object,
|
|
233
|
+
default: () => ({}),
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
/** The bound record's id, for `@objectId` in the filter. */
|
|
237
|
+
objectId: {
|
|
238
|
+
type: [String, Number],
|
|
239
|
+
default: '',
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/** The bound record, for `@object.<field>` in the filter. */
|
|
243
|
+
objectData: {
|
|
244
|
+
type: Object,
|
|
245
|
+
default: null,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
data() {
|
|
250
|
+
instanceCounter += 1
|
|
251
|
+
const now = new Date()
|
|
252
|
+
return {
|
|
253
|
+
uid: `cn-working-calendar-${instanceCounter}`,
|
|
254
|
+
loading: false,
|
|
255
|
+
error: '',
|
|
256
|
+
calendar: null,
|
|
257
|
+
previews: {},
|
|
258
|
+
previewFailed: false,
|
|
259
|
+
year: now.getFullYear(),
|
|
260
|
+
month: now.getMonth(),
|
|
261
|
+
focusDate: todayIso(),
|
|
262
|
+
adding: false,
|
|
263
|
+
newDate: '',
|
|
264
|
+
newName: '',
|
|
265
|
+
saving: false,
|
|
266
|
+
saveError: '',
|
|
267
|
+
liveText: '',
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
computed: {
|
|
272
|
+
/** @return {{register: string, schema: string, id: string, filter: (object|null)}|null} The calendar source, tokens resolved, or null while unresolved. */
|
|
273
|
+
source() {
|
|
274
|
+
const c = this.content?.calendar
|
|
275
|
+
if (!c || !c.register || !c.schema) {
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
const unwrap = (v) => (v && typeof v === 'object' && 'value' in v ? v.value : v)
|
|
279
|
+
const ctx = { objectId: this.objectId || null, object: this.objectData, workspace: unwrap(this.cnWorkspaceContext) || {} }
|
|
280
|
+
const filter = c.filter && typeof c.filter === 'object' ? resolveFilterTokens(c.filter, ctx) : null
|
|
281
|
+
if (filter && hasUnresolvedTokens(filter)) {
|
|
282
|
+
return null
|
|
283
|
+
}
|
|
284
|
+
return { register: String(c.register), schema: String(c.schema), id: c.id ? String(c.id) : '', filter }
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
/** @return {string} A key of the source, to read it again when it changes. */
|
|
288
|
+
sourceKey() {
|
|
289
|
+
return this.source ? JSON.stringify(this.source) : ''
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
/** @return {boolean} Whether Add day shows: administrators, unless the manifest turns it off. */
|
|
293
|
+
editable() {
|
|
294
|
+
return this.content?.editable !== false && Boolean(getCurrentUser()?.isAdmin)
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/** @return {number[]} The ISO weekdays that work. */
|
|
298
|
+
workingWeekdays() {
|
|
299
|
+
const days = this.calendar?.workingWeekdays
|
|
300
|
+
return Array.isArray(days) ? days.map((d) => Number(d)) : [1, 2, 3, 4, 5]
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
/** @return {Array<{date: string, name: string}>} The stored exceptions. */
|
|
304
|
+
exceptions() {
|
|
305
|
+
return normaliseExceptions(this.calendar?.exceptions)
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The closed dates of the year shown: the preview's, each marked as an
|
|
310
|
+
* exception when the calendar lists it, else as a holiday; without a
|
|
311
|
+
* preview, the stored exceptions.
|
|
312
|
+
*
|
|
313
|
+
* @return {Map<string, {name: string, kind: string}>}
|
|
314
|
+
* @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month
|
|
315
|
+
*/
|
|
316
|
+
closures() {
|
|
317
|
+
const own = new Set(this.exceptions.map((e) => e.date))
|
|
318
|
+
const map = new Map()
|
|
319
|
+
const preview = this.previews[this.year]
|
|
320
|
+
for (const entry of Array.isArray(preview) ? preview : []) {
|
|
321
|
+
if (entry && typeof entry.date === 'string') {
|
|
322
|
+
map.set(entry.date, { name: String(entry.name || ''), kind: own.has(entry.date) ? 'exception' : 'holiday' })
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (const e of this.exceptions) {
|
|
326
|
+
if (!map.has(e.date)) {
|
|
327
|
+
map.set(e.date, { name: e.name, kind: 'exception' })
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return map
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
/** @return {Array<Array<object|null>>} The weeks of the month shown. */
|
|
334
|
+
weeks() {
|
|
335
|
+
return buildMonth(this.year, this.month, { workingWeekdays: this.workingWeekdays, closures: this.closures, today: todayIso() })
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
/** @return {Array<{short: string, long: string}>} Monday to Sunday in the user's language. */
|
|
339
|
+
weekdayNames() {
|
|
340
|
+
const short = new Intl.DateTimeFormat(userLocale(), { weekday: 'short' })
|
|
341
|
+
const long = new Intl.DateTimeFormat(userLocale(), { weekday: 'long' })
|
|
342
|
+
return Array.from({ length: 7 }, (_v, i) => {
|
|
343
|
+
const day = new Date(Date.UTC(2024, 0, 1 + i, 12))
|
|
344
|
+
return { short: short.format(day).replace(/\.$/, ''), long: long.format(day) }
|
|
345
|
+
})
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
/** @return {string} "December 2026" in the user's language. */
|
|
349
|
+
monthTitle() {
|
|
350
|
+
const text = new Intl.DateTimeFormat(userLocale(), { month: 'long', year: 'numeric' }).format(new Date(this.year, this.month, 15))
|
|
351
|
+
return text.charAt(0).toUpperCase() + text.slice(1)
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
/** @return {string} Why holidays are missing, or ''. */
|
|
355
|
+
previewNote() {
|
|
356
|
+
return this.previewFailed ? t('nextcloud-vue', 'Holidays could not be read. Only the days added by hand are shown.') : ''
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
loadingLabel: () => t('nextcloud-vue', 'Loading …'),
|
|
360
|
+
previousMonthLabel: () => t('nextcloud-vue', 'Previous month'),
|
|
361
|
+
nextMonthLabel: () => t('nextcloud-vue', 'Next month'),
|
|
362
|
+
addLabel: () => t('nextcloud-vue', 'Add day'),
|
|
363
|
+
dateLabel: () => t('nextcloud-vue', 'Date'),
|
|
364
|
+
nameLabel: () => t('nextcloud-vue', 'Name'),
|
|
365
|
+
cancelLabel: () => t('nextcloud-vue', 'Cancel'),
|
|
366
|
+
saveLabel: () => t('nextcloud-vue', 'Save'),
|
|
367
|
+
|
|
368
|
+
/** @return {string} The empty-state text. */
|
|
369
|
+
emptyLabel() {
|
|
370
|
+
return this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'No working calendar found.')
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
watch: {
|
|
375
|
+
sourceKey: {
|
|
376
|
+
handler() {
|
|
377
|
+
this.load()
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
immediate: true,
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
year() {
|
|
384
|
+
this.loadPreview()
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
methods: {
|
|
389
|
+
/**
|
|
390
|
+
* The host's translate, or the text unchanged.
|
|
391
|
+
*
|
|
392
|
+
* @param {string} text The text.
|
|
393
|
+
* @return {string} The translation.
|
|
394
|
+
*/
|
|
395
|
+
translate(text) {
|
|
396
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
/** @return {string} The calendar object's API URL. */
|
|
400
|
+
objectUrl() {
|
|
401
|
+
const s = this.source
|
|
402
|
+
const id = idOf(this.calendar) || s.id
|
|
403
|
+
return generateUrl(`${OBJECT_API}/${encodeURIComponent(s.register)}/${encodeURIComponent(s.schema)}/${encodeURIComponent(id)}`)
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Read the calendar object, then the closures of the year shown.
|
|
408
|
+
*
|
|
409
|
+
* @return {Promise<void>}
|
|
410
|
+
*/
|
|
411
|
+
async load() {
|
|
412
|
+
const key = this.sourceKey
|
|
413
|
+
if (key === '') {
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
this.loading = true
|
|
417
|
+
this.error = ''
|
|
418
|
+
try {
|
|
419
|
+
const s = this.source
|
|
420
|
+
let calendar = null
|
|
421
|
+
if (s.id) {
|
|
422
|
+
const response = await axios.get(generateUrl(`${OBJECT_API}/${encodeURIComponent(s.register)}/${encodeURIComponent(s.schema)}/${encodeURIComponent(s.id)}`))
|
|
423
|
+
calendar = response?.data || null
|
|
424
|
+
} else {
|
|
425
|
+
const found = await fetchReferenceObjects({ register: s.register, schema: s.schema, filter: s.filter || {}, limit: 1 })
|
|
426
|
+
calendar = found[0] || null
|
|
427
|
+
}
|
|
428
|
+
if (key !== this.sourceKey) {
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
this.calendar = calendar
|
|
432
|
+
this.previews = {}
|
|
433
|
+
if (calendar) {
|
|
434
|
+
await this.loadPreview()
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
this.error = t('nextcloud-vue', 'The working calendar could not be loaded.')
|
|
438
|
+
} finally {
|
|
439
|
+
this.loading = false
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Ask the calendar preview for the closed dates of the year shown,
|
|
445
|
+
* once per year.
|
|
446
|
+
*
|
|
447
|
+
* @return {Promise<void>}
|
|
448
|
+
* @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month
|
|
449
|
+
*/
|
|
450
|
+
async loadPreview() {
|
|
451
|
+
const year = this.year
|
|
452
|
+
if (!this.calendar || this.previews[year]) {
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
const url = generateUrl(this.content?.previewUrl || PREVIEW_URL)
|
|
457
|
+
const response = await axios.post(url, { calendar: this.calendar, year })
|
|
458
|
+
const dates = Array.isArray(response?.data?.dates) ? response.data.dates : []
|
|
459
|
+
this.previews = { ...this.previews, [year]: dates }
|
|
460
|
+
this.previewFailed = false
|
|
461
|
+
} catch {
|
|
462
|
+
this.previewFailed = true
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Show another month, keeping focus on the same day where it exists.
|
|
468
|
+
*
|
|
469
|
+
* @param {number} delta Months to move.
|
|
470
|
+
* @return {void}
|
|
471
|
+
*/
|
|
472
|
+
shiftMonth(delta) {
|
|
473
|
+
const date = new Date(this.year, this.month + delta, 1)
|
|
474
|
+
this.year = date.getFullYear()
|
|
475
|
+
this.month = date.getMonth()
|
|
476
|
+
const day = Math.min(Number(this.focusDate.slice(8, 10)) || 1, new Date(this.year, this.month + 1, 0).getDate())
|
|
477
|
+
this.focusDate = isoDate(this.year, this.month, day)
|
|
478
|
+
this.liveText = this.monthTitle
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* A day's accessible label: the full date, whether it works, and the
|
|
483
|
+
* closure's name.
|
|
484
|
+
*
|
|
485
|
+
* @param {object} slot The day.
|
|
486
|
+
* @return {string} The label.
|
|
487
|
+
*/
|
|
488
|
+
dayLabel(slot) {
|
|
489
|
+
const [y, m, d] = slot.date.split('-').map(Number)
|
|
490
|
+
const date = new Intl.DateTimeFormat(userLocale(), { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }).format(new Date(y, m - 1, d))
|
|
491
|
+
if (slot.closure) {
|
|
492
|
+
const name = this.closureName(slot.closure)
|
|
493
|
+
return name
|
|
494
|
+
? t('nextcloud-vue', '{date}, closed: {name}', { date, name })
|
|
495
|
+
: t('nextcloud-vue', '{date}, closed', { date })
|
|
496
|
+
}
|
|
497
|
+
return slot.working ? t('nextcloud-vue', '{date}, working day', { date }) : t('nextcloud-vue', '{date}, not a working day', { date })
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* A closure's name, with a fallback for an unnamed added day.
|
|
502
|
+
*
|
|
503
|
+
* @param {{name: string, kind: string}} closure The closure.
|
|
504
|
+
* @return {string} The name.
|
|
505
|
+
*/
|
|
506
|
+
closureName(closure) {
|
|
507
|
+
if (closure.name) {
|
|
508
|
+
return this.translate(closure.name)
|
|
509
|
+
}
|
|
510
|
+
return closure.kind === 'exception' ? t('nextcloud-vue', 'Day added by hand') : ''
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Open Add day, with a date filled in.
|
|
515
|
+
*
|
|
516
|
+
* @param {string} [date] The date.
|
|
517
|
+
* @return {void}
|
|
518
|
+
*/
|
|
519
|
+
openAdd(date) {
|
|
520
|
+
this.newDate = date || this.focusDate || ''
|
|
521
|
+
this.newName = ''
|
|
522
|
+
this.saveError = ''
|
|
523
|
+
this.adding = true
|
|
524
|
+
this.$nextTick(() => this.$refs.dateInput?.focus?.())
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* A click on a day: open Add day for it when the reader may edit.
|
|
529
|
+
*
|
|
530
|
+
* @param {object} slot The day.
|
|
531
|
+
* @return {void}
|
|
532
|
+
*/
|
|
533
|
+
onDayClick(slot) {
|
|
534
|
+
this.focusDate = slot.date
|
|
535
|
+
if (this.editable) {
|
|
536
|
+
this.openAdd(slot.date)
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Append the new day to the calendar's exceptions with one update,
|
|
542
|
+
* under the reader's rights, then read the closures again.
|
|
543
|
+
*
|
|
544
|
+
* @return {Promise<void>}
|
|
545
|
+
* @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-day-can-be-added-to-the-calendar
|
|
546
|
+
*/
|
|
547
|
+
async saveDay() {
|
|
548
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(this.newDate) || !this.calendar) {
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
this.saving = true
|
|
552
|
+
this.saveError = ''
|
|
553
|
+
try {
|
|
554
|
+
const stored = Array.isArray(this.calendar.exceptions) ? this.calendar.exceptions : []
|
|
555
|
+
const name = this.newName.trim()
|
|
556
|
+
const exceptions = [...stored.filter((e) => (typeof e === 'string' ? e : e?.date) !== this.newDate), name ? { date: this.newDate, name } : { date: this.newDate }]
|
|
557
|
+
const response = await axios.patch(this.objectUrl(), { exceptions })
|
|
558
|
+
this.calendar = { ...this.calendar, ...(response?.data && typeof response.data === 'object' ? response.data : {}), exceptions }
|
|
559
|
+
const year = Number(this.newDate.slice(0, 4))
|
|
560
|
+
const { [year]: _stale, ...rest } = this.previews
|
|
561
|
+
this.previews = rest
|
|
562
|
+
const [y, m] = this.newDate.split('-').map(Number)
|
|
563
|
+
this.year = y
|
|
564
|
+
this.month = m - 1
|
|
565
|
+
this.focusDate = this.newDate
|
|
566
|
+
await this.loadPreview()
|
|
567
|
+
this.adding = false
|
|
568
|
+
this.liveText = t('nextcloud-vue', 'Day added.')
|
|
569
|
+
} catch (e) {
|
|
570
|
+
this.saveError = e?.response?.status === 403
|
|
571
|
+
? t('nextcloud-vue', 'Only an administrator can change the working calendar.')
|
|
572
|
+
: t('nextcloud-vue', 'The day could not be added. Try again.')
|
|
573
|
+
} finally {
|
|
574
|
+
this.saving = false
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Arrow keys, Home / End and Page Up / Down move focus across the days.
|
|
580
|
+
*
|
|
581
|
+
* @param {KeyboardEvent} event The key event.
|
|
582
|
+
* @return {void}
|
|
583
|
+
*/
|
|
584
|
+
onKeydown(event) {
|
|
585
|
+
const moves = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }
|
|
586
|
+
const [y, m, d] = (this.focusDate || todayIso()).split('-').map(Number)
|
|
587
|
+
let target
|
|
588
|
+
if (event.key in moves) {
|
|
589
|
+
target = new Date(y, m - 1, d + moves[event.key])
|
|
590
|
+
} else if (event.key === 'Home' || event.key === 'End') {
|
|
591
|
+
const weekday = (new Date(y, m - 1, d).getDay() + 6) % 7
|
|
592
|
+
target = new Date(y, m - 1, d + (event.key === 'Home' ? -weekday : 6 - weekday))
|
|
593
|
+
} else if (event.key === 'PageUp' || event.key === 'PageDown') {
|
|
594
|
+
target = new Date(y, m - 1 + (event.key === 'PageUp' ? -1 : 1), 1)
|
|
595
|
+
target.setDate(Math.min(d, new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate()))
|
|
596
|
+
} else if ((event.key === 'Enter' || event.key === ' ') && this.editable) {
|
|
597
|
+
event.preventDefault()
|
|
598
|
+
this.openAdd(this.focusDate)
|
|
599
|
+
return
|
|
600
|
+
} else {
|
|
601
|
+
return
|
|
602
|
+
}
|
|
603
|
+
event.preventDefault()
|
|
604
|
+
this.focusDate = isoDate(target.getFullYear(), target.getMonth(), target.getDate())
|
|
605
|
+
if (target.getFullYear() !== this.year || target.getMonth() !== this.month) {
|
|
606
|
+
this.year = target.getFullYear()
|
|
607
|
+
this.month = target.getMonth()
|
|
608
|
+
this.liveText = this.monthTitle
|
|
609
|
+
}
|
|
610
|
+
this.$nextTick(() => this.$el.querySelector?.(`[data-date="${this.focusDate}"]`)?.focus?.())
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
}
|
|
614
|
+
</script>
|
|
615
|
+
|
|
616
|
+
<style scoped>
|
|
617
|
+
.cn-working-calendar {
|
|
618
|
+
display: flex;
|
|
619
|
+
flex-direction: column;
|
|
620
|
+
gap: 12px;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.cn-working-calendar__head {
|
|
624
|
+
align-items: center;
|
|
625
|
+
display: flex;
|
|
626
|
+
gap: 4px;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.cn-working-calendar__title {
|
|
630
|
+
font-size: 15px;
|
|
631
|
+
min-width: 9em;
|
|
632
|
+
text-align: center;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.cn-working-calendar__add {
|
|
636
|
+
margin-inline-start: auto;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.cn-working-calendar__grid {
|
|
640
|
+
display: flex;
|
|
641
|
+
flex-direction: column;
|
|
642
|
+
gap: 4px;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.cn-working-calendar__row {
|
|
646
|
+
display: grid;
|
|
647
|
+
gap: 4px;
|
|
648
|
+
grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.cn-working-calendar__weekday {
|
|
652
|
+
color: var(--color-text-maxcontrast);
|
|
653
|
+
font-size: 12px;
|
|
654
|
+
text-align: center;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.cn-working-calendar__day {
|
|
658
|
+
align-items: center;
|
|
659
|
+
background: var(--color-main-background);
|
|
660
|
+
border: 1px solid var(--color-border);
|
|
661
|
+
border-radius: 6px;
|
|
662
|
+
cursor: default;
|
|
663
|
+
display: flex;
|
|
664
|
+
font-size: 13px;
|
|
665
|
+
height: 34px;
|
|
666
|
+
justify-content: center;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.cn-working-calendar__day:focus-visible {
|
|
670
|
+
outline: 2px solid var(--color-primary-element);
|
|
671
|
+
outline-offset: 1px;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.cn-working-calendar__day--off {
|
|
675
|
+
background: var(--color-background-dark);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.cn-working-calendar__day--closed {
|
|
679
|
+
background: var(--color-error-light, color-mix(in srgb, var(--color-error) 12%, var(--color-main-background)));
|
|
680
|
+
border-color: var(--color-error);
|
|
681
|
+
color: var(--color-error-text, var(--color-main-text));
|
|
682
|
+
font-weight: 600;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.cn-working-calendar__day--today {
|
|
686
|
+
box-shadow: inset 0 0 0 2px var(--color-primary-element);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.cn-working-calendar__live {
|
|
690
|
+
clip-path: inset(50%);
|
|
691
|
+
height: 1px;
|
|
692
|
+
overflow: hidden;
|
|
693
|
+
position: absolute;
|
|
694
|
+
white-space: nowrap;
|
|
695
|
+
width: 1px;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.cn-working-calendar__note {
|
|
699
|
+
color: var(--color-text-maxcontrast);
|
|
700
|
+
margin: 0;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.cn-working-calendar__form {
|
|
704
|
+
display: flex;
|
|
705
|
+
flex-wrap: wrap;
|
|
706
|
+
gap: 8px 12px;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.cn-working-calendar__field {
|
|
710
|
+
display: flex;
|
|
711
|
+
flex-direction: column;
|
|
712
|
+
gap: 4px;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.cn-working-calendar__form-actions {
|
|
716
|
+
align-items: flex-end;
|
|
717
|
+
display: flex;
|
|
718
|
+
gap: 8px;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.cn-working-calendar__error {
|
|
722
|
+
color: var(--color-error-text, var(--color-error));
|
|
723
|
+
margin: 0;
|
|
724
|
+
}
|
|
725
|
+
</style>
|