@conduction/nextcloud-vue 2.135.0 → 2.137.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/CnActionDialog/CnActionDialog.vue.js +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +2 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +65 -50
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +9 -0
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +4 -3
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +4 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +61 -28
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +6 -0
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js +9 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +4 -11
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +32 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +34 -26
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +24 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -2
- 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.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +39 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -5
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +11 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +10 -2
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +18 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +38 -23
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +1 -0
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue.js +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js +14 -4
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +10 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +29 -13
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +3 -1
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js +1 -1
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +69 -3
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/l10n/en.json.js +20 -20
- package/dist/esm/l10n/nl.json.js +21 -21
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +580 -324
- package/dist/esm/utils/contentHeaderLink.js +7 -1
- package/dist/esm/utils/contentHeaderLink.js.map +1 -1
- package/dist/esm/utils/savedViewMenu.js +4 -1
- package/dist/esm/utils/savedViewMenu.js.map +1 -1
- package/dist/esm/utils/savedViewSummary.js +11 -4
- package/dist/esm/utils/savedViewSummary.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +3 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1159 -812
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +580 -324
- package/l10n/en.json +20 -20
- package/l10n/nl.json +21 -21
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +45 -4
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +13 -1
- package/src/components/CnAppNav/CnAppNav.vue +28 -3
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +15 -0
- package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +2 -10
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +38 -1
- package/src/components/CnDetailPage/CnDetailPage.md +1 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +26 -2
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +1 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +44 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -0
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +21 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +38 -2
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +7 -0
- package/src/components/CnRetentionWidget/CnRetentionWidget.vue +13 -0
- package/src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue +14 -4
- package/src/components/CnTimelineWidget/CnTimelineManage.vue +23 -0
- package/src/components/CnTimelineWidget/dashboardRegistration.js +3 -1
- package/src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue +7 -0
- package/src/composables/indexSources.js +69 -3
- package/src/css/look-board-detail.css +57 -1
- package/src/css/look-board-index.css +76 -1
- package/src/css/look-board.css +51 -4
- package/src/css/table.css +5 -0
- package/src/schemas/app-manifest-v2.schema.json +4 -2
- package/src/utils/contentHeaderLink.js +7 -1
- package/src/utils/savedViewMenu.js +4 -1
- package/src/utils/savedViewSummary.js +11 -4
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/widgetDispatch.js +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnWorkingCalendarWidget.vue3.js","sources":["../../../../src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue?vue&type=template&id=5c34a46e&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-working-calendar\" data-testid=\"cn-working-calendar\">\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-working-calendar__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-working-calendar-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && !calendar\" class=\"cn-working-calendar__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" :name=\"loadingLabel\" />\n\t\t</div>\n\t\t<template v-else-if=\"calendar\">\n\t\t\t<div class=\"cn-working-calendar__head\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:aria-label=\"previousMonthLabel\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-previous\"\n\t\t\t\t\t@click=\"shiftMonth(-1)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcButton>\n\t\t\t\t<strong :id=\"`${uid}-title`\" class=\"cn-working-calendar__title\" data-testid=\"cn-working-calendar-title\">{{ monthTitle }}</strong>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:aria-label=\"nextMonthLabel\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-next\"\n\t\t\t\t\t@click=\"shiftMonth(1)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"editable && content.addButton !== false\"\n\t\t\t\t\tclass=\"cn-working-calendar__add\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-add\"\n\t\t\t\t\t@click=\"openAdd()\">\n\t\t\t\t\t{{ addLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p v-if=\"previewNote\" class=\"cn-working-calendar__note\" data-testid=\"cn-working-calendar-note\">\n\t\t\t\t{{ previewNote }}\n\t\t\t</p>\n\t\t\t<span class=\"cn-working-calendar__live\" aria-live=\"polite\">{{ liveText }}</span>\n\n\t\t\t<div\n\t\t\t\trole=\"grid\"\n\t\t\t\tclass=\"cn-working-calendar__grid\"\n\t\t\t\t:aria-labelledby=\"`${uid}-title`\"\n\t\t\t\tdata-testid=\"cn-working-calendar-grid\"\n\t\t\t\t@keydown=\"onKeydown\">\n\t\t\t\t<div role=\"row\" class=\"cn-working-calendar__row\">\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-for=\"name in weekdayNames\"\n\t\t\t\t\t\t:key=\"name.long\"\n\t\t\t\t\t\trole=\"columnheader\"\n\t\t\t\t\t\tclass=\"cn-working-calendar__weekday\"\n\t\t\t\t\t\t:aria-label=\"name.long\">{{ name.short }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(week, w) in weeks\"\n\t\t\t\t\t:key=\"w\"\n\t\t\t\t\trole=\"row\"\n\t\t\t\t\tclass=\"cn-working-calendar__row\">\n\t\t\t\t\t<template v-for=\"(slot, d) in week\">\n\t\t\t\t\t\t<span v-if=\"!slot\"\n\t\t\t\t\t\t\t:key=\"`empty-${w}-${d}`\"\n\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\tclass=\"cn-working-calendar__blank\" />\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:key=\"slot.date\"\n\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\tclass=\"cn-working-calendar__day\"\n\t\t\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--off': !slot.working && !slot.closure,\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--closed': slot.closure,\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--today': slot.today,\n\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t\t:tabindex=\"slot.date === focusDate ? 0 : -1\"\n\t\t\t\t\t\t\t:aria-label=\"dayLabel(slot)\"\n\t\t\t\t\t\t\t:aria-current=\"slot.today ? 'date' : null\"\n\t\t\t\t\t\t\t:title=\"slot.closure ? closureName(slot.closure) : null\"\n\t\t\t\t\t\t\t:data-date=\"slot.date\"\n\t\t\t\t\t\t\tdata-testid=\"cn-working-calendar-day\"\n\t\t\t\t\t\t\t@click=\"onDayClick(slot)\"\n\t\t\t\t\t\t\t@focus=\"focusDate = slot.date\">{{ slot.day }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<p v-if=\"content.footnote\" class=\"cn-working-calendar__footnote\" data-testid=\"cn-working-calendar-footnote\">\n\t\t\t\t{{ cnTranslate(content.footnote) }}\n\t\t\t</p>\n\n\t\t\t<form\n\t\t\t\tv-if=\"adding\"\n\t\t\t\tclass=\"cn-working-calendar__form\"\n\t\t\t\tdata-testid=\"cn-working-calendar-form\"\n\t\t\t\t@submit.prevent=\"saveDay\">\n\t\t\t\t<label class=\"cn-working-calendar__field\">\n\t\t\t\t\t{{ dateLabel }}\n\t\t\t\t\t<input\n\t\t\t\t\t\tref=\"dateInput\"\n\t\t\t\t\t\tv-model=\"newDate\"\n\t\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t\trequired\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-date\">\n\t\t\t\t</label>\n\t\t\t\t<label class=\"cn-working-calendar__field\">\n\t\t\t\t\t{{ nameLabel }}\n\t\t\t\t\t<input\n\t\t\t\t\t\tv-model=\"newName\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-name\">\n\t\t\t\t</label>\n\t\t\t\t<div class=\"cn-working-calendar__form-actions\">\n\t\t\t\t\t<NcButton variant=\"tertiary\" data-testid=\"cn-working-calendar-cancel\" @click=\"adding = false\">\n\t\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t:disabled=\"saving || !newDate\"\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-save\">\n\t\t\t\t\t\t{{ saveLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t\t<p v-if=\"saveError\"\n\t\t\t\t\tclass=\"cn-working-calendar__error\"\n\t\t\t\t\trole=\"alert\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-save-error\">\n\t\t\t\t\t{{ saveError }}\n\t\t\t\t</p>\n\t\t\t</form>\n\t\t</template>\n\t\t<p v-else class=\"cn-working-calendar__note\" data-testid=\"cn-working-calendar-empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcLoadingIcon } from '@nextcloud/vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport { fetchReferenceObjects } from '../../utils/boardReferenceColumns.js'\nimport { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { userLocale } from '../../utils/userLocale.js'\nimport { buildMonth, isoDate, normaliseExceptions } from '../../utils/workingCalendarMonth.js'\n\nconst OBJECT_API = '/apps/openregister/api/objects'\nconst PREVIEW_URL = '/apps/openregister/api/flow-timers/calendars/preview'\n\nlet instanceCounter = 0\n\n/**\n * The id of an object, wherever OpenRegister carries it.\n *\n * @param {object|null} object The object.\n * @return {string} The id, or ''.\n */\nfunction idOf(object) {\n\treturn String(object?.id ?? object?.['@self']?.id ?? object?.uuid ?? '')\n}\n\n/**\n * Today as `YYYY-MM-DD`, in local time.\n *\n * @return {string} The date.\n */\nfunction todayIso() {\n\tconst now = new Date()\n\treturn isoDate(now.getFullYear(), now.getMonth(), now.getDate())\n}\n\n/**\n * CnWorkingCalendarWidget — the `working-calendar` widget: one month of a\n * working calendar kept as an OpenRegister object (board\n * DqInstellingenAlgemeen, \"Werkdagen en feestdagen\").\n *\n * The grid runs Monday to Sunday. Weekdays the calendar does not work are\n * grey; the dates it is closed on are red, with their name on hover and in\n * the day's label. The closures are not computed here: the widget asks\n * OpenRegister's calendar preview (`/api/flow-timers/calendars/preview`) for\n * the year shown, which expands the calendar's rules and its exceptions.\n *\n * \"Add day\" appends `{ date, name }` to the object's `exceptions` with one\n * update through the object API, under the reader's own rights; it shows for\n * administrators, and the server decides. The preview is administrators-only\n * too; for anyone else the widget shows the stored exceptions and says the\n * holidays could not be read.\n *\n * ```json\n * {\n * \"type\": \"working-calendar\",\n * \"content\": {\n * \"calendar\": { \"register\": \"flow-timers\", \"schema\": \"working-calendar\", \"filter\": { \"slug\": \"default\" } }\n * }\n * }\n * ```\n *\n * Keyboard: the days form a grid with one tab stop; the arrow keys move a\n * day or a week, Home and End to the start and end of the week, Page Up and\n * Page Down a month. Enter on a day opens Add day for that date.\n */\nexport default {\n\tname: 'CnWorkingCalendarWidget',\n\n\tcomponents: { ChevronLeft, ChevronRight, NcButton, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The page's workspace values, for `@workspace.<key>` tokens in the filter. */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/** The host's translate, for the manifest's texts. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget config: `{ calendar: { register, schema, id? | filter? },\n\t\t * previewUrl?, editable?, addButton?, emptyText?, footnote? }`. `addButton: false` leaves the Add day button\n\t\t * out, for a host that draws it in its own title row and calls `openAdd()`. `footnote` is a\n\t\t * line under the month grid, through the host's translate: what the colours mean\n\t\t * (DqInstellingenAlgemeen \"Rood: feestdag of sluitingsdag.\").\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record's id, for `@objectId` in the filter. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The bound record, for `@object.<field>` in the filter. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\tinstanceCounter += 1\n\t\tconst now = new Date()\n\t\treturn {\n\t\t\tuid: `cn-working-calendar-${instanceCounter}`,\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tcalendar: null,\n\t\t\tpreviews: {},\n\t\t\tpreviewFailed: false,\n\t\t\tyear: now.getFullYear(),\n\t\t\tmonth: now.getMonth(),\n\t\t\tfocusDate: todayIso(),\n\t\t\tadding: false,\n\t\t\tnewDate: '',\n\t\t\tnewName: '',\n\t\t\tsaving: false,\n\t\t\tsaveError: '',\n\t\t\tliveText: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {{register: string, schema: string, id: string, filter: (object|null)}|null} The calendar source, tokens resolved, or null while unresolved. */\n\t\tsource() {\n\t\t\tconst c = this.content?.calendar\n\t\t\tif (!c || !c.register || !c.schema) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst unwrap = (v) => (v && typeof v === 'object' && 'value' in v ? v.value : v)\n\t\t\tconst ctx = { objectId: this.objectId || null, object: this.objectData, workspace: unwrap(this.cnWorkspaceContext) || {} }\n\t\t\tconst filter = c.filter && typeof c.filter === 'object' ? resolveFilterTokens(c.filter, ctx) : null\n\t\t\tif (filter && hasUnresolvedTokens(filter)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { register: String(c.register), schema: String(c.schema), id: c.id ? String(c.id) : '', filter }\n\t\t},\n\n\t\t/** @return {string} A key of the source, to read it again when it changes. */\n\t\tsourceKey() {\n\t\t\treturn this.source ? JSON.stringify(this.source) : ''\n\t\t},\n\n\t\t/** @return {boolean} Whether Add day shows: administrators, unless the manifest turns it off. */\n\t\teditable() {\n\t\t\treturn this.content?.editable !== false && Boolean(getCurrentUser()?.isAdmin)\n\t\t},\n\n\t\t/** @return {number[]} The ISO weekdays that work. */\n\t\tworkingWeekdays() {\n\t\t\tconst days = this.calendar?.workingWeekdays\n\t\t\treturn Array.isArray(days) ? days.map((d) => Number(d)) : [1, 2, 3, 4, 5]\n\t\t},\n\n\t\t/** @return {Array<{date: string, name: string}>} The stored exceptions. */\n\t\texceptions() {\n\t\t\treturn normaliseExceptions(this.calendar?.exceptions)\n\t\t},\n\n\t\t/**\n\t\t * The closed dates of the year shown: the preview's, each marked as an\n\t\t * exception when the calendar lists it, else as a holiday; without a\n\t\t * preview, the stored exceptions.\n\t\t *\n\t\t * @return {Map<string, {name: string, kind: string}>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month\n\t\t */\n\t\tclosures() {\n\t\t\tconst own = new Set(this.exceptions.map((e) => e.date))\n\t\t\tconst map = new Map()\n\t\t\tconst preview = this.previews[this.year]\n\t\t\tfor (const entry of Array.isArray(preview) ? preview : []) {\n\t\t\t\tif (entry && typeof entry.date === 'string') {\n\t\t\t\t\tmap.set(entry.date, { name: String(entry.name || ''), kind: own.has(entry.date) ? 'exception' : 'holiday' })\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const e of this.exceptions) {\n\t\t\t\tif (!map.has(e.date)) {\n\t\t\t\t\tmap.set(e.date, { name: e.name, kind: 'exception' })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn map\n\t\t},\n\n\t\t/** @return {Array<Array<object|null>>} The weeks of the month shown. */\n\t\tweeks() {\n\t\t\treturn buildMonth(this.year, this.month, { workingWeekdays: this.workingWeekdays, closures: this.closures, today: todayIso() })\n\t\t},\n\n\t\t/** @return {Array<{short: string, long: string}>} Monday to Sunday in the user's language. */\n\t\tweekdayNames() {\n\t\t\tconst short = new Intl.DateTimeFormat(userLocale(), { weekday: 'short' })\n\t\t\tconst long = new Intl.DateTimeFormat(userLocale(), { weekday: 'long' })\n\t\t\treturn Array.from({ length: 7 }, (_v, i) => {\n\t\t\t\tconst day = new Date(Date.UTC(2024, 0, 1 + i, 12))\n\t\t\t\treturn { short: short.format(day).replace(/\\.$/, ''), long: long.format(day) }\n\t\t\t})\n\t\t},\n\n\t\t/** @return {string} \"December 2026\" in the user's language. */\n\t\tmonthTitle() {\n\t\t\tconst text = new Intl.DateTimeFormat(userLocale(), { month: 'long', year: 'numeric' }).format(new Date(this.year, this.month, 15))\n\t\t\treturn text.charAt(0).toUpperCase() + text.slice(1)\n\t\t},\n\n\t\t/** @return {string} Why holidays are missing, or ''. */\n\t\tpreviewNote() {\n\t\t\treturn this.previewFailed ? t('nextcloud-vue', 'Holidays could not be read. Only the days added by hand are shown.') : ''\n\t\t},\n\n\t\tloadingLabel: () => t('nextcloud-vue', 'Loading …'),\n\t\tpreviousMonthLabel: () => t('nextcloud-vue', 'Previous month'),\n\t\tnextMonthLabel: () => t('nextcloud-vue', 'Next month'),\n\t\taddLabel: () => t('nextcloud-vue', 'Add day'),\n\t\tdateLabel: () => t('nextcloud-vue', 'Date'),\n\t\tnameLabel: () => t('nextcloud-vue', 'Name'),\n\t\tcancelLabel: () => t('nextcloud-vue', 'Cancel'),\n\t\tsaveLabel: () => t('nextcloud-vue', 'Save'),\n\n\t\t/** @return {string} The empty-state text. */\n\t\temptyLabel() {\n\t\t\treturn this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'No working calendar found.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\n\t\t\timmediate: true,\n\t\t},\n\n\t\tyear() {\n\t\t\tthis.loadPreview()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The host's translate, or the text unchanged.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The translation.\n\t\t */\n\t\ttranslate(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/** @return {string} The calendar object's API URL. */\n\t\tobjectUrl() {\n\t\t\tconst s = this.source\n\t\t\tconst id = idOf(this.calendar) || s.id\n\t\t\treturn generateUrl(`${OBJECT_API}/${encodeURIComponent(s.register)}/${encodeURIComponent(s.schema)}/${encodeURIComponent(id)}`)\n\t\t},\n\n\t\t/**\n\t\t * Read the calendar object, then the closures of the year shown.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst key = this.sourceKey\n\t\t\tif (key === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst s = this.source\n\t\t\t\tlet calendar = null\n\t\t\t\tif (s.id) {\n\t\t\t\t\tconst response = await axios.get(generateUrl(`${OBJECT_API}/${encodeURIComponent(s.register)}/${encodeURIComponent(s.schema)}/${encodeURIComponent(s.id)}`))\n\t\t\t\t\tcalendar = response?.data || null\n\t\t\t\t} else {\n\t\t\t\t\tconst found = await fetchReferenceObjects({ register: s.register, schema: s.schema, filter: s.filter || {}, limit: 1 })\n\t\t\t\t\tcalendar = found[0] || null\n\t\t\t\t}\n\t\t\t\tif (key !== this.sourceKey) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.calendar = calendar\n\t\t\t\tthis.previews = {}\n\t\t\t\tif (calendar) {\n\t\t\t\t\tawait this.loadPreview()\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tthis.error = t('nextcloud-vue', 'The working calendar could not be loaded.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Ask the calendar preview for the closed dates of the year shown,\n\t\t * once per year.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month\n\t\t */\n\t\tasync loadPreview() {\n\t\t\tconst year = this.year\n\t\t\tif (!this.calendar || this.previews[year]) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = generateUrl(this.content?.previewUrl || PREVIEW_URL)\n\t\t\t\tconst response = await axios.post(url, { calendar: this.calendar, year })\n\t\t\t\tconst dates = Array.isArray(response?.data?.dates) ? response.data.dates : []\n\t\t\t\tthis.previews = { ...this.previews, [year]: dates }\n\t\t\t\tthis.previewFailed = false\n\t\t\t} catch {\n\t\t\t\tthis.previewFailed = true\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Show another month, keeping focus on the same day where it exists.\n\t\t *\n\t\t * @param {number} delta Months to move.\n\t\t * @return {void}\n\t\t */\n\t\tshiftMonth(delta) {\n\t\t\tconst date = new Date(this.year, this.month + delta, 1)\n\t\t\tthis.year = date.getFullYear()\n\t\t\tthis.month = date.getMonth()\n\t\t\tconst day = Math.min(Number(this.focusDate.slice(8, 10)) || 1, new Date(this.year, this.month + 1, 0).getDate())\n\t\t\tthis.focusDate = isoDate(this.year, this.month, day)\n\t\t\tthis.liveText = this.monthTitle\n\t\t},\n\n\t\t/**\n\t\t * A day's accessible label: the full date, whether it works, and the\n\t\t * closure's name.\n\t\t *\n\t\t * @param {object} slot The day.\n\t\t * @return {string} The label.\n\t\t */\n\t\tdayLabel(slot) {\n\t\t\tconst [y, m, d] = slot.date.split('-').map(Number)\n\t\t\tconst date = new Intl.DateTimeFormat(userLocale(), { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }).format(new Date(y, m - 1, d))\n\t\t\tif (slot.closure) {\n\t\t\t\tconst name = this.closureName(slot.closure)\n\t\t\t\treturn name\n\t\t\t\t\t? t('nextcloud-vue', '{date}, closed: {name}', { date, name })\n\t\t\t\t\t: t('nextcloud-vue', '{date}, closed', { date })\n\t\t\t}\n\t\t\treturn slot.working ? t('nextcloud-vue', '{date}, working day', { date }) : t('nextcloud-vue', '{date}, not a working day', { date })\n\t\t},\n\n\t\t/**\n\t\t * A closure's name, with a fallback for an unnamed added day.\n\t\t *\n\t\t * @param {{name: string, kind: string}} closure The closure.\n\t\t * @return {string} The name.\n\t\t */\n\t\tclosureName(closure) {\n\t\t\tif (closure.name) {\n\t\t\t\treturn this.translate(closure.name)\n\t\t\t}\n\t\t\treturn closure.kind === 'exception' ? t('nextcloud-vue', 'Day added by hand') : ''\n\t\t},\n\n\t\t/**\n\t\t * Open Add day, with a date filled in.\n\t\t *\n\t\t * @param {string} [date] The date.\n\t\t * @return {void}\n\t\t */\n\t\topenAdd(date) {\n\t\t\tthis.newDate = date || this.focusDate || ''\n\t\t\tthis.newName = ''\n\t\t\tthis.saveError = ''\n\t\t\tthis.adding = true\n\t\t\tthis.$nextTick(() => this.$refs.dateInput?.focus?.())\n\t\t},\n\n\t\t/**\n\t\t * A click on a day: open Add day for it when the reader may edit.\n\t\t *\n\t\t * @param {object} slot The day.\n\t\t * @return {void}\n\t\t */\n\t\tonDayClick(slot) {\n\t\t\tthis.focusDate = slot.date\n\t\t\tif (this.editable) {\n\t\t\t\tthis.openAdd(slot.date)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Append the new day to the calendar's exceptions with one update,\n\t\t * under the reader's rights, then read the closures again.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-day-can-be-added-to-the-calendar\n\t\t */\n\t\tasync saveDay() {\n\t\t\tif (!/^\\d{4}-\\d{2}-\\d{2}$/.test(this.newDate) || !this.calendar) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\tthis.saveError = ''\n\t\t\ttry {\n\t\t\t\tconst stored = Array.isArray(this.calendar.exceptions) ? this.calendar.exceptions : []\n\t\t\t\tconst name = this.newName.trim()\n\t\t\t\tconst exceptions = [...stored.filter((e) => (typeof e === 'string' ? e : e?.date) !== this.newDate), name ? { date: this.newDate, name } : { date: this.newDate }]\n\t\t\t\tconst response = await axios.patch(this.objectUrl(), { exceptions })\n\t\t\t\tthis.calendar = { ...this.calendar, ...(response?.data && typeof response.data === 'object' ? response.data : {}), exceptions }\n\t\t\t\tconst year = Number(this.newDate.slice(0, 4))\n\t\t\t\tconst { [year]: _stale, ...rest } = this.previews\n\t\t\t\tthis.previews = rest\n\t\t\t\tconst [y, m] = this.newDate.split('-').map(Number)\n\t\t\t\tthis.year = y\n\t\t\t\tthis.month = m - 1\n\t\t\t\tthis.focusDate = this.newDate\n\t\t\t\tawait this.loadPreview()\n\t\t\t\tthis.adding = false\n\t\t\t\tthis.liveText = t('nextcloud-vue', 'Day added.')\n\t\t\t} catch (e) {\n\t\t\t\tthis.saveError = e?.response?.status === 403\n\t\t\t\t\t? t('nextcloud-vue', 'Only an administrator can change the working calendar.')\n\t\t\t\t\t: t('nextcloud-vue', 'The day could not be added. Try again.')\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home / End and Page Up / Down move focus across the days.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key event.\n\t\t * @return {void}\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst moves = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }\n\t\t\tconst [y, m, d] = (this.focusDate || todayIso()).split('-').map(Number)\n\t\t\tlet target\n\t\t\tif (event.key in moves) {\n\t\t\t\ttarget = new Date(y, m - 1, d + moves[event.key])\n\t\t\t} else if (event.key === 'Home' || event.key === 'End') {\n\t\t\t\tconst weekday = (new Date(y, m - 1, d).getDay() + 6) % 7\n\t\t\t\ttarget = new Date(y, m - 1, d + (event.key === 'Home' ? -weekday : 6 - weekday))\n\t\t\t} else if (event.key === 'PageUp' || event.key === 'PageDown') {\n\t\t\t\ttarget = new Date(y, m - 1 + (event.key === 'PageUp' ? -1 : 1), 1)\n\t\t\t\ttarget.setDate(Math.min(d, new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate()))\n\t\t\t} else if ((event.key === 'Enter' || event.key === ' ') && this.editable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.openAdd(this.focusDate)\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.focusDate = isoDate(target.getFullYear(), target.getMonth(), target.getDate())\n\t\t\tif (target.getFullYear() !== this.year || target.getMonth() !== this.month) {\n\t\t\t\tthis.year = target.getFullYear()\n\t\t\t\tthis.month = target.getMonth()\n\t\t\t\tthis.liveText = this.monthTitle\n\t\t\t}\n\t\t\tthis.$nextTick(() => this.$el.querySelector?.(`[data-date=\"${this.focusDate}\"]`)?.focus?.())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-working-calendar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-working-calendar__head {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-working-calendar__title {\n\tfont-size: 15px;\n\tmin-width: 9em;\n\ttext-align: center;\n}\n\n.cn-working-calendar__add {\n\tmargin-inline-start: auto;\n}\n\n.cn-working-calendar__grid {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-working-calendar__row {\n\tdisplay: grid;\n\tgap: 4px;\n\tgrid-template-columns: repeat(7, minmax(0, 1fr));\n}\n\n.cn-working-calendar__weekday {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\ttext-align: center;\n}\n\n.cn-working-calendar__day {\n\talign-items: center;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 6px;\n\tcursor: default;\n\tdisplay: flex;\n\tfont-size: 13px;\n\theight: 34px;\n\tjustify-content: center;\n}\n\n.cn-working-calendar__day:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 1px;\n}\n\n.cn-working-calendar__day--off {\n\tbackground: var(--color-background-dark);\n}\n\n.cn-working-calendar__day--closed {\n\tbackground: var(--color-error-light, color-mix(in srgb, var(--color-error) 12%, var(--color-main-background)));\n\tborder-color: var(--color-error);\n\tcolor: var(--color-error-text, var(--color-main-text));\n\tfont-weight: 600;\n}\n\n.cn-working-calendar__day--today {\n\tbox-shadow: inset 0 0 0 2px var(--color-primary-element);\n}\n\n.cn-working-calendar__live {\n\tclip-path: inset(50%);\n\theight: 1px;\n\toverflow: hidden;\n\tposition: absolute;\n\twhite-space: nowrap;\n\twidth: 1px;\n}\n\n.cn-working-calendar__note {\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0;\n}\n\n/* @spec openspec/changes/working-calendar-footnote/specs/working-calendar-footnote/spec.md */\n.cn-working-calendar__footnote {\n\tmargin: 0;\n\tfont-size: 13px;\n\tline-height: 1.3;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-working-calendar__form {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px 12px;\n}\n\n.cn-working-calendar__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-working-calendar__form-actions {\n\talign-items: flex-end;\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n.cn-working-calendar__error {\n\tcolor: var(--color-error-text, var(--color-error));\n\tmargin: 0;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createVNode","_Fragment","_createElementVNode","_createBlock","_renderList","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnWorkingCalendarWidget.vue3.js","sources":["../../../../src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue?vue&type=template&id=23316897&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-working-calendar\" data-testid=\"cn-working-calendar\">\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-working-calendar__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-working-calendar-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && !calendar\" class=\"cn-working-calendar__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" :name=\"loadingLabel\" />\n\t\t</div>\n\t\t<template v-else-if=\"calendar\">\n\t\t\t<div class=\"cn-working-calendar__head\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:aria-label=\"previousMonthLabel\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-previous\"\n\t\t\t\t\t@click=\"shiftMonth(-1)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcButton>\n\t\t\t\t<strong :id=\"`${uid}-title`\" class=\"cn-working-calendar__title\" data-testid=\"cn-working-calendar-title\">{{ monthTitle }}</strong>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:aria-label=\"nextMonthLabel\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-next\"\n\t\t\t\t\t@click=\"shiftMonth(1)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"editable && content.addButton !== false\"\n\t\t\t\t\tclass=\"cn-working-calendar__add\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-add\"\n\t\t\t\t\t@click=\"openAdd()\">\n\t\t\t\t\t{{ addLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p v-if=\"previewNote\" class=\"cn-working-calendar__note\" data-testid=\"cn-working-calendar-note\">\n\t\t\t\t{{ previewNote }}\n\t\t\t</p>\n\t\t\t<span class=\"cn-working-calendar__live\" aria-live=\"polite\">{{ liveText }}</span>\n\n\t\t\t<div\n\t\t\t\trole=\"grid\"\n\t\t\t\tclass=\"cn-working-calendar__grid\"\n\t\t\t\t:aria-labelledby=\"`${uid}-title`\"\n\t\t\t\tdata-testid=\"cn-working-calendar-grid\"\n\t\t\t\t@keydown=\"onKeydown\">\n\t\t\t\t<div role=\"row\" class=\"cn-working-calendar__row\">\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-for=\"name in weekdayNames\"\n\t\t\t\t\t\t:key=\"name.long\"\n\t\t\t\t\t\trole=\"columnheader\"\n\t\t\t\t\t\tclass=\"cn-working-calendar__weekday\"\n\t\t\t\t\t\t:aria-label=\"name.long\">{{ name.short }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(week, w) in weeks\"\n\t\t\t\t\t:key=\"w\"\n\t\t\t\t\trole=\"row\"\n\t\t\t\t\tclass=\"cn-working-calendar__row\">\n\t\t\t\t\t<template v-for=\"(slot, d) in week\">\n\t\t\t\t\t\t<span v-if=\"!slot\"\n\t\t\t\t\t\t\t:key=\"`empty-${w}-${d}`\"\n\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\tclass=\"cn-working-calendar__blank\" />\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:key=\"slot.date\"\n\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\tclass=\"cn-working-calendar__day\"\n\t\t\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--off': !slot.working && !slot.closure,\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--closed': slot.closure,\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--today': slot.today,\n\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t\t:tabindex=\"slot.date === focusDate ? 0 : -1\"\n\t\t\t\t\t\t\t:aria-label=\"dayLabel(slot)\"\n\t\t\t\t\t\t\t:aria-current=\"slot.today ? 'date' : null\"\n\t\t\t\t\t\t\t:title=\"slot.closure ? closureName(slot.closure) : null\"\n\t\t\t\t\t\t\t:data-date=\"slot.date\"\n\t\t\t\t\t\t\tdata-testid=\"cn-working-calendar-day\"\n\t\t\t\t\t\t\t@click=\"onDayClick(slot)\"\n\t\t\t\t\t\t\t@focus=\"focusDate = slot.date\">{{ slot.day }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<p v-if=\"content.footnote\" class=\"cn-working-calendar__footnote\" data-testid=\"cn-working-calendar-footnote\">\n\t\t\t\t{{ cnTranslate(content.footnote) }}\n\t\t\t</p>\n\n\t\t\t<form\n\t\t\t\tv-if=\"adding\"\n\t\t\t\tclass=\"cn-working-calendar__form\"\n\t\t\t\tdata-testid=\"cn-working-calendar-form\"\n\t\t\t\t@submit.prevent=\"saveDay\">\n\t\t\t\t<label class=\"cn-working-calendar__field\">\n\t\t\t\t\t{{ dateLabel }}\n\t\t\t\t\t<input\n\t\t\t\t\t\tref=\"dateInput\"\n\t\t\t\t\t\tv-model=\"newDate\"\n\t\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t\trequired\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-date\">\n\t\t\t\t</label>\n\t\t\t\t<label class=\"cn-working-calendar__field\">\n\t\t\t\t\t{{ nameLabel }}\n\t\t\t\t\t<input\n\t\t\t\t\t\tv-model=\"newName\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-name\">\n\t\t\t\t</label>\n\t\t\t\t<div class=\"cn-working-calendar__form-actions\">\n\t\t\t\t\t<NcButton variant=\"tertiary\" data-testid=\"cn-working-calendar-cancel\" @click=\"adding = false\">\n\t\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t:disabled=\"saving || !newDate\"\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-save\">\n\t\t\t\t\t\t{{ saveLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t\t<p v-if=\"saveError\"\n\t\t\t\t\tclass=\"cn-working-calendar__error\"\n\t\t\t\t\trole=\"alert\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-save-error\">\n\t\t\t\t\t{{ saveError }}\n\t\t\t\t</p>\n\t\t\t</form>\n\t\t</template>\n\t\t<p v-else class=\"cn-working-calendar__note\" data-testid=\"cn-working-calendar-empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcLoadingIcon } from '@nextcloud/vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport { fetchReferenceObjects } from '../../utils/boardReferenceColumns.js'\nimport { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { userLocale } from '../../utils/userLocale.js'\nimport { buildMonth, isoDate, normaliseExceptions } from '../../utils/workingCalendarMonth.js'\n\nconst OBJECT_API = '/apps/openregister/api/objects'\nconst PREVIEW_URL = '/apps/openregister/api/flow-timers/calendars/preview'\n\nlet instanceCounter = 0\n\n/**\n * The id of an object, wherever OpenRegister carries it.\n *\n * @param {object|null} object The object.\n * @return {string} The id, or ''.\n */\nfunction idOf(object) {\n\treturn String(object?.id ?? object?.['@self']?.id ?? object?.uuid ?? '')\n}\n\n/**\n * Today as `YYYY-MM-DD`, in local time.\n *\n * @return {string} The date.\n */\nfunction todayIso() {\n\tconst now = new Date()\n\treturn isoDate(now.getFullYear(), now.getMonth(), now.getDate())\n}\n\n/**\n * CnWorkingCalendarWidget — the `working-calendar` widget: one month of a\n * working calendar kept as an OpenRegister object (board\n * DqInstellingenAlgemeen, \"Werkdagen en feestdagen\").\n *\n * The grid runs Monday to Sunday. Weekdays the calendar does not work are\n * grey; the dates it is closed on are red, with their name on hover and in\n * the day's label. The closures are not computed here: the widget asks\n * OpenRegister's calendar preview (`/api/flow-timers/calendars/preview`) for\n * the year shown, which expands the calendar's rules and its exceptions.\n *\n * \"Add day\" appends `{ date, name }` to the object's `exceptions` with one\n * update through the object API, under the reader's own rights; it shows for\n * administrators, and the server decides. The preview is administrators-only\n * too; for anyone else the widget shows the stored exceptions and says the\n * holidays could not be read.\n *\n * ```json\n * {\n * \"type\": \"working-calendar\",\n * \"content\": {\n * \"calendar\": { \"register\": \"flow-timers\", \"schema\": \"working-calendar\", \"filter\": { \"slug\": \"default\" } }\n * }\n * }\n * ```\n *\n * Keyboard: the days form a grid with one tab stop; the arrow keys move a\n * day or a week, Home and End to the start and end of the week, Page Up and\n * Page Down a month. Enter on a day opens Add day for that date.\n */\nexport default {\n\tname: 'CnWorkingCalendarWidget',\n\n\tcomponents: { ChevronLeft, ChevronRight, NcButton, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The page's workspace values, for `@workspace.<key>` tokens in the filter. */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/** The host's translate, for the manifest's texts. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget config: `{ calendar: { register, schema, id? | filter? },\n\t\t * previewUrl?, editable?, addButton?, emptyText?, footnote? }`. `addButton: false` leaves the Add day button\n\t\t * out, for a host that draws it in its own title row and calls `openAdd()`. `footnote` is a\n\t\t * line under the month grid, through the host's translate: what the colours mean\n\t\t * (DqInstellingenAlgemeen \"Rood: feestdag of sluitingsdag.\").\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record's id, for `@objectId` in the filter. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The bound record, for `@object.<field>` in the filter. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\tinstanceCounter += 1\n\t\tconst now = new Date()\n\t\treturn {\n\t\t\tuid: `cn-working-calendar-${instanceCounter}`,\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tcalendar: null,\n\t\t\tpreviews: {},\n\t\t\tpreviewFailed: false,\n\t\t\tyear: now.getFullYear(),\n\t\t\tmonth: now.getMonth(),\n\t\t\tfocusDate: todayIso(),\n\t\t\tadding: false,\n\t\t\tnewDate: '',\n\t\t\tnewName: '',\n\t\t\tsaving: false,\n\t\t\tsaveError: '',\n\t\t\tliveText: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {{register: string, schema: string, id: string, filter: (object|null)}|null} The calendar source, tokens resolved, or null while unresolved. */\n\t\tsource() {\n\t\t\tconst c = this.content?.calendar\n\t\t\tif (!c || !c.register || !c.schema) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst unwrap = (v) => (v && typeof v === 'object' && 'value' in v ? v.value : v)\n\t\t\tconst ctx = { objectId: this.objectId || null, object: this.objectData, workspace: unwrap(this.cnWorkspaceContext) || {} }\n\t\t\tconst filter = c.filter && typeof c.filter === 'object' ? resolveFilterTokens(c.filter, ctx) : null\n\t\t\tif (filter && hasUnresolvedTokens(filter)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { register: String(c.register), schema: String(c.schema), id: c.id ? String(c.id) : '', filter }\n\t\t},\n\n\t\t/** @return {string} A key of the source, to read it again when it changes. */\n\t\tsourceKey() {\n\t\t\treturn this.source ? JSON.stringify(this.source) : ''\n\t\t},\n\n\t\t/** @return {boolean} Whether Add day shows: administrators, unless the manifest turns it off. */\n\t\teditable() {\n\t\t\treturn this.content?.editable !== false && Boolean(getCurrentUser()?.isAdmin)\n\t\t},\n\n\t\t/** @return {number[]} The ISO weekdays that work. */\n\t\tworkingWeekdays() {\n\t\t\tconst days = this.calendar?.workingWeekdays\n\t\t\treturn Array.isArray(days) ? days.map((d) => Number(d)) : [1, 2, 3, 4, 5]\n\t\t},\n\n\t\t/** @return {Array<{date: string, name: string}>} The stored exceptions. */\n\t\texceptions() {\n\t\t\treturn normaliseExceptions(this.calendar?.exceptions)\n\t\t},\n\n\t\t/**\n\t\t * The closed dates of the year shown: the preview's, each marked as an\n\t\t * exception when the calendar lists it, else as a holiday; without a\n\t\t * preview, the stored exceptions.\n\t\t *\n\t\t * @return {Map<string, {name: string, kind: string}>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month\n\t\t */\n\t\tclosures() {\n\t\t\tconst own = new Set(this.exceptions.map((e) => e.date))\n\t\t\tconst map = new Map()\n\t\t\tconst preview = this.previews[this.year]\n\t\t\tfor (const entry of Array.isArray(preview) ? preview : []) {\n\t\t\t\tif (entry && typeof entry.date === 'string') {\n\t\t\t\t\tmap.set(entry.date, { name: String(entry.name || ''), kind: own.has(entry.date) ? 'exception' : 'holiday' })\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const e of this.exceptions) {\n\t\t\t\tif (!map.has(e.date)) {\n\t\t\t\t\tmap.set(e.date, { name: e.name, kind: 'exception' })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn map\n\t\t},\n\n\t\t/** @return {Array<Array<object|null>>} The weeks of the month shown. */\n\t\tweeks() {\n\t\t\treturn buildMonth(this.year, this.month, { workingWeekdays: this.workingWeekdays, closures: this.closures, today: todayIso() })\n\t\t},\n\n\t\t/** @return {Array<{short: string, long: string}>} Monday to Sunday in the user's language. */\n\t\tweekdayNames() {\n\t\t\tconst short = new Intl.DateTimeFormat(userLocale(), { weekday: 'short' })\n\t\t\tconst long = new Intl.DateTimeFormat(userLocale(), { weekday: 'long' })\n\t\t\treturn Array.from({ length: 7 }, (_v, i) => {\n\t\t\t\tconst day = new Date(Date.UTC(2024, 0, 1 + i, 12))\n\t\t\t\treturn { short: short.format(day).replace(/\\.$/, ''), long: long.format(day) }\n\t\t\t})\n\t\t},\n\n\t\t/** @return {string} \"December 2026\" in the user's language. */\n\t\tmonthTitle() {\n\t\t\tconst text = new Intl.DateTimeFormat(userLocale(), { month: 'long', year: 'numeric' }).format(new Date(this.year, this.month, 15))\n\t\t\treturn text.charAt(0).toUpperCase() + text.slice(1)\n\t\t},\n\n\t\t/** @return {string} Why holidays are missing, or ''. */\n\t\tpreviewNote() {\n\t\t\treturn this.previewFailed ? t('nextcloud-vue', 'Holidays could not be read. Only the days added by hand are shown.') : ''\n\t\t},\n\n\t\tloadingLabel: () => t('nextcloud-vue', 'Loading …'),\n\t\tpreviousMonthLabel: () => t('nextcloud-vue', 'Previous month'),\n\t\tnextMonthLabel: () => t('nextcloud-vue', 'Next month'),\n\t\taddLabel: () => t('nextcloud-vue', 'Add day'),\n\t\tdateLabel: () => t('nextcloud-vue', 'Date'),\n\t\tnameLabel: () => t('nextcloud-vue', 'Name'),\n\t\tcancelLabel: () => t('nextcloud-vue', 'Cancel'),\n\t\tsaveLabel: () => t('nextcloud-vue', 'Save'),\n\n\t\t/** @return {string} The empty-state text. */\n\t\temptyLabel() {\n\t\t\treturn this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'No working calendar found.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\n\t\t\timmediate: true,\n\t\t},\n\n\t\tyear() {\n\t\t\tthis.loadPreview()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The host's translate, or the text unchanged.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The translation.\n\t\t */\n\t\ttranslate(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/** @return {string} The calendar object's API URL. */\n\t\tobjectUrl() {\n\t\t\tconst s = this.source\n\t\t\tconst id = idOf(this.calendar) || s.id\n\t\t\treturn generateUrl(`${OBJECT_API}/${encodeURIComponent(s.register)}/${encodeURIComponent(s.schema)}/${encodeURIComponent(id)}`)\n\t\t},\n\n\t\t/**\n\t\t * Read the calendar object, then the closures of the year shown.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst key = this.sourceKey\n\t\t\tif (key === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst s = this.source\n\t\t\t\tlet calendar = null\n\t\t\t\tif (s.id) {\n\t\t\t\t\tconst response = await axios.get(generateUrl(`${OBJECT_API}/${encodeURIComponent(s.register)}/${encodeURIComponent(s.schema)}/${encodeURIComponent(s.id)}`))\n\t\t\t\t\tcalendar = response?.data || null\n\t\t\t\t} else {\n\t\t\t\t\tconst found = await fetchReferenceObjects({ register: s.register, schema: s.schema, filter: s.filter || {}, limit: 1 })\n\t\t\t\t\tcalendar = found[0] || null\n\t\t\t\t}\n\t\t\t\tif (key !== this.sourceKey) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.calendar = calendar\n\t\t\t\tthis.previews = {}\n\t\t\t\tif (calendar) {\n\t\t\t\t\tawait this.loadPreview()\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tthis.error = t('nextcloud-vue', 'The working calendar could not be loaded.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Ask the calendar preview for the closed dates of the year shown,\n\t\t * once per year.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month\n\t\t */\n\t\tasync loadPreview() {\n\t\t\tconst year = this.year\n\t\t\tif (!this.calendar || this.previews[year]) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = generateUrl(this.content?.previewUrl || PREVIEW_URL)\n\t\t\t\tconst response = await axios.post(url, { calendar: this.calendar, year })\n\t\t\t\tconst dates = Array.isArray(response?.data?.dates) ? response.data.dates : []\n\t\t\t\tthis.previews = { ...this.previews, [year]: dates }\n\t\t\t\tthis.previewFailed = false\n\t\t\t} catch {\n\t\t\t\tthis.previewFailed = true\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Show another month, keeping focus on the same day where it exists.\n\t\t *\n\t\t * @param {number} delta Months to move.\n\t\t * @return {void}\n\t\t */\n\t\tshiftMonth(delta) {\n\t\t\tconst date = new Date(this.year, this.month + delta, 1)\n\t\t\tthis.year = date.getFullYear()\n\t\t\tthis.month = date.getMonth()\n\t\t\tconst day = Math.min(Number(this.focusDate.slice(8, 10)) || 1, new Date(this.year, this.month + 1, 0).getDate())\n\t\t\tthis.focusDate = isoDate(this.year, this.month, day)\n\t\t\tthis.liveText = this.monthTitle\n\t\t},\n\n\t\t/**\n\t\t * A day's accessible label: the full date, whether it works, and the\n\t\t * closure's name.\n\t\t *\n\t\t * @param {object} slot The day.\n\t\t * @return {string} The label.\n\t\t */\n\t\tdayLabel(slot) {\n\t\t\tconst [y, m, d] = slot.date.split('-').map(Number)\n\t\t\tconst date = new Intl.DateTimeFormat(userLocale(), { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }).format(new Date(y, m - 1, d))\n\t\t\tif (slot.closure) {\n\t\t\t\tconst name = this.closureName(slot.closure)\n\t\t\t\treturn name\n\t\t\t\t\t? t('nextcloud-vue', '{date}, closed: {name}', { date, name })\n\t\t\t\t\t: t('nextcloud-vue', '{date}, closed', { date })\n\t\t\t}\n\t\t\treturn slot.working ? t('nextcloud-vue', '{date}, working day', { date }) : t('nextcloud-vue', '{date}, not a working day', { date })\n\t\t},\n\n\t\t/**\n\t\t * A closure's name, with a fallback for an unnamed added day.\n\t\t *\n\t\t * @param {{name: string, kind: string}} closure The closure.\n\t\t * @return {string} The name.\n\t\t */\n\t\tclosureName(closure) {\n\t\t\tif (closure.name) {\n\t\t\t\treturn this.translate(closure.name)\n\t\t\t}\n\t\t\treturn closure.kind === 'exception' ? t('nextcloud-vue', 'Day added by hand') : ''\n\t\t},\n\n\t\t/**\n\t\t * Open Add day, with a date filled in.\n\t\t *\n\t\t * @param {string} [date] The date.\n\t\t * @return {void}\n\t\t */\n\t\topenAdd(date) {\n\t\t\tthis.newDate = date || this.focusDate || ''\n\t\t\tthis.newName = ''\n\t\t\tthis.saveError = ''\n\t\t\tthis.adding = true\n\t\t\tthis.$nextTick(() => this.$refs.dateInput?.focus?.())\n\t\t},\n\n\t\t/**\n\t\t * A click on a day: open Add day for it when the reader may edit.\n\t\t *\n\t\t * @param {object} slot The day.\n\t\t * @return {void}\n\t\t */\n\t\tonDayClick(slot) {\n\t\t\tthis.focusDate = slot.date\n\t\t\tif (this.editable) {\n\t\t\t\tthis.openAdd(slot.date)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Append the new day to the calendar's exceptions with one update,\n\t\t * under the reader's rights, then read the closures again.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-day-can-be-added-to-the-calendar\n\t\t */\n\t\tasync saveDay() {\n\t\t\tif (!/^\\d{4}-\\d{2}-\\d{2}$/.test(this.newDate) || !this.calendar) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\tthis.saveError = ''\n\t\t\ttry {\n\t\t\t\tconst stored = Array.isArray(this.calendar.exceptions) ? this.calendar.exceptions : []\n\t\t\t\tconst name = this.newName.trim()\n\t\t\t\tconst exceptions = [...stored.filter((e) => (typeof e === 'string' ? e : e?.date) !== this.newDate), name ? { date: this.newDate, name } : { date: this.newDate }]\n\t\t\t\tconst response = await axios.patch(this.objectUrl(), { exceptions })\n\t\t\t\tthis.calendar = { ...this.calendar, ...(response?.data && typeof response.data === 'object' ? response.data : {}), exceptions }\n\t\t\t\tconst year = Number(this.newDate.slice(0, 4))\n\t\t\t\tconst { [year]: _stale, ...rest } = this.previews\n\t\t\t\tthis.previews = rest\n\t\t\t\tconst [y, m] = this.newDate.split('-').map(Number)\n\t\t\t\tthis.year = y\n\t\t\t\tthis.month = m - 1\n\t\t\t\tthis.focusDate = this.newDate\n\t\t\t\tawait this.loadPreview()\n\t\t\t\tthis.adding = false\n\t\t\t\tthis.liveText = t('nextcloud-vue', 'Day added.')\n\t\t\t} catch (e) {\n\t\t\t\tthis.saveError = e?.response?.status === 403\n\t\t\t\t\t? t('nextcloud-vue', 'Only an administrator can change the working calendar.')\n\t\t\t\t\t: t('nextcloud-vue', 'The day could not be added. Try again.')\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home / End and Page Up / Down move focus across the days.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key event.\n\t\t * @return {void}\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst moves = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }\n\t\t\tconst [y, m, d] = (this.focusDate || todayIso()).split('-').map(Number)\n\t\t\tlet target\n\t\t\tif (event.key in moves) {\n\t\t\t\ttarget = new Date(y, m - 1, d + moves[event.key])\n\t\t\t} else if (event.key === 'Home' || event.key === 'End') {\n\t\t\t\tconst weekday = (new Date(y, m - 1, d).getDay() + 6) % 7\n\t\t\t\ttarget = new Date(y, m - 1, d + (event.key === 'Home' ? -weekday : 6 - weekday))\n\t\t\t} else if (event.key === 'PageUp' || event.key === 'PageDown') {\n\t\t\t\ttarget = new Date(y, m - 1 + (event.key === 'PageUp' ? -1 : 1), 1)\n\t\t\t\ttarget.setDate(Math.min(d, new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate()))\n\t\t\t} else if ((event.key === 'Enter' || event.key === ' ') && this.editable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.openAdd(this.focusDate)\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.focusDate = isoDate(target.getFullYear(), target.getMonth(), target.getDate())\n\t\t\tif (target.getFullYear() !== this.year || target.getMonth() !== this.month) {\n\t\t\t\tthis.year = target.getFullYear()\n\t\t\t\tthis.month = target.getMonth()\n\t\t\t\tthis.liveText = this.monthTitle\n\t\t\t}\n\t\t\tthis.$nextTick(() => this.$el.querySelector?.(`[data-date=\"${this.focusDate}\"]`)?.focus?.())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-working-calendar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-working-calendar__head {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-working-calendar__title {\n\tfont-size: 15px;\n\tmin-width: 9em;\n\ttext-align: center;\n}\n\n.cn-working-calendar__add {\n\tmargin-inline-start: auto;\n}\n\n.cn-working-calendar__grid {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-working-calendar__row {\n\tdisplay: grid;\n\tgap: 4px;\n\tgrid-template-columns: repeat(7, minmax(0, 1fr));\n}\n\n.cn-working-calendar__weekday {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\ttext-align: center;\n}\n\n.cn-working-calendar__day {\n\talign-items: center;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 6px;\n\tcursor: default;\n\tdisplay: flex;\n\tfont-size: 13px;\n\theight: 34px;\n\tjustify-content: center;\n}\n\n.cn-working-calendar__day:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 1px;\n}\n\n.cn-working-calendar__day--off {\n\tbackground: var(--color-background-dark);\n}\n\n.cn-working-calendar__day--closed {\n\tbackground: var(--color-error-light, color-mix(in srgb, var(--color-error) 12%, var(--color-main-background)));\n\tborder-color: var(--color-error);\n\tcolor: var(--color-error-text, var(--color-main-text));\n\tfont-weight: 600;\n}\n\n.cn-working-calendar__day--today {\n\tbox-shadow: inset 0 0 0 2px var(--color-primary-element);\n}\n\n.cn-working-calendar__live {\n\tclip-path: inset(50%);\n\theight: 1px;\n\toverflow: hidden;\n\tposition: absolute;\n\twhite-space: nowrap;\n\twidth: 1px;\n}\n\n/* A theme's paragraph rule sets every p's size and colour with !important\n (thematiq), so the note and the footnote also set its tokens.\n @spec openspec/changes/calendar-footnote-paragraph-tokens/specs/working-calendar-footnote/spec.md */\n.cn-working-calendar__note {\n\t--nldesign-component-paragraph-color: var(--color-text-maxcontrast);\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0;\n}\n\n/* @spec openspec/changes/working-calendar-footnote/specs/working-calendar-footnote/spec.md */\n.cn-working-calendar__footnote {\n\t--nldesign-component-paragraph-font-size: 13px;\n\t--nldesign-component-paragraph-line-height: 1.3;\n\t--nldesign-component-paragraph-color: var(--color-text-maxcontrast);\n\tmargin: 0;\n\tfont-size: 13px;\n\tline-height: 1.3;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-working-calendar__form {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px 12px;\n}\n\n.cn-working-calendar__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-working-calendar__form-actions {\n\talign-items: flex-end;\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n.cn-working-calendar__error {\n\tcolor: var(--color-error-text, var(--color-error));\n\tmargin: 0;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createVNode","_Fragment","_createElementVNode","_createBlock","_renderList","_createTextVNode"],"mappings":"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"}
|
|
@@ -1402,6 +1402,32 @@ function searchSource(config = {}, context = {}) {
|
|
|
1402
1402
|
}
|
|
1403
1403
|
}
|
|
1404
1404
|
|
|
1405
|
+
/**
|
|
1406
|
+
* The members that match a search, in the asked order, and the page of them
|
|
1407
|
+
* the pager asks for.
|
|
1408
|
+
*
|
|
1409
|
+
* @param {Array<object>} members The members, by name.
|
|
1410
|
+
* @param {object} config `search`, `sortKey`, `sortOrder`, `limit`, `offset`.
|
|
1411
|
+
* @return {{rows: Array<object>, total: number}} The page and how many match.
|
|
1412
|
+
*/
|
|
1413
|
+
function membersPage(members, config = {}) {
|
|
1414
|
+
const term = String(config.search || '').trim().toLowerCase();
|
|
1415
|
+
let rows = term === ''
|
|
1416
|
+
? members.slice()
|
|
1417
|
+
: members.filter((member) => ['displayname', 'function', 'roles', 'email'].some((field) => String(member[field] || '').toLowerCase().includes(term)));
|
|
1418
|
+
if (config.sortKey) {
|
|
1419
|
+
const sign = config.sortOrder === 'desc' ? -1 : 1;
|
|
1420
|
+
rows = rows.sort((a, b) => sign * String(a[config.sortKey] ?? '').localeCompare(String(b[config.sortKey] ?? '')));
|
|
1421
|
+
}
|
|
1422
|
+
const total = rows.length;
|
|
1423
|
+
const limit = Number(config.limit) || 0;
|
|
1424
|
+
if (limit > 0) {
|
|
1425
|
+
const offset = Number(config.offset) || 0;
|
|
1426
|
+
rows = rows.slice(offset, offset + limit);
|
|
1427
|
+
}
|
|
1428
|
+
return { rows, total }
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1405
1431
|
/**
|
|
1406
1432
|
* The users source: the members of a Nextcloud group, with their function,
|
|
1407
1433
|
* roles, e-mail and whether they are in today (`entitySource: "users"`,
|
|
@@ -1418,12 +1444,20 @@ function searchSource(config = {}, context = {}) {
|
|
|
1418
1444
|
* Listing a group needs an admin or the group's sub-admin; a refused request
|
|
1419
1445
|
* leaves the list empty.
|
|
1420
1446
|
*
|
|
1447
|
+
* The page gets a pager (`pages`), and the search term and the header's order
|
|
1448
|
+
* come as `search` and `sortKey` / `sortOrder`: the group's members are read
|
|
1449
|
+
* once and searched, sorted and paged here, so a new page, term or order does
|
|
1450
|
+
* not read every member's presence again.
|
|
1451
|
+
*
|
|
1421
1452
|
* @return {object} The source adapter.
|
|
1422
1453
|
* @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members
|
|
1423
1454
|
*/
|
|
1424
1455
|
function usersSource() {
|
|
1425
|
-
const state = reactive({ rows: [], loading: false });
|
|
1456
|
+
const state = reactive({ rows: [], loading: false, total: 0 });
|
|
1426
1457
|
let seq = 0;
|
|
1458
|
+
// The members last read, for the group and request they were read for.
|
|
1459
|
+
let readMembers = [];
|
|
1460
|
+
let membersKey = null;
|
|
1427
1461
|
const presenceLabels = {
|
|
1428
1462
|
present: translate('nextcloud-vue', 'Present'),
|
|
1429
1463
|
away: translate('nextcloud-vue', 'Away'),
|
|
@@ -1442,6 +1476,15 @@ function usersSource() {
|
|
|
1442
1476
|
*/
|
|
1443
1477
|
load: async (config) => {
|
|
1444
1478
|
const group = String((config && (config.group || config.defaultGroup)) || '');
|
|
1479
|
+
const { search, sortKey, sortOrder, limit, offset, ...request } = config || {};
|
|
1480
|
+
const key = JSON.stringify({ ...request, group });
|
|
1481
|
+
// A new page, term or order of the members already read: no request.
|
|
1482
|
+
if (key === membersKey && (offset !== undefined || search !== undefined || sortKey !== undefined)) {
|
|
1483
|
+
const page = membersPage(readMembers, config);
|
|
1484
|
+
state.rows = page.rows;
|
|
1485
|
+
state.total = page.total;
|
|
1486
|
+
return
|
|
1487
|
+
}
|
|
1445
1488
|
const mine = ++seq;
|
|
1446
1489
|
state.loading = true;
|
|
1447
1490
|
try {
|
|
@@ -1476,11 +1519,18 @@ function usersSource() {
|
|
|
1476
1519
|
}
|
|
1477
1520
|
}));
|
|
1478
1521
|
if (mine === seq) {
|
|
1479
|
-
|
|
1522
|
+
readMembers = rows.sort((a, b) => a.displayname.localeCompare(b.displayname));
|
|
1523
|
+
membersKey = key;
|
|
1524
|
+
const page = membersPage(readMembers, config);
|
|
1525
|
+
state.rows = page.rows;
|
|
1526
|
+
state.total = page.total;
|
|
1480
1527
|
}
|
|
1481
1528
|
} catch (error) {
|
|
1482
1529
|
if (mine === seq) {
|
|
1530
|
+
readMembers = [];
|
|
1531
|
+
membersKey = null;
|
|
1483
1532
|
state.rows = [];
|
|
1533
|
+
state.total = 0;
|
|
1484
1534
|
}
|
|
1485
1535
|
throw error
|
|
1486
1536
|
} finally {
|
|
@@ -1491,10 +1541,26 @@ function usersSource() {
|
|
|
1491
1541
|
},
|
|
1492
1542
|
|
|
1493
1543
|
/**
|
|
1494
|
-
* @return {Array<object>} The members.
|
|
1544
|
+
* @return {Array<object>} The members on this page.
|
|
1495
1545
|
*/
|
|
1496
1546
|
rows: () => state.rows,
|
|
1497
1547
|
|
|
1548
|
+
pages: true,
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* @return {number} The members that match, on every page.
|
|
1552
|
+
*/
|
|
1553
|
+
total: () => state.total,
|
|
1554
|
+
|
|
1555
|
+
searchParam: 'search',
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* @param {string} key The column.
|
|
1559
|
+
* @param {string} order `asc` or `desc`.
|
|
1560
|
+
* @return {object} The load arguments.
|
|
1561
|
+
*/
|
|
1562
|
+
sortParam: (key, order) => ({ sortKey: key, sortOrder: order }),
|
|
1563
|
+
|
|
1498
1564
|
/**
|
|
1499
1565
|
* @return {boolean} Whether a load is in flight.
|
|
1500
1566
|
*/
|