@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
@@ -6,6 +6,8 @@ import HandBackRight from 'vue-material-design-icons/HandBackRight.vue';
6
6
  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
+ import { fetchReferenceObjects } from '../utils/boardReferenceColumns.js';
10
+ import { fillTemplate } from '../utils/fillTemplate.js';
9
11
  import { flowRunStatuses, FLOW_RUN_ERROR_STATUSES } from '../utils/flowRunStatus.js';
10
12
  import { fetchGroupMembers, fetchStatuses, fetchPresence } from '../utils/nextcloudDirectory.js';
11
13
  import { readPath } from '../utils/readPath.js';
@@ -529,6 +531,15 @@ function tasksSource() {
529
531
  */
530
532
  loading: () => !!store.loading,
531
533
 
534
+ // The inbox pages: the page sends `limit` and `offset` and draws the
535
+ // pager from the datastore total.
536
+ pages: true,
537
+
538
+ /**
539
+ * @return {number} The datastore total, independent of the page size.
540
+ */
541
+ total: () => Number(store.total) || 0,
542
+
532
543
  /**
533
544
  * Count the inbox for a config without touching the rows on screen,
534
545
  * for a quick-filter tab with `showCount`.
@@ -649,6 +660,10 @@ function readRowsPath(body, path) {
649
660
  * requested: page-config keys must never become query parameters. The list
650
661
  * stays empty and one console error names the page.
651
662
  *
663
+ * A quick filter with `showCount` is counted with `count(config)`: the same
664
+ * request with that tab's filter, its total read at `sourceConfig.totalPath`
665
+ * (`total` by default), else the number of rows the answer holds.
666
+ *
652
667
  * @return {object} The source adapter.
653
668
  * @spec openspec/changes/index-endpoint-source/specs/index-page/spec.md#requirement-an-index-page-can-list-rows-from-an-app-endpoint
654
669
  */
@@ -657,6 +672,36 @@ function endpointSource() {
657
672
  let seq = 0;
658
673
  let warned = false;
659
674
 
675
+ /**
676
+ * The query parameters for a config: every key but the url and the paths,
677
+ * empty values left out, a list joined by commas.
678
+ *
679
+ * @param {object} rest The config without `url`, `rowsPath` and `totalPath`.
680
+ * @return {object} The parameters.
681
+ */
682
+ const paramsOf = (rest) => {
683
+ const params = {};
684
+ for (const [key, value] of Object.entries(rest)) {
685
+ if (value === undefined || value === null || value === '') {
686
+ continue
687
+ }
688
+ params[key] = Array.isArray(value) ? value.join(',') : (typeof value === 'object' ? JSON.stringify(value) : value);
689
+ }
690
+ return params
691
+ };
692
+
693
+ /**
694
+ * Whether a config can be requested: it names a url and is not the page
695
+ * config handed over in its place.
696
+ *
697
+ * @param {object} config The config.
698
+ * @return {boolean}
699
+ */
700
+ const requestable = (config) => {
701
+ const url = config && config.url;
702
+ return typeof url === 'string' && url !== '' && !Object.hasOwn(config, 'entitySource')
703
+ };
704
+
660
705
  return {
661
706
  /**
662
707
  * Fetch the rows.
@@ -665,10 +710,9 @@ function endpointSource() {
665
710
  * @return {Promise<void>} Resolves when loaded; a failed request leaves no rows and rethrows.
666
711
  */
667
712
  load: async (config) => {
668
- const { url, rowsPath, ...rest } = config || {};
713
+ const { url, rowsPath, totalPath, ...rest } = config || {};
669
714
  const mine = ++seq;
670
- const pageConfig = Boolean(config && Object.hasOwn(config, 'entitySource'));
671
- if (typeof url !== 'string' || url === '' || pageConfig) {
715
+ if (!requestable(config)) {
672
716
  state.rows = [];
673
717
  if (!warned) {
674
718
  warned = true;
@@ -678,13 +722,7 @@ function endpointSource() {
678
722
  }
679
723
  return
680
724
  }
681
- const params = {};
682
- for (const [key, value] of Object.entries(rest)) {
683
- if (value === undefined || value === null || value === '') {
684
- continue
685
- }
686
- params[key] = Array.isArray(value) ? value.join(',') : (typeof value === 'object' ? JSON.stringify(value) : value);
687
- }
725
+ const params = paramsOf(rest);
688
726
  state.loading = true;
689
727
  try {
690
728
  const { default: axios } = await import('@nextcloud/axios');
@@ -706,6 +744,29 @@ function endpointSource() {
706
744
  }
707
745
  },
708
746
 
747
+ /**
748
+ * Count the rows for a config without touching the rows on screen, for
749
+ * a quick-filter tab with `showCount`.
750
+ *
751
+ * @param {object} config The page's source config with the tab's filter merged in.
752
+ * @return {Promise<number>} The total, or NaN when nothing can be requested.
753
+ */
754
+ count: async (config) => {
755
+ if (!requestable(config)) {
756
+ return Number.NaN
757
+ }
758
+ const { url, rowsPath, totalPath, ...rest } = config;
759
+ const { default: axios } = await import('@nextcloud/axios');
760
+ const res = await axios.get(generateUrl(url), { params: paramsOf(rest) });
761
+ const body = res ? res.data : null;
762
+ const total = body && typeof body === 'object' && !Array.isArray(body) ? readRowsPath(body, totalPath || 'total') : undefined;
763
+ if (typeof total === 'number' && Number.isFinite(total)) {
764
+ return total
765
+ }
766
+ const rows = Array.isArray(body) ? body : readRowsPath(body, rowsPath || 'results');
767
+ return Array.isArray(rows) ? rows.length : Number.NaN
768
+ },
769
+
709
770
  /**
710
771
  * @return {Array<object>} The rows the endpoint returned.
711
772
  */
@@ -738,39 +799,102 @@ function fillRowTemplate(template, row) {
738
799
  * and its count (DqZoekresultaten).
739
800
  *
740
801
  * `sourceConfig.kinds` lists what is searched:
741
- * `{ id, label, register, schema, titleField?, subtitleField?, statusField?, href?, limit? }`
802
+ * `{ id, label, register, schema, titleField?, subtitleField?, statusField?, statusResolve?, linkedField?, linkedTemplate?, href?, limit? }`
742
803
  * for objects (OpenRegister's `_search`, so the reader sees what they may
743
804
  * read), or `{ id, label, files: true, limit? }` for the reader's files
744
805
  * (Nextcloud's unified search). The term is `sourceConfig.term`, else the
745
806
  * address's `?q=` (or `sourceConfig.termQuery`). A kind that fails shows no
746
807
  * rows and no count; the others still answer.
747
808
  *
809
+ * The kind's `label` goes through the app's translate (the "Soort" cell and
810
+ * the quick filters). `statusResolve: { register?, schema, labelField? }`
811
+ * reads `statusField` as a reference and shows the referenced object's label
812
+ * (`labelField`, else `title`, then `name`); the status column is a pill,
813
+ * coloured by `sourceConfig.statusColorMap`. `linkedTemplate` (a field
814
+ * template, "{case.title}") or `linkedField` (a path) fills the "Linked"
815
+ * column, drawn when a kind declares one; `sourceConfig.linkedLabel` names it.
816
+ * The page's title and count line can name the term as `{term}`, and the
817
+ * count is the active kind's total. Kind, link and status are filterable.
818
+ *
748
819
  * @param {object} [config] The page's source config.
820
+ * @param {object} [context] The page's helpers.
821
+ * @param {Function} [context.translate] The app's translate.
749
822
  * @return {object} The source adapter.
750
823
  * @spec openspec/changes/index-search-source/specs/index-page/spec.md#requirement-an-index-page-can-search-several-schemas-and-files
751
824
  */
752
- function searchSource(config = {}) {
825
+ function searchSource(config = {}, context = {}) {
826
+ const translate$1 = typeof context.translate === 'function' ? context.translate : (text) => text;
753
827
  const kinds = (Array.isArray(config && config.kinds) ? config.kinds : [])
754
- .filter((kind) => kind && typeof kind.id === 'string' && kind.id !== '' && (kind.files === true || (kind.register && kind.schema)));
755
- const state = reactive({ rows: [], totals: {}, loading: false, kind: 'all', term: '' });
828
+ .filter((kind) => kind && typeof kind.id === 'string' && kind.id !== '' && (kind.files === true || (kind.register && kind.schema)))
829
+ .map((kind) => ({ ...kind, label: translate$1(String(kind.label || kind.id)) }));
830
+ const hasLinked = kinds.some((kind) => kind.linkedTemplate || kind.linkedField);
831
+ const state = reactive({ rows: [], totals: {}, loading: false, kind: 'all', term: '', filters: {} });
756
832
  let seq = 0;
757
833
 
834
+ /**
835
+ * The labels of the objects a kind's status points at, by id: the
836
+ * referenced schema read once per search.
837
+ *
838
+ * @param {object} kind The kind with `statusResolve`.
839
+ * @return {Promise<Map<string, string>>} Labels by id; empty when it cannot be read.
840
+ */
841
+ const statusLabels = async (kind) => {
842
+ const resolve = kind.statusResolve;
843
+ if (!resolve || !resolve.schema) {
844
+ return new Map()
845
+ }
846
+ const objects = await fetchReferenceObjects({ register: resolve.register || kind.register, schema: resolve.schema }).catch(() => []);
847
+ const labels = new Map();
848
+ for (const object of objects) {
849
+ const id = object.id || object.uuid || (object['@self'] && object['@self'].id);
850
+ const label = readPath(object, resolve.labelField || 'title') ?? object.name;
851
+ if (id !== undefined && label !== undefined && label !== null) {
852
+ labels.set(String(id), String(label));
853
+ }
854
+ }
855
+ return labels
856
+ };
857
+
858
+ /**
859
+ * What a row shows in the Linked column.
860
+ *
861
+ * @param {object} kind The row's kind.
862
+ * @param {object} object The found object.
863
+ * @return {string}
864
+ */
865
+ const linkedOf = (kind, object) => {
866
+ if (kind.linkedTemplate) {
867
+ return fillTemplate(translate$1(kind.linkedTemplate), object)
868
+ }
869
+ if (kind.linkedField) {
870
+ const value = readPath(object, kind.linkedField);
871
+ return value === undefined || value === null ? '' : String(value)
872
+ }
873
+ return ''
874
+ };
875
+
758
876
  const searchObjects = async (axios, kind, term) => {
759
877
  const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: kind.register, schema: kind.schema });
760
878
  const res = await axios.get(url, { params: { _search: term, _limit: kind.limit || 20 } });
761
879
  const body = (res && res.data) || {};
762
880
  const results = Array.isArray(body.results) ? body.results : (Array.isArray(body) ? body : []);
881
+ const labels = kind.statusResolve && kind.statusField ? await statusLabels(kind) : null;
763
882
  return {
764
883
  total: typeof body.total === 'number' ? body.total : results.length,
765
884
  rows: results.map((object) => {
766
885
  const id = object.id || object.uuid || (object['@self'] && object['@self'].id);
886
+ const rawStatus = kind.statusField ? readPath(object, kind.statusField) : undefined;
887
+ const status = rawStatus === undefined || rawStatus === null
888
+ ? ''
889
+ : (labels ? (labels.get(String(rawStatus)) || '') : String(rawStatus));
767
890
  return {
768
891
  id: `${kind.id}:${id}`,
769
892
  kind: kind.id,
770
893
  kindLabel: kind.label,
771
894
  title: String(readPath(object, kind.titleField || 'title') ?? object.name ?? id ?? ''),
772
895
  subtitle: kind.subtitleField ? String(readPath(object, kind.subtitleField) ?? '') : '',
773
- status: kind.statusField ? String(readPath(object, kind.statusField) ?? '') : '',
896
+ status,
897
+ linked: linkedOf(kind, object),
774
898
  href: kind.href ? generateUrl(fillRowTemplate(kind.href, { ...object, id })) : '',
775
899
  object,
776
900
  }
@@ -793,6 +917,7 @@ function searchSource(config = {}) {
793
917
  title: entry.title || '',
794
918
  subtitle: entry.subline || '',
795
919
  status: '',
920
+ linked: '',
796
921
  href: entry.resourceUrl || '',
797
922
  })),
798
923
  }
@@ -807,6 +932,17 @@ function searchSource(config = {}) {
807
932
  */
808
933
  load: async (cfg) => {
809
934
  state.kind = cfg && typeof cfg.kind === 'string' ? cfg.kind : 'all';
935
+ // The header filters on Kind, Linked and Status narrow the answer
936
+ // already fetched; they do not search again.
937
+ const filters = {};
938
+ for (const key of ['kindLabel', 'linked', 'status']) {
939
+ const value = cfg && cfg[key];
940
+ const values = (Array.isArray(value) ? value : (value === undefined || value === null || value === '' ? [] : [value])).map(String);
941
+ if (values.length > 0) {
942
+ filters[key] = values;
943
+ }
944
+ }
945
+ state.filters = filters;
810
946
  const query = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : null;
811
947
  const term = String((cfg && cfg.term) || (query && query.get((cfg && cfg.termQuery) || 'q')) || '').trim();
812
948
  const mine = ++seq;
@@ -838,7 +974,20 @@ function searchSource(config = {}) {
838
974
  /**
839
975
  * @return {Array<object>} The results of the active kind, or all of them.
840
976
  */
841
- rows: () => (state.kind === 'all' ? state.rows : state.rows.filter((row) => row.kind === state.kind)),
977
+ rows: () => (state.kind === 'all' ? state.rows : state.rows.filter((row) => row.kind === state.kind))
978
+ .filter((row) => Object.entries(state.filters).every(([key, values]) => values.includes(String(row[key])))),
979
+
980
+ /**
981
+ * @return {string} The term searched for, for a `{term}` in the title or count line.
982
+ */
983
+ term: () => state.term,
984
+
985
+ /**
986
+ * @return {number} The active kind's total, or every kind's together on All.
987
+ */
988
+ total: () => (state.kind === 'all'
989
+ ? Object.values(state.totals).reduce((sum, value) => sum + value, 0)
990
+ : (state.totals[state.kind] || 0)),
842
991
 
843
992
  /**
844
993
  * @return {boolean} Whether a search is in flight.
@@ -847,8 +996,14 @@ function searchSource(config = {}) {
847
996
 
848
997
  columns: [
849
998
  { key: 'title', label: translate('nextcloud-vue', 'Result'), secondary: 'subtitle' },
850
- { key: 'kindLabel', label: translate('nextcloud-vue', 'Kind') },
851
- { key: 'status', label: translate('nextcloud-vue', 'Status') },
999
+ { key: 'kindLabel', label: translate('nextcloud-vue', 'Kind'), enum: kinds.map((kind) => kind.label) },
1000
+ ...(hasLinked ? [{ key: 'linked', label: config.linkedLabel ? translate$1(config.linkedLabel) : translate('nextcloud-vue', 'Linked') }] : []),
1001
+ {
1002
+ key: 'status',
1003
+ label: translate('nextcloud-vue', 'Status'),
1004
+ widget: 'badge',
1005
+ widgetProps: { colorMap: (config && config.statusColorMap) || {} },
1006
+ },
852
1007
  ],
853
1008
 
854
1009
  quickFilters: kinds.length > 0
@@ -1119,10 +1274,11 @@ const indexSources = {
1119
1274
  *
1120
1275
  * @param {string} name The source name from `config.source`.
1121
1276
  * @param {object} [config] The page's source config, for a source that builds its tabs from it.
1277
+ * @param {object} [context] The page's helpers a source may use, such as `translate` (the app's catalogue).
1122
1278
  *
1123
1279
  * @return {object|null} The adapter, or null when the name is not registered.
1124
1280
  */
1125
- function resolveIndexSource(name, config) {
1281
+ function resolveIndexSource(name, config, context) {
1126
1282
  const key = String(name || '');
1127
1283
  if (!key) {
1128
1284
  return null
@@ -1135,7 +1291,7 @@ function resolveIndexSource(name, config) {
1135
1291
  return null
1136
1292
  }
1137
1293
 
1138
- return factory(config || {})
1294
+ return factory(config || {}, context || {})
1139
1295
  }
1140
1296
 
1141
1297
  /**