@conduction/nextcloud-vue 2.58.0 → 2.60.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/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +39 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +224 -9
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +58 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +13 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +532 -29
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +300 -142
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +169 -12
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +199 -16
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +20 -10
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +100 -3
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +55 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +55 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +998 -7
- package/dist/esm/utils/actionLink.js +5 -0
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +16 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/dateVariant.js +124 -0
- package/dist/esm/utils/dateVariant.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +260 -0
- package/dist/esm/utils/detailActionModel.js.map +1 -0
- package/dist/esm/utils/dueRule.js +84 -0
- package/dist/esm/utils/dueRule.js.map +1 -0
- package/dist/esm/utils/fetchAggregate.js +37 -1
- package/dist/esm/utils/fetchAggregate.js.map +1 -1
- package/dist/esm/utils/fetchFilterCounts.js +186 -0
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +5 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/linkNavigation.js +6 -3
- package/dist/esm/utils/linkNavigation.js.map +1 -1
- package/dist/esm/utils/readPath.js +30 -0
- package/dist/esm/utils/readPath.js.map +1 -0
- package/dist/esm/utils/rowAuxClick.js +30 -5
- package/dist/esm/utils/rowAuxClick.js.map +1 -1
- package/dist/esm/utils/safeHref.js +18 -3
- package/dist/esm/utils/safeHref.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +15855 -9353
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +998 -7
- package/l10n/en.json +55 -1
- package/l10n/nl.json +55 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +3 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
- package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -4
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
- package/src/components/CnAppNav/CnAppNav.md +26 -0
- package/src/components/CnAppNav/CnAppNav.vue +104 -1
- package/src/components/CnBannerWidget/CnBannerWidget.vue +365 -10
- package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
- package/src/components/CnBoardView/CnBoardView.vue +90 -5
- package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
- package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
- package/src/components/CnBrandStripe/index.js +4 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
- package/src/components/CnConversationThread/CnConversationThread.md +51 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
- package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
- package/src/components/CnConversationThread/index.js +4 -0
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +131 -17
- package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
- package/src/components/CnDetailPage/CnDetailPage.md +17 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +712 -49
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
- package/src/components/CnDocumentReviewList/index.js +4 -0
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
- package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
- package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +189 -14
- package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +210 -17
- package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
- package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
- package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
- package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
- package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
- package/src/components/CnNextStepCard/index.js +4 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
- package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
- package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
- package/src/components/CnObjectList/CnObjectList.vue +19 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
- package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
- package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
- package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +100 -3
- package/src/components/CnRowActions/CnRowActions.vue +7 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
- package/src/components/CnSegmentedControl/index.js +4 -0
- package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
- package/src/components/CnStackedBarWidget/index.js +14 -0
- package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
- package/src/components/CnStackedBarWidgetForm/index.js +4 -0
- package/src/components/CnTabs/CnTab.vue +35 -0
- package/src/components/CnTabs/CnTabs.vue +251 -13
- package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
- package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
- package/src/components/CnWeekStripWidget/index.js +14 -0
- package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
- package/src/components/CnWeekStripWidgetForm/index.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
- package/src/components/index.js +11 -0
- package/src/css/detail-page.css +47 -0
- package/src/css/table.css +26 -0
- package/src/index.js +9 -0
- package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
- package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
- package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
- package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
- package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
- package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
- package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
- package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
- package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
- package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
- package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
- package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
- package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
- package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
- package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
- package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
- package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
- package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
- package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
- package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
- package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
- package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
- package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
- package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
- package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
- package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
- package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
- package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
- package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
- package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
- package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
- package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
- package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
- package/src/schemas/app-manifest-v2.schema.json +389 -1
- package/src/schemas/app-manifest.schema.json +2 -1
- package/src/utils/actionLink.js +5 -0
- package/src/utils/actionsDispatcher.js +15 -0
- package/src/utils/dateVariant.js +120 -0
- package/src/utils/detailActionModel.js +256 -0
- package/src/utils/dueRule.js +94 -0
- package/src/utils/fetchAggregate.js +36 -0
- package/src/utils/fetchFilterCounts.js +182 -0
- package/src/utils/index.js +1 -0
- package/src/utils/libraryWidgetKeys.js +5 -0
- package/src/utils/linkNavigation.js +6 -3
- package/src/utils/readPath.js +27 -0
- package/src/utils/rowAuxClick.js +29 -4
- package/src/utils/safeHref.js +18 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { readPath } from './readPath.js';
|
|
2
|
+
import { evaluateVisibleWhenLocal } from './visibleWhen.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
6
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
7
|
+
*
|
|
8
|
+
* detailActionModel: the rules behind a detail page's four action levels and
|
|
9
|
+
* the case surfaces that read the record's stage.
|
|
10
|
+
*
|
|
11
|
+
* 1. the next step: one primary button, chosen by the record's stage,
|
|
12
|
+
* 2. quick actions: at most three always visible buttons,
|
|
13
|
+
* 3. the overflow menu: the rest, in named groups,
|
|
14
|
+
* 4. admin actions: the last group, for admins only.
|
|
15
|
+
*
|
|
16
|
+
* Pure functions, no Vue and no fetch, so CnDetailPage, the widgets and the
|
|
17
|
+
* tests all read one implementation.
|
|
18
|
+
*
|
|
19
|
+
* @module utils/detailActionModel
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/** The most quick actions a page shows. Later entries are dropped. */
|
|
24
|
+
const MAX_QUICK_ACTIONS = 3;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The record's stage, as the string a stage map is keyed on.
|
|
28
|
+
*
|
|
29
|
+
* @param {object|null} object The record.
|
|
30
|
+
* @param {string} [stageField] Dot-path to the stage. Defaults to `status`.
|
|
31
|
+
* @return {string} The stage, or '' when the record has none.
|
|
32
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
33
|
+
*/
|
|
34
|
+
function stageOf(object, stageField = 'status') {
|
|
35
|
+
if (!object || typeof object !== 'object') {
|
|
36
|
+
return ''
|
|
37
|
+
}
|
|
38
|
+
const value = readPath(object, stageField || 'status');
|
|
39
|
+
if (value === null || value === undefined || typeof value === 'object') {
|
|
40
|
+
return ''
|
|
41
|
+
}
|
|
42
|
+
return String(value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Look a stage up in a stage-keyed map. An exact key wins; otherwise the
|
|
47
|
+
* match ignores case, because a status is stored as `in_behandeling` in one
|
|
48
|
+
* register and `In_Behandeling` in the next.
|
|
49
|
+
*
|
|
50
|
+
* @param {object|null} map The stage-keyed map.
|
|
51
|
+
* @param {string} stage The stage.
|
|
52
|
+
* @return {unknown} The entry, or undefined.
|
|
53
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage
|
|
54
|
+
*/
|
|
55
|
+
function stageEntry(map, stage) {
|
|
56
|
+
if (!map || typeof map !== 'object' || Array.isArray(map) || stage === '') {
|
|
57
|
+
return undefined
|
|
58
|
+
}
|
|
59
|
+
if (Object.hasOwn(map, stage)) {
|
|
60
|
+
return map[stage]
|
|
61
|
+
}
|
|
62
|
+
const wanted = stage.toLowerCase();
|
|
63
|
+
const key = Object.keys(map).find((candidate) => candidate.toLowerCase() === wanted);
|
|
64
|
+
return key === undefined ? undefined : map[key]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Turn a pinned action entry into a descriptor the page can dispatch.
|
|
69
|
+
*
|
|
70
|
+
* An entry is the id of a declared header action, or an action object of its
|
|
71
|
+
* own. An object without an id gets `fallbackId`, since every dispatched
|
|
72
|
+
* action is addressed by id.
|
|
73
|
+
*
|
|
74
|
+
* @param {string|object|null} entry The entry.
|
|
75
|
+
* @param {string} fallbackId Id for an inline action that names none.
|
|
76
|
+
* @return {{id: string, inline: object|null}|null} The id, plus the inline definition when the entry carried one.
|
|
77
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-quick-actions
|
|
78
|
+
*/
|
|
79
|
+
function normalisePinnedAction(entry, fallbackId) {
|
|
80
|
+
if (typeof entry === 'string') {
|
|
81
|
+
return entry === '' ? null : { id: entry, inline: null }
|
|
82
|
+
}
|
|
83
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
if (typeof entry.label !== 'string' || entry.label === '') {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
const id = typeof entry.id === 'string' && entry.id !== '' ? entry.id : fallbackId;
|
|
90
|
+
return { id, inline: { ...entry, id } }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Split menu entries into the groups the overflow menu draws.
|
|
95
|
+
*
|
|
96
|
+
* Ungrouped entries come first, under no caption. Named groups follow in the
|
|
97
|
+
* order they first appear. Admin only entries are dropped for everyone else
|
|
98
|
+
* and form the last group for admins, whatever `group` they also name.
|
|
99
|
+
*
|
|
100
|
+
* `adminOnly` hides a control. It is never an authorization decision: the
|
|
101
|
+
* endpoint behind the action decides who may call it.
|
|
102
|
+
*
|
|
103
|
+
* @param {Array<object>} entries Menu-ready entries, each with an `id`.
|
|
104
|
+
* @param {object} actionsById The declared actions by id, carrying `group` / `adminOnly`.
|
|
105
|
+
* @param {{isAdmin?: boolean, adminLabel?: string}} [options] Who is looking, and what the admin group is called.
|
|
106
|
+
* @return {Array<{key: string, label: string, entries: Array<object>}>} The non-empty groups, in render order.
|
|
107
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-grouped-and-admin-only-menu-actions
|
|
108
|
+
*/
|
|
109
|
+
function groupMenuEntries(entries, actionsById = {}, options = {}) {
|
|
110
|
+
const isAdmin = options.isAdmin === true;
|
|
111
|
+
const plain = { key: '', label: '', entries: [] };
|
|
112
|
+
const named = [];
|
|
113
|
+
const admin = { key: 'cn-admin', label: options.adminLabel || '', entries: [] };
|
|
114
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
115
|
+
const declared = (entry && actionsById[entry.id]) || {};
|
|
116
|
+
if (declared.adminOnly === true) {
|
|
117
|
+
if (isAdmin) {
|
|
118
|
+
admin.entries.push(entry);
|
|
119
|
+
}
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
const label = typeof declared.group === 'string' ? declared.group.trim() : '';
|
|
123
|
+
if (label === '') {
|
|
124
|
+
plain.entries.push(entry);
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
let group = named.find((candidate) => candidate.label === label);
|
|
128
|
+
if (!group) {
|
|
129
|
+
group = { key: `cn-group-${named.length}`, label, entries: [] };
|
|
130
|
+
named.push(group);
|
|
131
|
+
}
|
|
132
|
+
group.entries.push(entry);
|
|
133
|
+
}
|
|
134
|
+
return [plain, ...named, admin].filter((group) => group.entries.length > 0)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Whether one checklist item is done for this record.
|
|
139
|
+
*
|
|
140
|
+
* `done` (a literal) wins, then `doneWhen` (a local visibleWhen condition),
|
|
141
|
+
* then `doneField` (truthy, and for a list: not empty).
|
|
142
|
+
*
|
|
143
|
+
* @param {object} item The checklist item.
|
|
144
|
+
* @param {object|null} object The record.
|
|
145
|
+
* @return {boolean} True when the item is done.
|
|
146
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
147
|
+
*/
|
|
148
|
+
function isChecklistItemDone(item, object) {
|
|
149
|
+
if (!item || typeof item !== 'object') {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
if (typeof item.done === 'boolean') {
|
|
153
|
+
return item.done
|
|
154
|
+
}
|
|
155
|
+
const data = object && typeof object === 'object' ? object : {};
|
|
156
|
+
if (item.doneWhen && typeof item.doneWhen === 'object') {
|
|
157
|
+
return evaluateVisibleWhenLocal(item.doneWhen, data)
|
|
158
|
+
}
|
|
159
|
+
if (typeof item.doneField === 'string' && item.doneField !== '') {
|
|
160
|
+
const value = readPath(data, item.doneField);
|
|
161
|
+
return Array.isArray(value) ? value.length > 0 : Boolean(value)
|
|
162
|
+
}
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resolve the "what now" card for the record's stage.
|
|
168
|
+
*
|
|
169
|
+
* @param {{stages?: object, title?: string}|null} config The `nextStep` config.
|
|
170
|
+
* @param {object|null} object The record.
|
|
171
|
+
* @param {string} [stageField] Dot-path to the stage.
|
|
172
|
+
* @return {{stage: string, title: string, items: Array<{label: string, done: boolean, hint: string}>, after: string}|null} The card, or null when this stage declares no checklist.
|
|
173
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
174
|
+
*/
|
|
175
|
+
function resolveNextStep(config, object, stageField = 'status') {
|
|
176
|
+
if (!config || typeof config !== 'object') {
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
const stage = stageOf(object, config.field || stageField);
|
|
180
|
+
const declared = stageEntry(config.stages, stage);
|
|
181
|
+
if (!declared || typeof declared !== 'object' || !Array.isArray(declared.checklist)) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
const items = declared.checklist
|
|
185
|
+
.filter((item) => item && typeof item === 'object' && typeof item.label === 'string' && item.label !== '')
|
|
186
|
+
.map((item) => ({
|
|
187
|
+
label: item.label,
|
|
188
|
+
done: isChecklistItemDone(item, object),
|
|
189
|
+
hint: typeof item.hint === 'string' ? item.hint : '',
|
|
190
|
+
}));
|
|
191
|
+
if (items.length === 0) {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
stage,
|
|
196
|
+
title: typeof declared.title === 'string' ? declared.title : (typeof config.title === 'string' ? config.title : ''),
|
|
197
|
+
items,
|
|
198
|
+
after: typeof declared.after === 'string' ? declared.after : '',
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve a header pill from the record.
|
|
204
|
+
*
|
|
205
|
+
* @param {{field?: string, colorMap?: object, labels?: object, variant?: string}|null} config The pill config.
|
|
206
|
+
* @param {object|null} object The record.
|
|
207
|
+
* @return {{label: string, colorKey: string, colorMap: object|null, variant: string}|null} What CnStatusBadge needs, or null when the field is empty.
|
|
208
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-header-pills
|
|
209
|
+
*/
|
|
210
|
+
function resolvePill(config, object) {
|
|
211
|
+
if (!config || typeof config !== 'object' || typeof config.field !== 'string' || config.field === '') {
|
|
212
|
+
return null
|
|
213
|
+
}
|
|
214
|
+
const raw = stageOf(object, config.field);
|
|
215
|
+
if (raw === '') {
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
const mapped = stageEntry(config.labels, raw);
|
|
219
|
+
return {
|
|
220
|
+
label: typeof mapped === 'string' && mapped !== '' ? mapped : raw,
|
|
221
|
+
colorKey: raw,
|
|
222
|
+
colorMap: config.colorMap && typeof config.colorMap === 'object' ? config.colorMap : null,
|
|
223
|
+
variant: typeof config.variant === 'string' && config.variant !== '' ? config.variant : 'default',
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Resolve a tab's count.
|
|
229
|
+
*
|
|
230
|
+
* `count` (a number) wins. Otherwise `countField` is read off the record: a
|
|
231
|
+
* list counts its items, a number is taken as is.
|
|
232
|
+
*
|
|
233
|
+
* @param {{count?: number, countField?: string}|null} tab The tab declaration.
|
|
234
|
+
* @param {object|null} object The record.
|
|
235
|
+
* @return {number|null} The count, or null when the tab declares none.
|
|
236
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
237
|
+
*/
|
|
238
|
+
function resolveTabCount(tab, object) {
|
|
239
|
+
if (!tab || typeof tab !== 'object') {
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
if (typeof tab.count === 'number' && Number.isFinite(tab.count)) {
|
|
243
|
+
return tab.count
|
|
244
|
+
}
|
|
245
|
+
if (typeof tab.countField !== 'string' || tab.countField === '') {
|
|
246
|
+
return null
|
|
247
|
+
}
|
|
248
|
+
const value = readPath(object && typeof object === 'object' ? object : {}, tab.countField);
|
|
249
|
+
if (Array.isArray(value)) {
|
|
250
|
+
return value.length
|
|
251
|
+
}
|
|
252
|
+
if (value === null || value === undefined || value === '') {
|
|
253
|
+
return 0
|
|
254
|
+
}
|
|
255
|
+
const number = Number(value);
|
|
256
|
+
return Number.isFinite(number) ? number : null
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export { MAX_QUICK_ACTIONS, groupMenuEntries, isChecklistItemDone, normalisePinnedAction, resolveNextStep, resolvePill, resolveTabCount, stageEntry, stageOf };
|
|
260
|
+
//# sourceMappingURL=detailActionModel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detailActionModel.js","sources":["../../../src/utils/detailActionModel.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * detailActionModel: the rules behind a detail page's four action levels and\n * the case surfaces that read the record's stage.\n *\n * 1. the next step: one primary button, chosen by the record's stage,\n * 2. quick actions: at most three always visible buttons,\n * 3. the overflow menu: the rest, in named groups,\n * 4. admin actions: the last group, for admins only.\n *\n * Pure functions, no Vue and no fetch, so CnDetailPage, the widgets and the\n * tests all read one implementation.\n *\n * @module utils/detailActionModel\n */\n\nimport { readPath } from './readPath.js'\nimport { evaluateVisibleWhenLocal } from './visibleWhen.js'\n\n/** The most quick actions a page shows. Later entries are dropped. */\nexport const MAX_QUICK_ACTIONS = 3\n\n/**\n * The record's stage, as the string a stage map is keyed on.\n *\n * @param {object|null} object The record.\n * @param {string} [stageField] Dot-path to the stage. Defaults to `status`.\n * @return {string} The stage, or '' when the record has none.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage\n */\nexport function stageOf(object, stageField = 'status') {\n\tif (!object || typeof object !== 'object') {\n\t\treturn ''\n\t}\n\tconst value = readPath(object, stageField || 'status')\n\tif (value === null || value === undefined || typeof value === 'object') {\n\t\treturn ''\n\t}\n\treturn String(value)\n}\n\n/**\n * Look a stage up in a stage-keyed map. An exact key wins; otherwise the\n * match ignores case, because a status is stored as `in_behandeling` in one\n * register and `In_Behandeling` in the next.\n *\n * @param {object|null} map The stage-keyed map.\n * @param {string} stage The stage.\n * @return {unknown} The entry, or undefined.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-primary-action-follows-the-stage\n */\nexport function stageEntry(map, stage) {\n\tif (!map || typeof map !== 'object' || Array.isArray(map) || stage === '') {\n\t\treturn undefined\n\t}\n\tif (Object.hasOwn(map, stage)) {\n\t\treturn map[stage]\n\t}\n\tconst wanted = stage.toLowerCase()\n\tconst key = Object.keys(map).find((candidate) => candidate.toLowerCase() === wanted)\n\treturn key === undefined ? undefined : map[key]\n}\n\n/**\n * Turn a pinned action entry into a descriptor the page can dispatch.\n *\n * An entry is the id of a declared header action, or an action object of its\n * own. An object without an id gets `fallbackId`, since every dispatched\n * action is addressed by id.\n *\n * @param {string|object|null} entry The entry.\n * @param {string} fallbackId Id for an inline action that names none.\n * @return {{id: string, inline: object|null}|null} The id, plus the inline definition when the entry carried one.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-quick-actions\n */\nexport function normalisePinnedAction(entry, fallbackId) {\n\tif (typeof entry === 'string') {\n\t\treturn entry === '' ? null : { id: entry, inline: null }\n\t}\n\tif (!entry || typeof entry !== 'object' || Array.isArray(entry)) {\n\t\treturn null\n\t}\n\tif (typeof entry.label !== 'string' || entry.label === '') {\n\t\treturn null\n\t}\n\tconst id = typeof entry.id === 'string' && entry.id !== '' ? entry.id : fallbackId\n\treturn { id, inline: { ...entry, id } }\n}\n\n/**\n * Split menu entries into the groups the overflow menu draws.\n *\n * Ungrouped entries come first, under no caption. Named groups follow in the\n * order they first appear. Admin only entries are dropped for everyone else\n * and form the last group for admins, whatever `group` they also name.\n *\n * `adminOnly` hides a control. It is never an authorization decision: the\n * endpoint behind the action decides who may call it.\n *\n * @param {Array<object>} entries Menu-ready entries, each with an `id`.\n * @param {object} actionsById The declared actions by id, carrying `group` / `adminOnly`.\n * @param {{isAdmin?: boolean, adminLabel?: string}} [options] Who is looking, and what the admin group is called.\n * @return {Array<{key: string, label: string, entries: Array<object>}>} The non-empty groups, in render order.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-grouped-and-admin-only-menu-actions\n */\nexport function groupMenuEntries(entries, actionsById = {}, options = {}) {\n\tconst isAdmin = options.isAdmin === true\n\tconst plain = { key: '', label: '', entries: [] }\n\tconst named = []\n\tconst admin = { key: 'cn-admin', label: options.adminLabel || '', entries: [] }\n\tfor (const entry of Array.isArray(entries) ? entries : []) {\n\t\tconst declared = (entry && actionsById[entry.id]) || {}\n\t\tif (declared.adminOnly === true) {\n\t\t\tif (isAdmin) {\n\t\t\t\tadmin.entries.push(entry)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tconst label = typeof declared.group === 'string' ? declared.group.trim() : ''\n\t\tif (label === '') {\n\t\t\tplain.entries.push(entry)\n\t\t\tcontinue\n\t\t}\n\t\tlet group = named.find((candidate) => candidate.label === label)\n\t\tif (!group) {\n\t\t\tgroup = { key: `cn-group-${named.length}`, label, entries: [] }\n\t\t\tnamed.push(group)\n\t\t}\n\t\tgroup.entries.push(entry)\n\t}\n\treturn [plain, ...named, admin].filter((group) => group.entries.length > 0)\n}\n\n/**\n * Whether one checklist item is done for this record.\n *\n * `done` (a literal) wins, then `doneWhen` (a local visibleWhen condition),\n * then `doneField` (truthy, and for a list: not empty).\n *\n * @param {object} item The checklist item.\n * @param {object|null} object The record.\n * @return {boolean} True when the item is done.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n */\nexport function isChecklistItemDone(item, object) {\n\tif (!item || typeof item !== 'object') {\n\t\treturn false\n\t}\n\tif (typeof item.done === 'boolean') {\n\t\treturn item.done\n\t}\n\tconst data = object && typeof object === 'object' ? object : {}\n\tif (item.doneWhen && typeof item.doneWhen === 'object') {\n\t\treturn evaluateVisibleWhenLocal(item.doneWhen, data)\n\t}\n\tif (typeof item.doneField === 'string' && item.doneField !== '') {\n\t\tconst value = readPath(data, item.doneField)\n\t\treturn Array.isArray(value) ? value.length > 0 : Boolean(value)\n\t}\n\treturn false\n}\n\n/**\n * Resolve the \"what now\" card for the record's stage.\n *\n * @param {{stages?: object, title?: string}|null} config The `nextStep` config.\n * @param {object|null} object The record.\n * @param {string} [stageField] Dot-path to the stage.\n * @return {{stage: string, title: string, items: Array<{label: string, done: boolean, hint: string}>, after: string}|null} The card, or null when this stage declares no checklist.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n */\nexport function resolveNextStep(config, object, stageField = 'status') {\n\tif (!config || typeof config !== 'object') {\n\t\treturn null\n\t}\n\tconst stage = stageOf(object, config.field || stageField)\n\tconst declared = stageEntry(config.stages, stage)\n\tif (!declared || typeof declared !== 'object' || !Array.isArray(declared.checklist)) {\n\t\treturn null\n\t}\n\tconst items = declared.checklist\n\t\t.filter((item) => item && typeof item === 'object' && typeof item.label === 'string' && item.label !== '')\n\t\t.map((item) => ({\n\t\t\tlabel: item.label,\n\t\t\tdone: isChecklistItemDone(item, object),\n\t\t\thint: typeof item.hint === 'string' ? item.hint : '',\n\t\t}))\n\tif (items.length === 0) {\n\t\treturn null\n\t}\n\treturn {\n\t\tstage,\n\t\ttitle: typeof declared.title === 'string' ? declared.title : (typeof config.title === 'string' ? config.title : ''),\n\t\titems,\n\t\tafter: typeof declared.after === 'string' ? declared.after : '',\n\t}\n}\n\n/**\n * Resolve a header pill from the record.\n *\n * @param {{field?: string, colorMap?: object, labels?: object, variant?: string}|null} config The pill config.\n * @param {object|null} object The record.\n * @return {{label: string, colorKey: string, colorMap: object|null, variant: string}|null} What CnStatusBadge needs, or null when the field is empty.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-header-pills\n */\nexport function resolvePill(config, object) {\n\tif (!config || typeof config !== 'object' || typeof config.field !== 'string' || config.field === '') {\n\t\treturn null\n\t}\n\tconst raw = stageOf(object, config.field)\n\tif (raw === '') {\n\t\treturn null\n\t}\n\tconst mapped = stageEntry(config.labels, raw)\n\treturn {\n\t\tlabel: typeof mapped === 'string' && mapped !== '' ? mapped : raw,\n\t\tcolorKey: raw,\n\t\tcolorMap: config.colorMap && typeof config.colorMap === 'object' ? config.colorMap : null,\n\t\tvariant: typeof config.variant === 'string' && config.variant !== '' ? config.variant : 'default',\n\t}\n}\n\n/**\n * Resolve a tab's count.\n *\n * `count` (a number) wins. Otherwise `countField` is read off the record: a\n * list counts its items, a number is taken as is.\n *\n * @param {{count?: number, countField?: string}|null} tab The tab declaration.\n * @param {object|null} object The record.\n * @return {number|null} The count, or null when the tab declares none.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n */\nexport function resolveTabCount(tab, object) {\n\tif (!tab || typeof tab !== 'object') {\n\t\treturn null\n\t}\n\tif (typeof tab.count === 'number' && Number.isFinite(tab.count)) {\n\t\treturn tab.count\n\t}\n\tif (typeof tab.countField !== 'string' || tab.countField === '') {\n\t\treturn null\n\t}\n\tconst value = readPath(object && typeof object === 'object' ? object : {}, tab.countField)\n\tif (Array.isArray(value)) {\n\t\treturn value.length\n\t}\n\tif (value === null || value === undefined || value === '') {\n\t\treturn 0\n\t}\n\tconst number = Number(value)\n\treturn Number.isFinite(number) ? number : null\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { daysUntil, resolveDateVariant } from './dateVariant.js';
|
|
2
|
+
import { readPath } from './readPath.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
6
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
7
|
+
*
|
|
8
|
+
* dueRule: decides whether a dated card is overdue, due soon or fine.
|
|
9
|
+
*
|
|
10
|
+
* A board's rule is `{ field, soonDays?, variantWhen? }`. `field` is a
|
|
11
|
+
* dot-path to the date on the row. `soonDays` (default 3) is how many days
|
|
12
|
+
* ahead still counts as "soon"; a date before today is overdue.
|
|
13
|
+
*
|
|
14
|
+
* The deciding is done by `utils/dateVariant.js`, the same rules a table's
|
|
15
|
+
* date cell uses, so a list and its board agree on what late means.
|
|
16
|
+
* `{ soonDays: 3 }` is shorthand for
|
|
17
|
+
* `variantWhen: [{ op: 'lt', value: 0, variant: 'error' }, { op: 'lte', value: 3, variant: 'warning' }]`,
|
|
18
|
+
* and a rule that carries its own `variantWhen` is used as written:
|
|
19
|
+
* `error` reads as overdue, `warning` as due soon.
|
|
20
|
+
*
|
|
21
|
+
* @module utils/dueRule
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/** How many days ahead counts as "soon" when the rule does not say. */
|
|
26
|
+
const DEFAULT_SOON_DAYS = 3;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The `variantWhen` rules a due rule stands for.
|
|
30
|
+
*
|
|
31
|
+
* @param {{soonDays?: number, variantWhen?: Array<object>}|null} [rule] The due rule.
|
|
32
|
+
* @return {Array<{op: string, value: number, variant: string}>} The rules `resolveDateVariant` takes.
|
|
33
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
|
|
34
|
+
*/
|
|
35
|
+
function dueRuleToVariantWhen(rule = {}) {
|
|
36
|
+
if (Array.isArray(rule?.variantWhen) && rule.variantWhen.length > 0) {
|
|
37
|
+
return rule.variantWhen
|
|
38
|
+
}
|
|
39
|
+
const given = rule?.soonDays;
|
|
40
|
+
const soonDays = given !== null && given !== undefined && Number.isFinite(Number(given)) ? Number(given) : DEFAULT_SOON_DAYS;
|
|
41
|
+
return [
|
|
42
|
+
{ op: 'lt', value: 0, variant: 'error' },
|
|
43
|
+
{ op: 'lte', value: soonDays, variant: 'warning' },
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The due state of one date under a rule.
|
|
49
|
+
*
|
|
50
|
+
* @param {unknown} value The date.
|
|
51
|
+
* @param {{soonDays?: number, variantWhen?: Array<object>}|null} [rule] The rule.
|
|
52
|
+
* @param {Date} [now] The moment to measure from.
|
|
53
|
+
* @return {'overdue'|'soon'|'ok'|null} The state, or null when there is no readable date.
|
|
54
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
|
|
55
|
+
*/
|
|
56
|
+
function dueStateOf(value, rule = {}, now = new Date()) {
|
|
57
|
+
if (daysUntil(value, now) === null) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
const variant = resolveDateVariant(value, dueRuleToVariantWhen(rule), now);
|
|
61
|
+
if (variant === 'error') {
|
|
62
|
+
return 'overdue'
|
|
63
|
+
}
|
|
64
|
+
return variant === 'warning' ? 'soon' : 'ok'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The due state of a row under a `{ field, soonDays }` rule.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} row The row or card.
|
|
71
|
+
* @param {{field?: string, soonDays?: number, variantWhen?: Array<object>}|null} rule The rule. Null or field-less means no marking.
|
|
72
|
+
* @param {Date} [now] The moment to measure from.
|
|
73
|
+
* @return {'overdue'|'soon'|'ok'|null} The state, or null when the rule does not apply.
|
|
74
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
|
|
75
|
+
*/
|
|
76
|
+
function dueStateForRow(row, rule, now = new Date()) {
|
|
77
|
+
if (!rule || typeof rule.field !== 'string' || rule.field === '' || !row) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
return dueStateOf(readPath(row, rule.field), rule, now)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { DEFAULT_SOON_DAYS, daysUntil, dueRuleToVariantWhen, dueStateForRow, dueStateOf };
|
|
84
|
+
//# sourceMappingURL=dueRule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dueRule.js","sources":["../../../src/utils/dueRule.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * dueRule: decides whether a dated card is overdue, due soon or fine.\n *\n * A board's rule is `{ field, soonDays?, variantWhen? }`. `field` is a\n * dot-path to the date on the row. `soonDays` (default 3) is how many days\n * ahead still counts as \"soon\"; a date before today is overdue.\n *\n * The deciding is done by `utils/dateVariant.js`, the same rules a table's\n * date cell uses, so a list and its board agree on what late means.\n * `{ soonDays: 3 }` is shorthand for\n * `variantWhen: [{ op: 'lt', value: 0, variant: 'error' }, { op: 'lte', value: 3, variant: 'warning' }]`,\n * and a rule that carries its own `variantWhen` is used as written:\n * `error` reads as overdue, `warning` as due soon.\n *\n * @module utils/dueRule\n */\n\nimport { daysUntil, parseDateValue, resolveDateVariant } from './dateVariant.js'\nimport { readPath } from './readPath.js'\n\nexport { daysUntil }\n\n/** How many days ahead counts as \"soon\" when the rule does not say. */\nexport const DEFAULT_SOON_DAYS = 3\n\n/**\n * Local midnight of a date value. A bare `YYYY-MM-DD` is a LOCAL day.\n *\n * @param {unknown} value A Date, a timestamp or a date string.\n * @return {Date|null} Local midnight of that day, or null when unreadable.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards\n */\nexport function toLocalDay(value) {\n\tconst date = parseDateValue(value)\n\treturn date === null ? null : new Date(date.getFullYear(), date.getMonth(), date.getDate())\n}\n\n/**\n * The `variantWhen` rules a due rule stands for.\n *\n * @param {{soonDays?: number, variantWhen?: Array<object>}|null} [rule] The due rule.\n * @return {Array<{op: string, value: number, variant: string}>} The rules `resolveDateVariant` takes.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards\n */\nexport function dueRuleToVariantWhen(rule = {}) {\n\tif (Array.isArray(rule?.variantWhen) && rule.variantWhen.length > 0) {\n\t\treturn rule.variantWhen\n\t}\n\tconst given = rule?.soonDays\n\tconst soonDays = given !== null && given !== undefined && Number.isFinite(Number(given)) ? Number(given) : DEFAULT_SOON_DAYS\n\treturn [\n\t\t{ op: 'lt', value: 0, variant: 'error' },\n\t\t{ op: 'lte', value: soonDays, variant: 'warning' },\n\t]\n}\n\n/**\n * The due state of one date under a rule.\n *\n * @param {unknown} value The date.\n * @param {{soonDays?: number, variantWhen?: Array<object>}|null} [rule] The rule.\n * @param {Date} [now] The moment to measure from.\n * @return {'overdue'|'soon'|'ok'|null} The state, or null when there is no readable date.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards\n */\nexport function dueStateOf(value, rule = {}, now = new Date()) {\n\tif (daysUntil(value, now) === null) {\n\t\treturn null\n\t}\n\tconst variant = resolveDateVariant(value, dueRuleToVariantWhen(rule), now)\n\tif (variant === 'error') {\n\t\treturn 'overdue'\n\t}\n\treturn variant === 'warning' ? 'soon' : 'ok'\n}\n\n/**\n * The due state of a row under a `{ field, soonDays }` rule.\n *\n * @param {object} row The row or card.\n * @param {{field?: string, soonDays?: number, variantWhen?: Array<object>}|null} rule The rule. Null or field-less means no marking.\n * @param {Date} [now] The moment to measure from.\n * @return {'overdue'|'soon'|'ok'|null} The state, or null when the rule does not apply.\n * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards\n */\nexport function dueStateForRow(row, rule, now = new Date()) {\n\tif (!rule || typeof rule.field !== 'string' || rule.field === '' || !row) {\n\t\treturn null\n\t}\n\treturn dueStateOf(readPath(row, rule.field), rule, now)\n}\n"],"names":[],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACY,MAAC,iBAAiB,GAAG,EAAC;AAalC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,oBAAoB,CAAC,IAAI,GAAG,EAAE,EAAE;AAChD,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE;AACtE,EAAE,OAAO,IAAI,CAAC,WAAW;AACzB,EAAE;AACF,CAAC,MAAM,KAAK,GAAG,IAAI,EAAE,SAAQ;AAC7B,CAAC,MAAM,QAAQ,GAAG,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,kBAAiB;AAC7H,CAAC,OAAO;AACR,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE;AAC1C,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE;AACpD,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,UAAU,CAAC,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE;AAC/D,CAAC,IAAI,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE;AACrC,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,EAAE,oBAAoB,CAAC,IAAI,CAAC,EAAE,GAAG,EAAC;AAC3E,CAAC,IAAI,OAAO,KAAK,OAAO,EAAE;AAC1B,EAAE,OAAO,SAAS;AAClB,EAAE;AACF,CAAC,OAAO,OAAO,KAAK,SAAS,GAAG,MAAM,GAAG,IAAI;AAC7C,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE;AAC5D,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE;AAC3E,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,OAAO,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC;AACxD;;;;"}
|
|
@@ -79,5 +79,41 @@ async function fetchAggregateValue(source, ctx) {
|
|
|
79
79
|
return (res && res.data && res.data.value !== undefined) ? res.data.value : null
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Fetch the object count per distinct value of one field, in ONE request,
|
|
84
|
+
* from OpenRegister's `/grouped` aggregation endpoint.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} source The grouped descriptor.
|
|
87
|
+
* @param {string} source.register The register slug.
|
|
88
|
+
* @param {string} source.schema The schema slug.
|
|
89
|
+
* @param {string} source.groupBy The field to group on.
|
|
90
|
+
* @param {object} [source.filter] The filter map.
|
|
91
|
+
* @param {{objectId?: (string|number), object?: object, workspace?: object}} [ctx] Optional
|
|
92
|
+
* token-resolution context for the filter.
|
|
93
|
+
* @return {Promise<Array<{key: string, count: number}>>} One entry per group; [] when the source is incomplete.
|
|
94
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
|
|
95
|
+
*/
|
|
96
|
+
async function fetchGroupedCounts(source, ctx) {
|
|
97
|
+
const s = source || {};
|
|
98
|
+
if (!s.register || !s.schema || !s.groupBy) {
|
|
99
|
+
return []
|
|
100
|
+
}
|
|
101
|
+
const [{ default: axios }, { generateUrl }] = await Promise.all([
|
|
102
|
+
import('@nextcloud/axios'),
|
|
103
|
+
import('@nextcloud/router'),
|
|
104
|
+
]);
|
|
105
|
+
const url = generateUrl(
|
|
106
|
+
'/apps/openregister/api/objects/aggregations/{register}/{schema}/grouped',
|
|
107
|
+
{ register: s.register, schema: s.schema },
|
|
108
|
+
);
|
|
109
|
+
const params = { groupBy: s.groupBy, metric: 'count' };
|
|
110
|
+
flattenAggFilter(params, s.filter || {}, ctx);
|
|
111
|
+
const res = await axios.get(url, { params });
|
|
112
|
+
const groups = (res && res.data && Array.isArray(res.data.groups)) ? res.data.groups : [];
|
|
113
|
+
return groups
|
|
114
|
+
.filter((g) => g && g.key !== null && g.key !== undefined && g.key !== '')
|
|
115
|
+
.map((g) => ({ key: String(g.key), count: Number(g.value) || 0 }))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { fetchAggregateValue, fetchGroupedCounts, flattenAggFilter };
|
|
83
119
|
//# sourceMappingURL=fetchAggregate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchAggregate.js","sources":["../../../src/utils/fetchAggregate.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Shared helper for the abstract data widgets (CnStatWidget, CnDeltaWidget,\n * CnGaugeWidget, …) — fetches ONE scalar from OpenRegister's ad-hoc aggregation\n * `/value` endpoint given a `{ register, schema, metric, field, filter }`\n * descriptor. Filter values run through `resolveFilterTokens` first, so relative\n * tokens (`@me`, `@monthStart`, `@currentFiscalYear`, …) resolve at fetch time.\n *\n * @module utils/fetchAggregate\n */\n\nimport { resolveFilterTokens } from './resolveFilterTokens.js'\n\n/**\n * Flatten a filter map into operator-aware `filter[key]` / `filter[key][op]`\n * query params (the OpenRegister aggregation filter vocabulary). Resolves\n * relative tokens before writing.\n *\n * @param {object} target The params object to write into.\n * @param {object} filter The filter map.\n * @param {{objectId?: (string|number), object?: object, workspace?: object}} [ctx] Optional\n * token-resolution context forwarded to {@link resolveFilterTokens} so a\n * detail-page aggregate can scope its filter to the current object via\n * `@objectId` / `@object.<field>`.\n * @return {void}\n */\nexport function flattenAggFilter(target, filter, ctx) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn\n\t}\n\tfilter = resolveFilterTokens(filter, ctx)\n\tfor (const [k, v] of Object.entries(filter)) {\n\t\tif (v && typeof v === 'object') {\n\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\ttarget[`filter[${k}][${op}]`] = ov\n\t\t\t}\n\t\t} else if (v !== '' && v !== null && v !== undefined) {\n\t\t\ttarget[`filter[${k}]`] = v\n\t\t}\n\t}\n}\n\n/**\n * Fetch one aggregated scalar from OpenRegister.\n *\n * @param {object} source The aggregate descriptor.\n * @param {string} source.register The register slug.\n * @param {string} source.schema The schema slug.\n * @param {string} [source.metric] count | sum | avg | min | max (default count).\n * @param {string} [source.field] The numeric field (required for non-count metrics).\n * @param {object} [source.filter] The filter map.\n * @param {{objectId?: (string|number), object?: object, workspace?: object}} [ctx] Optional\n * token-resolution context (a detail page's object context) so the filter can\n * scope to the current object via `@objectId` / `@object.<field>`.\n * @return {Promise<number|null>} The aggregated value, or null when unavailable.\n */\nexport async function fetchAggregateValue(source, ctx) {\n\tconst s = source || {}\n\tif (!s.register || !s.schema) {\n\t\treturn null\n\t}\n\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\timport('@nextcloud/axios'),\n\t\timport('@nextcloud/router'),\n\t])\n\tconst url = generateUrl(\n\t\t'/apps/openregister/api/objects/aggregations/{register}/{schema}/value',\n\t\t{ register: s.register, schema: s.schema },\n\t)\n\tconst params = { metric: s.metric || 'count' }\n\tif (s.field) {\n\t\tparams.field = s.field\n\t}\n\tflattenAggFilter(params, s.filter || {}, ctx)\n\tconst res = await axios.get(url, { params })\n\treturn (res && res.data && res.data.value !== undefined) ? res.data.value : null\n}\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE;AACtD,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AAC5C,EAAE,MAAM;AACR,EAAE;AACF,CAAC,MAAM,GAAG,mBAAmB,CAAC,MAAM,EAAE,GAAG,EAAC;AAC1C,CAAC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;AAC9C,EAAE,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;AAClC,GAAG,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;AAC7C,IAAI,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAE;AACtC,IAAI;AACJ,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,EAAE;AACxD,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAC;AAC7B,GAAG;AACH,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAe,mBAAmB,CAAC,MAAM,EAAE,GAAG,EAAE;AACvD,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,GAAE;AACvB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE;AAC/B,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;AACjE,EAAE,OAAO,kBAAkB,CAAC;AAC5B,EAAE,OAAO,mBAAmB,CAAC;AAC7B,EAAE,EAAC;AACH,CAAC,MAAM,GAAG,GAAG,WAAW;AACxB,EAAE,uEAAuE;AACzE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;AAC5C,GAAE;AACF,CAAC,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,OAAO,GAAE;AAC/C,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE;AACd,EAAE,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,MAAK;AACxB,EAAE;AACF,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,GAAG,EAAC;AAC9C,CAAC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,EAAC;AAC7C,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI;AACjF;;;;"}
|
|
1
|
+
{"version":3,"file":"fetchAggregate.js","sources":["../../../src/utils/fetchAggregate.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Shared helper for the abstract data widgets (CnStatWidget, CnDeltaWidget,\n * CnGaugeWidget, …) — fetches ONE scalar from OpenRegister's ad-hoc aggregation\n * `/value` endpoint given a `{ register, schema, metric, field, filter }`\n * descriptor. Filter values run through `resolveFilterTokens` first, so relative\n * tokens (`@me`, `@monthStart`, `@currentFiscalYear`, …) resolve at fetch time.\n *\n * @module utils/fetchAggregate\n */\n\nimport { resolveFilterTokens } from './resolveFilterTokens.js'\n\n/**\n * Flatten a filter map into operator-aware `filter[key]` / `filter[key][op]`\n * query params (the OpenRegister aggregation filter vocabulary). Resolves\n * relative tokens before writing.\n *\n * @param {object} target The params object to write into.\n * @param {object} filter The filter map.\n * @param {{objectId?: (string|number), object?: object, workspace?: object}} [ctx] Optional\n * token-resolution context forwarded to {@link resolveFilterTokens} so a\n * detail-page aggregate can scope its filter to the current object via\n * `@objectId` / `@object.<field>`.\n * @return {void}\n */\nexport function flattenAggFilter(target, filter, ctx) {\n\tif (!filter || typeof filter !== 'object') {\n\t\treturn\n\t}\n\tfilter = resolveFilterTokens(filter, ctx)\n\tfor (const [k, v] of Object.entries(filter)) {\n\t\tif (v && typeof v === 'object') {\n\t\t\tfor (const [op, ov] of Object.entries(v)) {\n\t\t\t\ttarget[`filter[${k}][${op}]`] = ov\n\t\t\t}\n\t\t} else if (v !== '' && v !== null && v !== undefined) {\n\t\t\ttarget[`filter[${k}]`] = v\n\t\t}\n\t}\n}\n\n/**\n * Fetch one aggregated scalar from OpenRegister.\n *\n * @param {object} source The aggregate descriptor.\n * @param {string} source.register The register slug.\n * @param {string} source.schema The schema slug.\n * @param {string} [source.metric] count | sum | avg | min | max (default count).\n * @param {string} [source.field] The numeric field (required for non-count metrics).\n * @param {object} [source.filter] The filter map.\n * @param {{objectId?: (string|number), object?: object, workspace?: object}} [ctx] Optional\n * token-resolution context (a detail page's object context) so the filter can\n * scope to the current object via `@objectId` / `@object.<field>`.\n * @return {Promise<number|null>} The aggregated value, or null when unavailable.\n */\nexport async function fetchAggregateValue(source, ctx) {\n\tconst s = source || {}\n\tif (!s.register || !s.schema) {\n\t\treturn null\n\t}\n\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\timport('@nextcloud/axios'),\n\t\timport('@nextcloud/router'),\n\t])\n\tconst url = generateUrl(\n\t\t'/apps/openregister/api/objects/aggregations/{register}/{schema}/value',\n\t\t{ register: s.register, schema: s.schema },\n\t)\n\tconst params = { metric: s.metric || 'count' }\n\tif (s.field) {\n\t\tparams.field = s.field\n\t}\n\tflattenAggFilter(params, s.filter || {}, ctx)\n\tconst res = await axios.get(url, { params })\n\treturn (res && res.data && res.data.value !== undefined) ? res.data.value : null\n}\n\n/**\n * Fetch the object count per distinct value of one field, in ONE request,\n * from OpenRegister's `/grouped` aggregation endpoint.\n *\n * @param {object} source The grouped descriptor.\n * @param {string} source.register The register slug.\n * @param {string} source.schema The schema slug.\n * @param {string} source.groupBy The field to group on.\n * @param {object} [source.filter] The filter map.\n * @param {{objectId?: (string|number), object?: object, workspace?: object}} [ctx] Optional\n * token-resolution context for the filter.\n * @return {Promise<Array<{key: string, count: number}>>} One entry per group; [] when the source is incomplete.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n */\nexport async function fetchGroupedCounts(source, ctx) {\n\tconst s = source || {}\n\tif (!s.register || !s.schema || !s.groupBy) {\n\t\treturn []\n\t}\n\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\timport('@nextcloud/axios'),\n\t\timport('@nextcloud/router'),\n\t])\n\tconst url = generateUrl(\n\t\t'/apps/openregister/api/objects/aggregations/{register}/{schema}/grouped',\n\t\t{ register: s.register, schema: s.schema },\n\t)\n\tconst params = { groupBy: s.groupBy, metric: 'count' }\n\tflattenAggFilter(params, s.filter || {}, ctx)\n\tconst res = await axios.get(url, { params })\n\tconst groups = (res && res.data && Array.isArray(res.data.groups)) ? res.data.groups : []\n\treturn groups\n\t\t.filter((g) => g && g.key !== null && g.key !== undefined && g.key !== '')\n\t\t.map((g) => ({ key: String(g.key), count: Number(g.value) || 0 }))\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { fetchGroupedCounts, fetchAggregateValue } from './fetchAggregate.js';
|
|
2
|
+
import { buildQueryString, prefixUrl, buildHeaders } from './headers.js';
|
|
3
|
+
import { resolveFilterTokens } from './resolveFilterTokens.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
7
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
8
|
+
*
|
|
9
|
+
* fetchFilterCounts: how many records each of a set of filters matches, in as
|
|
10
|
+
* few requests as the API allows.
|
|
11
|
+
*
|
|
12
|
+
* Quick filters and saved views usually differ in ONE field (`status: open`,
|
|
13
|
+
* `status: closed`, ...). Those are answered by one `/grouped` request on that
|
|
14
|
+
* field. A filter that narrows on something else (two fields, an operator, a
|
|
15
|
+
* list of values) cannot be read off a grouped result, so each of those costs
|
|
16
|
+
* one `/value` count. A filter that holds a LIST of values is counted through
|
|
17
|
+
* the list endpoint, because the aggregation endpoints cannot express it.
|
|
18
|
+
* Nothing is requested for an entry that did not ask.
|
|
19
|
+
*
|
|
20
|
+
* @module utils/fetchFilterCounts
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether a filter holds a list of values (`{ status: ['open', 'hold'] }`).
|
|
26
|
+
*
|
|
27
|
+
* The aggregation endpoints have no spelling for "one of these values", so a
|
|
28
|
+
* filter like this is never sent to them. It is counted through the list
|
|
29
|
+
* endpoint instead, which is where the page's own request sends it.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} filter A filter map.
|
|
32
|
+
* @return {boolean} True when any value is an array.
|
|
33
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
34
|
+
*/
|
|
35
|
+
function hasListValue(filter) {
|
|
36
|
+
if (!filter || typeof filter !== 'object') {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
return Object.values(filter).some((value) => Array.isArray(value))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Count the records a filter matches by asking the LIST endpoint for one
|
|
44
|
+
* record and reading its `total`. The filter is sent the way the list request
|
|
45
|
+
* sends it: a list of values as repeated `field[]=`, an operator as
|
|
46
|
+
* `field[op]=`.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} register The register slug.
|
|
49
|
+
* @param {string} schema The schema slug.
|
|
50
|
+
* @param {object} filter The filter map.
|
|
51
|
+
* @param {object} [ctx] Token-resolution context for the filter.
|
|
52
|
+
* @return {Promise<number|null>} The total, or null when the response carries none.
|
|
53
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
54
|
+
*/
|
|
55
|
+
async function fetchListTotal(register, schema, filter, ctx) {
|
|
56
|
+
const resolved = resolveFilterTokens(filter || {}, ctx || {});
|
|
57
|
+
const params = {};
|
|
58
|
+
for (const [key, value] of Object.entries(resolved || {})) {
|
|
59
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
60
|
+
for (const [op, operand] of Object.entries(value)) {
|
|
61
|
+
params[`${key}[${op}]`] = operand;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
params[key] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const qs = buildQueryString({ ...params, _limit: 1 });
|
|
68
|
+
const url = prefixUrl(`/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}${qs}`);
|
|
69
|
+
const response = await fetch(url, { headers: buildHeaders() });
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`list returned ${response.status}`)
|
|
72
|
+
}
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
return typeof data.total === 'number' ? data.total : null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The single field a filter narrows on with a plain equality, or null when the
|
|
79
|
+
* filter is anything else.
|
|
80
|
+
*
|
|
81
|
+
* @param {object} filter A filter map.
|
|
82
|
+
* @return {{field: string, value: string}|null} The field and the value it must equal.
|
|
83
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
84
|
+
*/
|
|
85
|
+
function singleEqualityOf(filter) {
|
|
86
|
+
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
const keys = Object.keys(filter);
|
|
90
|
+
if (keys.length !== 1) {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
const value = filter[keys[0]];
|
|
94
|
+
if (value === null || value === undefined || value === '' || typeof value === 'object') {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
// A token (`@me`, `@today`) resolves to a value the grouped keys are
|
|
98
|
+
// compared against only after resolution, which the count request does.
|
|
99
|
+
if (typeof value === 'string' && value.charAt(0) === '@') {
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
return { field: keys[0], value: String(value) }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Count the records each entry's filter matches.
|
|
107
|
+
*
|
|
108
|
+
* @param {object} options The request.
|
|
109
|
+
* @param {string} options.register The register slug.
|
|
110
|
+
* @param {string} options.schema The schema slug.
|
|
111
|
+
* @param {Array<{key: (string|number), filter: object}>} options.entries The filters to count, each under its own key.
|
|
112
|
+
* @param {object} [options.baseFilter] A filter every count is narrowed by (the page's fixed filter).
|
|
113
|
+
* @param {object} [options.ctx] Token-resolution context for the filters.
|
|
114
|
+
* @return {Promise<{[key: string]: number}>} The count per entry key. An entry whose count failed is left out.
|
|
115
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views
|
|
116
|
+
*/
|
|
117
|
+
async function fetchFilterCounts({ register, schema, entries, baseFilter, ctx } = {}) {
|
|
118
|
+
const counts = {};
|
|
119
|
+
if (!register || !schema || !Array.isArray(entries) || entries.length === 0) {
|
|
120
|
+
return counts
|
|
121
|
+
}
|
|
122
|
+
const base = (baseFilter && typeof baseFilter === 'object') ? baseFilter : {};
|
|
123
|
+
|
|
124
|
+
// Entries that share one equality field go into one grouped request, but
|
|
125
|
+
// only when at least two do: a lone entry costs one request either way,
|
|
126
|
+
// and `/value` returns exactly its number.
|
|
127
|
+
// A list of values anywhere in a request rules the aggregation endpoints
|
|
128
|
+
// out for it. A base filter with one rules them out for every entry.
|
|
129
|
+
const baseHasList = hasListValue(base);
|
|
130
|
+
const byField = {};
|
|
131
|
+
const single = [];
|
|
132
|
+
const viaList = [];
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (baseHasList || hasListValue(entry.filter)) {
|
|
135
|
+
viaList.push(entry);
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
const eq = singleEqualityOf(entry.filter);
|
|
139
|
+
if (eq !== null && !(eq.field in base)) {
|
|
140
|
+
(byField[eq.field] = byField[eq.field] || []).push({ entry, value: eq.value });
|
|
141
|
+
} else {
|
|
142
|
+
single.push(entry);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const jobs = [];
|
|
146
|
+
for (const [field, members] of Object.entries(byField)) {
|
|
147
|
+
if (members.length < 2) {
|
|
148
|
+
single.push(members[0].entry);
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
jobs.push(fetchGroupedCounts({ register, schema, groupBy: field, filter: base }, ctx)
|
|
152
|
+
.then((groups) => {
|
|
153
|
+
const map = {};
|
|
154
|
+
for (const group of groups) {
|
|
155
|
+
map[group.key] = group.count;
|
|
156
|
+
}
|
|
157
|
+
for (const member of members) {
|
|
158
|
+
counts[member.entry.key] = map[member.value] || 0;
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
.catch(() => {}));
|
|
162
|
+
}
|
|
163
|
+
for (const entry of single) {
|
|
164
|
+
jobs.push(fetchAggregateValue({ register, schema, metric: 'count', filter: { ...base, ...(entry.filter || {}) } }, ctx)
|
|
165
|
+
.then((value) => {
|
|
166
|
+
if (value !== null && Number.isFinite(Number(value))) {
|
|
167
|
+
counts[entry.key] = Number(value);
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
.catch(() => {}));
|
|
171
|
+
}
|
|
172
|
+
for (const entry of viaList) {
|
|
173
|
+
jobs.push(fetchListTotal(register, schema, { ...base, ...(entry.filter || {}) }, ctx)
|
|
174
|
+
.then((total) => {
|
|
175
|
+
if (total !== null) {
|
|
176
|
+
counts[entry.key] = total;
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
.catch(() => {}));
|
|
180
|
+
}
|
|
181
|
+
await Promise.all(jobs);
|
|
182
|
+
return counts
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { fetchFilterCounts, fetchListTotal, hasListValue, singleEqualityOf };
|
|
186
|
+
//# sourceMappingURL=fetchFilterCounts.js.map
|