@conduction/nextcloud-vue 2.78.0 → 2.80.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.
- package/dist/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +115 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +39 -3
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +158 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +157 -96
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +5 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +62 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +85 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +736 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/index.js +30 -0
- package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +551 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +183 -0
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFieldListWidget/index.js +29 -0
- package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +66 -18
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +18 -3
- package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +330 -74
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +34 -15
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +387 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +169 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/index.js +31 -0
- package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSectionsWidget/index.js +35 -0
- package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +152 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +84 -23
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +106 -2
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +105 -53
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/composables/useFormDraft.js +140 -9
- package/dist/esm/composables/useFormDraft.js.map +1 -1
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection.js +21 -0
- package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
- package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
- package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
- package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
- package/dist/esm/integrations/offline/index.js +2 -1
- package/dist/esm/integrations/offline/index.js.map +1 -1
- package/dist/esm/integrations/offline/offlineDb.js +6 -1
- package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
- package/dist/esm/integrations/offline/syncReplayService.js +8 -2
- package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
- package/dist/esm/l10n/en.json.js +72 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +120 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +1314 -74
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +60 -3
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +114 -0
- package/dist/esm/utils/fillTemplate.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +4 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/queryPresets.js +96 -0
- package/dist/esm/utils/queryPresets.js.map +1 -0
- package/dist/esm/utils/resolveFilterTokens.js +11 -0
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/schema.js +21 -0
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +3 -3
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +7 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +14278 -8169
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1314 -74
- package/l10n/en.json +72 -1
- package/l10n/nl.json +120 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
- package/src/components/CnActionsBar/CnActionsBar.md +4 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +83 -42
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnBannerWidget/CnBannerWidget.vue +39 -3
- package/src/components/CnCellRenderer/CnCellRenderer.vue +306 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +82 -2
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +63 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +127 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +1050 -0
- package/src/components/CnFactsWidget/index.js +29 -0
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +746 -0
- package/src/components/CnFieldListWidget/index.js +28 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.md +9 -3
- package/src/components/CnFormDialog/CnFormDialog.vue +70 -22
- package/src/components/CnFormPage/CnFormPage.vue +18 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +281 -9
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +603 -0
- package/src/components/CnRecordListWidget/index.js +30 -0
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
- package/src/components/CnSectionsWidget/index.js +34 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
- package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +214 -9
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +188 -4
- package/src/components/index.js +5 -1
- package/src/composables/__tests__/useFormDraft.spec.js +69 -0
- package/src/composables/useAppManifest.js +8 -3
- package/src/composables/useFormDraft.js +139 -8
- package/src/css/form-field.css +14 -0
- package/src/css/index-page.css +71 -0
- package/src/css/kpi-card.css +32 -0
- package/src/css/look-board-detail.css +87 -0
- package/src/css/look-board-index.css +143 -14
- package/src/css/look-board.css +114 -2
- package/src/css/table.css +56 -0
- package/src/index.js +8 -0
- package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
- package/src/integrations/builtin/field-inspection.js +21 -0
- package/src/integrations/offline/checklistSubmission.js +137 -0
- package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
- package/src/integrations/offline/index.js +3 -0
- package/src/integrations/offline/offlineDb.js +6 -1
- package/src/integrations/offline/syncReplayService.js +8 -2
- package/src/schemas/app-manifest-v2.schema.json +149 -5
- package/src/utils/boardDate.js +189 -0
- package/src/utils/detailActionModel.js +59 -2
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/fillTemplate.js +110 -0
- package/src/utils/libraryWidgetKeys.js +4 -0
- package/src/utils/queryPresets.js +93 -0
- package/src/utils/resolveFilterTokens.js +11 -0
- package/src/utils/schema.js +21 -0
- package/src/utils/sentinelTokens.js +3 -3
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/src/utils/widgetDispatch.js +7 -1
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
<!-- @slot title-meta Content on the title's row, right of the title (e.g. a page-wide picker). Fill it from a manifest with `page.slots: { "title-meta": "<RegistryName>" }`. -->
|
|
34
34
|
<slot name="title-meta" />
|
|
35
35
|
</div>
|
|
36
|
-
<p v-if="
|
|
37
|
-
{{
|
|
36
|
+
<p v-if="subtitleText" class="cn-dashboard-page__description" data-testid="cn-dashboard-page-subtitle">
|
|
37
|
+
{{ subtitleText }}
|
|
38
38
|
</p>
|
|
39
39
|
</div>
|
|
40
40
|
<div class="cn-dashboard-page__header-actions">
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
widget on the page draws it. Its options control the view
|
|
43
43
|
region below the grid. -->
|
|
44
44
|
<CnSegmentedControl
|
|
45
|
-
v-if="showsPageViewSwitch"
|
|
45
|
+
v-if="showsPageViewSwitch && !switchInRow"
|
|
46
46
|
class="cn-page-view-switch"
|
|
47
47
|
:options="viewSwitchOptions"
|
|
48
48
|
:modelValue="activeViewId"
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
Separate from the per-widget menus. `docs-anchor` deep-links
|
|
98
98
|
the docs item to THIS page's section. -->
|
|
99
99
|
<CnActionsMenu
|
|
100
|
+
v-if="showActionsMenu"
|
|
100
101
|
:showRefresh="showRefresh"
|
|
101
102
|
:showRequestFeature="showRequestFeature"
|
|
102
103
|
:showReportBug="showReportBug"
|
|
@@ -138,6 +139,39 @@
|
|
|
138
139
|
</div>
|
|
139
140
|
</div>
|
|
140
141
|
|
|
142
|
+
<!-- The switch row (screens-dashboard-greeting-header): in the board
|
|
143
|
+
look the page's view switch sits under the header, left, and the
|
|
144
|
+
page's `viewLinks` sit right of it as small pills (DqMijnWerk). -->
|
|
145
|
+
<div
|
|
146
|
+
v-if="showHeader && (switchInRow || resolvedViewLinks.length > 0)"
|
|
147
|
+
class="cn-dashboard-page__switch-row"
|
|
148
|
+
data-testid="cn-dashboard-page-switch-row">
|
|
149
|
+
<CnSegmentedControl
|
|
150
|
+
v-if="switchInRow"
|
|
151
|
+
class="cn-page-view-switch"
|
|
152
|
+
:options="viewSwitchOptions"
|
|
153
|
+
:modelValue="activeViewId"
|
|
154
|
+
:ariaLabel="viewSwitchLabel"
|
|
155
|
+
:controls="viewRegionId"
|
|
156
|
+
data-testid="cn-dashboard-page-view-switch"
|
|
157
|
+
@update:modelValue="selectView" />
|
|
158
|
+
<span class="cn-dashboard-page__switch-row-spacer" />
|
|
159
|
+
<component
|
|
160
|
+
:is="link.tag"
|
|
161
|
+
v-for="link in resolvedViewLinks"
|
|
162
|
+
:key="link.key"
|
|
163
|
+
v-bind="link.attrs"
|
|
164
|
+
class="cn-dashboard-page__view-link"
|
|
165
|
+
data-testid="cn-dashboard-page-view-link">
|
|
166
|
+
<CnWidgetIcon
|
|
167
|
+
v-if="link.icon"
|
|
168
|
+
:name="link.icon"
|
|
169
|
+
:size="14"
|
|
170
|
+
class="cn-dashboard-page__view-link-icon" />
|
|
171
|
+
{{ link.label }}
|
|
172
|
+
</component>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
141
175
|
<!-- Date-range header (optional).
|
|
142
176
|
Rendered only when `dateRange.enabled === true` AND
|
|
143
177
|
`dateRange.showHeaderPicker !== false`. The range STATE
|
|
@@ -868,7 +902,8 @@
|
|
|
868
902
|
</template>
|
|
869
903
|
|
|
870
904
|
<script>
|
|
871
|
-
import {
|
|
905
|
+
import { getCurrentUser } from '@nextcloud/auth'
|
|
906
|
+
import { getCanonicalLocale, translate as t } from '@nextcloud/l10n'
|
|
872
907
|
import { NcActionButton, NcActionInput, NcActions, NcActionSeparator, NcButton, NcLoadingIcon, NcSelect } from '@nextcloud/vue'
|
|
873
908
|
import { provide, ref, watch } from 'vue'
|
|
874
909
|
import CalendarRange from 'vue-material-design-icons/CalendarRange.vue'
|
|
@@ -889,6 +924,7 @@ import CnKpiGrid from '../CnKpiGrid/CnKpiGrid.vue'
|
|
|
889
924
|
import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
|
|
890
925
|
import CnStatsBlockWidget from '../CnStatsBlockWidget/CnStatsBlockWidget.vue'
|
|
891
926
|
import CnTileWidget from '../CnTileWidget/CnTileWidget.vue'
|
|
927
|
+
import CnWidgetIcon from '../CnWidgetGrid/CnWidgetIcon.vue'
|
|
892
928
|
import CnWidgetRefItem from '../CnWidgetRefItem/CnWidgetRefItem.vue'
|
|
893
929
|
import CnWidgetRenderer from '../CnWidgetRenderer/CnWidgetRenderer.vue'
|
|
894
930
|
import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
|
|
@@ -1056,6 +1092,7 @@ export default {
|
|
|
1056
1092
|
CnLeafMountHost,
|
|
1057
1093
|
CnSegmentedControl,
|
|
1058
1094
|
CnKpiGrid,
|
|
1095
|
+
CnWidgetIcon,
|
|
1059
1096
|
},
|
|
1060
1097
|
|
|
1061
1098
|
mixins: [pageViews],
|
|
@@ -1156,6 +1193,50 @@ export default {
|
|
|
1156
1193
|
default: () => [],
|
|
1157
1194
|
},
|
|
1158
1195
|
|
|
1196
|
+
/**
|
|
1197
|
+
* Greet the reader in the header subtitle (manifest `config.greeting`):
|
|
1198
|
+
* `true` (or `"first"`) writes "Good afternoon, Pieter · Monday 5
|
|
1199
|
+
* October 2026", `"full"` uses the whole display name. The page
|
|
1200
|
+
* `description`, when set, follows after another middle dot. Off by
|
|
1201
|
+
* default: the subtitle is the description alone, as before.
|
|
1202
|
+
*
|
|
1203
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-greet-the-reader
|
|
1204
|
+
* @type {boolean|string}
|
|
1205
|
+
*/
|
|
1206
|
+
greeting: {
|
|
1207
|
+
type: [Boolean, String],
|
|
1208
|
+
default: false,
|
|
1209
|
+
},
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Links drawn as small pills on the switch row under the header
|
|
1213
|
+
* (manifest `config.viewLinks`), as DqMijnWerk's "Uw wachtrij / Aan
|
|
1214
|
+
* mij toegewezen / Dag afsluiten". Each `{ label, icon?, route?,
|
|
1215
|
+
* href? }`: `route` is a route name or a `{ name, params?, query? }`
|
|
1216
|
+
* location, `href` a plain URL; `label` goes through the host
|
|
1217
|
+
* translate function. An entry without a label or a target is skipped.
|
|
1218
|
+
*
|
|
1219
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-a-switch-row-carries-the-view-switch-and-link-pills
|
|
1220
|
+
* @type {Array<object>}
|
|
1221
|
+
*/
|
|
1222
|
+
viewLinks: {
|
|
1223
|
+
type: Array,
|
|
1224
|
+
default: () => [],
|
|
1225
|
+
},
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Whether the header draws the page Actions menu (manifest
|
|
1229
|
+
* `config.showActionsMenu`). `false` removes it, for a board page
|
|
1230
|
+
* whose header carries only the buildiq square and the primary.
|
|
1231
|
+
*
|
|
1232
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-can-drop-the-page-actions-menu
|
|
1233
|
+
* @type {boolean}
|
|
1234
|
+
*/
|
|
1235
|
+
showActionsMenu: {
|
|
1236
|
+
type: Boolean,
|
|
1237
|
+
default: true,
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1159
1240
|
/**
|
|
1160
1241
|
* Whether a widget draws its overflow Actions menu when neither its
|
|
1161
1242
|
* placement nor its definition says (`showActions`). `false` (manifest
|
|
@@ -1848,6 +1929,97 @@ export default {
|
|
|
1848
1929
|
return typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
|
|
1849
1930
|
},
|
|
1850
1931
|
|
|
1932
|
+
/**
|
|
1933
|
+
* The greeting for the time of day, with the reader's first (or
|
|
1934
|
+
* full) name when known, or '' when `greeting` is off.
|
|
1935
|
+
*
|
|
1936
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-greet-the-reader
|
|
1937
|
+
* @return {string}
|
|
1938
|
+
*/
|
|
1939
|
+
greetingLine() {
|
|
1940
|
+
if (this.greeting !== true && this.greeting !== 'first' && this.greeting !== 'full') {
|
|
1941
|
+
return ''
|
|
1942
|
+
}
|
|
1943
|
+
let user
|
|
1944
|
+
try {
|
|
1945
|
+
user = getCurrentUser()
|
|
1946
|
+
} catch {
|
|
1947
|
+
user = null
|
|
1948
|
+
}
|
|
1949
|
+
const displayName = (user && typeof user.displayName === 'string') ? user.displayName.trim() : ''
|
|
1950
|
+
const name = this.greeting === 'full' ? displayName : displayName.split(/\s+/)[0]
|
|
1951
|
+
const hour = new Date().getHours()
|
|
1952
|
+
if (hour < 12) {
|
|
1953
|
+
return name ? t('nextcloud-vue', 'Good morning, {name}', { name }) : t('nextcloud-vue', 'Good morning')
|
|
1954
|
+
}
|
|
1955
|
+
if (hour < 18) {
|
|
1956
|
+
return name ? t('nextcloud-vue', 'Good afternoon, {name}', { name }) : t('nextcloud-vue', 'Good afternoon')
|
|
1957
|
+
}
|
|
1958
|
+
return name ? t('nextcloud-vue', 'Good evening, {name}', { name }) : t('nextcloud-vue', 'Good evening')
|
|
1959
|
+
},
|
|
1960
|
+
|
|
1961
|
+
/**
|
|
1962
|
+
* The header subtitle: with `greeting` on, the greeting, today's date
|
|
1963
|
+
* written out and the description, joined by a middle dot; else the
|
|
1964
|
+
* description alone, as before.
|
|
1965
|
+
*
|
|
1966
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-the-header-subtitle-can-greet-the-reader
|
|
1967
|
+
* @return {string}
|
|
1968
|
+
*/
|
|
1969
|
+
subtitleText() {
|
|
1970
|
+
const description = this.description ? this.resolvedDescription : ''
|
|
1971
|
+
const greeting = this.greetingLine
|
|
1972
|
+
if (greeting === '') {
|
|
1973
|
+
return description
|
|
1974
|
+
}
|
|
1975
|
+
const options = { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }
|
|
1976
|
+
let date
|
|
1977
|
+
try {
|
|
1978
|
+
date = new Intl.DateTimeFormat(getCanonicalLocale() || undefined, options).format(new Date())
|
|
1979
|
+
} catch {
|
|
1980
|
+
date = new Intl.DateTimeFormat(undefined, options).format(new Date())
|
|
1981
|
+
}
|
|
1982
|
+
return [greeting, date, description].filter((part) => part !== '').join(' · ')
|
|
1983
|
+
},
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Whether the page's view switch sits on the switch row under the
|
|
1987
|
+
* header (board look) rather than among the header actions.
|
|
1988
|
+
*
|
|
1989
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-a-switch-row-carries-the-view-switch-and-link-pills
|
|
1990
|
+
* @return {boolean}
|
|
1991
|
+
*/
|
|
1992
|
+
switchInRow() {
|
|
1993
|
+
return this.isBoardLook && this.showsPageViewSwitch
|
|
1994
|
+
},
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* `viewLinks` as render descriptors: a router link for a route, an
|
|
1998
|
+
* anchor for an href, the label translated. Entries without a label
|
|
1999
|
+
* or a target are dropped.
|
|
2000
|
+
*
|
|
2001
|
+
* @spec openspec/changes/screens-dashboard-greeting-header/specs/dashboard-page/spec.md#requirement-a-switch-row-carries-the-view-switch-and-link-pills
|
|
2002
|
+
* @return {Array<{key: string, tag: string, attrs: object, label: string, icon: string}>}
|
|
2003
|
+
*/
|
|
2004
|
+
resolvedViewLinks() {
|
|
2005
|
+
const links = Array.isArray(this.viewLinks) ? this.viewLinks : []
|
|
2006
|
+
const out = []
|
|
2007
|
+
links.forEach((link, index) => {
|
|
2008
|
+
if (!link || typeof link.label !== 'string' || link.label === '') {
|
|
2009
|
+
return
|
|
2010
|
+
}
|
|
2011
|
+
const icon = typeof link.icon === 'string' ? link.icon : ''
|
|
2012
|
+
const label = this.effectiveTranslate(link.label)
|
|
2013
|
+
if (link.route && this.$router) {
|
|
2014
|
+
const to = typeof link.route === 'string' ? { name: link.route } : link.route
|
|
2015
|
+
out.push({ key: `route-${index}`, tag: 'router-link', attrs: { to }, label, icon })
|
|
2016
|
+
} else if (typeof link.href === 'string' && link.href !== '') {
|
|
2017
|
+
out.push({ key: `href-${index}`, tag: 'a', attrs: { href: link.href }, label, icon })
|
|
2018
|
+
}
|
|
2019
|
+
})
|
|
2020
|
+
return out
|
|
2021
|
+
},
|
|
2022
|
+
|
|
1851
2023
|
/**
|
|
1852
2024
|
* The page title run through the host translate function so a
|
|
1853
2025
|
* manifest-authored source string localises.
|
|
@@ -4392,6 +4564,49 @@ export default {
|
|
|
4392
4564
|
height: var(--cn-board-control-height, 40px);
|
|
4393
4565
|
}
|
|
4394
4566
|
|
|
4567
|
+
/* The switch row under the header (screens-dashboard-greeting-header): the
|
|
4568
|
+
view switch left, the `viewLinks` pills right (DqMijnWerk: pills 34px high,
|
|
4569
|
+
radius 17, 13px/600, a 14px icon, primary light on primary deep). */
|
|
4570
|
+
.cn-dashboard-page__switch-row {
|
|
4571
|
+
display: flex;
|
|
4572
|
+
flex-wrap: wrap;
|
|
4573
|
+
align-items: center;
|
|
4574
|
+
gap: 10px;
|
|
4575
|
+
margin-bottom: var(--cn-board-section-gap, 20px);
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4578
|
+
.cn-dashboard-page__switch-row-spacer {
|
|
4579
|
+
flex: 1 1 auto;
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
.cn-dashboard-page__view-link {
|
|
4583
|
+
display: inline-flex;
|
|
4584
|
+
align-items: center;
|
|
4585
|
+
gap: 6px;
|
|
4586
|
+
height: 34px;
|
|
4587
|
+
padding: 0 12px;
|
|
4588
|
+
border-radius: 17px;
|
|
4589
|
+
background: var(--color-primary-element-light);
|
|
4590
|
+
color: var(--color-primary-element-light-text);
|
|
4591
|
+
font-size: 13px;
|
|
4592
|
+
font-weight: 600;
|
|
4593
|
+
white-space: nowrap;
|
|
4594
|
+
text-decoration: none !important;
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
.cn-dashboard-page__view-link:hover {
|
|
4598
|
+
background: var(--color-primary-element-light-hover, var(--color-primary-element-light));
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
.cn-dashboard-page__view-link:focus-visible {
|
|
4602
|
+
outline: 2px solid var(--color-main-text);
|
|
4603
|
+
outline-offset: 2px;
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
.cn-dashboard-page__view-link-icon {
|
|
4607
|
+
display: inline-flex;
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4395
4610
|
/* The period group, in a row of its own between the header and the KPI row. */
|
|
4396
4611
|
.cn-dashboard-page__date-range--segmented {
|
|
4397
4612
|
margin-bottom: 16px;
|
|
@@ -198,9 +198,11 @@ export default {
|
|
|
198
198
|
| `rowIndicatorCap` | Number | `3` | How many declared indicators render on the row before the rest move into the row menu, reachable as `indicatorsFor(row).overflow`. |
|
|
199
199
|
| `selectAllLabel` | String | `'Select all rows'` | Accessible name (`aria-label`) for the header select-all checkbox, so screen readers announce a named control (WCAG 4.1.2) |
|
|
200
200
|
| `selectRowLabel` | String | `'Select row'` | Accessible name (`aria-label`) for each per-row select checkbox, so screen readers announce a named control (WCAG 4.1.2) |
|
|
201
|
+
| `rowTitle` | String | `'link'` | Board look only: how the title column draws its title. `link` underlines the title (not its secondary line). `plain` draws it as 15px bold text without an underline over a 13px muted secondary line, as the PqTickets board does; the row stays clickable. The Nextcloud look ignores it. |
|
|
201
202
|
| `hideHeader` | Boolean | `false` | Hide the column-header row (`<thead>`) — for compact dashboard list widgets that want a plain bordered-row list without column labels |
|
|
202
203
|
| `pinnedCount` | Number | `0` | How many data columns are pinned to the start of the table. They, and the selection and icon columns, stay in view when the table scrolls sideways |
|
|
203
204
|
| `fixedLayout` | Boolean | `false` | Switch to `table-layout: fixed` so each column's `width` is authoritative rather than a hint the browser may override from cell content. Long unbreakable values (a PHP FQCN, a UUID) then wrap inside their cell instead of widening the column or painting past it, and no single unsized column soaks up all remaining width. Size every column when using it — percentages summing to 100 are easiest to reason about |
|
|
205
|
+
| `fitWidth` | Boolean | `false` | Fit the table to its container instead of growing past it: one column (a column's `grow: true`, else `title`, else `name`, else the first) takes the room that is left and ends in an ellipsis, and every other column keeps its content on one line, so trailing columns stay visible in a narrow tile. `CnWidgetObjectTable` turns it on |
|
|
204
206
|
| `fillHeight` | Boolean | `false` | Fill the parent's height (a flex-column card / widget content area) so an optional `#footer` is pushed to the bottom instead of floating under a short list; the footer stays pinned via its sticky rule when the list overflows. No-op outside a height-constrained parent — opt-in |
|
|
205
207
|
| `filterable` | Boolean | `false` | Show a filter button in every header whose column can filter: enum, boolean, text, number, date and reference columns backed by a schema property. A column opts out with `filterable: false`. Applying a filter emits `column-filter`; the host owns the state and passes it back as `activeFilters`. `CnIndexPage` turns it on by default. |
|
|
206
208
|
| `activeFilters` | Object | `{}` | The active filter map, `{ paramKey: values[] }`: the same map the facet sidebar writes, so a header filter and a sidebar filter on one field show the same state. |
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
data-testid="cn-object-list"
|
|
5
5
|
:class="{
|
|
6
6
|
'cn-table-container--board': isBoardLook,
|
|
7
|
+
'cn-table-container--title-plain': isBoardLook && rowTitle === 'plain',
|
|
7
8
|
'cn-table-container--scrollable': scrollable,
|
|
8
9
|
'cn-table-container--borderless': borderless,
|
|
9
10
|
'cn-table-container--fill': fillHeight,
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
:aria-label="isScrollable ? scrollRegionLabel : undefined">
|
|
97
98
|
<table
|
|
98
99
|
class="cn-data-table"
|
|
99
|
-
:class="{ 'cn-data-table--fixed': fixedLayout }"
|
|
100
|
+
:class="{ 'cn-data-table--fixed': fixedLayout, 'cn-data-table--fit': fitWidth }"
|
|
100
101
|
data-testid="cn-object-list-table">
|
|
101
102
|
<thead v-if="!hideHeader">
|
|
102
103
|
<tr>
|
|
@@ -128,6 +129,8 @@
|
|
|
128
129
|
col.sortable ? 'cn-table-header--sortable' : '',
|
|
129
130
|
col.class || '',
|
|
130
131
|
pinClass(leadingCount + colIndex),
|
|
132
|
+
isGrowColumn(col) ? 'cn-table-col--grow' : '',
|
|
133
|
+
colIndex === 0 ? 'cn-table-head--title' : '',
|
|
131
134
|
]"
|
|
132
135
|
:style="{ ...(col.width ? { width: col.width } : {}), ...pinStyle(leadingCount + colIndex) }"
|
|
133
136
|
:aria-sort="ariaSortFor(col)"
|
|
@@ -256,7 +259,7 @@
|
|
|
256
259
|
<td
|
|
257
260
|
v-for="(col, colIndex) in effectiveColumns"
|
|
258
261
|
:key="col.key"
|
|
259
|
-
:class="[col.class || '', col.cellClass || '', cellClass ? cellClass(row, col) : '', pinClass(leadingCount + colIndex), colIndex === 0 ? 'cn-table-col--title' : '']"
|
|
262
|
+
:class="[col.class || '', col.cellClass || '', cellClass ? cellClass(row, col) : '', pinClass(leadingCount + colIndex), colIndex === 0 ? 'cn-table-col--title' : '', isGrowColumn(col) ? 'cn-table-col--grow' : '']"
|
|
260
263
|
:style="{ ...(col.width ? { maxWidth: col.width } : {}), ...pinStyle(leadingCount + colIndex) }"
|
|
261
264
|
@mouseenter="titleWhenClipped">
|
|
262
265
|
<!-- A row with a `rowClickRoute` is a real link: this anchor
|
|
@@ -499,6 +502,16 @@ function fillSecondaryTemplate(template, valueOf) {
|
|
|
499
502
|
* </CnDataTable>
|
|
500
503
|
* ```
|
|
501
504
|
*/
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Column keys that name an object's system dates in OpenRegister's `@self`
|
|
508
|
+
* block. A column may declare one as its key (`@self.created`); it renders as
|
|
509
|
+
* a date-time although the schema has no property behind it.
|
|
510
|
+
*
|
|
511
|
+
* @type {string[]}
|
|
512
|
+
*/
|
|
513
|
+
const SYSTEM_DATE_KEYS = ['@self.created', '@self.updated', '@self.published', '@self.depublished']
|
|
514
|
+
|
|
502
515
|
export default {
|
|
503
516
|
name: 'CnDataTable',
|
|
504
517
|
|
|
@@ -761,6 +774,21 @@ export default {
|
|
|
761
774
|
default: '',
|
|
762
775
|
},
|
|
763
776
|
|
|
777
|
+
/**
|
|
778
|
+
* How the board look draws the title column: `link` (the default) is
|
|
779
|
+
* the 15px weight 600 underlined title of the DqZaken board; `plain` is
|
|
780
|
+
* the 15px bold title without an underline and a 13px muted secondary
|
|
781
|
+
* line, as the PqTickets and OcPublicaties boards draw it. The row stays
|
|
782
|
+
* clickable either way. The Nextcloud look ignores it.
|
|
783
|
+
*
|
|
784
|
+
* @spec openspec/changes/screens-table-rows-parity/specs/index-list-board-look/spec.md#requirement-a-row-title-can-be-plain-text
|
|
785
|
+
*/
|
|
786
|
+
rowTitle: {
|
|
787
|
+
type: String,
|
|
788
|
+
default: 'link',
|
|
789
|
+
validator: (value) => ['link', 'plain'].includes(value),
|
|
790
|
+
},
|
|
791
|
+
|
|
764
792
|
/**
|
|
765
793
|
* Drop the container's card chrome (border, radius, shadow) so the table
|
|
766
794
|
* sits flush inside a parent that already provides a card (e.g. a
|
|
@@ -820,6 +848,23 @@ export default {
|
|
|
820
848
|
default: false,
|
|
821
849
|
},
|
|
822
850
|
|
|
851
|
+
/**
|
|
852
|
+
* Fit the table to its container instead of letting it grow past it.
|
|
853
|
+
* One column takes the room that is left and cuts its text with an
|
|
854
|
+
* ellipsis (the full value stays in the tooltip); every other column
|
|
855
|
+
* keeps its content on one line at its own width, so trailing columns
|
|
856
|
+
* such as a date stay visible in a narrow tile. The column that grows
|
|
857
|
+
* is the first with `grow: true`, else the one keyed `title` or `name`,
|
|
858
|
+
* else the first column. CnWidgetObjectTable turns this on.
|
|
859
|
+
*
|
|
860
|
+
* @type {boolean}
|
|
861
|
+
* @spec openspec/changes/narrow-object-table-widgets/specs/narrow-object-table-widgets/spec.md#requirement-a-narrow-object-table-keeps-its-trailing-columns-in-view
|
|
862
|
+
*/
|
|
863
|
+
fitWidth: {
|
|
864
|
+
type: Boolean,
|
|
865
|
+
default: false,
|
|
866
|
+
},
|
|
867
|
+
|
|
823
868
|
/**
|
|
824
869
|
* Max number of rows to display. When the total exceeds it, only the first
|
|
825
870
|
* `limit` render and the "View all" footer appears (with `viewAllRoute`).
|
|
@@ -1025,6 +1070,25 @@ export default {
|
|
|
1025
1070
|
},
|
|
1026
1071
|
|
|
1027
1072
|
/** @return {number} Leading cells before the data columns (selection, icon). */
|
|
1073
|
+
/**
|
|
1074
|
+
* The key of the column that takes the spare width under `fitWidth`:
|
|
1075
|
+
* the first with `grow: true`, else `title` or `name`, else the first.
|
|
1076
|
+
*
|
|
1077
|
+
* @return {string|null} The column key, or null when not fitting.
|
|
1078
|
+
* @spec openspec/changes/narrow-object-table-widgets/specs/narrow-object-table-widgets/spec.md#requirement-a-narrow-object-table-keeps-its-trailing-columns-in-view
|
|
1079
|
+
*/
|
|
1080
|
+
growColumnKey() {
|
|
1081
|
+
if (!this.fitWidth || this.effectiveColumns.length === 0) {
|
|
1082
|
+
return null
|
|
1083
|
+
}
|
|
1084
|
+
const cols = this.effectiveColumns
|
|
1085
|
+
const chosen = cols.find((c) => c && c.grow === true)
|
|
1086
|
+
|| cols.find((c) => c && c.key === 'title')
|
|
1087
|
+
|| cols.find((c) => c && c.key === 'name')
|
|
1088
|
+
|| cols[0]
|
|
1089
|
+
return chosen ? chosen.key : null
|
|
1090
|
+
},
|
|
1091
|
+
|
|
1028
1092
|
leadingCount() {
|
|
1029
1093
|
return (this.selectable ? 1 : 0) + (this.rowIcon ? 1 : 0)
|
|
1030
1094
|
},
|
|
@@ -1321,6 +1385,16 @@ export default {
|
|
|
1321
1385
|
},
|
|
1322
1386
|
|
|
1323
1387
|
methods: {
|
|
1388
|
+
/**
|
|
1389
|
+
* Whether a column is the one that takes the spare width (`fitWidth`).
|
|
1390
|
+
*
|
|
1391
|
+
* @param {object} col The column.
|
|
1392
|
+
* @return {boolean}
|
|
1393
|
+
*/
|
|
1394
|
+
isGrowColumn(col) {
|
|
1395
|
+
return this.growColumnKey !== null && !!col && col.key === this.growColumnKey
|
|
1396
|
+
},
|
|
1397
|
+
|
|
1324
1398
|
/**
|
|
1325
1399
|
* Whether a row's record changed since the user last looked
|
|
1326
1400
|
* (`@self.unread`). A row without the marker renders as it always did.
|
|
@@ -1651,11 +1725,17 @@ export default {
|
|
|
1651
1725
|
* the schema property's own `colorMap` / `x-color-map`.
|
|
1652
1726
|
*
|
|
1653
1727
|
* @param {object} col Column definition.
|
|
1728
|
+
* @spec openspec/changes/screens-table-footer-and-system-dates-parity/specs/index-list-board-look/spec.md#requirement-a-system-date-can-be-a-column
|
|
1654
1729
|
* @return {object} Property definition for CnCellRenderer.
|
|
1655
1730
|
* @spec openspec/changes/screens-cell-pill-parity/specs/cell-pill-tones/spec.md#requirement-a-manifest-column-colours-its-enum-pills
|
|
1656
1731
|
*/
|
|
1657
1732
|
columnProperty(col) {
|
|
1658
1733
|
const base = this.getSchemaProperty(col.key)
|
|
1734
|
+
// A system date (`@self.created`, `@self.updated`, ...) has no
|
|
1735
|
+
// schema property behind it; it renders as a date-time.
|
|
1736
|
+
if (Object.keys(base).length === 0 && SYSTEM_DATE_KEYS.includes(col.key) && !col.type && !col.format) {
|
|
1737
|
+
return { type: 'string', format: 'date-time' }
|
|
1738
|
+
}
|
|
1659
1739
|
const colorMap = col && col.colorMap && typeof col.colorMap === 'object' && !Array.isArray(col.colorMap) ? col.colorMap : null
|
|
1660
1740
|
if (col && (col.format || col.type || col.enum || colorMap)) {
|
|
1661
1741
|
return {
|
|
@@ -422,7 +422,7 @@ Four levels of action and the case surfaces around them. All opt in. See [the re
|
|
|
422
422
|
| `quickActions` | Array | `[]` | At most three always visible header buttons. |
|
|
423
423
|
| `actionsMenu` | Object \| null | `null` | `{ showRefresh?, showHelpLinks?, label? }` for the overflow menu. |
|
|
424
424
|
| `nextStep` | Object \| null | `null` | The "what now" card per stage, rendered by `CnNextStepCard`. |
|
|
425
|
-
| `typePill` | Object \| null | `null` | `{ field, colorMap?, labels?, variant? }`, a pill above the title. |
|
|
425
|
+
| `typePill` | Object \| null | `null` | `{ field, colorMap?, labels?, variant? }`, a pill above the title. Instead of `field`: `text` (a constant) or `countFrom` + `countLabel` (a count of a related list). |
|
|
426
426
|
| `statusPill` | Object \| null | `null` | A second pill, same shape. |
|
|
427
427
|
| `headerFields` | Array | `[]` | Fields shown as chips under the title (manifest `config.headerFields`). Each entry is a property key, or `{ key, format, labelField, colorField, warnWhenPast }`; `format` is `text` (default), `mono`, `badge`, `user` or `date`. A `$ref` value shows `labelField` of the referenced object and falls back to the raw id in mono; a badge maps `colorField` (`success`, `warning`, `error`, `info`, `neutral`) to a variant; `warnWhenPast` gives a past date the error variant. An empty value shows no chip, a page without the key shows no row. Each chip reads "Title: value" to a screen reader, and the row prints as plain text. |
|
|
428
428
|
| `sideColumn` | Array | `[]` | Widget definitions or widget ids, rendered as a column of cards beside the body. |
|
|
@@ -1231,12 +1231,15 @@ import {
|
|
|
1231
1231
|
groupMenuEntries,
|
|
1232
1232
|
MAX_QUICK_ACTIONS,
|
|
1233
1233
|
normalisePinnedAction,
|
|
1234
|
+
pillCountSource,
|
|
1235
|
+
resolveCountPill,
|
|
1234
1236
|
resolveNextStep,
|
|
1235
1237
|
resolvePill,
|
|
1236
1238
|
stageEntry,
|
|
1237
1239
|
stageOf,
|
|
1238
1240
|
} from '../../utils/detailActionModel.js'
|
|
1239
1241
|
import { reportBindingProblems } from '../../utils/diagnostics.js'
|
|
1242
|
+
import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
|
|
1240
1243
|
import { cnGridCellStyle, hasGridRow } from '../../utils/grid.js'
|
|
1241
1244
|
import { isActivityWidget, resolveHeaderMeta } from '../../utils/headerMeta.js'
|
|
1242
1245
|
import { patchStoredSelf } from '../../utils/patchStoredSelf.js'
|
|
@@ -2393,9 +2396,13 @@ export default {
|
|
|
2393
2396
|
* A pill above the title saying what kind of record this is
|
|
2394
2397
|
* (manifest `config.typePill`): `{ field, colorMap?, labels?, variant? }`.
|
|
2395
2398
|
* Rendered through `CnStatusBadge`; `colorMap` is keyed on the field's
|
|
2396
|
-
* raw value, `labels` maps a raw value to the text to show.
|
|
2399
|
+
* raw value, `labels` maps a raw value to the text to show. Instead of
|
|
2400
|
+
* `field`: `text`, a constant ("Inwoner"), or `countFrom`
|
|
2401
|
+
* (`{ register, schema, filter? }`) with `countLabel` (`{ one, other }`
|
|
2402
|
+
* with `{count}`), a count of a related list ("1 lopende zaak") that
|
|
2403
|
+
* shows nothing at 0.
|
|
2397
2404
|
*
|
|
2398
|
-
* @type {{field
|
|
2405
|
+
* @type {{field?: string, text?: string, countFrom?: object, countLabel?: object, colorMap?: object, labels?: object, variant?: string}|null}
|
|
2399
2406
|
*/
|
|
2400
2407
|
typePill: {
|
|
2401
2408
|
type: Object,
|
|
@@ -2747,6 +2754,10 @@ export default {
|
|
|
2747
2754
|
|
|
2748
2755
|
data() {
|
|
2749
2756
|
return {
|
|
2757
|
+
/** The counts of the header's count pills (`countFrom`), by pill key (`type`, `status`). */
|
|
2758
|
+
pillCounts: {},
|
|
2759
|
+
/** Sequence of the last pill count round, so a late answer does not overwrite a newer one. */
|
|
2760
|
+
pillCountSeq: 0,
|
|
2750
2761
|
/** `register/schema/id` the read-state call was last made for, so it is sent once per load. */
|
|
2751
2762
|
readStateSentFor: '',
|
|
2752
2763
|
/** Whether the user is arranging the chosen view (`userLayout`). */
|
|
@@ -3259,12 +3270,25 @@ export default {
|
|
|
3259
3270
|
headerPills() {
|
|
3260
3271
|
return [['type', this.typePill], ['status', this.statusPill]]
|
|
3261
3272
|
.map(([key, config]) => {
|
|
3262
|
-
const pill =
|
|
3273
|
+
const pill = pillCountSource(config)
|
|
3274
|
+
? resolveCountPill(config, this.pillCounts[key], (text) => this.effectiveTranslate(text))
|
|
3275
|
+
: resolvePill(config, this.resolvedObject)
|
|
3263
3276
|
return pill ? { ...pill, key, label: this.effectiveTranslate(pill.label) } : null
|
|
3264
3277
|
})
|
|
3265
3278
|
.filter(Boolean)
|
|
3266
3279
|
},
|
|
3267
3280
|
|
|
3281
|
+
/**
|
|
3282
|
+
* What the count pills depend on: the record id and the lists they
|
|
3283
|
+
* count. A change counts again.
|
|
3284
|
+
*
|
|
3285
|
+
* @return {string} The key.
|
|
3286
|
+
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-header-pill-can-be-a-constant-or-a-count
|
|
3287
|
+
*/
|
|
3288
|
+
pillCountKey() {
|
|
3289
|
+
return JSON.stringify([this.objectId, pillCountSource(this.typePill), pillCountSource(this.statusPill)])
|
|
3290
|
+
},
|
|
3291
|
+
|
|
3268
3292
|
/**
|
|
3269
3293
|
* The widget the header holds (`headerWidget`): the definition in
|
|
3270
3294
|
* `widgets` with that id, or null without the key or a match.
|
|
@@ -4337,6 +4361,14 @@ export default {
|
|
|
4337
4361
|
},
|
|
4338
4362
|
|
|
4339
4363
|
watch: {
|
|
4364
|
+
// The header's count pills count again for another record or other lists.
|
|
4365
|
+
pillCountKey: {
|
|
4366
|
+
immediate: true,
|
|
4367
|
+
handler() {
|
|
4368
|
+
this.refreshPillCounts()
|
|
4369
|
+
},
|
|
4370
|
+
},
|
|
4371
|
+
|
|
4340
4372
|
// Opening a record marks it read, once, after its data has rendered.
|
|
4341
4373
|
readStateDue: {
|
|
4342
4374
|
immediate: true,
|
|
@@ -4754,6 +4786,33 @@ export default {
|
|
|
4754
4786
|
}
|
|
4755
4787
|
},
|
|
4756
4788
|
|
|
4789
|
+
/**
|
|
4790
|
+
* Count the lists behind the header's count pills (`countFrom`): one
|
|
4791
|
+
* list request each (`_limit=1`, the `total`). A failed count, or a
|
|
4792
|
+
* record without an id yet, shows no pill.
|
|
4793
|
+
*
|
|
4794
|
+
* @return {Promise<void>}
|
|
4795
|
+
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-header-pill-can-be-a-constant-or-a-count
|
|
4796
|
+
*/
|
|
4797
|
+
async refreshPillCounts() {
|
|
4798
|
+
const pills = [['type', this.typePill], ['status', this.statusPill]]
|
|
4799
|
+
.map(([key, config]) => [key, pillCountSource(config)])
|
|
4800
|
+
.filter(([, source]) => source)
|
|
4801
|
+
const seq = ++this.pillCountSeq
|
|
4802
|
+
if (pills.length === 0 || this.objectId === null || this.objectId === undefined || this.objectId === '') {
|
|
4803
|
+
this.pillCounts = {}
|
|
4804
|
+
return
|
|
4805
|
+
}
|
|
4806
|
+
const ctx = { objectId: this.objectId, object: this.resolvedObject || undefined }
|
|
4807
|
+
const counts = {}
|
|
4808
|
+
await Promise.all(pills.map(async ([key, source]) => {
|
|
4809
|
+
counts[key] = await fetchListTotal(source.register, source.schema, source.filter, ctx).catch(() => null)
|
|
4810
|
+
}))
|
|
4811
|
+
if (seq === this.pillCountSeq) {
|
|
4812
|
+
this.pillCounts = counts
|
|
4813
|
+
}
|
|
4814
|
+
},
|
|
4815
|
+
|
|
4757
4816
|
/**
|
|
4758
4817
|
* A `cn:page:refresh` reached the page — re-read the object so every
|
|
4759
4818
|
* object-bound widget re-renders from what the backend now holds.
|
|
@@ -4768,6 +4827,7 @@ export default {
|
|
|
4768
4827
|
* @return {void}
|
|
4769
4828
|
*/
|
|
4770
4829
|
onPageRefreshBus(payload) {
|
|
4830
|
+
this.refreshPillCounts()
|
|
4771
4831
|
if (!this.hasSchemaDrivenFetch) {
|
|
4772
4832
|
return
|
|
4773
4833
|
}
|