@conduction/nextcloud-vue 2.97.0 → 2.99.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 (160) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +12 -0
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +11 -2
  4. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +17 -0
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -1
  9. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +1 -1
  11. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  12. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  13. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +106 -2
  14. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  15. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +9 -0
  17. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js.map +1 -0
  18. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js +964 -0
  19. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -0
  20. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js +282 -0
  21. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -0
  22. package/dist/esm/components/CnChecklistRunWidget/index.js +28 -0
  23. package/dist/esm/components/CnChecklistRunWidget/index.js.map +1 -0
  24. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -8
  29. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  31. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +188 -5
  32. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  35. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  36. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +3 -3
  37. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  38. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
  40. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +27 -0
  41. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
  42. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +16 -2
  43. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +43 -3
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  46. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  47. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +9 -0
  48. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js.map +1 -0
  49. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +239 -0
  50. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -0
  51. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +182 -0
  52. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -0
  53. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +9 -0
  54. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js.map +1 -0
  55. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js +265 -0
  56. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -0
  57. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +191 -0
  58. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -0
  59. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +9 -0
  60. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js.map +1 -0
  61. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +508 -0
  62. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -0
  63. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js +99 -0
  64. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -0
  65. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  66. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +18 -70
  67. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  68. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +6 -44
  69. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  70. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
  71. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +83 -9
  72. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
  73. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +80 -11
  74. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
  75. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +2 -0
  76. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  77. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js +9 -0
  78. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js.map +1 -0
  79. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js +472 -0
  80. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js.map +1 -0
  81. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js +231 -0
  82. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js.map +1 -0
  83. package/dist/esm/components/CnWorkingCalendarWidget/index.js +27 -0
  84. package/dist/esm/components/CnWorkingCalendarWidget/index.js.map +1 -0
  85. package/dist/esm/composables/indexSources.js +238 -2
  86. package/dist/esm/composables/indexSources.js.map +1 -1
  87. package/dist/esm/composables/useFlowStore.js +2 -1
  88. package/dist/esm/composables/useFlowStore.js.map +1 -1
  89. package/dist/esm/composables/useOrganisationStore.js +115 -0
  90. package/dist/esm/composables/useOrganisationStore.js.map +1 -0
  91. package/dist/esm/index.js +5 -0
  92. package/dist/esm/index.js.map +1 -1
  93. package/dist/esm/l10n/en.json.js +109 -1
  94. package/dist/esm/l10n/en.json.js.map +1 -1
  95. package/dist/esm/l10n/nl.json.js +138 -1
  96. package/dist/esm/l10n/nl.json.js.map +1 -1
  97. package/dist/esm/library-style.js +1 -1
  98. package/dist/esm/nextcloud-vue.css +1071 -274
  99. package/dist/esm/utils/builtInFormatters.js +34 -1
  100. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  101. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  102. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  103. package/dist/esm/utils/matrixCells.js +158 -0
  104. package/dist/esm/utils/matrixCells.js.map +1 -0
  105. package/dist/esm/utils/nextcloudDirectory.js +121 -0
  106. package/dist/esm/utils/nextcloudDirectory.js.map +1 -0
  107. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  108. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  109. package/dist/esm/utils/workingCalendarMonth.js +85 -0
  110. package/dist/esm/utils/workingCalendarMonth.js.map +1 -0
  111. package/dist/nextcloud-vue.cjs.js +14888 -10014
  112. package/dist/nextcloud-vue.cjs.js.map +2 -2
  113. package/dist/nextcloud-vue.css +1071 -274
  114. package/l10n/en.json +109 -1
  115. package/l10n/nl.json +138 -1
  116. package/package.json +2 -2
  117. package/scripts/merge-l10n.js +196 -0
  118. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  119. package/src/components/CnAppRoot/CnAppRoot.md +1 -0
  120. package/src/components/CnAppRoot/CnAppRoot.vue +17 -0
  121. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +1 -0
  122. package/src/components/CnChartWidget/CnChartWidget.md +2 -1
  123. package/src/components/CnChartWidget/CnChartWidget.vue +106 -2
  124. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +1269 -0
  125. package/src/components/CnChecklistRunWidget/index.js +27 -0
  126. package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
  127. package/src/components/CnDataTable/CnDataTable.vue +26 -8
  128. package/src/components/CnDetailPage/CnDetailPage.md +48 -0
  129. package/src/components/CnDetailPage/CnDetailPage.vue +189 -5
  130. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +3 -3
  131. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +38 -2
  132. package/src/components/CnIndexPage/CnIndexPage.md +28 -0
  133. package/src/components/CnIndexPage/CnIndexPage.vue +43 -3
  134. package/src/components/CnOrderedListField/CnOrderedListField.vue +458 -0
  135. package/src/components/CnOrderedListField/index.js +7 -0
  136. package/src/components/CnSettingsField/CnSettingsField.vue +449 -0
  137. package/src/components/CnSettingsField/index.js +4 -0
  138. package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +613 -0
  139. package/src/components/CnSettingsFieldsSection/index.js +4 -0
  140. package/src/components/CnSettingsPage/CnSettingsPage.vue +6 -86
  141. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +139 -10
  142. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +2 -0
  143. package/src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue +725 -0
  144. package/src/components/CnWorkingCalendarWidget/index.js +26 -0
  145. package/src/components/index.js +5 -0
  146. package/src/composables/indexSources.js +237 -1
  147. package/src/composables/useFlowStore.js +2 -1
  148. package/src/composables/useOrganisationStore.js +111 -0
  149. package/src/css/badge.css +32 -16
  150. package/src/css/look-board-detail.css +80 -8
  151. package/src/css/look-board-index.css +52 -8
  152. package/src/css/look-board.css +219 -1
  153. package/src/index.js +5 -0
  154. package/src/schemas/app-manifest-v2.schema.json +33 -3
  155. package/src/utils/builtInFormatters.js +33 -0
  156. package/src/utils/libraryWidgetKeys.js +2 -0
  157. package/src/utils/matrixCells.js +154 -0
  158. package/src/utils/nextcloudDirectory.js +117 -0
  159. package/src/utils/validateManifestV2.compiled.js +3 -3
  160. package/src/utils/workingCalendarMonth.js +82 -0
@@ -0,0 +1,231 @@
1
+ import { resolveComponent, openBlock, createElementBlock, toDisplayString, createVNode, Fragment, createElementVNode, withCtx, createBlock, createTextVNode, createCommentVNode, renderList, normalizeClass, withModifiers, withDirectives, vModelText } from 'vue';
2
+
3
+ const _hoisted_1 = {
4
+ class: "cn-working-calendar",
5
+ "data-testid": "cn-working-calendar"
6
+ };
7
+ const _hoisted_2 = {
8
+ key: 0,
9
+ class: "cn-working-calendar__error",
10
+ role: "alert",
11
+ "data-testid": "cn-working-calendar-error"
12
+ };
13
+ const _hoisted_3 = {
14
+ key: 1,
15
+ class: "cn-working-calendar__loading"
16
+ };
17
+ const _hoisted_4 = { class: "cn-working-calendar__head" };
18
+ const _hoisted_5 = ["id"];
19
+ const _hoisted_6 = {
20
+ key: 0,
21
+ class: "cn-working-calendar__note",
22
+ "data-testid": "cn-working-calendar-note"
23
+ };
24
+ const _hoisted_7 = {
25
+ class: "cn-working-calendar__live",
26
+ "aria-live": "polite"
27
+ };
28
+ const _hoisted_8 = ["aria-labelledby"];
29
+ const _hoisted_9 = {
30
+ role: "row",
31
+ class: "cn-working-calendar__row"
32
+ };
33
+ const _hoisted_10 = ["aria-label"];
34
+ const _hoisted_11 = ["tabindex", "aria-label", "aria-current", "title", "data-date", "onClick", "onFocus"];
35
+ const _hoisted_12 = { class: "cn-working-calendar__field" };
36
+ const _hoisted_13 = { class: "cn-working-calendar__field" };
37
+ const _hoisted_14 = { class: "cn-working-calendar__form-actions" };
38
+ const _hoisted_15 = {
39
+ key: 0,
40
+ class: "cn-working-calendar__error",
41
+ role: "alert",
42
+ "data-testid": "cn-working-calendar-save-error"
43
+ };
44
+ const _hoisted_16 = {
45
+ key: 3,
46
+ class: "cn-working-calendar__note",
47
+ "data-testid": "cn-working-calendar-empty"
48
+ };
49
+
50
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
51
+ const _component_NcLoadingIcon = resolveComponent("NcLoadingIcon");
52
+ const _component_ChevronLeft = resolveComponent("ChevronLeft");
53
+ const _component_NcButton = resolveComponent("NcButton");
54
+ const _component_ChevronRight = resolveComponent("ChevronRight");
55
+
56
+ return (openBlock(), createElementBlock("div", _hoisted_1, [
57
+ ($data.error)
58
+ ? (openBlock(), createElementBlock("p", _hoisted_2, toDisplayString($data.error), 1 /* TEXT */))
59
+ : ($data.loading && !$data.calendar)
60
+ ? (openBlock(), createElementBlock("div", _hoisted_3, [
61
+ createVNode(_component_NcLoadingIcon, {
62
+ size: 20,
63
+ name: $options.loadingLabel
64
+ }, null, 8 /* PROPS */, ["name"])
65
+ ]))
66
+ : ($data.calendar)
67
+ ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
68
+ createElementVNode("div", _hoisted_4, [
69
+ createVNode(_component_NcButton, {
70
+ variant: "tertiary",
71
+ "aria-label": $options.previousMonthLabel,
72
+ "data-testid": "cn-working-calendar-previous",
73
+ onClick: _cache[0] || (_cache[0] = $event => ($options.shiftMonth(-1)))
74
+ }, {
75
+ icon: withCtx(() => [
76
+ createVNode(_component_ChevronLeft, { size: 20 })
77
+ ]),
78
+ _: 1 /* STABLE */
79
+ }, 8 /* PROPS */, ["aria-label"]),
80
+ createElementVNode("strong", {
81
+ id: `${$data.uid}-title`,
82
+ class: "cn-working-calendar__title",
83
+ "data-testid": "cn-working-calendar-title"
84
+ }, toDisplayString($options.monthTitle), 9 /* TEXT, PROPS */, _hoisted_5),
85
+ createVNode(_component_NcButton, {
86
+ variant: "tertiary",
87
+ "aria-label": $options.nextMonthLabel,
88
+ "data-testid": "cn-working-calendar-next",
89
+ onClick: _cache[1] || (_cache[1] = $event => ($options.shiftMonth(1)))
90
+ }, {
91
+ icon: withCtx(() => [
92
+ createVNode(_component_ChevronRight, { size: 20 })
93
+ ]),
94
+ _: 1 /* STABLE */
95
+ }, 8 /* PROPS */, ["aria-label"]),
96
+ ($options.editable)
97
+ ? (openBlock(), createBlock(_component_NcButton, {
98
+ key: 0,
99
+ class: "cn-working-calendar__add",
100
+ variant: "secondary",
101
+ "data-testid": "cn-working-calendar-add",
102
+ onClick: _cache[2] || (_cache[2] = $event => ($options.openAdd()))
103
+ }, {
104
+ default: withCtx(() => [
105
+ createTextVNode(toDisplayString($options.addLabel), 1 /* TEXT */)
106
+ ]),
107
+ _: 1 /* STABLE */
108
+ }))
109
+ : createCommentVNode("v-if", true)
110
+ ]),
111
+ ($options.previewNote)
112
+ ? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString($options.previewNote), 1 /* TEXT */))
113
+ : createCommentVNode("v-if", true),
114
+ createElementVNode("span", _hoisted_7, toDisplayString($data.liveText), 1 /* TEXT */),
115
+ createElementVNode("div", {
116
+ role: "grid",
117
+ class: "cn-working-calendar__grid",
118
+ "aria-labelledby": `${$data.uid}-title`,
119
+ "data-testid": "cn-working-calendar-grid",
120
+ onKeydown: _cache[3] || (_cache[3] = (...args) => ($options.onKeydown && $options.onKeydown(...args)))
121
+ }, [
122
+ createElementVNode("div", _hoisted_9, [
123
+ (openBlock(true), createElementBlock(Fragment, null, renderList($options.weekdayNames, (name) => {
124
+ return (openBlock(), createElementBlock("span", {
125
+ key: name.long,
126
+ role: "columnheader",
127
+ class: "cn-working-calendar__weekday",
128
+ "aria-label": name.long
129
+ }, toDisplayString(name.short), 9 /* TEXT, PROPS */, _hoisted_10))
130
+ }), 128 /* KEYED_FRAGMENT */))
131
+ ]),
132
+ (openBlock(true), createElementBlock(Fragment, null, renderList($options.weeks, (week, w) => {
133
+ return (openBlock(), createElementBlock("div", {
134
+ key: w,
135
+ role: "row",
136
+ class: "cn-working-calendar__row"
137
+ }, [
138
+ (openBlock(true), createElementBlock(Fragment, null, renderList(week, (slot, d) => {
139
+ return (openBlock(), createElementBlock(Fragment, null, [
140
+ (!slot)
141
+ ? (openBlock(), createElementBlock("span", {
142
+ key: `empty-${w}-${d}`,
143
+ role: "gridcell",
144
+ class: "cn-working-calendar__blank"
145
+ }))
146
+ : (openBlock(), createElementBlock("span", {
147
+ key: slot.date,
148
+ role: "gridcell",
149
+ class: normalizeClass(["cn-working-calendar__day", {
150
+ 'cn-working-calendar__day--off': !slot.working && !slot.closure,
151
+ 'cn-working-calendar__day--closed': slot.closure,
152
+ 'cn-working-calendar__day--today': slot.today,
153
+ }]),
154
+ tabindex: slot.date === $data.focusDate ? 0 : -1,
155
+ "aria-label": $options.dayLabel(slot),
156
+ "aria-current": slot.today ? 'date' : null,
157
+ title: slot.closure ? $options.closureName(slot.closure) : null,
158
+ "data-date": slot.date,
159
+ "data-testid": "cn-working-calendar-day",
160
+ onClick: $event => ($options.onDayClick(slot)),
161
+ onFocus: $event => ($data.focusDate = slot.date)
162
+ }, toDisplayString(slot.day), 43 /* TEXT, CLASS, PROPS, NEED_HYDRATION */, _hoisted_11))
163
+ ], 64 /* STABLE_FRAGMENT */))
164
+ }), 256 /* UNKEYED_FRAGMENT */))
165
+ ]))
166
+ }), 128 /* KEYED_FRAGMENT */))
167
+ ], 40 /* PROPS, NEED_HYDRATION */, _hoisted_8),
168
+ ($data.adding)
169
+ ? (openBlock(), createElementBlock("form", {
170
+ key: 1,
171
+ class: "cn-working-calendar__form",
172
+ "data-testid": "cn-working-calendar-form",
173
+ onSubmit: _cache[7] || (_cache[7] = withModifiers((...args) => ($options.saveDay && $options.saveDay(...args)), ["prevent"]))
174
+ }, [
175
+ createElementVNode("label", _hoisted_12, [
176
+ createTextVNode(toDisplayString($options.dateLabel) + " ", 1 /* TEXT */),
177
+ withDirectives(createElementVNode("input", {
178
+ ref: "dateInput",
179
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => (($data.newDate) = $event)),
180
+ type: "date",
181
+ required: "",
182
+ "data-testid": "cn-working-calendar-date"
183
+ }, null, 512 /* NEED_PATCH */), [
184
+ [vModelText, $data.newDate]
185
+ ])
186
+ ]),
187
+ createElementVNode("label", _hoisted_13, [
188
+ createTextVNode(toDisplayString($options.nameLabel) + " ", 1 /* TEXT */),
189
+ withDirectives(createElementVNode("input", {
190
+ "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => (($data.newName) = $event)),
191
+ type: "text",
192
+ "data-testid": "cn-working-calendar-name"
193
+ }, null, 512 /* NEED_PATCH */), [
194
+ [vModelText, $data.newName]
195
+ ])
196
+ ]),
197
+ createElementVNode("div", _hoisted_14, [
198
+ createVNode(_component_NcButton, {
199
+ variant: "tertiary",
200
+ "data-testid": "cn-working-calendar-cancel",
201
+ onClick: _cache[6] || (_cache[6] = $event => ($data.adding = false))
202
+ }, {
203
+ default: withCtx(() => [
204
+ createTextVNode(toDisplayString($options.cancelLabel), 1 /* TEXT */)
205
+ ]),
206
+ _: 1 /* STABLE */
207
+ }),
208
+ createVNode(_component_NcButton, {
209
+ variant: "primary",
210
+ type: "submit",
211
+ disabled: $data.saving || !$data.newDate,
212
+ "data-testid": "cn-working-calendar-save"
213
+ }, {
214
+ default: withCtx(() => [
215
+ createTextVNode(toDisplayString($options.saveLabel), 1 /* TEXT */)
216
+ ]),
217
+ _: 1 /* STABLE */
218
+ }, 8 /* PROPS */, ["disabled"])
219
+ ]),
220
+ ($data.saveError)
221
+ ? (openBlock(), createElementBlock("p", _hoisted_15, toDisplayString($data.saveError), 1 /* TEXT */))
222
+ : createCommentVNode("v-if", true)
223
+ ], 32 /* NEED_HYDRATION */))
224
+ : createCommentVNode("v-if", true)
225
+ ], 64 /* STABLE_FRAGMENT */))
226
+ : (openBlock(), createElementBlock("p", _hoisted_16, toDisplayString($options.emptyLabel), 1 /* TEXT */))
227
+ ]))
228
+ }
229
+
230
+ export { render };
231
+ //# sourceMappingURL=CnWorkingCalendarWidget.vue3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnWorkingCalendarWidget.vue3.js","sources":["../../../../src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue?vue&type=template&id=03ee459c&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-working-calendar\" data-testid=\"cn-working-calendar\">\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-working-calendar__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-working-calendar-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && !calendar\" class=\"cn-working-calendar__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" :name=\"loadingLabel\" />\n\t\t</div>\n\t\t<template v-else-if=\"calendar\">\n\t\t\t<div class=\"cn-working-calendar__head\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:aria-label=\"previousMonthLabel\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-previous\"\n\t\t\t\t\t@click=\"shiftMonth(-1)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcButton>\n\t\t\t\t<strong :id=\"`${uid}-title`\" class=\"cn-working-calendar__title\" data-testid=\"cn-working-calendar-title\">{{ monthTitle }}</strong>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:aria-label=\"nextMonthLabel\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-next\"\n\t\t\t\t\t@click=\"shiftMonth(1)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"editable\"\n\t\t\t\t\tclass=\"cn-working-calendar__add\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-add\"\n\t\t\t\t\t@click=\"openAdd()\">\n\t\t\t\t\t{{ addLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p v-if=\"previewNote\" class=\"cn-working-calendar__note\" data-testid=\"cn-working-calendar-note\">\n\t\t\t\t{{ previewNote }}\n\t\t\t</p>\n\t\t\t<span class=\"cn-working-calendar__live\" aria-live=\"polite\">{{ liveText }}</span>\n\n\t\t\t<div\n\t\t\t\trole=\"grid\"\n\t\t\t\tclass=\"cn-working-calendar__grid\"\n\t\t\t\t:aria-labelledby=\"`${uid}-title`\"\n\t\t\t\tdata-testid=\"cn-working-calendar-grid\"\n\t\t\t\t@keydown=\"onKeydown\">\n\t\t\t\t<div role=\"row\" class=\"cn-working-calendar__row\">\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-for=\"name in weekdayNames\"\n\t\t\t\t\t\t:key=\"name.long\"\n\t\t\t\t\t\trole=\"columnheader\"\n\t\t\t\t\t\tclass=\"cn-working-calendar__weekday\"\n\t\t\t\t\t\t:aria-label=\"name.long\">{{ name.short }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(week, w) in weeks\"\n\t\t\t\t\t:key=\"w\"\n\t\t\t\t\trole=\"row\"\n\t\t\t\t\tclass=\"cn-working-calendar__row\">\n\t\t\t\t\t<template v-for=\"(slot, d) in week\">\n\t\t\t\t\t\t<span v-if=\"!slot\"\n\t\t\t\t\t\t\t:key=\"`empty-${w}-${d}`\"\n\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\tclass=\"cn-working-calendar__blank\" />\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:key=\"slot.date\"\n\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\tclass=\"cn-working-calendar__day\"\n\t\t\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--off': !slot.working && !slot.closure,\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--closed': slot.closure,\n\t\t\t\t\t\t\t\t'cn-working-calendar__day--today': slot.today,\n\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t\t:tabindex=\"slot.date === focusDate ? 0 : -1\"\n\t\t\t\t\t\t\t:aria-label=\"dayLabel(slot)\"\n\t\t\t\t\t\t\t:aria-current=\"slot.today ? 'date' : null\"\n\t\t\t\t\t\t\t:title=\"slot.closure ? closureName(slot.closure) : null\"\n\t\t\t\t\t\t\t:data-date=\"slot.date\"\n\t\t\t\t\t\t\tdata-testid=\"cn-working-calendar-day\"\n\t\t\t\t\t\t\t@click=\"onDayClick(slot)\"\n\t\t\t\t\t\t\t@focus=\"focusDate = slot.date\">{{ slot.day }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<form\n\t\t\t\tv-if=\"adding\"\n\t\t\t\tclass=\"cn-working-calendar__form\"\n\t\t\t\tdata-testid=\"cn-working-calendar-form\"\n\t\t\t\t@submit.prevent=\"saveDay\">\n\t\t\t\t<label class=\"cn-working-calendar__field\">\n\t\t\t\t\t{{ dateLabel }}\n\t\t\t\t\t<input\n\t\t\t\t\t\tref=\"dateInput\"\n\t\t\t\t\t\tv-model=\"newDate\"\n\t\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t\trequired\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-date\">\n\t\t\t\t</label>\n\t\t\t\t<label class=\"cn-working-calendar__field\">\n\t\t\t\t\t{{ nameLabel }}\n\t\t\t\t\t<input\n\t\t\t\t\t\tv-model=\"newName\"\n\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-name\">\n\t\t\t\t</label>\n\t\t\t\t<div class=\"cn-working-calendar__form-actions\">\n\t\t\t\t\t<NcButton variant=\"tertiary\" data-testid=\"cn-working-calendar-cancel\" @click=\"adding = false\">\n\t\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t:disabled=\"saving || !newDate\"\n\t\t\t\t\t\tdata-testid=\"cn-working-calendar-save\">\n\t\t\t\t\t\t{{ saveLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t\t<p v-if=\"saveError\"\n\t\t\t\t\tclass=\"cn-working-calendar__error\"\n\t\t\t\t\trole=\"alert\"\n\t\t\t\t\tdata-testid=\"cn-working-calendar-save-error\">\n\t\t\t\t\t{{ saveError }}\n\t\t\t\t</p>\n\t\t\t</form>\n\t\t</template>\n\t\t<p v-else class=\"cn-working-calendar__note\" data-testid=\"cn-working-calendar-empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcLoadingIcon } from '@nextcloud/vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport { fetchReferenceObjects } from '../../utils/boardReferenceColumns.js'\nimport { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { userLocale } from '../../utils/userLocale.js'\nimport { buildMonth, isoDate, normaliseExceptions } from '../../utils/workingCalendarMonth.js'\n\nconst OBJECT_API = '/apps/openregister/api/objects'\nconst PREVIEW_URL = '/apps/openregister/api/flow-timers/calendars/preview'\n\nlet instanceCounter = 0\n\n/**\n * The id of an object, wherever OpenRegister carries it.\n *\n * @param {object|null} object The object.\n * @return {string} The id, or ''.\n */\nfunction idOf(object) {\n\treturn String(object?.id ?? object?.['@self']?.id ?? object?.uuid ?? '')\n}\n\n/**\n * Today as `YYYY-MM-DD`, in local time.\n *\n * @return {string} The date.\n */\nfunction todayIso() {\n\tconst now = new Date()\n\treturn isoDate(now.getFullYear(), now.getMonth(), now.getDate())\n}\n\n/**\n * CnWorkingCalendarWidget — the `working-calendar` widget: one month of a\n * working calendar kept as an OpenRegister object (board\n * DqInstellingenAlgemeen, \"Werkdagen en feestdagen\").\n *\n * The grid runs Monday to Sunday. Weekdays the calendar does not work are\n * grey; the dates it is closed on are red, with their name on hover and in\n * the day's label. The closures are not computed here: the widget asks\n * OpenRegister's calendar preview (`/api/flow-timers/calendars/preview`) for\n * the year shown, which expands the calendar's rules and its exceptions.\n *\n * \"Add day\" appends `{ date, name }` to the object's `exceptions` with one\n * update through the object API, under the reader's own rights; it shows for\n * administrators, and the server decides. The preview is administrators-only\n * too; for anyone else the widget shows the stored exceptions and says the\n * holidays could not be read.\n *\n * ```json\n * {\n * \"type\": \"working-calendar\",\n * \"content\": {\n * \"calendar\": { \"register\": \"flow-timers\", \"schema\": \"working-calendar\", \"filter\": { \"slug\": \"default\" } }\n * }\n * }\n * ```\n *\n * Keyboard: the days form a grid with one tab stop; the arrow keys move a\n * day or a week, Home and End to the start and end of the week, Page Up and\n * Page Down a month. Enter on a day opens Add day for that date.\n */\nexport default {\n\tname: 'CnWorkingCalendarWidget',\n\n\tcomponents: { ChevronLeft, ChevronRight, NcButton, NcLoadingIcon },\n\n\tinject: {\n\t\t/** The page's workspace values, for `@workspace.<key>` tokens in the filter. */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/** The host's translate, for the manifest's texts. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget config: `{ calendar: { register, schema, id? | filter? },\n\t\t * previewUrl?, editable?, emptyText? }`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The bound record's id, for `@objectId` in the filter. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The bound record, for `@object.<field>` in the filter. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\tinstanceCounter += 1\n\t\tconst now = new Date()\n\t\treturn {\n\t\t\tuid: `cn-working-calendar-${instanceCounter}`,\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tcalendar: null,\n\t\t\tpreviews: {},\n\t\t\tpreviewFailed: false,\n\t\t\tyear: now.getFullYear(),\n\t\t\tmonth: now.getMonth(),\n\t\t\tfocusDate: todayIso(),\n\t\t\tadding: false,\n\t\t\tnewDate: '',\n\t\t\tnewName: '',\n\t\t\tsaving: false,\n\t\t\tsaveError: '',\n\t\t\tliveText: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {{register: string, schema: string, id: string, filter: (object|null)}|null} The calendar source, tokens resolved, or null while unresolved. */\n\t\tsource() {\n\t\t\tconst c = this.content?.calendar\n\t\t\tif (!c || !c.register || !c.schema) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst unwrap = (v) => (v && typeof v === 'object' && 'value' in v ? v.value : v)\n\t\t\tconst ctx = { objectId: this.objectId || null, object: this.objectData, workspace: unwrap(this.cnWorkspaceContext) || {} }\n\t\t\tconst filter = c.filter && typeof c.filter === 'object' ? resolveFilterTokens(c.filter, ctx) : null\n\t\t\tif (filter && hasUnresolvedTokens(filter)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { register: String(c.register), schema: String(c.schema), id: c.id ? String(c.id) : '', filter }\n\t\t},\n\n\t\t/** @return {string} A key of the source, to read it again when it changes. */\n\t\tsourceKey() {\n\t\t\treturn this.source ? JSON.stringify(this.source) : ''\n\t\t},\n\n\t\t/** @return {boolean} Whether Add day shows: administrators, unless the manifest turns it off. */\n\t\teditable() {\n\t\t\treturn this.content?.editable !== false && Boolean(getCurrentUser()?.isAdmin)\n\t\t},\n\n\t\t/** @return {number[]} The ISO weekdays that work. */\n\t\tworkingWeekdays() {\n\t\t\tconst days = this.calendar?.workingWeekdays\n\t\t\treturn Array.isArray(days) ? days.map((d) => Number(d)) : [1, 2, 3, 4, 5]\n\t\t},\n\n\t\t/** @return {Array<{date: string, name: string}>} The stored exceptions. */\n\t\texceptions() {\n\t\t\treturn normaliseExceptions(this.calendar?.exceptions)\n\t\t},\n\n\t\t/**\n\t\t * The closed dates of the year shown: the preview's, each marked as an\n\t\t * exception when the calendar lists it, else as a holiday; without a\n\t\t * preview, the stored exceptions.\n\t\t *\n\t\t * @return {Map<string, {name: string, kind: string}>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month\n\t\t */\n\t\tclosures() {\n\t\t\tconst own = new Set(this.exceptions.map((e) => e.date))\n\t\t\tconst map = new Map()\n\t\t\tconst preview = this.previews[this.year]\n\t\t\tfor (const entry of Array.isArray(preview) ? preview : []) {\n\t\t\t\tif (entry && typeof entry.date === 'string') {\n\t\t\t\t\tmap.set(entry.date, { name: String(entry.name || ''), kind: own.has(entry.date) ? 'exception' : 'holiday' })\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const e of this.exceptions) {\n\t\t\t\tif (!map.has(e.date)) {\n\t\t\t\t\tmap.set(e.date, { name: e.name, kind: 'exception' })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn map\n\t\t},\n\n\t\t/** @return {Array<Array<object|null>>} The weeks of the month shown. */\n\t\tweeks() {\n\t\t\treturn buildMonth(this.year, this.month, { workingWeekdays: this.workingWeekdays, closures: this.closures, today: todayIso() })\n\t\t},\n\n\t\t/** @return {Array<{short: string, long: string}>} Monday to Sunday in the user's language. */\n\t\tweekdayNames() {\n\t\t\tconst short = new Intl.DateTimeFormat(userLocale(), { weekday: 'short' })\n\t\t\tconst long = new Intl.DateTimeFormat(userLocale(), { weekday: 'long' })\n\t\t\treturn Array.from({ length: 7 }, (_v, i) => {\n\t\t\t\tconst day = new Date(Date.UTC(2024, 0, 1 + i, 12))\n\t\t\t\treturn { short: short.format(day).replace(/\\.$/, ''), long: long.format(day) }\n\t\t\t})\n\t\t},\n\n\t\t/** @return {string} \"December 2026\" in the user's language. */\n\t\tmonthTitle() {\n\t\t\tconst text = new Intl.DateTimeFormat(userLocale(), { month: 'long', year: 'numeric' }).format(new Date(this.year, this.month, 15))\n\t\t\treturn text.charAt(0).toUpperCase() + text.slice(1)\n\t\t},\n\n\t\t/** @return {string} Why holidays are missing, or ''. */\n\t\tpreviewNote() {\n\t\t\treturn this.previewFailed ? t('nextcloud-vue', 'Holidays could not be read. Only the days added by hand are shown.') : ''\n\t\t},\n\n\t\tloadingLabel: () => t('nextcloud-vue', 'Loading …'),\n\t\tpreviousMonthLabel: () => t('nextcloud-vue', 'Previous month'),\n\t\tnextMonthLabel: () => t('nextcloud-vue', 'Next month'),\n\t\taddLabel: () => t('nextcloud-vue', 'Add day'),\n\t\tdateLabel: () => t('nextcloud-vue', 'Date'),\n\t\tnameLabel: () => t('nextcloud-vue', 'Name'),\n\t\tcancelLabel: () => t('nextcloud-vue', 'Cancel'),\n\t\tsaveLabel: () => t('nextcloud-vue', 'Save'),\n\n\t\t/** @return {string} The empty-state text. */\n\t\temptyLabel() {\n\t\t\treturn this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'No working calendar found.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\n\t\t\timmediate: true,\n\t\t},\n\n\t\tyear() {\n\t\t\tthis.loadPreview()\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The host's translate, or the text unchanged.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The translation.\n\t\t */\n\t\ttranslate(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\t/** @return {string} The calendar object's API URL. */\n\t\tobjectUrl() {\n\t\t\tconst s = this.source\n\t\t\tconst id = idOf(this.calendar) || s.id\n\t\t\treturn generateUrl(`${OBJECT_API}/${encodeURIComponent(s.register)}/${encodeURIComponent(s.schema)}/${encodeURIComponent(id)}`)\n\t\t},\n\n\t\t/**\n\t\t * Read the calendar object, then the closures of the year shown.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst key = this.sourceKey\n\t\t\tif (key === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst s = this.source\n\t\t\t\tlet calendar = null\n\t\t\t\tif (s.id) {\n\t\t\t\t\tconst response = await axios.get(generateUrl(`${OBJECT_API}/${encodeURIComponent(s.register)}/${encodeURIComponent(s.schema)}/${encodeURIComponent(s.id)}`))\n\t\t\t\t\tcalendar = response?.data || null\n\t\t\t\t} else {\n\t\t\t\t\tconst found = await fetchReferenceObjects({ register: s.register, schema: s.schema, filter: s.filter || {}, limit: 1 })\n\t\t\t\t\tcalendar = found[0] || null\n\t\t\t\t}\n\t\t\t\tif (key !== this.sourceKey) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.calendar = calendar\n\t\t\t\tthis.previews = {}\n\t\t\t\tif (calendar) {\n\t\t\t\t\tawait this.loadPreview()\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tthis.error = t('nextcloud-vue', 'The working calendar could not be loaded.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Ask the calendar preview for the closed dates of the year shown,\n\t\t * once per year.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-working-calendar-shows-a-month\n\t\t */\n\t\tasync loadPreview() {\n\t\t\tconst year = this.year\n\t\t\tif (!this.calendar || this.previews[year]) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = generateUrl(this.content?.previewUrl || PREVIEW_URL)\n\t\t\t\tconst response = await axios.post(url, { calendar: this.calendar, year })\n\t\t\t\tconst dates = Array.isArray(response?.data?.dates) ? response.data.dates : []\n\t\t\t\tthis.previews = { ...this.previews, [year]: dates }\n\t\t\t\tthis.previewFailed = false\n\t\t\t} catch {\n\t\t\t\tthis.previewFailed = true\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Show another month, keeping focus on the same day where it exists.\n\t\t *\n\t\t * @param {number} delta Months to move.\n\t\t * @return {void}\n\t\t */\n\t\tshiftMonth(delta) {\n\t\t\tconst date = new Date(this.year, this.month + delta, 1)\n\t\t\tthis.year = date.getFullYear()\n\t\t\tthis.month = date.getMonth()\n\t\t\tconst day = Math.min(Number(this.focusDate.slice(8, 10)) || 1, new Date(this.year, this.month + 1, 0).getDate())\n\t\t\tthis.focusDate = isoDate(this.year, this.month, day)\n\t\t\tthis.liveText = this.monthTitle\n\t\t},\n\n\t\t/**\n\t\t * A day's accessible label: the full date, whether it works, and the\n\t\t * closure's name.\n\t\t *\n\t\t * @param {object} slot The day.\n\t\t * @return {string} The label.\n\t\t */\n\t\tdayLabel(slot) {\n\t\t\tconst [y, m, d] = slot.date.split('-').map(Number)\n\t\t\tconst date = new Intl.DateTimeFormat(userLocale(), { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }).format(new Date(y, m - 1, d))\n\t\t\tif (slot.closure) {\n\t\t\t\tconst name = this.closureName(slot.closure)\n\t\t\t\treturn name\n\t\t\t\t\t? t('nextcloud-vue', '{date}, closed: {name}', { date, name })\n\t\t\t\t\t: t('nextcloud-vue', '{date}, closed', { date })\n\t\t\t}\n\t\t\treturn slot.working ? t('nextcloud-vue', '{date}, working day', { date }) : t('nextcloud-vue', '{date}, not a working day', { date })\n\t\t},\n\n\t\t/**\n\t\t * A closure's name, with a fallback for an unnamed added day.\n\t\t *\n\t\t * @param {{name: string, kind: string}} closure The closure.\n\t\t * @return {string} The name.\n\t\t */\n\t\tclosureName(closure) {\n\t\t\tif (closure.name) {\n\t\t\t\treturn this.translate(closure.name)\n\t\t\t}\n\t\t\treturn closure.kind === 'exception' ? t('nextcloud-vue', 'Day added by hand') : ''\n\t\t},\n\n\t\t/**\n\t\t * Open Add day, with a date filled in.\n\t\t *\n\t\t * @param {string} [date] The date.\n\t\t * @return {void}\n\t\t */\n\t\topenAdd(date) {\n\t\t\tthis.newDate = date || this.focusDate || ''\n\t\t\tthis.newName = ''\n\t\t\tthis.saveError = ''\n\t\t\tthis.adding = true\n\t\t\tthis.$nextTick(() => this.$refs.dateInput?.focus?.())\n\t\t},\n\n\t\t/**\n\t\t * A click on a day: open Add day for it when the reader may edit.\n\t\t *\n\t\t * @param {object} slot The day.\n\t\t * @return {void}\n\t\t */\n\t\tonDayClick(slot) {\n\t\t\tthis.focusDate = slot.date\n\t\t\tif (this.editable) {\n\t\t\t\tthis.openAdd(slot.date)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Append the new day to the calendar's exceptions with one update,\n\t\t * under the reader's rights, then read the closures again.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/working-calendar-widget/specs/working-calendar-widget/spec.md#requirement-a-day-can-be-added-to-the-calendar\n\t\t */\n\t\tasync saveDay() {\n\t\t\tif (!/^\\d{4}-\\d{2}-\\d{2}$/.test(this.newDate) || !this.calendar) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\tthis.saveError = ''\n\t\t\ttry {\n\t\t\t\tconst stored = Array.isArray(this.calendar.exceptions) ? this.calendar.exceptions : []\n\t\t\t\tconst name = this.newName.trim()\n\t\t\t\tconst exceptions = [...stored.filter((e) => (typeof e === 'string' ? e : e?.date) !== this.newDate), name ? { date: this.newDate, name } : { date: this.newDate }]\n\t\t\t\tconst response = await axios.patch(this.objectUrl(), { exceptions })\n\t\t\t\tthis.calendar = { ...this.calendar, ...(response?.data && typeof response.data === 'object' ? response.data : {}), exceptions }\n\t\t\t\tconst year = Number(this.newDate.slice(0, 4))\n\t\t\t\tconst { [year]: _stale, ...rest } = this.previews\n\t\t\t\tthis.previews = rest\n\t\t\t\tconst [y, m] = this.newDate.split('-').map(Number)\n\t\t\t\tthis.year = y\n\t\t\t\tthis.month = m - 1\n\t\t\t\tthis.focusDate = this.newDate\n\t\t\t\tawait this.loadPreview()\n\t\t\t\tthis.adding = false\n\t\t\t\tthis.liveText = t('nextcloud-vue', 'Day added.')\n\t\t\t} catch (e) {\n\t\t\t\tthis.saveError = e?.response?.status === 403\n\t\t\t\t\t? t('nextcloud-vue', 'Only an administrator can change the working calendar.')\n\t\t\t\t\t: t('nextcloud-vue', 'The day could not be added. Try again.')\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home / End and Page Up / Down move focus across the days.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key event.\n\t\t * @return {void}\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst moves = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }\n\t\t\tconst [y, m, d] = (this.focusDate || todayIso()).split('-').map(Number)\n\t\t\tlet target\n\t\t\tif (event.key in moves) {\n\t\t\t\ttarget = new Date(y, m - 1, d + moves[event.key])\n\t\t\t} else if (event.key === 'Home' || event.key === 'End') {\n\t\t\t\tconst weekday = (new Date(y, m - 1, d).getDay() + 6) % 7\n\t\t\t\ttarget = new Date(y, m - 1, d + (event.key === 'Home' ? -weekday : 6 - weekday))\n\t\t\t} else if (event.key === 'PageUp' || event.key === 'PageDown') {\n\t\t\t\ttarget = new Date(y, m - 1 + (event.key === 'PageUp' ? -1 : 1), 1)\n\t\t\t\ttarget.setDate(Math.min(d, new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate()))\n\t\t\t} else if ((event.key === 'Enter' || event.key === ' ') && this.editable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.openAdd(this.focusDate)\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.focusDate = isoDate(target.getFullYear(), target.getMonth(), target.getDate())\n\t\t\tif (target.getFullYear() !== this.year || target.getMonth() !== this.month) {\n\t\t\t\tthis.year = target.getFullYear()\n\t\t\t\tthis.month = target.getMonth()\n\t\t\t\tthis.liveText = this.monthTitle\n\t\t\t}\n\t\t\tthis.$nextTick(() => this.$el.querySelector?.(`[data-date=\"${this.focusDate}\"]`)?.focus?.())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-working-calendar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-working-calendar__head {\n\talign-items: center;\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-working-calendar__title {\n\tfont-size: 15px;\n\tmin-width: 9em;\n\ttext-align: center;\n}\n\n.cn-working-calendar__add {\n\tmargin-inline-start: auto;\n}\n\n.cn-working-calendar__grid {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-working-calendar__row {\n\tdisplay: grid;\n\tgap: 4px;\n\tgrid-template-columns: repeat(7, minmax(0, 1fr));\n}\n\n.cn-working-calendar__weekday {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\ttext-align: center;\n}\n\n.cn-working-calendar__day {\n\talign-items: center;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 6px;\n\tcursor: default;\n\tdisplay: flex;\n\tfont-size: 13px;\n\theight: 34px;\n\tjustify-content: center;\n}\n\n.cn-working-calendar__day:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 1px;\n}\n\n.cn-working-calendar__day--off {\n\tbackground: var(--color-background-dark);\n}\n\n.cn-working-calendar__day--closed {\n\tbackground: var(--color-error-light, color-mix(in srgb, var(--color-error) 12%, var(--color-main-background)));\n\tborder-color: var(--color-error);\n\tcolor: var(--color-error-text, var(--color-main-text));\n\tfont-weight: 600;\n}\n\n.cn-working-calendar__day--today {\n\tbox-shadow: inset 0 0 0 2px var(--color-primary-element);\n}\n\n.cn-working-calendar__live {\n\tclip-path: inset(50%);\n\theight: 1px;\n\toverflow: hidden;\n\tposition: absolute;\n\twhite-space: nowrap;\n\twidth: 1px;\n}\n\n.cn-working-calendar__note {\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0;\n}\n\n.cn-working-calendar__form {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px 12px;\n}\n\n.cn-working-calendar__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-working-calendar__form-actions {\n\talign-items: flex-end;\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n.cn-working-calendar__error {\n\tcolor: var(--color-error-text, var(--color-error));\n\tmargin: 0;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createVNode","_Fragment","_createElementVNode","_createBlock","_renderList","_createTextVNode"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import __sfc_main from './CnWorkingCalendarWidget.vue.js';
2
+ import { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js';
3
+
4
+ /**
5
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
6
+ * SPDX-License-Identifier: EUPL-1.2
7
+ *
8
+ * CnWorkingCalendarWidget — self-registers the `working-calendar` widget type
9
+ * (one month of a working calendar kept as an OpenRegister object, with its
10
+ * closed dates and Add day) into the shared dashboardWidgetRegistry at module
11
+ * load. Renderer only: the calendar object is named in the manifest.
12
+ */
13
+
14
+
15
+ registerDashboardWidget('working-calendar', {
16
+ renderer: __sfc_main,
17
+ form: null,
18
+ defaultContent: {
19
+ calendar: { register: 'flow-timers', schema: 'working-calendar', filter: {} },
20
+ },
21
+ displayName: 'Working calendar',
22
+ icon: 'CalendarMonthOutline',
23
+ surfaces: ['app-dashboard', 'detail-page'],
24
+ });
25
+
26
+ export { __sfc_main as CnWorkingCalendarWidget, __sfc_main as default };
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/CnWorkingCalendarWidget/index.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * CnWorkingCalendarWidget — self-registers the `working-calendar` widget type\n * (one month of a working calendar kept as an OpenRegister object, with its\n * closed dates and Add day) into the shared dashboardWidgetRegistry at module\n * load. Renderer only: the calendar object is named in the manifest.\n */\n\nimport CnWorkingCalendarWidget from './CnWorkingCalendarWidget.vue'\nimport { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\n\nregisterDashboardWidget('working-calendar', {\n\trenderer: CnWorkingCalendarWidget,\n\tform: null,\n\tdefaultContent: {\n\t\tcalendar: { register: 'flow-timers', schema: 'working-calendar', filter: {} },\n\t},\n\tdisplayName: 'Working calendar',\n\ticon: 'CalendarMonthOutline',\n\tsurfaces: ['app-dashboard', 'detail-page'],\n})\n\nexport { CnWorkingCalendarWidget }\nexport default CnWorkingCalendarWidget\n"],"names":["CnWorkingCalendarWidget"],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA,uBAAuB,CAAC,kBAAkB,EAAE;AAC5C,CAAC,QAAQ,EAAEA,UAAuB;AAClC,CAAC,IAAI,EAAE,IAAI;AACX,CAAC,cAAc,EAAE;AACjB,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,EAAE,EAAE;AAC/E,EAAE;AACF,CAAC,WAAW,EAAE,kBAAkB;AAChC,CAAC,IAAI,EAAE,sBAAsB;AAC7B,CAAC,QAAQ,EAAE,CAAC,eAAe,EAAE,aAAa,CAAC;AAC3C,CAAC;;;;"}
@@ -1,4 +1,4 @@
1
- import { translate } from '@nextcloud/l10n';
1
+ import { translate, getCanonicalLocale } from '@nextcloud/l10n';
2
2
  import { generateUrl, generateOcsUrl } from '@nextcloud/router';
3
3
  import { reactive } from 'vue';
4
4
  import CalendarClock from 'vue-material-design-icons/CalendarClock.vue';
@@ -7,8 +7,10 @@ import LightningBolt from 'vue-material-design-icons/LightningBolt.vue';
7
7
  import Pencil from 'vue-material-design-icons/Pencil.vue';
8
8
  import { formatRelativeDate } from '../utils/boardDate.js';
9
9
  import { flowRunStatuses, FLOW_RUN_ERROR_STATUSES } from '../utils/flowRunStatus.js';
10
+ import { fetchGroupMembers, fetchStatuses, fetchPresence } from '../utils/nextcloudDirectory.js';
10
11
  import { readPath } from '../utils/readPath.js';
11
12
  import { useFlowStore } from './useFlowStore.js';
13
+ import { useOrganisationStore, organisationRecord } from './useOrganisationStore.js';
12
14
  import { useTaskInboxStore } from './useTaskInboxStore.js';
13
15
 
14
16
  /**
@@ -892,12 +894,218 @@ function searchSource(config = {}) {
892
894
  }
893
895
  }
894
896
 
897
+ /**
898
+ * The users source: the members of a Nextcloud group, with their function,
899
+ * roles, e-mail and whether they are in today (`entitySource: "users"`,
900
+ * DqAfdelingen). Pair it with `folderSidebar: { source: 'groups',
901
+ * filterField: 'group' }`, which lists the groups as the folder tree; the
902
+ * selected group arrives as `group`. `sourceConfig.group` is the group shown
903
+ * before one is picked.
904
+ *
905
+ * Today is the dav app's out-of-office period when one runs now ("Absent
906
+ * until 14 Oct"), else the user status (present, away) and its message. The
907
+ * function is the profile `headline`, the roles the profile `role`, unless
908
+ * `sourceConfig.functionField` / `rolesField` name other user fields.
909
+ * Listing a group needs an admin or the group's sub-admin; a refused request
910
+ * leaves the list empty.
911
+ *
912
+ * @return {object} The source adapter.
913
+ * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members
914
+ */
915
+ function usersSource() {
916
+ const state = reactive({ rows: [], loading: false });
917
+ let seq = 0;
918
+ const presenceLabels = {
919
+ present: translate('nextcloud-vue', 'Present'),
920
+ away: translate('nextcloud-vue', 'Away'),
921
+ };
922
+ const presenceColors = {
923
+ [presenceLabels.present]: 'success',
924
+ [presenceLabels.away]: 'info',
925
+ };
926
+
927
+ return {
928
+ /**
929
+ * Load the group's members and their presence.
930
+ *
931
+ * @param {object} config The page's source config with the picked `group`.
932
+ * @return {Promise<void>} Resolves when loaded.
933
+ */
934
+ load: async (config) => {
935
+ const group = String((config && (config.group || config.defaultGroup)) || '');
936
+ const mine = ++seq;
937
+ if (group === '') {
938
+ state.rows = [];
939
+ return
940
+ }
941
+ state.loading = true;
942
+ try {
943
+ const { default: axios } = await import('@nextcloud/axios');
944
+ const [members, statuses] = await Promise.all([fetchGroupMembers(axios, group), fetchStatuses(axios)]);
945
+ const functionField = (config && config.functionField) || 'headline';
946
+ const rolesField = (config && config.rolesField) || 'role';
947
+ const rows = await Promise.all(members.map(async (user) => {
948
+ const presence = await fetchPresence(axios, user.id, statuses[user.id] || null);
949
+ let today = '';
950
+ if (presence.state === 'absent') {
951
+ const until = presence.until ? new Date(`${presence.until}T12:00:00`) : null;
952
+ today = until
953
+ ? translate('nextcloud-vue', 'Absent until {date}', { date: until.toLocaleDateString(getCanonicalLocale() || undefined, { day: 'numeric', month: 'short' }) })
954
+ : translate('nextcloud-vue', 'Absent');
955
+ } else if (presence.message) {
956
+ today = presence.message;
957
+ } else if (presenceLabels[presence.state]) {
958
+ today = presenceLabels[presence.state];
959
+ }
960
+ return {
961
+ id: user.id,
962
+ displayname: user.displayname || user['display-name'] || user.id,
963
+ function: String(user[functionField] || ''),
964
+ roles: String(user[rolesField] || ''),
965
+ email: user.email || '',
966
+ today,
967
+ presence: presence.state,
968
+ }
969
+ }));
970
+ if (mine === seq) {
971
+ state.rows = rows.sort((a, b) => a.displayname.localeCompare(b.displayname));
972
+ }
973
+ } catch (error) {
974
+ if (mine === seq) {
975
+ state.rows = [];
976
+ }
977
+ throw error
978
+ } finally {
979
+ if (mine === seq) {
980
+ state.loading = false;
981
+ }
982
+ }
983
+ },
984
+
985
+ /**
986
+ * @return {Array<object>} The members.
987
+ */
988
+ rows: () => state.rows,
989
+
990
+ /**
991
+ * @return {boolean} Whether a load is in flight.
992
+ */
993
+ loading: () => state.loading,
994
+
995
+ columns: [
996
+ { key: 'displayname', label: translate('nextcloud-vue', 'Employee'), secondary: 'function' },
997
+ { key: 'roles', label: translate('nextcloud-vue', 'Roles') },
998
+ { key: 'email', label: translate('nextcloud-vue', 'E-mail') },
999
+ { key: 'today', label: translate('nextcloud-vue', 'Today'), widget: 'badge', widgetProps: { colorMap: presenceColors, variant: 'warning' } },
1000
+ ],
1001
+
1002
+ searchFields: {
1003
+ group: { param: 'group', single: true },
1004
+ },
1005
+
1006
+ showAdd: false,
1007
+ }
1008
+ }
1009
+
1010
+ /**
1011
+ * The organisations source: OpenRegister organisations, which are not
1012
+ * register/schema objects.
1013
+ *
1014
+ * The list is the viewer's own organisations and the single read refuses an
1015
+ * organisation the viewer has no access to; both are decided by OpenRegister
1016
+ * from the session. The adapter forwards nothing that could widen that.
1017
+ *
1018
+ * It is a RECORD source as well (`loadRecord` + `record`), so a
1019
+ * `type: "detail"` page with the same `entitySource` shows one organisation
1020
+ * with the header, pills, facts and `@objectId` / `@object.*` tokens an object
1021
+ * page has.
1022
+ *
1023
+ * @return {object} The source adapter.
1024
+ * @spec openspec/changes/organisations-entity-source/specs/organisations-entity-source/spec.md
1025
+ */
1026
+ function organisationsSource() {
1027
+ const store = useOrganisationStore();
1028
+
1029
+ const statusLabels = {
1030
+ provisioning: translate('nextcloud-vue', 'Provisioning'),
1031
+ active: translate('nextcloud-vue', 'Active'),
1032
+ suspended: translate('nextcloud-vue', 'Suspended'),
1033
+ deprovisioning: translate('nextcloud-vue', 'Deprovisioning'),
1034
+ archived: translate('nextcloud-vue', 'Archived'),
1035
+ retained: translate('nextcloud-vue', 'Retained'),
1036
+ };
1037
+
1038
+ return {
1039
+ store,
1040
+
1041
+ /**
1042
+ * Load the viewer's organisations. The endpoint takes no parameters.
1043
+ *
1044
+ * @return {Promise<void>} Resolves when loaded.
1045
+ */
1046
+ load: () => store.load(),
1047
+
1048
+ /**
1049
+ * @return {Array<object>} The rows, keyed on the organisation uuid.
1050
+ */
1051
+ rows: () => (store.organisations || []).map(organisationRecord),
1052
+
1053
+ /**
1054
+ * @return {boolean} Whether a load is in flight.
1055
+ */
1056
+ loading: () => !!store.loading,
1057
+
1058
+ /**
1059
+ * Read one organisation for a detail page.
1060
+ *
1061
+ * @param {string} id The organisation uuid.
1062
+ * @return {Promise<object|null>} The organisation, or null when it could not be read.
1063
+ */
1064
+ loadRecord: (id) => store.fetchRecord(id),
1065
+
1066
+ /**
1067
+ * @param {string} id The organisation uuid.
1068
+ * @return {object|null} The organisation read for this id, keyed on its uuid, or null.
1069
+ */
1070
+ record: (id) => {
1071
+ const organisation = store.records?.[String(id)];
1072
+ return organisation ? organisationRecord(organisation) : null
1073
+ },
1074
+
1075
+ /**
1076
+ * @param {string} id The organisation uuid.
1077
+ * @return {number|null} The HTTP status of a failed read of this id, or null.
1078
+ */
1079
+ recordError: (id) => store.recordErrors?.[String(id)] ?? null,
1080
+
1081
+ columns: [
1082
+ { key: 'name', label: translate('nextcloud-vue', 'Name') },
1083
+ {
1084
+ key: 'status',
1085
+ label: translate('nextcloud-vue', 'Status'),
1086
+ enum: Object.keys(statusLabels),
1087
+ enumLabels: statusLabels,
1088
+ colorMap: { active: 'success', provisioning: 'info', suspended: 'warning', deprovisioning: 'error', archived: 'default', retained: 'default' },
1089
+ },
1090
+ { key: 'type', label: translate('nextcloud-vue', 'Type') },
1091
+ { key: 'created', label: translate('nextcloud-vue', 'Created'), type: 'string', format: 'date-time' },
1092
+ ],
1093
+
1094
+ // Creating an organisation is not the index's object form, and
1095
+ // OpenRegister has no delete for one, so neither action is offered.
1096
+ showAdd: false,
1097
+ title: translate('nextcloud-vue', 'Organisations'),
1098
+ }
1099
+ }
1100
+
895
1101
  const indexSources = {
896
1102
  flows: flowsSource,
897
1103
  flowRuns: flowRunsSource,
898
1104
  tasks: tasksSource,
899
1105
  endpoint: endpointSource,
900
1106
  search: searchSource,
1107
+ users: usersSource,
1108
+ organisations: organisationsSource,
901
1109
  };
902
1110
 
903
1111
  /**
@@ -930,5 +1138,33 @@ function resolveIndexSource(name, config) {
930
1138
  return factory(config || {})
931
1139
  }
932
1140
 
933
- export { flowRunStatuses, flowTriggerCell, indexSources, resolveIndexSource, startOfWeek, taskDeepLink, taskDueLabel };
1141
+ /**
1142
+ * Resolve a named source for ONE record, for a `type: "detail"` page.
1143
+ *
1144
+ * The same registry as the index: a source that can also read a single record
1145
+ * supplies `loadRecord(id)` and `record(id)`, and the detail page names it with
1146
+ * the same `entitySource`. A name that is unknown, or a source that cannot read
1147
+ * one record, resolves to null with a console warning, and the page shows no
1148
+ * record rather than the wrong one.
1149
+ *
1150
+ * @param {string} name The source name from `config.entitySource`.
1151
+ * @return {object|null} The adapter, or null.
1152
+ * @spec openspec/changes/organisations-entity-source/specs/organisations-entity-source/spec.md
1153
+ */
1154
+ function resolveRecordSource(name) {
1155
+ const key = String(name || '');
1156
+ if (!key) {
1157
+ return null
1158
+ }
1159
+ const factory = indexSources[key];
1160
+ const source = typeof factory === 'function' ? factory() : null;
1161
+ if (!source || typeof source.loadRecord !== 'function' || typeof source.record !== 'function') {
1162
+ // eslint-disable-next-line no-console
1163
+ console.warn(`[CnDetailPage] entitySource "${key}" is not a source that reads one record. The page shows no record.`);
1164
+ return null
1165
+ }
1166
+ return source
1167
+ }
1168
+
1169
+ export { flowRunStatuses, flowTriggerCell, indexSources, resolveIndexSource, resolveRecordSource, startOfWeek, taskDeepLink, taskDueLabel };
934
1170
  //# sourceMappingURL=indexSources.js.map