@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.
Files changed (167) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +7 -2
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  6. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +27 -0
  7. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js +2 -1
  12. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js.map +1 -1
  13. package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js +9 -0
  14. package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js.map +1 -1
  15. package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js +21 -22
  16. package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js.map +1 -1
  17. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  18. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +29 -1
  19. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  20. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js +28 -13
  21. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  22. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  23. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  24. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +2 -1
  25. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  26. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +1 -1
  27. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -1
  28. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -1
  29. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  30. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  31. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  32. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  33. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  34. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +1 -1
  35. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  36. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  38. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  40. package/dist/esm/components/CnEventsCard/CnEventsCard.vue.js +1 -1
  41. package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js +9 -0
  42. package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js.map +1 -1
  43. package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js +24 -13
  44. package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  46. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +29 -3
  47. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +4 -2
  49. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +17 -4
  51. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  52. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +21 -1
  53. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +18 -2
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  57. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  58. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +3 -0
  59. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  60. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js +19 -14
  61. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  62. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
  63. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +30 -0
  64. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
  65. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +158 -102
  66. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
  67. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +6 -1
  68. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -1
  69. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -1
  70. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  71. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +12 -1
  72. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  73. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +2 -2
  74. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
  76. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +12 -0
  78. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
  79. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +1 -1
  80. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -1
  82. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue.js +1 -1
  83. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +110 -2
  84. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  85. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +146 -80
  86. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  87. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +1 -1
  88. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +4 -0
  89. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -1
  90. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +18 -8
  91. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -1
  92. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
  93. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +45 -0
  94. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
  95. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js +67 -34
  96. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
  97. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
  98. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +3 -22
  99. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
  100. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
  101. package/dist/esm/integrations/builtin/calendar/eventStatus.js +67 -0
  102. package/dist/esm/integrations/builtin/calendar/eventStatus.js.map +1 -0
  103. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
  104. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +39 -0
  105. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  106. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +81 -32
  107. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  108. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
  109. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +5 -6
  110. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  111. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/deck/deckCardParts.js +59 -0
  113. package/dist/esm/integrations/builtin/deck/deckCardParts.js.map +1 -0
  114. package/dist/esm/l10n/en.json.js +6 -0
  115. package/dist/esm/l10n/en.json.js.map +1 -1
  116. package/dist/esm/l10n/nl.json.js +7 -1
  117. package/dist/esm/l10n/nl.json.js.map +1 -1
  118. package/dist/esm/library-style.js +1 -1
  119. package/dist/esm/nextcloud-vue.css +1264 -597
  120. package/dist/esm/utils/fetchFilterCounts.js +5 -2
  121. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  122. package/dist/esm/utils/nextcloudDirectory.js +19 -6
  123. package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
  124. package/dist/nextcloud-vue.cjs.js +1619 -849
  125. package/dist/nextcloud-vue.cjs.js.map +2 -2
  126. package/dist/nextcloud-vue.css +1264 -597
  127. package/l10n/en.json +6 -0
  128. package/l10n/nl.json +7 -1
  129. package/package.json +1 -1
  130. package/src/components/CnActionDialog/CnActionDialog.vue +28 -3
  131. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +28 -1
  132. package/src/components/CnAiCompanion/CnAiChatPanel.vue +12 -0
  133. package/src/components/CnAiCompanion/CnAiInput.vue +32 -1
  134. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +34 -4
  135. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +1 -0
  136. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +8 -0
  137. package/src/components/CnChoiceCards/CnChoiceCards.vue +1 -0
  138. package/src/components/CnConversationThread/CnConversationThread.vue +5 -2
  139. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +1 -0
  140. package/src/components/CnEventsCard/CnEventsCard.vue +27 -5
  141. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +43 -4
  142. package/src/components/CnFilesBrowser/filesBrowser.js +17 -4
  143. package/src/components/CnFilesBrowser/filesBrowserColumns.js +20 -1
  144. package/src/components/CnIndexPage/CnIndexPage.vue +18 -2
  145. package/src/components/CnNotesCard/CnNotesCard.vue +8 -0
  146. package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +114 -46
  147. package/src/components/CnPageHeader/CnHeaderTrail.vue +6 -1
  148. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +21 -1
  149. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +26 -1
  150. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +6 -0
  151. package/src/components/CnTasksWidget/CnTasksWidget.vue +170 -13
  152. package/src/components/CnTimelineWidget/CnTimelineManage.vue +10 -1
  153. package/src/css/dialog.css +25 -1
  154. package/src/css/kpi-card.css +3 -0
  155. package/src/css/look-board-detail.css +348 -0
  156. package/src/css/look-board-index.css +56 -1
  157. package/src/css/look-board.css +98 -1
  158. package/src/integrations/builtin/calendar/CnCalendarCard.vue +76 -11
  159. package/src/integrations/builtin/calendar/CnCalendarTab.vue +3 -22
  160. package/src/integrations/builtin/calendar/__tests__/CnCalendarCardBoardRows.spec.js +52 -0
  161. package/src/integrations/builtin/calendar/eventStatus.js +63 -0
  162. package/src/integrations/builtin/deck/CnDeckCard.vue +66 -0
  163. package/src/integrations/builtin/deck/CnDeckTab.vue +5 -6
  164. package/src/integrations/builtin/deck/__tests__/CnDeckCardBoardRows.spec.js +49 -0
  165. package/src/integrations/builtin/deck/deckCardParts.js +56 -0
  166. package/src/utils/fetchFilterCounts.js +5 -2
  167. 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, createElementVNode, createCommentVNode } from 'vue';
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 = { class: "cn-events-card__summary" };
18
- const _hoisted_5 = { class: "cn-events-card__when" };
19
- const _hoisted_6 = {
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 _hoisted_7 = {
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 _hoisted_8 = {
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
- createElementVNode("span", _hoisted_4, toDisplayString($options.summaryOf(event)), 1 /* TEXT */),
57
- createElementVNode("span", _hoisted_5, toDisplayString($options.whenOf(event)), 1 /* TEXT */),
58
- (event.location)
59
- ? (openBlock(), createElementBlock("span", _hoisted_6, toDisplayString(event.location), 1 /* TEXT */))
60
- : createCommentVNode("v-if", true)
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", _hoisted_7, toDisplayString($options.emptyText), 1 /* TEXT */)),
75
+ : (openBlock(), createElementBlock("p", _hoisted_9, toDisplayString($options.emptyText), 1 /* TEXT */)),
65
76
  ($data.linkError)
66
- ? (openBlock(), createElementBlock("p", _hoisted_8, toDisplayString($data.linkError), 1 /* TEXT */))
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-ebaa2c0e";
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
- modified: node.mtime,
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 : []).filter((action) => action && action.id),
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
  /**