@conduction/nextcloud-vue 2.96.0 → 2.98.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 (163) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +17 -0
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  4. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +383 -359
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  9. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  10. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +106 -2
  11. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  12. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  13. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +9 -0
  14. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js.map +1 -0
  15. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js +964 -0
  16. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -0
  17. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js +282 -0
  18. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -0
  19. package/dist/esm/components/CnChecklistRunWidget/index.js +28 -0
  20. package/dist/esm/components/CnChecklistRunWidget/index.js.map +1 -0
  21. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  22. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +17 -3
  23. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  24. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +61 -43
  25. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  26. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
  27. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +159 -18
  28. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +9 -7
  30. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  32. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
  33. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  35. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -8
  36. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +188 -5
  39. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  42. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +48 -3
  43. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +46 -12
  45. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  47. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +3 -3
  48. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  49. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
  51. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +27 -0
  52. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +16 -2
  54. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
  55. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  56. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +144 -0
  57. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +488 -399
  59. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  60. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
  61. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +344 -21
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +29 -7
  65. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  67. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +2 -2
  68. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +2 -2
  70. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/useNamedSource.js +1 -1
  72. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  73. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  74. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +13 -0
  75. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  76. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +5 -1
  77. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  78. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +9 -0
  79. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js.map +1 -0
  80. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +220 -0
  81. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -0
  82. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +121 -0
  83. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -0
  84. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
  85. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +83 -9
  86. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
  87. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +80 -11
  88. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
  89. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  90. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  91. package/dist/esm/composables/indexSources.js +420 -5
  92. package/dist/esm/composables/indexSources.js.map +1 -1
  93. package/dist/esm/composables/useOrganisationStore.js +115 -0
  94. package/dist/esm/composables/useOrganisationStore.js.map +1 -0
  95. package/dist/esm/index.js +2 -0
  96. package/dist/esm/index.js.map +1 -1
  97. package/dist/esm/l10n/en.json.js +21 -1
  98. package/dist/esm/l10n/en.json.js.map +1 -1
  99. package/dist/esm/l10n/nl.json.js +36 -1
  100. package/dist/esm/l10n/nl.json.js.map +1 -1
  101. package/dist/esm/library-style.js +1 -1
  102. package/dist/esm/nextcloud-vue.css +725 -383
  103. package/dist/esm/utils/builtInFormatters.js +34 -1
  104. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  105. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  106. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  107. package/dist/esm/utils/matrixCells.js +158 -0
  108. package/dist/esm/utils/matrixCells.js.map +1 -0
  109. package/dist/esm/utils/nextcloudDirectory.js +121 -0
  110. package/dist/esm/utils/nextcloudDirectory.js.map +1 -0
  111. package/dist/esm/utils/queryPresets.js +13 -2
  112. package/dist/esm/utils/queryPresets.js.map +1 -1
  113. package/dist/esm/utils/scopeListLayout.js +37 -1
  114. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  115. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  116. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  117. package/dist/nextcloud-vue.cjs.js +14443 -10519
  118. package/dist/nextcloud-vue.cjs.js.map +2 -2
  119. package/dist/nextcloud-vue.css +725 -383
  120. package/l10n/en.json +21 -1
  121. package/l10n/nl.json +36 -1
  122. package/package.json +2 -2
  123. package/scripts/merge-l10n.js +196 -0
  124. package/src/components/CnAppRoot/CnAppRoot.md +1 -0
  125. package/src/components/CnAppRoot/CnAppRoot.vue +17 -0
  126. package/src/components/CnCellRenderer/CnCellRenderer.vue +57 -1
  127. package/src/components/CnChartWidget/CnChartWidget.md +2 -1
  128. package/src/components/CnChartWidget/CnChartWidget.vue +106 -2
  129. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +1269 -0
  130. package/src/components/CnChecklistRunWidget/index.js +27 -0
  131. package/src/components/CnConversationThread/CnConversationThread.md +12 -0
  132. package/src/components/CnConversationThread/CnConversationThread.vue +30 -4
  133. package/src/components/CnConversationThread/CnConversationWidget.vue +167 -24
  134. package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
  135. package/src/components/CnDataTable/CnDataTable.vue +26 -8
  136. package/src/components/CnDetailPage/CnDetailPage.md +48 -0
  137. package/src/components/CnDetailPage/CnDetailPage.vue +188 -5
  138. package/src/components/CnFactsWidget/CnFactsWidget.vue +114 -3
  139. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +3 -3
  140. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +38 -2
  141. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +508 -236
  142. package/src/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
  143. package/src/components/CnIndexPage/CnIndexPage.md +28 -0
  144. package/src/components/CnIndexPage/CnIndexPage.vue +367 -27
  145. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +13 -4
  146. package/src/components/CnIndexPage/useNamedSource.js +1 -1
  147. package/src/components/CnObjectSidebar/CnFilesTab.vue +17 -0
  148. package/src/components/CnOrderedListField/CnOrderedListField.vue +386 -0
  149. package/src/components/CnOrderedListField/index.js +7 -0
  150. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +139 -10
  151. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  152. package/src/components/index.js +2 -0
  153. package/src/composables/indexSources.js +419 -4
  154. package/src/composables/useOrganisationStore.js +111 -0
  155. package/src/index.js +2 -0
  156. package/src/schemas/app-manifest-v2.schema.json +42 -5
  157. package/src/utils/builtInFormatters.js +33 -0
  158. package/src/utils/libraryWidgetKeys.js +1 -0
  159. package/src/utils/matrixCells.js +154 -0
  160. package/src/utils/nextcloudDirectory.js +117 -0
  161. package/src/utils/queryPresets.js +13 -2
  162. package/src/utils/scopeListLayout.js +37 -1
  163. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -1,6 +1,6 @@
1
1
  import { getCurrentUser } from '@nextcloud/auth';
2
2
  import { translate, translatePlural } from '@nextcloud/l10n';
3
- import { NcLoadingIcon, NcActions, NcActionLink, NcActionButton, NcActionCaption, NcActionSeparator, NcActionCheckbox, NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue';
3
+ import { NcNoteCard, NcLoadingIcon, NcActions, NcActionLink, NcActionButton, NcActionCaption, NcActionSeparator, NcActionCheckbox, NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue';
4
4
  import { ref, getCurrentInstance, markRaw, inject } from 'vue';
5
5
  import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue';
6
6
  import Close from 'vue-material-design-icons/Close.vue';
@@ -31,13 +31,14 @@ import { resolveRegisteredHandler, buildOnSuccessRoute } from '../../utils/actio
31
31
  import { columnFieldsOf, refIdOf, buildValueStages, buildReferenceStages, columnKeyOfRow, resolveDropValue, fullTargetOf, referenceTargetOf, fetchReferenceObjects } from '../../utils/boardReferenceColumns.js';
32
32
  import { reportBindingProblems } from '../../utils/diagnostics.js';
33
33
  import { fetchGroupedCounts } from '../../utils/fetchAggregate.js';
34
- import { fetchFilterCounts } from '../../utils/fetchFilterCounts.js';
34
+ import { fetchListTotal, fetchFilterCounts } from '../../utils/fetchFilterCounts.js';
35
35
  import { buildExportUrl } from '../../utils/indexExportHelpers.js';
36
36
  import { lensUnavailableText } from '../../utils/lensAvailability.js';
37
37
  import { isModifiedClick, openRowTarget } from '../../utils/linkNavigation.js';
38
38
  import { resolveClaimedTeams, splitViewsIntoTabs, viewAsTab, resolveClaimTokens, viewIdOf } from '../../utils/listLenses.js';
39
39
  import { LIST_SHORTCUTS, listPaletteCommands, shortcutFor } from '../../utils/listShortcuts.js';
40
40
  import { multiKeySort } from '../../utils/multiKeySort.js';
41
+ import { fetchGroups } from '../../utils/nextcloudDirectory.js';
41
42
  import { OBJECTS_CHANGED_EVENT } from '../../utils/objectSignals.js';
42
43
  import { objectUrlOf, patchObject } from '../../utils/objectWrite.js';
43
44
  import { withPersonalLenses } from '../../utils/personalLenses.js';
@@ -50,7 +51,7 @@ import { isNewTabClick, preventMiddleClickAutoscroll, isRowMiddleClick, isNewTab
50
51
  import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js';
51
52
  import { viewMatchesScope, extractViewState, buildRouteQueryFromViewState, extractViewStateFromRouteQuery, buildViewCreatePayload, savedViewScope, normalizeSharedWith } from '../../utils/savedViewHelpers.js';
52
53
  import { fieldsFromSchema, columnsFromSchema } from '../../utils/schema.js';
53
- import { resolveScopeLayout } from '../../utils/scopeListLayout.js';
54
+ import { resolveScopeLayout, resolveScopePresentation } from '../../utils/scopeListLayout.js';
54
55
  import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js';
55
56
  import __sfc_main$6 from '../CnActionButtons/CnActionButtons.vue.js';
56
57
  import __sfc_main$7 from '../CnActionsBar/CnActionsBar.vue.js';
@@ -325,6 +326,7 @@ var script = {
325
326
 
326
327
  components: {
327
328
  CnIndexPageWidgets: __sfc_main,
329
+ NcNoteCard,
328
330
  CnBuildiqEditButton: __sfc_main$1,
329
331
  CnFollowToggle: __sfc_main$2,
330
332
  NcLoadingIcon,
@@ -493,6 +495,35 @@ var script = {
493
495
  default: '',
494
496
  },
495
497
 
498
+ /**
499
+ * Tabs to sibling pages above the toolbar (manifest `config.pageTabs`),
500
+ * each `{ id?, label, route?, params?, query?, href?, countFrom?: {
501
+ * register, schema, filter? } }`: DqPrivacy's "AVG-verzoeken |
502
+ * Datalekken". The current page's tab is marked; `countFrom` adds the
503
+ * count of that list. Wins over a named source's own page tabs.
504
+ *
505
+ * @type {Array<object>|null}
506
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
507
+ */
508
+ pageTabs: {
509
+ type: Array,
510
+ default: null,
511
+ },
512
+
513
+ /**
514
+ * A notice under the list (manifest `config.notice`): `{ text,
515
+ * variant?: 'info' | 'success' | 'warning' }`, a band that explains
516
+ * the list, such as who decides what. The text goes through the host
517
+ * translate.
518
+ *
519
+ * @type {{text: string, variant?: string}|null}
520
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-show-a-notice-under-its-list
521
+ */
522
+ notice: {
523
+ type: Object,
524
+ default: null,
525
+ },
526
+
496
527
  /** Optional description shown below the title */
497
528
  description: {
498
529
  type: String,
@@ -1880,6 +1911,33 @@ var script = {
1880
1911
  default: () => [],
1881
1912
  },
1882
1913
 
1914
+ /**
1915
+ * Cards after the list (manifest `config.afterList`): dashboard widget
1916
+ * definitions `{ id, type, title?, content, headerLink? }` stacked
1917
+ * under the list in the listed order, the counterpart of `statRow`
1918
+ * above it. A query preset or a scope can set its own.
1919
+ *
1920
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-an-index-page-can-draw-widgets-after-its-list
1921
+ * @type {Array<object>}
1922
+ */
1923
+ afterList: {
1924
+ type: Array,
1925
+ default: () => [],
1926
+ },
1927
+
1928
+ /**
1929
+ * The route query pairs that chose this page's query preset, set by
1930
+ * CnPageRenderer. They scope the list, so they show no removable
1931
+ * active-filter chip.
1932
+ *
1933
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-the-query-that-chose-a-preset-is-not-a-removable-filter
1934
+ * @type {object|null}
1935
+ */
1936
+ fixedFilters: {
1937
+ type: Object,
1938
+ default: null,
1939
+ },
1940
+
1883
1941
  /**
1884
1942
  * Where a row carries the actions the server says this caller may run
1885
1943
  * on it: a dotted path, read off the rows the list already fetched, so
@@ -2480,6 +2538,8 @@ var script = {
2480
2538
  tabCounts: null,
2481
2539
  // The counts of the `filterStrip` pills, by pill key ('all', 'v0', ...).
2482
2540
  filterStripCounts: null,
2541
+ /** The `countFrom` counts of the manifest page tabs, by index. */
2542
+ pageTabCounts: {},
2483
2543
  /** The tabs `quickFiltersFrom` produced, or null before they load. */
2484
2544
  generatedQuickFilters: null,
2485
2545
  /** Count per saved-view id, for the views control; null = none. */
@@ -2508,6 +2568,8 @@ var script = {
2508
2568
  */
2509
2569
  folderSidebarAllValues: [],
2510
2570
  folderRegisterList: [],
2571
+ /** The Nextcloud groups a `folderSidebar.source: 'groups'` pane lists. */
2572
+ folderGroupList: [],
2511
2573
  // `x-index` blocks read off the schema rows a `source: 'register'`
2512
2574
  // folder list was built from, keyed by folder id. The layout a case
2513
2575
  // type carries travels with the record rather than being restated
@@ -3029,7 +3091,8 @@ var script = {
3029
3091
  }
3030
3092
  // The index boards draw all four segments, so the board look does
3031
3093
  // too unless the manifest names its own list.
3032
- const listed = Array.isArray(this.viewSwitch) ? this.viewSwitch : ['table', 'cards', 'board', 'map'];
3094
+ const viewSwitch = this.presented('viewSwitch');
3095
+ const listed = Array.isArray(viewSwitch) ? viewSwitch : ['table', 'cards', 'board', 'map'];
3033
3096
  const offered = [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])];
3034
3097
  return listed.filter((mode) => ['table', 'cards', 'board', 'map'].includes(mode) && !offered.includes(mode))
3035
3098
  },
@@ -3424,7 +3487,7 @@ var script = {
3424
3487
  */
3425
3488
  folderSidebarSource() {
3426
3489
  const s = this.folderSidebar && this.folderSidebar.source;
3427
- return (s === 'register' || !s) ? 'custom' : s
3490
+ return (s === 'register' || s === 'groups' || !s) ? 'custom' : s
3428
3491
  },
3429
3492
 
3430
3493
  /**
@@ -3441,6 +3504,9 @@ var script = {
3441
3504
  if (this.folderSidebar.source === 'register') {
3442
3505
  return this.folderRegisterList
3443
3506
  }
3507
+ if (this.folderSidebar.source === 'groups') {
3508
+ return this.folderGroupList
3509
+ }
3444
3510
  return this.folderSidebar.folders || []
3445
3511
  },
3446
3512
 
@@ -3500,6 +3566,58 @@ var script = {
3500
3566
  })
3501
3567
  },
3502
3568
 
3569
+ /**
3570
+ * This page's own value of each presentation key, which a query preset
3571
+ * may have set: what `presented()` falls back to without a scope.
3572
+ *
3573
+ * @return {object}
3574
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
3575
+ */
3576
+ ownPresentation() {
3577
+ return {
3578
+ title: this.title,
3579
+ quickFilters: this.quickFilters,
3580
+ cardFields: this.cardFields,
3581
+ countText: this.countText,
3582
+ searchPlaceholder: this.searchPlaceholder,
3583
+ footerNote: this.footerNote,
3584
+ viewSwitch: this.viewSwitch,
3585
+ addLabel: this.addLabel,
3586
+ headerButtons: this.headerButtons,
3587
+ breadcrumb: this.breadcrumb,
3588
+ headerMeta: this.headerMeta,
3589
+ afterList: this.afterList,
3590
+ }
3591
+ },
3592
+
3593
+ /**
3594
+ * The presentation the active scope declares (`title`, `countText`,
3595
+ * `quickFilters`, `addLabel`, `afterList` and the other query preset
3596
+ * keys), from its folder entry or its row's `x-index`. Empty without a
3597
+ * scope, so the page's own values stand.
3598
+ *
3599
+ * @return {object}
3600
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
3601
+ */
3602
+ activeScopePresentation() {
3603
+ if (!this.folderSidebar) {
3604
+ return {}
3605
+ }
3606
+ return resolveScopePresentation({ scope: this.activeScope, row: this.activeScopeRow })
3607
+ },
3608
+
3609
+ /**
3610
+ * The widgets drawn after the list: the active scope's `afterList`,
3611
+ * else the page's.
3612
+ *
3613
+ * @return {Array<object>}
3614
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-an-index-page-can-draw-widgets-after-its-list
3615
+ */
3616
+ presentedAfterList() {
3617
+ const list = this.presented('afterList');
3618
+ return Array.isArray(list) ? list : []
3619
+ },
3620
+
3503
3621
  /**
3504
3622
  * The fields the search box searches over while a scope declaring
3505
3623
  * `searchFields` is active. Read by `useSelfFetchList`'s fixed-filter
@@ -3959,8 +4077,9 @@ var script = {
3959
4077
  * @return {?Array<string>}
3960
4078
  */
3961
4079
  resolvedCardFields() {
3962
- if (Array.isArray(this.cardFields) && this.cardFields.length > 0) {
3963
- return this.cardFields
4080
+ const cardFields = this.presented('cardFields');
4081
+ if (Array.isArray(cardFields) && cardFields.length > 0) {
4082
+ return cardFields
3964
4083
  }
3965
4084
  if (!this.isBoardLook) {
3966
4085
  return null
@@ -3999,6 +4118,10 @@ var script = {
3999
4118
  }
4000
4119
  });
4001
4120
  }
4121
+ // An `actions` column: the page's row actions it names, as buttons.
4122
+ cols = cols.map((col) => (col && typeof col === 'object' && col.widget === 'actions'
4123
+ ? { ...col, sortable: false, widgetProps: { ...(col.widgetProps || {}), items: (row) => this.actionColumnItems(col, row), run: this.runColumnAction } }
4124
+ : col));
4002
4125
  // The Recent lens owns the order: no column sorts while it is on.
4003
4126
  if (this.recentLensActive) {
4004
4127
  cols = cols.map((col) => (col && typeof col === 'object' ? { ...col, sortable: false } : col));
@@ -4149,7 +4272,7 @@ var script = {
4149
4272
  * @return {string} The id field key.
4150
4273
  */
4151
4274
  folderPassthroughIdField() {
4152
- if (this.folderSidebar && this.folderSidebar.source === 'register') {
4275
+ if (this.folderSidebar && (this.folderSidebar.source === 'register' || this.folderSidebar.source === 'groups')) {
4153
4276
  return 'id'
4154
4277
  }
4155
4278
  return (this.folderSidebar && this.folderSidebar.idField) || 'id'
@@ -4159,7 +4282,7 @@ var script = {
4159
4282
  * @return {string} The name field key for CnFolderSidebar's custom list.
4160
4283
  */
4161
4284
  folderPassthroughNameField() {
4162
- if (this.folderSidebar && this.folderSidebar.source === 'register') {
4285
+ if (this.folderSidebar && (this.folderSidebar.source === 'register' || this.folderSidebar.source === 'groups')) {
4163
4286
  return 'name'
4164
4287
  }
4165
4288
  return (this.folderSidebar && this.folderSidebar.nameField) || 'name'
@@ -4875,6 +4998,51 @@ var script = {
4875
4998
  return (source.pageTabs() || []).filter((tab) => tab && tab.to && tab.label)
4876
4999
  },
4877
5000
 
5001
+ /**
5002
+ * The page tabs to draw: the manifest's `pageTabs`, each with its
5003
+ * `countFrom` count once known, else a named source's own.
5004
+ *
5005
+ * @return {Array<object>} The tabs, `{ id, label, to, count? }`.
5006
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
5007
+ */
5008
+ /**
5009
+ * The notice band's NcNoteCard type: its `variant`, else info.
5010
+ *
5011
+ * @return {string}
5012
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-show-a-notice-under-its-list
5013
+ */
5014
+ noticeType() {
5015
+ const variant = this.notice && this.notice.variant;
5016
+ return ['success', 'warning', 'error'].includes(variant) ? variant : 'info'
5017
+ },
5018
+
5019
+ effectivePageTabs() {
5020
+ if (!Array.isArray(this.pageTabs) || this.pageTabs.length === 0) {
5021
+ return this.sourcePageTabs
5022
+ }
5023
+ return this.pageTabs
5024
+ .filter((tab) => tab && typeof tab.label === 'string' && tab.label !== '' && (tab.route || tab.to || tab.href))
5025
+ .map((tab, index) => {
5026
+ const to = tab.route
5027
+ ? { name: tab.route, ...(tab.params ? { params: tab.params } : {}), ...(tab.query ? { query: tab.query } : {}) }
5028
+ : (tab.to || tab.href);
5029
+ const count = this.pageTabCounts[index];
5030
+ return { id: tab.id || `page-tab-${index}`, label: this.cnTranslate(tab.label), to, ...(typeof count === 'number' ? { count } : {}) }
5031
+ })
5032
+ },
5033
+
5034
+ /**
5035
+ * What the page tab counts depend on, as one key.
5036
+ *
5037
+ * @return {string}
5038
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
5039
+ */
5040
+ pageTabCountKey() {
5041
+ const tabs = Array.isArray(this.pageTabs) ? this.pageTabs : [];
5042
+ const sources = tabs.map((tab) => (tab && tab.countFrom && tab.countFrom.register && tab.countFrom.schema ? tab.countFrom : null));
5043
+ return sources.some(Boolean) ? JSON.stringify(sources) : ''
5044
+ },
5045
+
4878
5046
  /**
4879
5047
  * The numbers on the quick filters: a named source counts its own
4880
5048
  * tabs (`tabCounts()`), a self-fetching page asks OpenRegister.
@@ -4896,11 +5064,15 @@ var script = {
4896
5064
  * @return {string} The note, translated, or ''.
4897
5065
  */
4898
5066
  effectiveFooterNote() {
4899
- if (this.footerNote) {
4900
- return this.cnTranslate(this.footerNote)
5067
+ // The scope's note, else the page's (a preset may have set it),
5068
+ // else the named source's.
5069
+ const scoped = this.activeScopePresentation ? this.activeScopePresentation.footerNote : undefined;
5070
+ const footerNote = scoped !== undefined ? scoped : this.footerNote;
5071
+ if (footerNote) {
5072
+ return this.cnTranslate(footerNote)
4901
5073
  }
4902
5074
  const source = this.isNamedSource ? this.namedSource : null;
4903
- return (source && source.footerNote) || this.footerNote
5075
+ return (source && source.footerNote) || footerNote
4904
5076
  },
4905
5077
 
4906
5078
  /**
@@ -4911,7 +5083,7 @@ var script = {
4911
5083
  * @return {Array<object>|null} The tabs, or null when there are none.
4912
5084
  */
4913
5085
  effectiveQuickFilters() {
4914
- const own = withPersonalLenses(this.quickFilters || this.generatedQuickFilters, this.personalLenses);
5086
+ const own = withPersonalLenses(this.presented('quickFilters') || this.generatedQuickFilters, this.personalLenses);
4915
5087
  if (own && own.length > 0) {
4916
5088
  return own
4917
5089
  }
@@ -4977,7 +5149,8 @@ var script = {
4977
5149
  * @return {Array<{key: string, label: string, action: string, variant: string, icon: string, format: string}>}
4978
5150
  */
4979
5151
  resolvedHeaderButtons() {
4980
- const declared = Array.isArray(this.headerButtons) ? this.headerButtons : [];
5152
+ const headerButtons = this.presented('headerButtons');
5153
+ const declared = Array.isArray(headerButtons) ? headerButtons : [];
4981
5154
  return declared
4982
5155
  .filter((button) => button && typeof button === 'object' && typeof button.action === 'string' && button.action !== '')
4983
5156
  .map((button, index) => {
@@ -5102,6 +5275,7 @@ var script = {
5102
5275
  for (const [key, raw] of Object.entries(this.effectiveActiveFilters || {})) {
5103
5276
  const values = (Array.isArray(raw) ? raw : [raw])
5104
5277
  .filter((value) => value !== null && value !== undefined && value !== '')
5278
+ .filter((value) => !this.isFixedFilter(key, value))
5105
5279
  .map((value) => this.activeFilterValueLabel(key, value));
5106
5280
  if (values.length === 0) {
5107
5281
  continue
@@ -5285,11 +5459,12 @@ var script = {
5285
5459
  * @return {string}
5286
5460
  */
5287
5461
  headerMetaText() {
5288
- if (!this.headerMeta) {
5462
+ const headerMeta = this.presented('headerMeta');
5463
+ if (!headerMeta) {
5289
5464
  return ''
5290
5465
  }
5291
5466
  const total = this.effectivePagination?.total;
5292
- return this.cnTranslate(this.headerMeta)
5467
+ return this.cnTranslate(headerMeta)
5293
5468
  .replace('{shown}', String(this.effectiveObjects.length))
5294
5469
  .replace('{total}', typeof total === 'number' ? String(total) : '')
5295
5470
  },
@@ -5303,11 +5478,12 @@ var script = {
5303
5478
  // The default count line is the library's own string, so it
5304
5479
  // reads "14 van 14" in Dutch; a page's own template goes
5305
5480
  // through the app's label lookup.
5306
- if (!this.countText && !this.countSubtitle) {
5481
+ const countText = this.presented('countText');
5482
+ if (!countText && !this.countSubtitle) {
5307
5483
  return translate('nextcloud-vue', '{shown} of {total}', { shown, total })
5308
5484
  }
5309
- if (this.countText) {
5310
- return this.cnTranslate(this.countText)
5485
+ if (countText) {
5486
+ return this.cnTranslate(countText)
5311
5487
  .replace('{shown}', String(shown))
5312
5488
  .replace('{total}', String(total))
5313
5489
  }
@@ -5321,8 +5497,10 @@ var script = {
5321
5497
 
5322
5498
  /** Add button label — derived from schema.title if not explicitly set */
5323
5499
  resolvedAddLabel() {
5324
- if (this.addLabel) {
5325
- return this.cnTranslate(this.addLabel)
5500
+ const scoped = this.activeScopePresentation ? this.activeScopePresentation.addLabel : undefined;
5501
+ const addLabel = scoped !== undefined ? scoped : this.addLabel;
5502
+ if (addLabel) {
5503
+ return this.cnTranslate(addLabel)
5326
5504
  }
5327
5505
  // A named source names its own create action. Without this the button
5328
5506
  // falls back to a schema-derived noun, and a named source has no
@@ -5547,6 +5725,14 @@ var script = {
5547
5725
  },
5548
5726
  },
5549
5727
 
5728
+ pageTabCountKey: {
5729
+ immediate: true,
5730
+ /** @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs */
5731
+ handler() {
5732
+ this.loadPageTabCounts();
5733
+ },
5734
+ },
5735
+
5550
5736
  filterStripCountKey: {
5551
5737
  immediate: true,
5552
5738
  /** @spec openspec/changes/screens-filter-strip/specs/index-filter-strip/spec.md#requirement-an-index-page-can-draw-a-second-filter-strip */
@@ -5751,6 +5937,7 @@ var script = {
5751
5937
  this.maybeOpenCreateFromQuery();
5752
5938
  if (this.folderSidebar) {
5753
5939
  this.loadFolderRegister();
5940
+ this.loadFolderGroups();
5754
5941
  // Reflect a deep-link filter (e.g. ?caseType=<id>) as the active folder.
5755
5942
  const key = this.folderSidebar.filterField || this.folderSidebar.field;
5756
5943
  const active = key && this.effectiveActiveFilters[key];
@@ -5814,6 +6001,36 @@ var script = {
5814
6001
  },
5815
6002
 
5816
6003
  methods: {
6004
+ /**
6005
+ * A presentation value: the active scope's when it declares the key,
6006
+ * else this page's own prop (which a query preset may have set).
6007
+ *
6008
+ * @param {string} key One of the query preset keys.
6009
+ * @return {unknown} The value.
6010
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
6011
+ */
6012
+ presented(key) {
6013
+ const scoped = this.activeScopePresentation;
6014
+ return scoped[key] !== undefined ? scoped[key] : this.ownPresentation[key]
6015
+ },
6016
+
6017
+ /**
6018
+ * Whether a filter value is one of the query pairs that chose the
6019
+ * preset, so it shows no removable chip.
6020
+ *
6021
+ * @param {string} key The filter key.
6022
+ * @param {unknown} value One filter value.
6023
+ * @return {boolean}
6024
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-the-query-that-chose-a-preset-is-not-a-removable-filter
6025
+ */
6026
+ isFixedFilter(key, value) {
6027
+ const fixed = this.fixedFilters;
6028
+ if (!fixed || !Object.hasOwn(fixed, key)) {
6029
+ return false
6030
+ }
6031
+ return String(fixed[key]) === String(value)
6032
+ },
6033
+
5817
6034
  /**
5818
6035
  * The manifest page that shows one object of a schema, for a linked
5819
6036
  * label. Null without a manifest or a matching detail page.
@@ -5927,6 +6144,32 @@ var script = {
5927
6144
  ];
5928
6145
  },
5929
6146
 
6147
+ /**
6148
+ * Count the lists behind the manifest page tabs (`countFrom`), one list
6149
+ * request each. A failed count shows no number.
6150
+ *
6151
+ * @return {Promise<void>}
6152
+ * @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
6153
+ */
6154
+ async loadPageTabCounts() {
6155
+ const key = this.pageTabCountKey;
6156
+ if (key === '') {
6157
+ this.pageTabCounts = {};
6158
+ return
6159
+ }
6160
+ const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {};
6161
+ const counts = {};
6162
+ await Promise.all(this.pageTabs.map(async (tab, index) => {
6163
+ const from = tab && tab.countFrom;
6164
+ if (from && from.register && from.schema) {
6165
+ counts[index] = await fetchListTotal(from.register, from.schema, from.filter || {}, ctx).catch(() => null);
6166
+ }
6167
+ }));
6168
+ if (key === this.pageTabCountKey) {
6169
+ this.pageTabCounts = counts;
6170
+ }
6171
+ },
6172
+
5930
6173
  /**
5931
6174
  * Read the counts of the `filterStrip` pills in one grouped request,
5932
6175
  * narrowed by the page filter and the active quick filter. A failed
@@ -7360,6 +7603,50 @@ var script = {
7360
7603
  this.$emit('quick-edit-save', { id, patch });
7361
7604
  },
7362
7605
 
7606
+ /**
7607
+ * The buttons an `actions` column shows on a row: the row actions its
7608
+ * `widgetProps.actions` names (by id, else by label), in that order,
7609
+ * that are visible for the row.
7610
+ *
7611
+ * @param {object} col The column.
7612
+ * @param {object} row The row.
7613
+ * @return {Array<{key: string, label: string, variant: string, disabled: boolean}>}
7614
+ * @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons
7615
+ */
7616
+ actionColumnItems(col, row) {
7617
+ const wanted = Array.isArray(col.widgetProps && col.widgetProps.actions) ? col.widgetProps.actions : [];
7618
+ const available = this.rowActionsFor(row);
7619
+ return wanted
7620
+ .map((ref) => available.find((action) => action.id === ref) || available.find((action) => action.label === ref))
7621
+ .filter((action) => action && isRowActionVisible(action, row))
7622
+ .map((action, index) => ({
7623
+ key: action.id || action.label,
7624
+ label: this.cnTranslate(action.label),
7625
+ variant: action.variant || (index === 0 ? 'secondary' : 'tertiary'),
7626
+ disabled: typeof action.disabled === 'function' ? !!action.disabled(row) : action.disabled === true,
7627
+ }))
7628
+ },
7629
+
7630
+ /**
7631
+ * Run one `actions` column button: its handler, then the same `action`
7632
+ * event a row menu entry emits.
7633
+ *
7634
+ * @param {string} key The action's id, else its label.
7635
+ * @param {object} row The row.
7636
+ * @return {void}
7637
+ * @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons
7638
+ */
7639
+ runColumnAction(key, row) {
7640
+ const action = this.rowActionsFor(row).find((entry) => (entry.id || entry.label) === key);
7641
+ if (!action) {
7642
+ return
7643
+ }
7644
+ if (typeof action.handler === 'function') {
7645
+ action.handler(row);
7646
+ }
7647
+ this.onRowAction(rowActionPayload(action, row));
7648
+ },
7649
+
7363
7650
  /**
7364
7651
  * Take the server's version of a row after a conflict. Nothing of this
7365
7652
  * person's edit is written, and the row in the list is refreshed to
@@ -7523,6 +7810,39 @@ var script = {
7523
7810
  }
7524
7811
  },
7525
7812
 
7813
+ /**
7814
+ * Load the Nextcloud groups for `folderSidebar.source: 'groups'`, each
7815
+ * with its member count. `folderSidebar.groups` (`[{ id, parent? }]`)
7816
+ * picks and orders them and nests a group under its `parent`; without
7817
+ * it every group shows, by name. A refused request lists none.
7818
+ *
7819
+ * @return {Promise<void>}
7820
+ * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members
7821
+ */
7822
+ async loadFolderGroups() {
7823
+ const cfg = this.folderSidebar;
7824
+ if (!cfg || cfg.source !== 'groups') {
7825
+ return
7826
+ }
7827
+ try {
7828
+ const { default: axios } = await import('@nextcloud/axios');
7829
+ const groups = await fetchGroups(axios, { search: cfg.search || '', limit: cfg.limit || 200 });
7830
+ const declared = Array.isArray(cfg.groups) ? cfg.groups.filter((entry) => entry && entry.id) : null;
7831
+ if (!declared) {
7832
+ this.folderGroupList = [...groups].sort((a, b) => a.name.localeCompare(b.name));
7833
+ return
7834
+ }
7835
+ const byId = new Map(groups.map((group) => [group.id, group]));
7836
+ this.folderGroupList = declared
7837
+ .filter((entry) => byId.has(String(entry.id)))
7838
+ .map((entry) => ({ ...byId.get(String(entry.id)), parentId: entry.parent ? String(entry.parent) : null }));
7839
+ } catch (e) {
7840
+ // eslint-disable-next-line no-console
7841
+ console.error('[CnIndexPage] failed to load the Nextcloud groups', e);
7842
+ this.folderGroupList = [];
7843
+ }
7844
+ },
7845
+
7526
7846
  /**
7527
7847
  * Load the folder list from an OpenRegister register/schema for a
7528
7848
  * `folderSidebar.source === 'register'` config. Maps each object to
@@ -8212,6 +8532,9 @@ var script = {
8212
8532
  * @return {boolean} True for the current page.
8213
8533
  */
8214
8534
  isActivePageTab(tab) {
8535
+ if (tab.to && typeof tab.to === 'object') {
8536
+ return !!this.$route && tab.to.name !== undefined && this.$route.name === tab.to.name
8537
+ }
8215
8538
  const path = this.$route && this.$route.path;
8216
8539
  return !!path && String(path).replace(/\/+$/, '') === String(tab.to).replace(/\/+$/, '')
8217
8540
  },