@conduction/nextcloud-vue 2.100.0 → 2.102.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 (168) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +22 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  6. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
  7. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  8. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
  9. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  10. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  11. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
  12. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  13. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
  14. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +70 -6
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +105 -74
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
  21. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
  22. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
  23. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
  24. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
  25. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
  26. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  27. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
  28. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  29. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
  30. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  31. package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
  32. package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
  33. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
  34. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
  35. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
  36. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
  37. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
  38. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
  39. package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
  40. package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
  41. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +320 -74
  42. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +323 -213
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/useNamedSource.js +30 -3
  46. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  47. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  48. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
  49. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  50. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  51. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
  52. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
  53. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
  54. package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.js.map +1 -1
  55. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  56. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
  57. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
  59. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  60. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  61. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
  64. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  65. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  66. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
  67. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  68. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
  69. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  70. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
  71. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
  72. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  73. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
  74. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  75. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
  76. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
  77. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
  78. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
  79. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
  80. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
  81. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  82. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +35 -2
  83. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  84. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  85. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
  86. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  87. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  88. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  89. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  90. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
  91. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  92. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  93. package/dist/esm/composables/indexSources.js +176 -20
  94. package/dist/esm/composables/indexSources.js.map +1 -1
  95. package/dist/esm/composables/useEndpointSource.js +28 -6
  96. package/dist/esm/composables/useEndpointSource.js.map +1 -1
  97. package/dist/esm/index.js +1 -0
  98. package/dist/esm/index.js.map +1 -1
  99. package/dist/esm/l10n/en.json.js +41 -1
  100. package/dist/esm/l10n/en.json.js.map +1 -1
  101. package/dist/esm/l10n/nl.json.js +39 -1
  102. package/dist/esm/l10n/nl.json.js.map +1 -1
  103. package/dist/esm/library-style.js +1 -1
  104. package/dist/esm/nextcloud-vue.css +939 -376
  105. package/dist/esm/utils/builtInFormatters.js +24 -4
  106. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  107. package/dist/esm/utils/languageMapText.js +50 -0
  108. package/dist/esm/utils/languageMapText.js.map +1 -0
  109. package/dist/esm/utils/libraryWidgetKeys.js +3 -0
  110. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  111. package/dist/esm/utils/mapList.js +71 -0
  112. package/dist/esm/utils/mapList.js.map +1 -0
  113. package/dist/esm/utils/rowClassRules.js +43 -0
  114. package/dist/esm/utils/rowClassRules.js.map +1 -0
  115. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  116. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  117. package/dist/esm/utils/visibleWhen.js +19 -2
  118. package/dist/esm/utils/visibleWhen.js.map +1 -1
  119. package/dist/nextcloud-vue.cjs.js +9681 -7136
  120. package/dist/nextcloud-vue.cjs.js.map +2 -2
  121. package/dist/nextcloud-vue.css +939 -376
  122. package/l10n/en.json +41 -1
  123. package/l10n/nl.json +39 -1
  124. package/package.json +1 -1
  125. package/src/components/CnActionsBar/CnActionsBar.md +1 -0
  126. package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
  127. package/src/components/CnChartWidget/CnChartWidget.md +3 -2
  128. package/src/components/CnChartWidget/CnChartWidget.vue +42 -4
  129. package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
  130. package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
  131. package/src/components/CnDashboardPage/CnDashboardPage.vue +110 -21
  132. package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
  133. package/src/components/CnExportsWidget/index.js +7 -0
  134. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
  135. package/src/components/CnFilesBrowser/filesReview.js +57 -0
  136. package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
  137. package/src/components/CnGroupedCardsWidget/index.js +28 -0
  138. package/src/components/CnIndexPage/CnIndexPage.md +5 -1
  139. package/src/components/CnIndexPage/CnIndexPage.vue +313 -9
  140. package/src/components/CnIndexPage/useNamedSource.js +30 -3
  141. package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
  142. package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
  143. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
  144. package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
  145. package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
  146. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
  147. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
  148. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
  149. package/src/components/CnSavedViewAlertWidget/index.js +7 -0
  150. package/src/components/CnSetupWizard/CnSetupWizard.vue +35 -2
  151. package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
  152. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  153. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
  154. package/src/components/index.js +3 -0
  155. package/src/composables/indexSources.js +175 -19
  156. package/src/composables/useEndpointSource.js +28 -6
  157. package/src/css/index-page.css +117 -0
  158. package/src/css/look-board-index.css +10 -0
  159. package/src/css/look-board.css +44 -0
  160. package/src/index.js +1 -0
  161. package/src/schemas/app-manifest-v2.schema.json +85 -3
  162. package/src/utils/builtInFormatters.js +24 -4
  163. package/src/utils/languageMapText.js +46 -0
  164. package/src/utils/libraryWidgetKeys.js +3 -0
  165. package/src/utils/mapList.js +67 -0
  166. package/src/utils/rowClassRules.js +39 -0
  167. package/src/utils/validateManifestV2.compiled.js +3 -3
  168. package/src/utils/visibleWhen.js +19 -2
@@ -42,12 +42,13 @@
42
42
  group ends the description line ("Week 41 · alle teams ·
43
43
  [Deze week | Dit kwartaal | Dit jaar]"). -->
44
44
  <div
45
- v-if="datePeriodInDescription"
45
+ v-if="datePeriodInDescription || descriptionPageFilters.length > 0"
46
46
  class="cn-dashboard-page__description cn-dashboard-page__description--with-period"
47
47
  data-testid="cn-dashboard-page-subtitle">
48
48
  <span v-if="subtitleText">{{ subtitleText }}</span>
49
49
  <span v-if="subtitleText" aria-hidden="true">&middot;</span>
50
50
  <CnSegmentedControl
51
+ v-if="datePeriodInDescription"
51
52
  size="compact"
52
53
  mode="toggle"
53
54
  :options="segmentedOptions"
@@ -55,6 +56,20 @@
55
56
  :ariaLabel="datePeriodLabel"
56
57
  data-testid="cn-dashboard-page-date-segmented"
57
58
  @update:modelValue="onSegmentPick" />
59
+ <!-- A page filter with `control: "segmented"` and
60
+ `placement: "description"` (DqProcesanalyse
61
+ "Gemiddeld | Mediaan"): a segmented group on the
62
+ description line that writes the workspace context. -->
63
+ <CnSegmentedControl
64
+ v-for="pf in descriptionPageFilters"
65
+ :key="pf.key"
66
+ size="compact"
67
+ mode="toggle"
68
+ :options="pageFilterSegments(pf)"
69
+ :modelValue="workspaceContext[pf.key]"
70
+ :ariaLabel="pf.label ? effectiveTranslate(pf.label) : pf.key"
71
+ :data-testid="'cn-page-filter-' + pf.key"
72
+ @update:modelValue="onPageFilterChange(pf, $event)" />
58
73
  </div>
59
74
  <p v-else-if="subtitleText" class="cn-dashboard-page__description" data-testid="cn-dashboard-page-subtitle">
60
75
  {{ subtitleText }}
@@ -96,7 +111,7 @@
96
111
  <Pencil v-if="!isEditing" :size="20" />
97
112
  <Check v-else :size="20" />
98
113
  </template>
99
- {{ isEditing ? doneLabel : editLabel }}
114
+ {{ isEditing ? shownDoneLabel : shownEditLabel }}
100
115
  </NcButton>
101
116
  <!-- With `userLayout`, edit mode offers a way back to the
102
117
  manifest: the page's own grid and the chosen view return
@@ -316,23 +331,33 @@
316
331
  `@page.<key>` / `@workspace.<key>` token (e.g. an endpoint KPI's URL
317
332
  interpolating a chosen period). -->
318
333
  <div
319
- v-if="pageFilters && pageFilters.length"
334
+ v-if="rowPageFilters.length > 0"
320
335
  class="cn-dashboard-page__page-filters"
321
336
  data-testid="cn-dashboard-page-page-filters">
322
- <label
323
- v-for="pf in pageFilters"
324
- :key="pf.key"
325
- class="cn-dashboard-page__page-filter">
326
- <span v-if="pf.label" class="cn-dashboard-page__page-filter-label">{{ pf.label }}</span>
327
- <NcSelect
328
- :modelValue="selectedPageFilterOption(pf)"
329
- :options="pf.options || []"
330
- :clearable="false"
331
- :inputLabel="pf.label || pf.key"
332
- label="label"
337
+ <template v-for="pf in rowPageFilters" :key="pf.key">
338
+ <CnSegmentedControl
339
+ v-if="pf.control === 'segmented'"
340
+ size="compact"
341
+ mode="toggle"
342
+ :options="pageFilterSegments(pf)"
343
+ :modelValue="workspaceContext[pf.key]"
344
+ :ariaLabel="pf.label ? effectiveTranslate(pf.label) : pf.key"
333
345
  :data-testid="'cn-page-filter-' + pf.key"
334
346
  @update:modelValue="onPageFilterChange(pf, $event)" />
335
- </label>
347
+ <label
348
+ v-else
349
+ class="cn-dashboard-page__page-filter">
350
+ <span v-if="pf.label" class="cn-dashboard-page__page-filter-label">{{ pf.label }}</span>
351
+ <NcSelect
352
+ :modelValue="selectedPageFilterOption(pf)"
353
+ :options="pf.options || []"
354
+ :clearable="false"
355
+ :inputLabel="pf.label || pf.key"
356
+ label="label"
357
+ :data-testid="'cn-page-filter-' + pf.key"
358
+ @update:modelValue="onPageFilterChange(pf, $event)" />
359
+ </label>
360
+ </template>
336
361
  </div>
337
362
 
338
363
  <!-- Declarative in-body sections, `placement: "before-grid"` — host-app
@@ -1080,6 +1105,7 @@ const CHART_PROP_KEYS = [
1080
1105
  'footnote',
1081
1106
  'highlight',
1082
1107
  'cellSource',
1108
+ 'emptyCellLabel',
1083
1109
  ]
1084
1110
 
1085
1111
  /**
@@ -1678,13 +1704,18 @@ export default {
1678
1704
  * read it via a `@page.<key>` / `@workspace.<key>` token — e.g. a period
1679
1705
  * selector that every endpoint-bound KPI's URL interpolates.
1680
1706
  *
1681
- * Each entry: `{ key, label?, type?: 'select', options: [{ value, label }],
1682
- * default? }`. `key` is the workspace-context key written on change;
1683
- * `default` (or the first option) seeds it on mount. Only `'select'` is
1684
- * supported today. Empty (the default) renders no controls and leaves the
1685
- * context untouched.
1707
+ * Each entry: `{ key, label?, type?: 'select', control?, placement?,
1708
+ * options: [{ value, label }], default? }`. `key` is the workspace-context
1709
+ * key written on change; `default` (or the first option) seeds it on
1710
+ * mount. The control is a select, or with `control: 'segmented'` a
1711
+ * compact segmented group; `placement: 'description'` puts a segmented
1712
+ * group on the header's description line, after the period group
1713
+ * (DqProcesanalyse "Gemiddeld | Mediaan"). An endpoint widget can read
1714
+ * the value in its `responsePath` too (`phaseDays.@workspace.measure`).
1715
+ * Empty (the default) renders no controls and leaves the context
1716
+ * untouched.
1686
1717
  *
1687
- * @type {Array<{key: string, label?: string, type?: string, options: Array<{value: (string|number), label: string}>, default?: (string|number)}>}
1718
+ * @type {Array<{key: string, label?: string, type?: string, control?: ('select'|'segmented'), placement?: ('row'|'description'), options: Array<{value: (string|number), label: string}>, default?: (string|number)}>}
1688
1719
  */
1689
1720
  pageFilters: {
1690
1721
  type: Array,
@@ -2343,6 +2374,52 @@ export default {
2343
2374
  && this.dateRange.placement === 'description' && !this.hasCustomPreset
2344
2375
  },
2345
2376
 
2377
+ /**
2378
+ * The page filters drawn as a segmented group on the description
2379
+ * line (`control: "segmented"`, `placement: "description"`).
2380
+ * Without a header row they fall back to the filter row.
2381
+ *
2382
+ * @spec openspec/changes/dashboard-board-termijnen/specs/dashboard-page/spec.md#requirement-a-page-filter-as-a-segmented-group-on-the-description-line
2383
+ * @return {Array<object>}
2384
+ */
2385
+ descriptionPageFilters() {
2386
+ if (!this.showHeader) {
2387
+ return []
2388
+ }
2389
+ return (this.pageFilters || []).filter((pf) => pf && pf.key && pf.control === 'segmented' && pf.placement === 'description')
2390
+ },
2391
+
2392
+ /**
2393
+ * The page filters drawn in the filter row under the header: every
2394
+ * filter the description line does not draw.
2395
+ *
2396
+ * @spec openspec/changes/dashboard-board-termijnen/specs/dashboard-page/spec.md#requirement-a-page-filter-as-a-segmented-group-on-the-description-line
2397
+ * @return {Array<object>}
2398
+ */
2399
+ rowPageFilters() {
2400
+ const inDescription = this.descriptionPageFilters
2401
+ return (this.pageFilters || []).filter((pf) => pf && pf.key && !inDescription.includes(pf))
2402
+ },
2403
+
2404
+ /**
2405
+ * The edit button's label through the app's translate: a manifest's
2406
+ * `editLabel` ("Edit layout", DqDashboard) reads in the user's language.
2407
+ *
2408
+ * @return {string}
2409
+ */
2410
+ shownEditLabel() {
2411
+ return this.effectiveTranslate(this.editLabel)
2412
+ },
2413
+
2414
+ /**
2415
+ * The done button's label through the app's translate.
2416
+ *
2417
+ * @return {string}
2418
+ */
2419
+ shownDoneLabel() {
2420
+ return this.effectiveTranslate(this.doneLabel)
2421
+ },
2422
+
2346
2423
  /**
2347
2424
  * The preset list passed to the picker. Falls back to the
2348
2425
  * `DEFAULT_DATE_RANGE_PRESETS` constant when the consumer
@@ -2795,6 +2872,18 @@ export default {
2795
2872
  return (pf.options || []).find((o) => o.value === current) || null
2796
2873
  },
2797
2874
 
2875
+ /**
2876
+ * A page filter's options as segments, labels through the host
2877
+ * translate.
2878
+ *
2879
+ * @spec openspec/changes/dashboard-board-termijnen/specs/dashboard-page/spec.md#requirement-a-page-filter-as-a-segmented-group-on-the-description-line
2880
+ * @param {object} pf The page-filter descriptor.
2881
+ * @return {Array<{value: (string|number), label: string}>}
2882
+ */
2883
+ pageFilterSegments(pf) {
2884
+ return (pf.options || []).map((o) => ({ value: o.value, label: o.label ? this.effectiveTranslate(o.label) : String(o.value) }))
2885
+ },
2886
+
2798
2887
  /**
2799
2888
  * Write a page filter's new selection into the reactive workspace
2800
2889
  * context so every `@page.<key>` / `@workspace.<key>` token re-resolves.
@@ -0,0 +1,233 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <div class="cn-exports-widget" data-testid="cn-exports-widget">
7
+ <p v-if="loading && runs.length === 0" class="cn-exports-widget__muted">
8
+ {{ t('nextcloud-vue', 'Loading …') }}
9
+ </p>
10
+ <p v-else-if="failed" class="cn-exports-widget__muted" data-testid="cn-exports-error">
11
+ {{ t('nextcloud-vue', 'Your exports could not be loaded.') }}
12
+ </p>
13
+ <p v-else-if="runs.length === 0" class="cn-exports-widget__muted" data-testid="cn-exports-empty">
14
+ {{ emptyText }}
15
+ </p>
16
+ <ul v-else class="cn-exports-widget__list">
17
+ <li v-for="run in shownRuns"
18
+ :key="run.key"
19
+ class="cn-exports-widget__row"
20
+ data-testid="cn-exports-row">
21
+ <span class="cn-exports-widget__text">
22
+ <span class="cn-exports-widget__name">{{ run.filename }}</span>
23
+ <span class="cn-exports-widget__meta">{{ run.meta }}</span>
24
+ </span>
25
+ <span v-if="run.expiresToday" class="cn-exports-widget__tag" data-testid="cn-exports-expires-today">
26
+ {{ t('nextcloud-vue', 'Expires today') }}
27
+ </span>
28
+ <NcButton v-else-if="run.href"
29
+ :href="run.href"
30
+ data-testid="cn-exports-download">
31
+ {{ t('nextcloud-vue', 'Download') }}
32
+ </NcButton>
33
+ </li>
34
+ </ul>
35
+ </div>
36
+ </template>
37
+
38
+ <script>
39
+ import { getCurrentUser } from '@nextcloud/auth'
40
+ import axios from '@nextcloud/axios'
41
+ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
42
+ import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
43
+ import { NcButton } from '@nextcloud/vue'
44
+ import { formatBoardDate } from '../../utils/boardDate.js'
45
+
46
+ /**
47
+ * CnExportsWidget — the reader's own exports (DqZoekresultaten "Uw exports"):
48
+ * each file with when it was made, how often it was downloaded and when it
49
+ * expires, a Download button while the file is there, and "Expires today" on
50
+ * the day it goes.
51
+ *
52
+ * Reads OpenRegister's export runs (`GET /apps/openregister/api/exports`)
53
+ * with the reader as the actor, so an administrator sees their own here too.
54
+ * `content.register` and `content.schema` narrow it to one list's exports.
55
+ * A run whose file was swept, or that never produced one, is left out.
56
+ *
57
+ * Example (manifest, in an index page's `sidePanel`):
58
+ * ```json
59
+ * { "id": "exports", "type": "exports", "title": "Your exports", "content": { "register": "dossiq", "schema": "case", "limit": 5 } }
60
+ * ```
61
+ */
62
+ export default {
63
+ name: 'CnExportsWidget',
64
+
65
+ components: { NcButton },
66
+
67
+ inject: {
68
+ /** Host translate function provided by CnAppRoot. */
69
+ cnTranslate: { default: () => (key) => key },
70
+ },
71
+
72
+ props: {
73
+ /** `{ register?, schema?, limit?, emptyText? }`; `limit` defaults to 5. */
74
+ content: {
75
+ type: Object,
76
+ default: () => ({}),
77
+ },
78
+ },
79
+
80
+ data() {
81
+ return { loading: false, failed: false, runs: [], now: new Date() }
82
+ },
83
+
84
+ computed: {
85
+ /** @return {number} How many exports to show. */
86
+ limit() {
87
+ const limit = Number(this.content && this.content.limit)
88
+ return Number.isInteger(limit) && limit > 0 ? limit : 5
89
+ },
90
+
91
+ /** @return {Array<object>} The exports to draw, newest first. */
92
+ shownRuns() {
93
+ return this.runs
94
+ .filter((run) => run && run.filename && run.fileId !== null && run.fileId !== undefined && run.status !== 'expired')
95
+ .slice(0, this.limit)
96
+ .map((run) => this.view(run))
97
+ },
98
+
99
+ /** @return {string} The empty line. */
100
+ emptyText() {
101
+ const own = this.content && this.content.emptyText
102
+ return own ? this.cnTranslate(own) : t('nextcloud-vue', 'You have no exports here.')
103
+ },
104
+ },
105
+
106
+ mounted() {
107
+ this.load()
108
+ },
109
+
110
+ methods: {
111
+ t,
112
+
113
+ /**
114
+ * Read the reader's export runs.
115
+ *
116
+ * @return {Promise<void>}
117
+ */
118
+ async load() {
119
+ const user = getCurrentUser()
120
+ if (!user || !user.uid) {
121
+ return
122
+ }
123
+ this.loading = true
124
+ this.failed = false
125
+ try {
126
+ const params = { actor: user.uid, limit: Math.max(this.limit * 2, 10) }
127
+ for (const key of ['register', 'schema']) {
128
+ if (this.content && typeof this.content[key] === 'string' && this.content[key] !== '') {
129
+ params[key] = this.content[key]
130
+ }
131
+ }
132
+ const response = await axios.get(generateUrl('/apps/openregister/api/exports'), { params })
133
+ const results = response && response.data && Array.isArray(response.data.results) ? response.data.results : []
134
+ this.runs = [...results].sort((a, b) => String(b.producedAt || b.created || '').localeCompare(String(a.producedAt || a.created || '')))
135
+ } catch {
136
+ this.failed = true
137
+ this.runs = []
138
+ } finally {
139
+ this.loading = false
140
+ }
141
+ },
142
+
143
+ /**
144
+ * One export as the list draws it.
145
+ *
146
+ * @param {object} run An export run.
147
+ * @return {{key: string, filename: string, meta: string, href: string, expiresToday: boolean}}
148
+ */
149
+ view(run) {
150
+ const made = run.producedAt || run.created
151
+ const parts = []
152
+ if (made) {
153
+ parts.push(t('nextcloud-vue', 'Made {date}', { date: formatBoardDate(made, this.now) }))
154
+ }
155
+ if (typeof run.downloadCount === 'number') {
156
+ parts.push(n('nextcloud-vue', 'downloaded %n time', 'downloaded %n times', run.downloadCount))
157
+ }
158
+ if (run.expiresAt) {
159
+ parts.push(t('nextcloud-vue', 'expires {date}', { date: formatBoardDate(run.expiresAt, this.now) }))
160
+ }
161
+ const user = getCurrentUser()
162
+ const href = run.filePath && user && user.uid
163
+ ? generateRemoteUrl(`dav/files/${encodeURIComponent(user.uid)}`) + String(run.filePath).split('/').map(encodeURIComponent).join('/')
164
+ : ''
165
+ return {
166
+ key: String(run.uuid || run.id),
167
+ filename: run.filename,
168
+ meta: parts.join(' · '),
169
+ href,
170
+ expiresToday: Boolean(run.expiresAt) && new Date(run.expiresAt).toDateString() === this.now.toDateString(),
171
+ }
172
+ },
173
+ },
174
+ }
175
+ </script>
176
+
177
+ <style scoped>
178
+ .cn-exports-widget__list {
179
+ display: flex;
180
+ flex-direction: column;
181
+ margin: 0;
182
+ padding: 0;
183
+ list-style: none;
184
+ }
185
+
186
+ .cn-exports-widget__row {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 12px;
190
+ padding: 10px 0;
191
+ }
192
+
193
+ .cn-exports-widget__row + .cn-exports-widget__row {
194
+ border-top: 1px solid var(--color-border);
195
+ }
196
+
197
+ .cn-exports-widget__text {
198
+ display: flex;
199
+ flex: 1;
200
+ flex-direction: column;
201
+ gap: 2px;
202
+ min-width: 0;
203
+ }
204
+
205
+ .cn-exports-widget__name {
206
+ overflow: hidden;
207
+ font-size: 14px;
208
+ font-weight: 600;
209
+ text-overflow: ellipsis;
210
+ white-space: nowrap;
211
+ }
212
+
213
+ .cn-exports-widget__meta,
214
+ .cn-exports-widget__muted {
215
+ color: var(--color-text-maxcontrast);
216
+ font-size: 13px;
217
+ }
218
+
219
+ .cn-exports-widget__muted {
220
+ margin: 0;
221
+ }
222
+
223
+ .cn-exports-widget__tag {
224
+ flex: none;
225
+ padding: 3px 10px;
226
+ border-radius: 12px;
227
+ background: var(--color-warning-light, color-mix(in srgb, var(--color-warning) 18%, transparent));
228
+ color: var(--color-warning-text, var(--color-main-text));
229
+ font-size: 13px;
230
+ font-weight: 600;
231
+ white-space: nowrap;
232
+ }
233
+ </style>
@@ -0,0 +1,7 @@
1
+ // SPDX-FileCopyrightText: 2026 Conduction B.V.
2
+ // SPDX-License-Identifier: EUPL-1.2
3
+
4
+ import CnExportsWidget from './CnExportsWidget.vue'
5
+
6
+ export default CnExportsWidget
7
+ export { CnExportsWidget }