@conduction/nextcloud-vue 2.142.0 → 2.144.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 +7 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- 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 +27 -0
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue2.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js +2 -1
- package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js +9 -0
- package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js.map +1 -1
- package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js +21 -22
- package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +29 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js +28 -13
- 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 +2 -1
- package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +1 -1
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.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/CnEventsCard/CnEventsCard.vue.js +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js +9 -0
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js.map +1 -1
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js +24 -13
- package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +29 -3
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +4 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +17 -4
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +21 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +18 -2
- 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/CnNotesCard/CnNotesCard.vue.js +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +3 -0
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js +19 -14
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +30 -0
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +158 -102
- package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +6 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +12 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +2 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.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 +12 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue.js +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +110 -2
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +146 -80
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +4 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +18 -8
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +45 -0
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js +67 -34
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +3 -22
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/eventStatus.js +67 -0
- package/dist/esm/integrations/builtin/calendar/eventStatus.js.map +1 -0
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +39 -0
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +81 -32
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +5 -6
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/deckCardParts.js +59 -0
- package/dist/esm/integrations/builtin/deck/deckCardParts.js.map +1 -0
- package/dist/esm/l10n/en.json.js +6 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +7 -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 +1264 -597
- package/dist/esm/utils/fetchFilterCounts.js +5 -2
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
- package/dist/esm/utils/nextcloudDirectory.js +19 -6
- package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1619 -849
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1264 -597
- package/l10n/en.json +6 -0
- package/l10n/nl.json +7 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +28 -3
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +28 -1
- package/src/components/CnAiCompanion/CnAiChatPanel.vue +12 -0
- package/src/components/CnAiCompanion/CnAiInput.vue +32 -1
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +34 -4
- package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +1 -0
- package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +8 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +1 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +5 -2
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +1 -0
- package/src/components/CnEventsCard/CnEventsCard.vue +27 -5
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +43 -4
- package/src/components/CnFilesBrowser/filesBrowser.js +17 -4
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +20 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +18 -2
- package/src/components/CnNotesCard/CnNotesCard.vue +8 -0
- package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +114 -46
- package/src/components/CnPageHeader/CnHeaderTrail.vue +6 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +21 -1
- package/src/components/CnRetentionWidget/CnRetentionWidget.vue +26 -1
- package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +6 -0
- package/src/components/CnTasksWidget/CnTasksWidget.vue +170 -13
- package/src/components/CnTimelineWidget/CnTimelineManage.vue +10 -1
- package/src/css/dialog.css +25 -1
- package/src/css/kpi-card.css +3 -0
- package/src/css/look-board-detail.css +348 -0
- package/src/css/look-board-index.css +56 -1
- package/src/css/look-board.css +98 -1
- package/src/integrations/builtin/calendar/CnCalendarCard.vue +76 -11
- package/src/integrations/builtin/calendar/CnCalendarTab.vue +3 -22
- package/src/integrations/builtin/calendar/__tests__/CnCalendarCardBoardRows.spec.js +52 -0
- package/src/integrations/builtin/calendar/eventStatus.js +63 -0
- package/src/integrations/builtin/deck/CnDeckCard.vue +66 -0
- package/src/integrations/builtin/deck/CnDeckTab.vue +5 -6
- package/src/integrations/builtin/deck/__tests__/CnDeckCardBoardRows.spec.js +49 -0
- package/src/integrations/builtin/deck/deckCardParts.js +56 -0
- package/src/utils/fetchFilterCounts.js +5 -2
- package/src/utils/nextcloudDirectory.js +19 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnEventsCard.vue2.js","sources":["../../../../src/components/CnEventsCard/CnEventsCard.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnEventsCard — the appointments booked on a record, as a list\n (`type: \"events\"`). Each row is a calendar event as OpenRegister answers\n it: `summary`, `dtstart` / `dtend` and `location`. It reads a `source`\n (`{ kind: \"flow-task\" }` reads `/api/flow-tasks/{id}/events`) or a plain\n `endpoint`. With `content.headerActions` (`link`, `create`) it reports\n \"Bestaande koppelen\" and \"Afspraak maken\" for its card's title row and runs\n the calendar leaf's own flows against that same address.\n-->\n<template>\n\t<div class=\"cn-events-card\" data-testid=\"cn-events-card\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t<p\n\t\t\tv-else-if=\"failed\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-failed\">\n\t\t\t{{ failedText }}\n\t\t</p>\n\t\t<ul v-else-if=\"events.length > 0\" class=\"cn-events-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"(event, index) in events\"\n\t\t\t\t:key=\"keyOf(event, index)\"\n\t\t\t\tclass=\"cn-events-card__event\"\n\t\t\t\tdata-testid=\"cn-events-card-event\">\n\t\t\t\t<span class=\"cn-events-card__summary\">{{ summaryOf(event) }}</span>\n\t\t\t\t<span class=\"cn-events-card__when\">{{ whenOf(event) }}</span>\n\t\t\t\t<span v-if=\"event.location\" class=\"cn-events-card__where\">{{ event.location }}</span>\n\t\t\t</li>\n\t\t</ul>\n\t\t<p v-else class=\"cn-events-card__muted\" data-testid=\"cn-events-card-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\t\t<p\n\t\t\tv-if=\"linkError\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-link-error\">\n\t\t\t{{ linkError }}\n\t\t</p>\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"showPicker = false\" />\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:objectId=\"String(objectId)\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:eventsUrl=\"url\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"showCreate = false\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CnCalendarEventCreate from '../CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport { linkCalendarEvent } from '../../integrations/builtin/calendar/calendarEventLink.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { leafSourceUrl } from '../../utils/leafSourceUrl.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnEventsCard — the appointments on a record, read from a `source` or an\n * `endpoint`, as a list of summary, time and place.\n *\n * ```vue\n * <CnEventsCard :source=\"{ kind: 'flow-task' }\" :objectId=\"taskUuid\" />\n * ```\n *\n * As a manifest widget:\n *\n * ```json\n * { \"id\": \"task-appointments\", \"type\": \"events\", \"title\": \"Appointments\", \"content\": { \"source\": { \"kind\": \"flow-task\" } } }\n * ```\n *\n * @spec openspec/changes/leaf-cards-read-a-source/specs/leaf-cards-read-a-source/spec.md#requirement-a-leaf-card-reads-a-source-or-an-endpoint\n */\nexport default {\n\tname: 'CnEventsCard',\n\n\tcomponents: { CnCalendarEventCreate, CnCalendarEventPicker, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The host app's translate function (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Where the events come from: `{ kind: 'flow-task', id? }`. `id` may be\n\t\t * left out or written `@objectId` for `objectId`. Also read from\n\t\t * `content.source`.\n\t\t *\n\t\t * @type {{kind: string, id?: (string|number)}|null}\n\t\t */\n\t\tsource: { type: Object, default: null },\n\n\t\t/** An events URL of its own; `{id}` is filled with the record id. Also read from `content.endpoint`. */\n\t\tendpoint: { type: String, default: '' },\n\n\t\t/** The page's record id. */\n\t\tobjectId: { type: [String, Number], default: '' },\n\n\t\t/** The OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t/**\n\t\t * The widget's manifest content: `source`, `endpoint`, `emptyText`\n\t\t * (translated through the host) and `headerActions` (`link`, `create`):\n\t\t * the buttons it reports for its card's title row.\n\t\t *\n\t\t * `createVariant: \"secondary\"` and `createIcon` draw the create action\n\t\t * as the quiet header action with its own icon. `menuActions` (`link`,\n\t\t * `create`) puts those flows in the card's Actions menu instead.\n\t\t *\n\t\t * @type {{source?: object, endpoint?: string, emptyText?: string, headerActions?: Array<string>, menuActions?: Array<string>, createVariant?: string, createIcon?: string}}\n\t\t */\n\t\tcontent: { type: Object, default: () => ({}) },\n\n\t\t/** Shown when the record has no appointments. */\n\t\temptyLabel: { type: String, default: '' },\n\n\t\t/** Shown when the read failed. */\n\t\tfailedLabel: { type: String, default: '' },\n\t},\n\n\temits: ['header'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t\tshowCreate: false,\n\t\t\tshowPicker: false,\n\t\t\tlinkError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The URL the card reads, '' while the record id is unknown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\turl() {\n\t\t\tconst content = this.content || {}\n\t\t\tconst source = this.source || (content.source && typeof content.source === 'object' ? content.source : null)\n\t\t\tconst endpoint = this.endpoint || (typeof content.endpoint === 'string' ? content.endpoint : '')\n\t\t\treturn leafSourceUrl({ source, endpoint, apiBase: this.apiBase, objectId: this.objectId }, 'events') || ''\n\t\t},\n\n\t\t/** @return {string} The empty line. */\n\t\temptyText() {\n\t\t\tif (this.emptyLabel) {\n\t\t\t\treturn this.emptyLabel\n\t\t\t}\n\t\t\tconst override = this.content && this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(override) : override\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'No appointments yet')\n\t\t},\n\n\t\t/** @return {string} The failure line. */\n\t\tfailedText() {\n\t\t\treturn this.failedLabel || t('nextcloud-vue', 'The appointments could not be loaded')\n\t\t},\n\t},\n\n\twatch: {\n\t\turl: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t\tthis.reportHeader()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Report the title-row buttons (`content.headerActions`) to the host\n\t\t * card: \"Bestaande koppelen\" opens the event picker, \"Afspraak maken\"\n\t\t * the meeting form. Nothing while the record is not known yet.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\treportHeader() {\n\t\t\tconst asked = Array.isArray(this.content?.headerActions) ? this.content.headerActions : []\n\t\t\tconst known = {\n\t\t\t\tlink: { label: t('nextcloud-vue', 'Link an existing one'), icon: 'LinkVariant', run: () => { this.showPicker = true } },\n\t\t\t\tcreate: {\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Make an appointment'),\n\t\t\t\t\ticon: typeof this.content?.createIcon === 'string' && this.content.createIcon !== '' ? this.content.createIcon : 'Plus',\n\t\t\t\t\t...(this.content?.createVariant === 'secondary' ? {} : { variant: 'primary' }),\n\t\t\t\t\trun: () => { this.showCreate = true },\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst pick = (keys) => (this.url === '' ? [] : keys.filter((key, index) => known[key] && keys.indexOf(key) === index).map((key) => known[key]))\n\t\t\tconst actions = pick(asked)\n\t\t\tconst inMenu = Array.isArray(this.content?.menuActions) ? this.content.menuActions.filter((key) => !asked.includes(key)) : []\n\t\t\t/**\n\t\t\t * @event header The card's title-row actions and Actions-menu items, `{ actions, menuActions }`, for the host to draw.\n\t\t\t */\n\t\t\tthis.$emit('header', { actions, menuActions: pick(inMenu) })\n\t\t},\n\n\t\t/**\n\t\t * A meeting was made on the record: close the form, read the list again.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonEventCreated() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.load()\n\t\t},\n\n\t\t/**\n\t\t * Link the picked meeting to the record, then read the list again, or say why not.\n\t\t *\n\t\t * @param {{calendarUri: string, eventUid: string}} payload The picked event.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.showPicker = false\n\t\t\tconst result = await linkCalendarEvent(prefixUrl(this.url), payload)\n\t\t\tthis.linkError = result.ok ? '' : result.error\n\t\t\tif (result.ok) {\n\t\t\t\tthis.load()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the events. A refused or failed read says so, rather than\n\t\t * showing an empty list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst url = this.url\n\t\t\tif (url === '') {\n\t\t\t\tthis.events = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tconst list = data && Array.isArray(data.results) ? data.results : data\n\t\t\t\tthis.events = Array.isArray(list) ? list : []\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure the card already shows\n\t\t\t\tconsole.error('[CnEventsCard] Loading the events failed', error)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failed = true\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 * @param {object} event The event.\n\t\t * @param {number} index Its position, for an event that carries no id.\n\t\t * @return {string} A stable key.\n\t\t */\n\t\tkeyOf(event, index) {\n\t\t\treturn String(event?.id ?? event?.uid ?? `${index}-${event?.summary ?? ''}`)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} Its summary, or \"Appointment\" when it has none.\n\t\t */\n\t\tsummaryOf(event) {\n\t\t\tconst summary = String(event?.summary ?? '').trim()\n\t\t\treturn summary === '' ? t('nextcloud-vue', 'Appointment') : summary\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} When it is: its start and end, or whichever it has.\n\t\t */\n\t\twhenOf(event) {\n\t\t\tconst start = formatMoment(event?.dtstart)\n\t\t\tconst end = formatMoment(event?.dtend)\n\t\t\tif (start === '' || end === '') {\n\t\t\t\treturn start || end\n\t\t\t}\n\t\t\treturn `${start} - ${end}`\n\t\t},\n\t},\n}\n\n/**\n * @param {unknown} value An ISO date-time.\n * @return {string} It in the user's locale, as written when unreadable, '' when empty.\n */\nfunction formatMoment(value) {\n\tconst raw = String(value ?? '').trim()\n\tif (raw === '') {\n\t\treturn ''\n\t}\n\tconst parsed = new Date(raw)\n\treturn Number.isNaN(parsed.getTime()) ? raw : parsed.toLocaleString(userLocale())\n}\n</script>\n\n<style scoped>\n.cn-events-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-events-card__event {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tpadding-bottom: 8px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-events-card__event:last-child {\n\tpadding-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cn-events-card__summary {\n\tfont-weight: 600;\n}\n\n.cn-events-card__when,\n.cn-events-card__where,\n.cn-events-card__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-events-card__muted {\n\tmargin: 0;\n}\n</style>\n"],"names":["CnCalendarEventCreate","CnCalendarEventPicker","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnEventsCard.vue2.js","sources":["../../../../src/components/CnEventsCard/CnEventsCard.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnEventsCard — the appointments booked on a record, as a list\n (`type: \"events\"`). Each row is a calendar event as OpenRegister answers\n it: `summary`, `dtstart` / `dtend` and `location`. It reads a `source`\n (`{ kind: \"flow-task\" }` reads `/api/flow-tasks/{id}/events`) or a plain\n `endpoint`. With `content.headerActions` (`link`, `create`) it reports\n \"Bestaande koppelen\" and \"Afspraak maken\" for its card's title row and runs\n the calendar leaf's own flows against that same address.\n-->\n<template>\n\t<div class=\"cn-events-card\" data-testid=\"cn-events-card\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t<p\n\t\t\tv-else-if=\"failed\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-failed\">\n\t\t\t{{ failedText }}\n\t\t</p>\n\t\t<ul v-else-if=\"events.length > 0\" class=\"cn-events-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"(event, index) in events\"\n\t\t\t\t:key=\"keyOf(event, index)\"\n\t\t\t\tclass=\"cn-events-card__event\"\n\t\t\t\tdata-testid=\"cn-events-card-event\">\n\t\t\t\t<span v-if=\"dayOf(event)\" class=\"cn-events-card__day\" aria-hidden=\"true\">{{ dayOf(event) }}</span>\n\t\t\t\t<span class=\"cn-events-card__text\">\n\t\t\t\t\t<span class=\"cn-events-card__summary\">{{ summaryOf(event) }}</span>\n\t\t\t\t\t<span class=\"cn-events-card__when\">{{ whenOf(event) }}</span>\n\t\t\t\t\t<span v-if=\"event.location\" class=\"cn-events-card__where\">{{ event.location }}</span>\n\t\t\t\t</span>\n\t\t\t</li>\n\t\t</ul>\n\t\t<p v-else class=\"cn-events-card__muted\" data-testid=\"cn-events-card-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\t\t<p\n\t\t\tv-if=\"linkError\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-link-error\">\n\t\t\t{{ linkError }}\n\t\t</p>\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"showPicker = false\" />\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:objectId=\"String(objectId)\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:eventsUrl=\"url\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"showCreate = false\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CnCalendarEventCreate from '../CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport { linkCalendarEvent } from '../../integrations/builtin/calendar/calendarEventLink.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { leafSourceUrl } from '../../utils/leafSourceUrl.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnEventsCard — the appointments on a record, read from a `source` or an\n * `endpoint`, as a list of summary, time and place.\n *\n * ```vue\n * <CnEventsCard :source=\"{ kind: 'flow-task' }\" :objectId=\"taskUuid\" />\n * ```\n *\n * As a manifest widget:\n *\n * ```json\n * { \"id\": \"task-appointments\", \"type\": \"events\", \"title\": \"Appointments\", \"content\": { \"source\": { \"kind\": \"flow-task\" } } }\n * ```\n *\n * @spec openspec/changes/leaf-cards-read-a-source/specs/leaf-cards-read-a-source/spec.md#requirement-a-leaf-card-reads-a-source-or-an-endpoint\n */\nexport default {\n\tname: 'CnEventsCard',\n\n\tcomponents: { CnCalendarEventCreate, CnCalendarEventPicker, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The host app's translate function (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Where the events come from: `{ kind: 'flow-task', id? }`. `id` may be\n\t\t * left out or written `@objectId` for `objectId`. Also read from\n\t\t * `content.source`.\n\t\t *\n\t\t * @type {{kind: string, id?: (string|number)}|null}\n\t\t */\n\t\tsource: { type: Object, default: null },\n\n\t\t/** An events URL of its own; `{id}` is filled with the record id. Also read from `content.endpoint`. */\n\t\tendpoint: { type: String, default: '' },\n\n\t\t/** The page's record id. */\n\t\tobjectId: { type: [String, Number], default: '' },\n\n\t\t/** The OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t/**\n\t\t * The widget's manifest content: `source`, `endpoint`, `emptyText`\n\t\t * (translated through the host) and `headerActions` (`link`, `create`):\n\t\t * the buttons it reports for its card's title row.\n\t\t *\n\t\t * `createVariant: \"secondary\"` and `createIcon` draw the create action\n\t\t * as the quiet header action with its own icon. `menuActions` (`link`,\n\t\t * `create`) puts those flows in the card's Actions menu instead.\n\t\t *\n\t\t * @type {{source?: object, endpoint?: string, emptyText?: string, headerActions?: Array<string>, menuActions?: Array<string>, createVariant?: string, createIcon?: string}}\n\t\t */\n\t\tcontent: { type: Object, default: () => ({}) },\n\n\t\t/** Shown when the record has no appointments. */\n\t\temptyLabel: { type: String, default: '' },\n\n\t\t/** Shown when the read failed. */\n\t\tfailedLabel: { type: String, default: '' },\n\t},\n\n\temits: ['header'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t\tshowCreate: false,\n\t\t\tshowPicker: false,\n\t\t\tlinkError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The URL the card reads, '' while the record id is unknown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\turl() {\n\t\t\tconst content = this.content || {}\n\t\t\tconst source = this.source || (content.source && typeof content.source === 'object' ? content.source : null)\n\t\t\tconst endpoint = this.endpoint || (typeof content.endpoint === 'string' ? content.endpoint : '')\n\t\t\treturn leafSourceUrl({ source, endpoint, apiBase: this.apiBase, objectId: this.objectId }, 'events') || ''\n\t\t},\n\n\t\t/** @return {string} The empty line. */\n\t\temptyText() {\n\t\t\tif (this.emptyLabel) {\n\t\t\t\treturn this.emptyLabel\n\t\t\t}\n\t\t\tconst override = this.content && this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(override) : override\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'No appointments yet')\n\t\t},\n\n\t\t/** @return {string} The failure line. */\n\t\tfailedText() {\n\t\t\treturn this.failedLabel || t('nextcloud-vue', 'The appointments could not be loaded')\n\t\t},\n\t},\n\n\twatch: {\n\t\turl: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t\tthis.reportHeader()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Report the title-row buttons (`content.headerActions`) to the host\n\t\t * card: \"Bestaande koppelen\" opens the event picker, \"Afspraak maken\"\n\t\t * the meeting form. Nothing while the record is not known yet.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\treportHeader() {\n\t\t\tconst asked = Array.isArray(this.content?.headerActions) ? this.content.headerActions : []\n\t\t\tconst known = {\n\t\t\t\tlink: { label: t('nextcloud-vue', 'Link an existing one'), icon: 'LinkVariant', run: () => { this.showPicker = true } },\n\t\t\t\tcreate: {\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Make an appointment'),\n\t\t\t\t\ticon: typeof this.content?.createIcon === 'string' && this.content.createIcon !== '' ? this.content.createIcon : 'Plus',\n\t\t\t\t\t...(this.content?.createVariant === 'secondary' ? {} : { variant: 'primary' }),\n\t\t\t\t\trun: () => { this.showCreate = true },\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst pick = (keys) => (this.url === '' ? [] : keys.filter((key, index) => known[key] && keys.indexOf(key) === index).map((key) => known[key]))\n\t\t\tconst actions = pick(asked)\n\t\t\tconst inMenu = Array.isArray(this.content?.menuActions) ? this.content.menuActions.filter((key) => !asked.includes(key)) : []\n\t\t\t/**\n\t\t\t * @event header The card's title-row actions and Actions-menu items, `{ actions, menuActions }`, for the host to draw.\n\t\t\t */\n\t\t\tthis.$emit('header', { actions, menuActions: pick(inMenu) })\n\t\t},\n\n\t\t/**\n\t\t * A meeting was made on the record: close the form, read the list again.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonEventCreated() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.load()\n\t\t},\n\n\t\t/**\n\t\t * Link the picked meeting to the record, then read the list again, or say why not.\n\t\t *\n\t\t * @param {{calendarUri: string, eventUid: string}} payload The picked event.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.showPicker = false\n\t\t\tconst result = await linkCalendarEvent(prefixUrl(this.url), payload)\n\t\t\tthis.linkError = result.ok ? '' : result.error\n\t\t\tif (result.ok) {\n\t\t\t\tthis.load()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the events. A refused or failed read says so, rather than\n\t\t * showing an empty list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst url = this.url\n\t\t\tif (url === '') {\n\t\t\t\tthis.events = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tconst list = data && Array.isArray(data.results) ? data.results : data\n\t\t\t\tthis.events = Array.isArray(list) ? list : []\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure the card already shows\n\t\t\t\tconsole.error('[CnEventsCard] Loading the events failed', error)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failed = true\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 * @param {object} event The event.\n\t\t * @param {number} index Its position, for an event that carries no id.\n\t\t * @return {string} A stable key.\n\t\t */\n\t\tkeyOf(event, index) {\n\t\t\treturn String(event?.id ?? event?.uid ?? `${index}-${event?.summary ?? ''}`)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} Its summary, or \"Appointment\" when it has none.\n\t\t */\n\t\tsummaryOf(event) {\n\t\t\tconst summary = String(event?.summary ?? '').trim()\n\t\t\treturn summary === '' ? t('nextcloud-vue', 'Appointment') : summary\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} The day of the month it starts on, '' when it has no readable start.\n\t\t */\n\t\tdayOf(event) {\n\t\t\tconst parsed = new Date(String(event?.dtstart ?? '').trim())\n\t\t\treturn Number.isNaN(parsed.getTime()) ? '' : String(parsed.getDate())\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} When it is: its start and end, or whichever it has.\n\t\t */\n\t\twhenOf(event) {\n\t\t\tconst start = formatMoment(event?.dtstart)\n\t\t\tconst end = formatMoment(event?.dtend)\n\t\t\tif (start === '' || end === '') {\n\t\t\t\treturn start || end\n\t\t\t}\n\t\t\treturn `${start} - ${end}`\n\t\t},\n\t},\n}\n\n/**\n * @param {unknown} value An ISO date-time.\n * @return {string} It in the user's locale, as written when unreadable, '' when empty.\n */\nfunction formatMoment(value) {\n\tconst raw = String(value ?? '').trim()\n\tif (raw === '') {\n\t\treturn ''\n\t}\n\tconst parsed = new Date(raw)\n\treturn Number.isNaN(parsed.getTime()) ? raw : parsed.toLocaleString(userLocale())\n}\n</script>\n\n<style scoped>\n.cn-events-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-events-card__event {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding-bottom: 8px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-events-card__event:last-child {\n\tpadding-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cn-events-card__day {\n\tdisplay: none;\n}\n\n.cn-events-card__text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-events-card__summary {\n\tfont-weight: 600;\n}\n\n.cn-events-card__when,\n.cn-events-card__where,\n.cn-events-card__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-events-card__muted {\n\tmargin: 0;\n}\n</style>\n"],"names":["CnCalendarEventCreate","CnCalendarEventPicker","t"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveComponent, openBlock, createElementBlock, createBlock, toDisplayString, Fragment, renderList,
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, createBlock, toDisplayString, Fragment, renderList, createCommentVNode, createElementVNode } from 'vue';
|
|
2
2
|
|
|
3
3
|
const _hoisted_1 = {
|
|
4
4
|
class: "cn-events-card",
|
|
@@ -14,18 +14,24 @@ const _hoisted_3 = {
|
|
|
14
14
|
key: 2,
|
|
15
15
|
class: "cn-events-card__list"
|
|
16
16
|
};
|
|
17
|
-
const _hoisted_4 = {
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const _hoisted_4 = {
|
|
18
|
+
key: 0,
|
|
19
|
+
class: "cn-events-card__day",
|
|
20
|
+
"aria-hidden": "true"
|
|
21
|
+
};
|
|
22
|
+
const _hoisted_5 = { class: "cn-events-card__text" };
|
|
23
|
+
const _hoisted_6 = { class: "cn-events-card__summary" };
|
|
24
|
+
const _hoisted_7 = { class: "cn-events-card__when" };
|
|
25
|
+
const _hoisted_8 = {
|
|
20
26
|
key: 0,
|
|
21
27
|
class: "cn-events-card__where"
|
|
22
28
|
};
|
|
23
|
-
const
|
|
29
|
+
const _hoisted_9 = {
|
|
24
30
|
key: 3,
|
|
25
31
|
class: "cn-events-card__muted",
|
|
26
32
|
"data-testid": "cn-events-card-empty"
|
|
27
33
|
};
|
|
28
|
-
const
|
|
34
|
+
const _hoisted_10 = {
|
|
29
35
|
key: 4,
|
|
30
36
|
class: "cn-events-card__muted",
|
|
31
37
|
role: "alert",
|
|
@@ -53,17 +59,22 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
53
59
|
class: "cn-events-card__event",
|
|
54
60
|
"data-testid": "cn-events-card-event"
|
|
55
61
|
}, [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
($options.dayOf(event))
|
|
63
|
+
? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString($options.dayOf(event)), 1 /* TEXT */))
|
|
64
|
+
: createCommentVNode("v-if", true),
|
|
65
|
+
createElementVNode("span", _hoisted_5, [
|
|
66
|
+
createElementVNode("span", _hoisted_6, toDisplayString($options.summaryOf(event)), 1 /* TEXT */),
|
|
67
|
+
createElementVNode("span", _hoisted_7, toDisplayString($options.whenOf(event)), 1 /* TEXT */),
|
|
68
|
+
(event.location)
|
|
69
|
+
? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(event.location), 1 /* TEXT */))
|
|
70
|
+
: createCommentVNode("v-if", true)
|
|
71
|
+
])
|
|
61
72
|
]))
|
|
62
73
|
}), 128 /* KEYED_FRAGMENT */))
|
|
63
74
|
]))
|
|
64
|
-
: (openBlock(), createElementBlock("p",
|
|
75
|
+
: (openBlock(), createElementBlock("p", _hoisted_9, toDisplayString($options.emptyText), 1 /* TEXT */)),
|
|
65
76
|
($data.linkError)
|
|
66
|
-
? (openBlock(), createElementBlock("p",
|
|
77
|
+
? (openBlock(), createElementBlock("p", _hoisted_10, toDisplayString($data.linkError), 1 /* TEXT */))
|
|
67
78
|
: createCommentVNode("v-if", true),
|
|
68
79
|
($data.showPicker)
|
|
69
80
|
? (openBlock(), createBlock(_component_CnCalendarEventPicker, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnEventsCard.vue3.js","sources":["../../../../src/components/CnEventsCard/CnEventsCard.vue?vue&type=template&id=079bed63&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnEventsCard — the appointments booked on a record, as a list\n (`type: \"events\"`). Each row is a calendar event as OpenRegister answers\n it: `summary`, `dtstart` / `dtend` and `location`. It reads a `source`\n (`{ kind: \"flow-task\" }` reads `/api/flow-tasks/{id}/events`) or a plain\n `endpoint`. With `content.headerActions` (`link`, `create`) it reports\n \"Bestaande koppelen\" and \"Afspraak maken\" for its card's title row and runs\n the calendar leaf's own flows against that same address.\n-->\n<template>\n\t<div class=\"cn-events-card\" data-testid=\"cn-events-card\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t<p\n\t\t\tv-else-if=\"failed\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-failed\">\n\t\t\t{{ failedText }}\n\t\t</p>\n\t\t<ul v-else-if=\"events.length > 0\" class=\"cn-events-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"(event, index) in events\"\n\t\t\t\t:key=\"keyOf(event, index)\"\n\t\t\t\tclass=\"cn-events-card__event\"\n\t\t\t\tdata-testid=\"cn-events-card-event\">\n\t\t\t\t<span class=\"cn-events-card__summary\">{{ summaryOf(event) }}</span>\n\t\t\t\t<span class=\"cn-events-card__when\">{{ whenOf(event) }}</span>\n\t\t\t\t<span v-if=\"event.location\" class=\"cn-events-card__where\">{{ event.location }}</span>\n\t\t\t</li>\n\t\t</ul>\n\t\t<p v-else class=\"cn-events-card__muted\" data-testid=\"cn-events-card-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\t\t<p\n\t\t\tv-if=\"linkError\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-link-error\">\n\t\t\t{{ linkError }}\n\t\t</p>\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"showPicker = false\" />\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:objectId=\"String(objectId)\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:eventsUrl=\"url\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"showCreate = false\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CnCalendarEventCreate from '../CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport { linkCalendarEvent } from '../../integrations/builtin/calendar/calendarEventLink.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { leafSourceUrl } from '../../utils/leafSourceUrl.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnEventsCard — the appointments on a record, read from a `source` or an\n * `endpoint`, as a list of summary, time and place.\n *\n * ```vue\n * <CnEventsCard :source=\"{ kind: 'flow-task' }\" :objectId=\"taskUuid\" />\n * ```\n *\n * As a manifest widget:\n *\n * ```json\n * { \"id\": \"task-appointments\", \"type\": \"events\", \"title\": \"Appointments\", \"content\": { \"source\": { \"kind\": \"flow-task\" } } }\n * ```\n *\n * @spec openspec/changes/leaf-cards-read-a-source/specs/leaf-cards-read-a-source/spec.md#requirement-a-leaf-card-reads-a-source-or-an-endpoint\n */\nexport default {\n\tname: 'CnEventsCard',\n\n\tcomponents: { CnCalendarEventCreate, CnCalendarEventPicker, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The host app's translate function (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Where the events come from: `{ kind: 'flow-task', id? }`. `id` may be\n\t\t * left out or written `@objectId` for `objectId`. Also read from\n\t\t * `content.source`.\n\t\t *\n\t\t * @type {{kind: string, id?: (string|number)}|null}\n\t\t */\n\t\tsource: { type: Object, default: null },\n\n\t\t/** An events URL of its own; `{id}` is filled with the record id. Also read from `content.endpoint`. */\n\t\tendpoint: { type: String, default: '' },\n\n\t\t/** The page's record id. */\n\t\tobjectId: { type: [String, Number], default: '' },\n\n\t\t/** The OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t/**\n\t\t * The widget's manifest content: `source`, `endpoint`, `emptyText`\n\t\t * (translated through the host) and `headerActions` (`link`, `create`):\n\t\t * the buttons it reports for its card's title row.\n\t\t *\n\t\t * `createVariant: \"secondary\"` and `createIcon` draw the create action\n\t\t * as the quiet header action with its own icon. `menuActions` (`link`,\n\t\t * `create`) puts those flows in the card's Actions menu instead.\n\t\t *\n\t\t * @type {{source?: object, endpoint?: string, emptyText?: string, headerActions?: Array<string>, menuActions?: Array<string>, createVariant?: string, createIcon?: string}}\n\t\t */\n\t\tcontent: { type: Object, default: () => ({}) },\n\n\t\t/** Shown when the record has no appointments. */\n\t\temptyLabel: { type: String, default: '' },\n\n\t\t/** Shown when the read failed. */\n\t\tfailedLabel: { type: String, default: '' },\n\t},\n\n\temits: ['header'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t\tshowCreate: false,\n\t\t\tshowPicker: false,\n\t\t\tlinkError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The URL the card reads, '' while the record id is unknown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\turl() {\n\t\t\tconst content = this.content || {}\n\t\t\tconst source = this.source || (content.source && typeof content.source === 'object' ? content.source : null)\n\t\t\tconst endpoint = this.endpoint || (typeof content.endpoint === 'string' ? content.endpoint : '')\n\t\t\treturn leafSourceUrl({ source, endpoint, apiBase: this.apiBase, objectId: this.objectId }, 'events') || ''\n\t\t},\n\n\t\t/** @return {string} The empty line. */\n\t\temptyText() {\n\t\t\tif (this.emptyLabel) {\n\t\t\t\treturn this.emptyLabel\n\t\t\t}\n\t\t\tconst override = this.content && this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(override) : override\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'No appointments yet')\n\t\t},\n\n\t\t/** @return {string} The failure line. */\n\t\tfailedText() {\n\t\t\treturn this.failedLabel || t('nextcloud-vue', 'The appointments could not be loaded')\n\t\t},\n\t},\n\n\twatch: {\n\t\turl: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t\tthis.reportHeader()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Report the title-row buttons (`content.headerActions`) to the host\n\t\t * card: \"Bestaande koppelen\" opens the event picker, \"Afspraak maken\"\n\t\t * the meeting form. Nothing while the record is not known yet.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\treportHeader() {\n\t\t\tconst asked = Array.isArray(this.content?.headerActions) ? this.content.headerActions : []\n\t\t\tconst known = {\n\t\t\t\tlink: { label: t('nextcloud-vue', 'Link an existing one'), icon: 'LinkVariant', run: () => { this.showPicker = true } },\n\t\t\t\tcreate: {\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Make an appointment'),\n\t\t\t\t\ticon: typeof this.content?.createIcon === 'string' && this.content.createIcon !== '' ? this.content.createIcon : 'Plus',\n\t\t\t\t\t...(this.content?.createVariant === 'secondary' ? {} : { variant: 'primary' }),\n\t\t\t\t\trun: () => { this.showCreate = true },\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst pick = (keys) => (this.url === '' ? [] : keys.filter((key, index) => known[key] && keys.indexOf(key) === index).map((key) => known[key]))\n\t\t\tconst actions = pick(asked)\n\t\t\tconst inMenu = Array.isArray(this.content?.menuActions) ? this.content.menuActions.filter((key) => !asked.includes(key)) : []\n\t\t\t/**\n\t\t\t * @event header The card's title-row actions and Actions-menu items, `{ actions, menuActions }`, for the host to draw.\n\t\t\t */\n\t\t\tthis.$emit('header', { actions, menuActions: pick(inMenu) })\n\t\t},\n\n\t\t/**\n\t\t * A meeting was made on the record: close the form, read the list again.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonEventCreated() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.load()\n\t\t},\n\n\t\t/**\n\t\t * Link the picked meeting to the record, then read the list again, or say why not.\n\t\t *\n\t\t * @param {{calendarUri: string, eventUid: string}} payload The picked event.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.showPicker = false\n\t\t\tconst result = await linkCalendarEvent(prefixUrl(this.url), payload)\n\t\t\tthis.linkError = result.ok ? '' : result.error\n\t\t\tif (result.ok) {\n\t\t\t\tthis.load()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the events. A refused or failed read says so, rather than\n\t\t * showing an empty list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst url = this.url\n\t\t\tif (url === '') {\n\t\t\t\tthis.events = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tconst list = data && Array.isArray(data.results) ? data.results : data\n\t\t\t\tthis.events = Array.isArray(list) ? list : []\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure the card already shows\n\t\t\t\tconsole.error('[CnEventsCard] Loading the events failed', error)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failed = true\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 * @param {object} event The event.\n\t\t * @param {number} index Its position, for an event that carries no id.\n\t\t * @return {string} A stable key.\n\t\t */\n\t\tkeyOf(event, index) {\n\t\t\treturn String(event?.id ?? event?.uid ?? `${index}-${event?.summary ?? ''}`)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} Its summary, or \"Appointment\" when it has none.\n\t\t */\n\t\tsummaryOf(event) {\n\t\t\tconst summary = String(event?.summary ?? '').trim()\n\t\t\treturn summary === '' ? t('nextcloud-vue', 'Appointment') : summary\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} When it is: its start and end, or whichever it has.\n\t\t */\n\t\twhenOf(event) {\n\t\t\tconst start = formatMoment(event?.dtstart)\n\t\t\tconst end = formatMoment(event?.dtend)\n\t\t\tif (start === '' || end === '') {\n\t\t\t\treturn start || end\n\t\t\t}\n\t\t\treturn `${start} - ${end}`\n\t\t},\n\t},\n}\n\n/**\n * @param {unknown} value An ISO date-time.\n * @return {string} It in the user's locale, as written when unreadable, '' when empty.\n */\nfunction formatMoment(value) {\n\tconst raw = String(value ?? '').trim()\n\tif (raw === '') {\n\t\treturn ''\n\t}\n\tconst parsed = new Date(raw)\n\treturn Number.isNaN(parsed.getTime()) ? raw : parsed.toLocaleString(userLocale())\n}\n</script>\n\n<style scoped>\n.cn-events-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-events-card__event {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tpadding-bottom: 8px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-events-card__event:last-child {\n\tpadding-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cn-events-card__summary {\n\tfont-weight: 600;\n}\n\n.cn-events-card__when,\n.cn-events-card__where,\n.cn-events-card__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-events-card__muted {\n\tmargin: 0;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createBlock","_toDisplayString","_Fragment","_renderList","_createElementVNode"],"mappings":";;;AA6BM,EAAA,KAAK,EAAC,gBAAgB;AAAC,EAAA,aAAS,EAAA,gBAAA;;;;AAInC,EAAA,KAAI,EAAA,uBAAA;AACJ,EAAA,IAAG,EAAA,OAAA;AACH,EAAA,aAAW,EAAC,uBAAuB;;;;AAGnC,EAAA,KAAA,EAAA,sBAAA;;AAME,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,yBAAA,GAAA;AACK,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,sBAAO,GAAA;;;AACJ,EAAA,KAAA,EAAA,uBAAA;;;;AAGP,EAAA,KAAK,EAAC,uBAAc;AAAA,EAAA,aAAA,EAAA,sBAAA;;;;AAK7B,EAAA,KAAK,EAAC,uBAAS;AACf,EAAA,IAAI,EAAC,OAAO;AACZ,EAAA,aAAW,EAAC,2BAAiB;;;;;;;;AA3B5B,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;KACmB,KAAO,CAAA,OAAA;sBAA1BC,WAAyC,CAAA,wBAAA,EAAA;;AAAZ,UAAA,IAAI,EAAE,EAAE;;SAE3B,KAAM,CAAA,MAAA;0CAKf,CAAA,GAAA,YAAA,EAAAC,eAAA,CADC,QAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;AAEb,UAAA,CAAA,KAAA,CAAA,MAAA,CAAA,MAAA,GAAA,CAAA;AAAD,aAAAH,SAAA,EAAA,EAAAC,kBAAA,CAUK,MAVL,UAUK,EAAA;AATJ,eAAAD,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,CAQKG,QAPqB,EAAA,IAAA,EAAAC,UAAA,CAAA,KAAA,CAAA,MAAE,EAAnB,CAAA,KAAK,EAAE,KAAK,KAAA;qCADrBJ,kBAQK,CAAA,IAAA,EAAA;AANH,kBAAA,GAAG,EAAE,QAAA,CAAA,KAAI,CAAA,KAAA,EAAA,KAAA,CAAA;AACV,kBAAA,KAAK,EAAC,uBAAuB;AAC7B,kBAAA,aAAU,EAAE,sBAAC;;AACb,kBAAAK,kBAAA,CAAC,MAAD,EAAA,UAAC,EAAAH,eAAA,CAAA,QAAA,CAAA,SAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;AACD,kBAAAG,kBAAA,CAAmB,MAAnB,EAAA,UAAmB,EAAAH,eAAA,CAAA,QAAA,CAAA,MAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;AACP,kBAAA,CAAA,KAAG,CAAA,QAAA;AAAf,uBAAAH,SAAA,EAAA,EAAAC,kBAAA,CAAe,MAAf,EAAA,UAAe,EAAAE,eAAA,CAAA,KAAA,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;;;;;4CAKb,CAAA,GAAA,YAAA,EAAAA,eAAA,CADA,QAAS,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA,CAAA;KAGN,KAAS,CAAA,SAAA;0EAIZ,KAAA,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnEventsCard.vue3.js","sources":["../../../../src/components/CnEventsCard/CnEventsCard.vue?vue&type=template&id=bb3ee7b4&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnEventsCard — the appointments booked on a record, as a list\n (`type: \"events\"`). Each row is a calendar event as OpenRegister answers\n it: `summary`, `dtstart` / `dtend` and `location`. It reads a `source`\n (`{ kind: \"flow-task\" }` reads `/api/flow-tasks/{id}/events`) or a plain\n `endpoint`. With `content.headerActions` (`link`, `create`) it reports\n \"Bestaande koppelen\" and \"Afspraak maken\" for its card's title row and runs\n the calendar leaf's own flows against that same address.\n-->\n<template>\n\t<div class=\"cn-events-card\" data-testid=\"cn-events-card\">\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t<p\n\t\t\tv-else-if=\"failed\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-failed\">\n\t\t\t{{ failedText }}\n\t\t</p>\n\t\t<ul v-else-if=\"events.length > 0\" class=\"cn-events-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"(event, index) in events\"\n\t\t\t\t:key=\"keyOf(event, index)\"\n\t\t\t\tclass=\"cn-events-card__event\"\n\t\t\t\tdata-testid=\"cn-events-card-event\">\n\t\t\t\t<span v-if=\"dayOf(event)\" class=\"cn-events-card__day\" aria-hidden=\"true\">{{ dayOf(event) }}</span>\n\t\t\t\t<span class=\"cn-events-card__text\">\n\t\t\t\t\t<span class=\"cn-events-card__summary\">{{ summaryOf(event) }}</span>\n\t\t\t\t\t<span class=\"cn-events-card__when\">{{ whenOf(event) }}</span>\n\t\t\t\t\t<span v-if=\"event.location\" class=\"cn-events-card__where\">{{ event.location }}</span>\n\t\t\t\t</span>\n\t\t\t</li>\n\t\t</ul>\n\t\t<p v-else class=\"cn-events-card__muted\" data-testid=\"cn-events-card-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\t\t<p\n\t\t\tv-if=\"linkError\"\n\t\t\tclass=\"cn-events-card__muted\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-events-card-link-error\">\n\t\t\t{{ linkError }}\n\t\t</p>\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"showPicker = false\" />\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:objectId=\"String(objectId)\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t:eventsUrl=\"url\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"showCreate = false\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport CnCalendarEventCreate from '../CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport { linkCalendarEvent } from '../../integrations/builtin/calendar/calendarEventLink.js'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { leafSourceUrl } from '../../utils/leafSourceUrl.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnEventsCard — the appointments on a record, read from a `source` or an\n * `endpoint`, as a list of summary, time and place.\n *\n * ```vue\n * <CnEventsCard :source=\"{ kind: 'flow-task' }\" :objectId=\"taskUuid\" />\n * ```\n *\n * As a manifest widget:\n *\n * ```json\n * { \"id\": \"task-appointments\", \"type\": \"events\", \"title\": \"Appointments\", \"content\": { \"source\": { \"kind\": \"flow-task\" } } }\n * ```\n *\n * @spec openspec/changes/leaf-cards-read-a-source/specs/leaf-cards-read-a-source/spec.md#requirement-a-leaf-card-reads-a-source-or-an-endpoint\n */\nexport default {\n\tname: 'CnEventsCard',\n\n\tcomponents: { CnCalendarEventCreate, CnCalendarEventPicker, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The host app's translate function (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Where the events come from: `{ kind: 'flow-task', id? }`. `id` may be\n\t\t * left out or written `@objectId` for `objectId`. Also read from\n\t\t * `content.source`.\n\t\t *\n\t\t * @type {{kind: string, id?: (string|number)}|null}\n\t\t */\n\t\tsource: { type: Object, default: null },\n\n\t\t/** An events URL of its own; `{id}` is filled with the record id. Also read from `content.endpoint`. */\n\t\tendpoint: { type: String, default: '' },\n\n\t\t/** The page's record id. */\n\t\tobjectId: { type: [String, Number], default: '' },\n\n\t\t/** The OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t/**\n\t\t * The widget's manifest content: `source`, `endpoint`, `emptyText`\n\t\t * (translated through the host) and `headerActions` (`link`, `create`):\n\t\t * the buttons it reports for its card's title row.\n\t\t *\n\t\t * `createVariant: \"secondary\"` and `createIcon` draw the create action\n\t\t * as the quiet header action with its own icon. `menuActions` (`link`,\n\t\t * `create`) puts those flows in the card's Actions menu instead.\n\t\t *\n\t\t * @type {{source?: object, endpoint?: string, emptyText?: string, headerActions?: Array<string>, menuActions?: Array<string>, createVariant?: string, createIcon?: string}}\n\t\t */\n\t\tcontent: { type: Object, default: () => ({}) },\n\n\t\t/** Shown when the record has no appointments. */\n\t\temptyLabel: { type: String, default: '' },\n\n\t\t/** Shown when the read failed. */\n\t\tfailedLabel: { type: String, default: '' },\n\t},\n\n\temits: ['header'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t\tshowCreate: false,\n\t\t\tshowPicker: false,\n\t\t\tlinkError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The URL the card reads, '' while the record id is unknown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\turl() {\n\t\t\tconst content = this.content || {}\n\t\t\tconst source = this.source || (content.source && typeof content.source === 'object' ? content.source : null)\n\t\t\tconst endpoint = this.endpoint || (typeof content.endpoint === 'string' ? content.endpoint : '')\n\t\t\treturn leafSourceUrl({ source, endpoint, apiBase: this.apiBase, objectId: this.objectId }, 'events') || ''\n\t\t},\n\n\t\t/** @return {string} The empty line. */\n\t\temptyText() {\n\t\t\tif (this.emptyLabel) {\n\t\t\t\treturn this.emptyLabel\n\t\t\t}\n\t\t\tconst override = this.content && this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(override) : override\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'No appointments yet')\n\t\t},\n\n\t\t/** @return {string} The failure line. */\n\t\tfailedText() {\n\t\t\treturn this.failedLabel || t('nextcloud-vue', 'The appointments could not be loaded')\n\t\t},\n\t},\n\n\twatch: {\n\t\turl: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t\tthis.reportHeader()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Report the title-row buttons (`content.headerActions`) to the host\n\t\t * card: \"Bestaande koppelen\" opens the event picker, \"Afspraak maken\"\n\t\t * the meeting form. Nothing while the record is not known yet.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\treportHeader() {\n\t\t\tconst asked = Array.isArray(this.content?.headerActions) ? this.content.headerActions : []\n\t\t\tconst known = {\n\t\t\t\tlink: { label: t('nextcloud-vue', 'Link an existing one'), icon: 'LinkVariant', run: () => { this.showPicker = true } },\n\t\t\t\tcreate: {\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Make an appointment'),\n\t\t\t\t\ticon: typeof this.content?.createIcon === 'string' && this.content.createIcon !== '' ? this.content.createIcon : 'Plus',\n\t\t\t\t\t...(this.content?.createVariant === 'secondary' ? {} : { variant: 'primary' }),\n\t\t\t\t\trun: () => { this.showCreate = true },\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst pick = (keys) => (this.url === '' ? [] : keys.filter((key, index) => known[key] && keys.indexOf(key) === index).map((key) => known[key]))\n\t\t\tconst actions = pick(asked)\n\t\t\tconst inMenu = Array.isArray(this.content?.menuActions) ? this.content.menuActions.filter((key) => !asked.includes(key)) : []\n\t\t\t/**\n\t\t\t * @event header The card's title-row actions and Actions-menu items, `{ actions, menuActions }`, for the host to draw.\n\t\t\t */\n\t\t\tthis.$emit('header', { actions, menuActions: pick(inMenu) })\n\t\t},\n\n\t\t/**\n\t\t * A meeting was made on the record: close the form, read the list again.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonEventCreated() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.load()\n\t\t},\n\n\t\t/**\n\t\t * Link the picked meeting to the record, then read the list again, or say why not.\n\t\t *\n\t\t * @param {{calendarUri: string, eventUid: string}} payload The picked event.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onPickerLink(payload) {\n\t\t\tthis.showPicker = false\n\t\t\tconst result = await linkCalendarEvent(prefixUrl(this.url), payload)\n\t\t\tthis.linkError = result.ok ? '' : result.error\n\t\t\tif (result.ok) {\n\t\t\t\tthis.load()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the events. A refused or failed read says so, rather than\n\t\t * showing an empty list.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst url = this.url\n\t\t\tif (url === '') {\n\t\t\t\tthis.events = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tconst list = data && Array.isArray(data.results) ? data.results : data\n\t\t\t\tthis.events = Array.isArray(list) ? list : []\n\t\t\t} catch (error) {\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure the card already shows\n\t\t\t\tconsole.error('[CnEventsCard] Loading the events failed', error)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.failed = true\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 * @param {object} event The event.\n\t\t * @param {number} index Its position, for an event that carries no id.\n\t\t * @return {string} A stable key.\n\t\t */\n\t\tkeyOf(event, index) {\n\t\t\treturn String(event?.id ?? event?.uid ?? `${index}-${event?.summary ?? ''}`)\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} Its summary, or \"Appointment\" when it has none.\n\t\t */\n\t\tsummaryOf(event) {\n\t\t\tconst summary = String(event?.summary ?? '').trim()\n\t\t\treturn summary === '' ? t('nextcloud-vue', 'Appointment') : summary\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} The day of the month it starts on, '' when it has no readable start.\n\t\t */\n\t\tdayOf(event) {\n\t\t\tconst parsed = new Date(String(event?.dtstart ?? '').trim())\n\t\t\treturn Number.isNaN(parsed.getTime()) ? '' : String(parsed.getDate())\n\t\t},\n\n\t\t/**\n\t\t * @param {object} event The event.\n\t\t * @return {string} When it is: its start and end, or whichever it has.\n\t\t */\n\t\twhenOf(event) {\n\t\t\tconst start = formatMoment(event?.dtstart)\n\t\t\tconst end = formatMoment(event?.dtend)\n\t\t\tif (start === '' || end === '') {\n\t\t\t\treturn start || end\n\t\t\t}\n\t\t\treturn `${start} - ${end}`\n\t\t},\n\t},\n}\n\n/**\n * @param {unknown} value An ISO date-time.\n * @return {string} It in the user's locale, as written when unreadable, '' when empty.\n */\nfunction formatMoment(value) {\n\tconst raw = String(value ?? '').trim()\n\tif (raw === '') {\n\t\treturn ''\n\t}\n\tconst parsed = new Date(raw)\n\treturn Number.isNaN(parsed.getTime()) ? raw : parsed.toLocaleString(userLocale())\n}\n</script>\n\n<style scoped>\n.cn-events-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-events-card__event {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding-bottom: 8px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-events-card__event:last-child {\n\tpadding-bottom: 0;\n\tborder-bottom: none;\n}\n\n.cn-events-card__day {\n\tdisplay: none;\n}\n\n.cn-events-card__text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-events-card__summary {\n\tfont-weight: 600;\n}\n\n.cn-events-card__when,\n.cn-events-card__where,\n.cn-events-card__muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-events-card__muted {\n\tmargin: 0;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createBlock","_toDisplayString","_Fragment","_renderList","_createElementVNode"],"mappings":";;;AA6BM,EAAA,KAAK,EAAC,gBAAgB;AAAC,EAAA,aAAS,EAAA,gBAAA;;;;OAI9B,EAAC,uBAAuB;AAC3B,EAAA,IAAE,EAAC,OAAO;AACX,EAAA,aAAM,EAAA,uBAAA;;;;AAG0B,EAAA,KAAI,EAAE,sBAAsB;;;;AAMlD,EAAA,KAAA,EAAA,qBAAA;AAAA,EAAA,aAAA,EAAA,MAAA;;AACJ,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,sBAAsB,GAAA;AAC3B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,yBAAA,GAAA;AACZ,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,sBAAA,GAAA;;;AACkB,EAAA,KAAA,EAAA,uBAAA;;;;AAIX,EAAA,KAAK,EAAC,uBAAc;AAAA,EAAA,aAAA,EAAA,sBAAA;;;;AAK7B,EAAA,KAAK,EAAC,uBAAS;AACf,EAAA,IAAI,EAAC,OAAO;AACZ,EAAA,aAAW,EAAC,2BAAiB;;;;;;;;AA9B5B,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;KACmB,KAAO,CAAA,OAAA;sBAA1BC,WAAyC,CAAA,wBAAA,EAAA;;AAAZ,UAAA,IAAI,EAAE,EAAE;;SAE3B,KAAM,CAAA,MAAA;wBADhBD,kBAME,CAAA,GAAA,EANF,UAME,EAAAE,eAAA,CADA,QAAC,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;AAEU,UAAA,CAAA,KAAA,CAAA,MAAM,CAAC,MAAM,GAAA,CAAA;AAA5B,aAAAH,SAAA,EAAA,EAAAC,kBAAA,CAaK,MAbL,UAaK,EAAA;AAZJ,eAAAD,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,CAWKG,QAVH,EAAA,IAAA,EAAAC,UAAA,CAAA,KAAA,CAAA,MAAA,EAAA,CAAA,KAAA,EAAA,KAAA,KAAA;qCADFJ,kBAWK,CAAA,IAAA,EAAA;AATL,kBAAA,GAAA,EAAA,QAAA,CAAA,KAAA,CAAA,KAAA,EAAA,KAAA,CAAA;AACC,kBAAA,KAAK,EAAC,uBAAQ;AACd,kBAAA,aAAW,EAAC,sBAAe;;AACjB,kBAAA,CAAA,QAAA,CAAA,KAAA,CAAA,KAAA,CAAA;AAAV,uBAAAD,SAAA,EAAA,EAAAC,kBAAA,CAAU,MAAV,EAAA,UAAU,EAAAE,eAAA,CAAA,QAAA,CAAA,KAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;AACV,kBAAAG,kBAAA,CAIO,QAJP,UAIO,EAAA;AAHP,oBAAAA,kBAAA,CAAa,MAAb,EAAA,UAAa,EAAAH,eAAA,CAAA,QAAA,CAAA,SAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;AACZ,oBAAAG,kBAAA,CAAA,MAAA,EAAA,UAAA,EAAAH,eAAA,CAAA,QAAA,CAAA,MAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;AACY,oBAAA,CAAA,KAAK,CAAC,QAAA;AAAlB,yBAAAH,SAAA,EAAA,EAAAC,kBAAA,CAAkB,MAAlB,EAAA,UAAkB,EAAAE,eAAA,CAAA,KAAA,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;;;;;;4CAMjB,CAAA,GAAA,YAAA,EAAAA,eAAA,CADA,QAAS,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA,CAAA;KAGN,KAAS,CAAA,SAAA;2EAIZ,KAAA,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -2,7 +2,7 @@ import script from './CnFilesBrowser.vue2.js';
|
|
|
2
2
|
import { render } from './CnFilesBrowser.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-5246192c";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -711,6 +711,19 @@ var script = {
|
|
|
711
711
|
return crumbsFor(this.rootPath, this.currentPath, this.rootLabel)
|
|
712
712
|
},
|
|
713
713
|
|
|
714
|
+
/**
|
|
715
|
+
* Whether the open folder is the browser's root: the trail then ends on
|
|
716
|
+
* the root itself (the board look draws no trail there).
|
|
717
|
+
*
|
|
718
|
+
* @return {boolean}
|
|
719
|
+
* @spec openspec/changes/board-files-root-crumb/specs/board-files-root-crumb/spec.md#requirement-the-board-files-bar-draws-no-trail-at-the-root
|
|
720
|
+
*/
|
|
721
|
+
atRoot() {
|
|
722
|
+
const root = String(this.rootPath || '/').replace(/\/+$/, '') || '/';
|
|
723
|
+
const last = this.crumbs[this.crumbs.length - 1];
|
|
724
|
+
return Boolean(last) && last.path === root
|
|
725
|
+
},
|
|
726
|
+
|
|
714
727
|
/**
|
|
715
728
|
* The children in the chosen order, folders first the way the Files app
|
|
716
729
|
* lists them.
|
|
@@ -1405,7 +1418,8 @@ var script = {
|
|
|
1405
1418
|
const object = {
|
|
1406
1419
|
...entry,
|
|
1407
1420
|
name: node.basename,
|
|
1408
|
-
|
|
1421
|
+
// A template drops an object value, so the Date is handed over as ISO text.
|
|
1422
|
+
modified: node.mtime instanceof Date ? node.mtime.toISOString() : node.mtime,
|
|
1409
1423
|
size: this.formatSize(node.size),
|
|
1410
1424
|
type: this.typeLabelOf(node),
|
|
1411
1425
|
...(owner ? { owner } : {}),
|
|
@@ -2008,7 +2022,8 @@ var script = {
|
|
|
2008
2022
|
}
|
|
2009
2023
|
})
|
|
2010
2024
|
.filter((field) => field.value !== null && field.value !== undefined && field.value !== ''),
|
|
2011
|
-
actions: (Array.isArray(card.actions) ? card.actions : [])
|
|
2025
|
+
actions: (Array.isArray(card.actions) ? card.actions : [])
|
|
2026
|
+
.filter((action) => action && action.id && hostActionApplies(action, node, this.rowValueOf(node))),
|
|
2012
2027
|
}))
|
|
2013
2028
|
},
|
|
2014
2029
|
|
|
@@ -2304,7 +2319,18 @@ var script = {
|
|
|
2304
2319
|
* @spec openspec/changes/files-browser-hosts-a-documents-list/specs/files-browser/spec.md
|
|
2305
2320
|
*/
|
|
2306
2321
|
hostActionsFor(node) {
|
|
2307
|
-
return this.rowActions.filter((action) => hostActionApplies(action, node))
|
|
2322
|
+
return this.rowActions.filter((action) => hostActionApplies(action, node, this.rowValueOf(node)))
|
|
2323
|
+
},
|
|
2324
|
+
|
|
2325
|
+
/**
|
|
2326
|
+
* A reader of the host's row fields for one file, for `visibleIf.row`.
|
|
2327
|
+
*
|
|
2328
|
+
* @param {object} node The file.
|
|
2329
|
+
* @return {(key: string) => unknown} `(key) => value` over the row the host gave for the file.
|
|
2330
|
+
* @spec openspec/changes/files-browser-action-visible-if-row/specs/files-browser/spec.md#requirement-a-host-action-can-be-offered-by-a-field-of-the-files-row
|
|
2331
|
+
*/
|
|
2332
|
+
rowValueOf(node) {
|
|
2333
|
+
return (key) => fileColumnValue({ key, source: 'row' }, node, this.resolvedRowData)
|
|
2308
2334
|
},
|
|
2309
2335
|
|
|
2310
2336
|
/**
|