@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
|
@@ -90,7 +90,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
90
90
|
sortKeys: $options.effectiveSortKeys,
|
|
91
91
|
emptyText: $props.emptyText,
|
|
92
92
|
onSort: $options.onSort,
|
|
93
|
-
onRowClick: $options.onRowClick
|
|
93
|
+
onRowClick: $options.onRowClick,
|
|
94
|
+
onRowAuxClick: $options.onRowAuxClick
|
|
94
95
|
}, createSlots({ _: 2 /* DYNAMIC */ }, [
|
|
95
96
|
renderList($options.slotColumns, (col) => {
|
|
96
97
|
return {
|
|
@@ -112,7 +113,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
112
113
|
key: "0"
|
|
113
114
|
}
|
|
114
115
|
: undefined
|
|
115
|
-
]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["schema", "columns", "rows", "rowKey", "fixedLayout", "sortKey", "sortOrder", "sortKeys", "emptyText", "onSort", "onRowClick"]),
|
|
116
|
+
]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["schema", "columns", "rows", "rowKey", "fixedLayout", "sortKey", "sortOrder", "sortKeys", "emptyText", "onSort", "onRowClick", "onRowAuxClick"]),
|
|
116
117
|
($options.paginationState && $options.paginationState.pages > 1)
|
|
117
118
|
? (openBlock(), createBlock(_component_CnPagination, {
|
|
118
119
|
key: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnLogsPage.vue3.js","sources":["../../../../src/components/CnLogsPage/CnLogsPage.vue?vue&type=template&id=18d3ce6f&lang.js"],"sourcesContent":["<!--\n CnLogsPage — Read-only audit-trail / activity-log surface.\n\n Wraps CnDataTable with server-side sorting, pagination and filtering.\n Reads its data source from `config`:\n - { register, schema } → fetched via the OpenRegister object store\n - { source: '/api/url' } → fetched via axios.get(source)\n\n In store mode the list state (params, paging, sort, filters) is owned by\n the shared `useListView` composable, so a `?jobId=<uuid>`-style deep link\n lands the page pre-filtered — the same behaviour CnIndexPage has.\n\n Mounted by CnPageRenderer when a manifest page declares\n `type: \"logs\"`. Honours `headerComponent`, `actionsComponent`, and\n the generic `slots` map from `pages[]` (forwarded by the renderer\n through scoped slots; this component just exposes `#header`,\n `#actions`, and `#row-actions` slots which the renderer fills).\n\n Backwards-compat: when neither `register+schema` nor `source` is\n set, the component renders an empty-state with a console warning\n rather than throwing — this keeps a misconfigured manifest from\n breaking the app shell.\n-->\n<template>\n\t<div class=\"cn-logs-page\" data-testid=\"cn-logs-page\">\n\t\t<!-- @slot Replaces the default CnPageHeader. CnPageRenderer fills it from `pages[].headerComponent` / `slots.header`. -->\n\t\t<!-- @binding {string} title The page title. -->\n\t\t<!-- @binding {string} description The page description. -->\n\t\t<!-- @binding {string} icon The header's MDI icon name. -->\n\t\t<slot\n\t\t\tname=\"header\"\n\t\t\t:title=\"title\"\n\t\t\t:description=\"description\"\n\t\t\t:icon=\"icon\">\n\t\t\t<CnPageHeader\n\t\t\t\tv-if=\"title\"\n\t\t\t\t:title=\"title\"\n\t\t\t\t:description=\"description\"\n\t\t\t\t:icon=\"icon\"\n\t\t\t\t:visuallyHidden=\"!showTitle\" />\n\t\t</slot>\n\n\t\t<div v-if=\"$slots.actions\" class=\"cn-logs-page__actions\">\n\t\t\t<!-- @slot Right-aligned action area (refresh, export, …). CnPageRenderer fills it from `pages[].actionsComponent`. -->\n\t\t\t<slot name=\"actions\" />\n\t\t</div>\n\n\t\t<!-- Body -->\n\t\t<div class=\"cn-logs-page__body\">\n\t\t\t<!-- Loading state -->\n\t\t\t<div v-if=\"loading\" class=\"cn-logs-page__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t\t</div>\n\n\t\t\t<!-- The error block below is a SIBLING of this chain, not part of it, so\n\t\t\t BOTH guards here are load-bearing and neither is sufficient alone.\n\t\t\t A failed fetch leaves the collection empty, so:\n\t\t\t - without `&& !error`, this branch renders \"no log entries\"\n\t\t\t stacked above \"could not load log entries\";\n\t\t\t - without the `rows.length > 0` guard on the table branch, the\n\t\t\t `v-else` catches that same state instead and mounts CnDataTable\n\t\t\t with zero rows, which renders its OWN empty row from the same\n\t\t\t `emptyText` — the identical contradiction, just inside a table.\n\t\t\t So: error + no rows shows the error alone; no error + no rows shows\n\t\t\t this block; rows present shows the table, with the error beneath it\n\t\t\t when a refresh failed over a still-populated collection. -->\n\t\t\t<div v-else-if=\"rows.length === 0 && !error\" class=\"cn-logs-page__empty\">\n\t\t\t\t<!-- @slot Replaces the empty-state block shown when there are no log entries. -->\n\t\t\t\t<slot name=\"empty\">\n\t\t\t\t\t<NcEmptyContent :name=\"emptyText\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<HistoryIcon :size=\"64\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcEmptyContent>\n\t\t\t\t</slot>\n\t\t\t</div>\n\n\t\t\t<!-- Table. `v-else-if=\"rows.length > 0\"` rather than a bare `v-else` —\n\t\t\t see the guard note above. -->\n\t\t\t<template v-else-if=\"rows.length > 0\">\n\t\t\t\t<CnDataTable\n\t\t\t\t\t:schema=\"tableSchema\"\n\t\t\t\t\t:columns=\"resolvedColumns\"\n\t\t\t\t\t:rows=\"rows\"\n\t\t\t\t\t:rowKey=\"rowKey\"\n\t\t\t\t\t:fixedLayout=\"fixedLayout\"\n\t\t\t\t\t:sortKey=\"effectiveSortKey\"\n\t\t\t\t\t:sortOrder=\"effectiveSortOrder\"\n\t\t\t\t\t:sortKeys=\"effectiveSortKeys\"\n\t\t\t\t\t:emptyText=\"emptyText\"\n\t\t\t\t\t@sort=\"onSort\"\n\t\t\t\t\t@rowClick=\"onRowClick\">\n\t\t\t\t\t<template\n\t\t\t\t\t\tv-for=\"col in slotColumns\"\n\t\t\t\t\t\t#[`column-${col}`]=\"{ row, value }\">\n\t\t\t\t\t\t<!-- @slot Per-column cell renderer (`#column-<key>`), one per configured column key. -->\n\t\t\t\t\t\t<!-- @binding {object} row The log entry being rendered. -->\n\t\t\t\t\t\t<!-- @binding {*} value The cell's resolved value. -->\n\t\t\t\t\t\t<slot :name=\"'column-' + col\" :row=\"row\" :value=\"value\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"$slots['row-actions']\" #row-actions=\"{ row }\">\n\t\t\t\t\t\t<!-- @slot Per-row action menu cell. Supplying it adds the trailing actions column. -->\n\t\t\t\t\t\t<!-- @binding {object} row The log entry for this row. -->\n\t\t\t\t\t\t<slot name=\"row-actions\" :row=\"row\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnDataTable>\n\n\t\t\t\t<CnPagination\n\t\t\t\t\tv-if=\"paginationState && paginationState.pages > 1\"\n\t\t\t\t\tclass=\"cn-logs-page__pagination\"\n\t\t\t\t\t:currentPage=\"paginationState.page || 1\"\n\t\t\t\t\t:totalPages=\"paginationState.pages || 1\"\n\t\t\t\t\t:totalItems=\"paginationState.total || 0\"\n\t\t\t\t\t:currentPageSize=\"paginationState.limit || 20\"\n\t\t\t\t\t@pageChanged=\"onPageChange\"\n\t\t\t\t\t@pageSizeChanged=\"onPageSizeChange\" />\n\t\t\t</template>\n\n\t\t\t<div v-if=\"error\" class=\"cn-logs-page__error\">\n\t\t\t\t<!-- @slot Replaces the error block shown when the fetch fails. -->\n\t\t\t\t<!-- @binding {*} error The recorded fetch error. -->\n\t\t\t\t<slot name=\"error\" :error=\"error\">\n\t\t\t\t\t<NcEmptyContent :name=\"errorText\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<AlertCircleOutline :size=\"64\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcEmptyContent>\n\t\t\t\t</slot>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Row-detail dialog (opt-in via `rowDetail`). A log row's interesting\n\t\t payload is usually a nested object (a stack trace, an argument bag)\n\t\t that a table cell can only summarise. -->\n\t\t<NcDialog\n\t\t\tv-if=\"detailRow\"\n\t\t\t:open=\"true\"\n\t\t\t:name=\"detailTitle\"\n\t\t\tsize=\"large\"\n\t\t\t@update:open=\"closeDetail\">\n\t\t\t<!-- @slot Replaces the built-in row-detail dialog body. -->\n\t\t\t<!-- @binding {object} row The clicked log row. -->\n\t\t\t<slot name=\"row-detail\" :row=\"detailRow\">\n\t\t\t\t<div class=\"cn-logs-page__detail\">\n\t\t\t\t\t<CnDetailGrid\n\t\t\t\t\t\tv-if=\"detailScalarItems.length > 0\"\n\t\t\t\t\t\tlayout=\"horizontal\"\n\t\t\t\t\t\t:items=\"detailScalarItems\" />\n\t\t\t\t\t<template v-for=\"block in detailObjectBlocks\" :key=\"block.key\">\n\t\t\t\t\t\t<h4 class=\"cn-logs-page__detail-heading\">\n\t\t\t\t\t\t\t{{ block.label }}\n\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t<CnDetailGrid\n\t\t\t\t\t\t\tv-if=\"block.items\"\n\t\t\t\t\t\t\tlayout=\"horizontal\"\n\t\t\t\t\t\t\t:items=\"block.items\" />\n\t\t\t\t\t\t<pre v-else class=\"cn-logs-page__detail-json\">{{ block.json }}</pre>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</slot>\n\t\t\t<template #actions>\n\t\t\t\t<NcButton @click=\"closeDetail\">\n\t\t\t\t\t{{ closeLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</NcDialog>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport { getCurrentInstance, inject } from 'vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport HistoryIcon from 'vue-material-design-icons/History.vue'\nimport { useListView } from '../../composables/index.js'\nimport { useObjectStore } from '../../store/index.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { multiKeySort } from '../../utils/multiKeySort.js'\nimport { parseSortKeysFromQuery, resolveFilterMap, resolveQueryFilters } from '../../utils/routeFilters.js'\nimport { markNewTabHandled } from '../../utils/rowAuxClick.js'\nimport { CnDataTable } from '../CnDataTable/index.js'\nimport { CnDetailGrid } from '../CnDetailGrid/index.js'\nimport { CnPageHeader } from '../CnPageHeader/index.js'\nimport { CnPagination } from '../CnPagination/index.js'\n\n/**\n * Legacy default columns. Retained ONLY for `source` mode and for a store\n * mode whose schema could not be loaded — see `resolvedColumns`. They match\n * no OpenRegister log schema, so a store-backed page derives its columns\n * from the schema instead.\n *\n * A function, not a constant: `t()` at module scope would resolve before the\n * consuming app calls `registerTranslations()`, pinning the labels to English.\n *\n * @return {Array<{key: string, label: string, sortable?: boolean}>} The legacy column set.\n */\nfunction legacyDefaultColumns() {\n\treturn [\n\t\t{ key: 'timestamp', label: t('nextcloud-vue', 'Timestamp'), sortable: true },\n\t\t{ key: 'actor', label: t('nextcloud-vue', 'Actor'), sortable: true },\n\t\t{ key: 'action', label: t('nextcloud-vue', 'Action'), sortable: true },\n\t\t{ key: 'target', label: t('nextcloud-vue', 'Target') },\n\t\t{ key: 'details', label: t('nextcloud-vue', 'Details') },\n\t]\n}\n\n/**\n * CnLogsPage — Read-only audit-trail / activity-log page.\n *\n * Renders a CnDataTable of log entries with server-side sorting,\n * pagination and filtering. Columns come from `columns` (manifest:\n * `pages[].config.columns`); when none are given, a store-backed page\n * derives them from the schema.\n *\n * Two data-fetch modes:\n * - `register` + `schema`: fetched via `useObjectStore` as a regular\n * OpenRegister collection. The store registers the type as\n * `${register}-${schema}` per existing convention. List state is\n * owned by `useListView`, so `_limit` / `_page` / `_order` and any\n * `$route.query` / `filter` entries are sent with every request.\n * - `source`: a custom URL fetched via `axios.get(source)`. The\n * response shape may be either `{ results: [...] }` or a bare\n * array; the component handles both. This escape hatch supports\n * apps whose log surface is not OR-backed (e.g. a flat file).\n * Sorting is applied client-side; there is no pagination.\n *\n * Filtering (store mode) merges two sources, `filter` winning a collision:\n * 1. `$route.query` — every non-`_`-prefixed entry becomes a filter, so\n * `/jobs/logs?jobId=<uuid>` lands the page scoped to one job.\n * 2. `filter` — the manifest's own scoping, with `@route.<param>` /\n * `:<param>` / `@me` / `@today±Nd` tokens resolved at fetch time.\n *\n * The merged result is watched, so anything that moves it — a same-path query\n * change, a route param, the `filter` prop, or the workspace / app-config bag a\n * token in it reads — refetches from page 1, and nothing else does.\n *\n * Slots:\n * - `#header` — Replaces the default CnPageHeader.\n * - `#actions` — Renders a right-aligned action area (refresh,\n * export, etc.). The CnPageRenderer wires this from\n * `pages[].actionsComponent` when present.\n * - `#empty` — Replaces the empty-state block.\n * - `#error` — Replaces the error block. Scope: `{ error }`.\n * - `#row-actions` — Per-row action menu in the table. Scope:\n * `{ row }`.\n * - `#row-detail` — Replaces the row-detail dialog body. Scope: `{ row }`.\n * - `#column-<key>` — Per-column custom cell renderer. Scope:\n * `{ row, value }`.\n *\n * Props are intentionally permissive — every default has a sensible\n * fallback, so a manifest entry as small as\n * `{ type: \"logs\", config: { source: \"/api/logs\" } }` renders.\n */\nexport default {\n\tname: 'CnLogsPage',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcDialog,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tHistoryIcon,\n\t\tAlertCircleOutline,\n\t\tCnDataTable,\n\t\tCnDetailGrid,\n\t\tCnPageHeader,\n\t\tCnPagination,\n\t},\n\n\tprops: {\n\t\t/** Page title. Defaults to \"Activity log\" so a bare manifest entry still renders meaningfully. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Activity log'),\n\t\t},\n\n\t\t/** Description shown under the title when `showTitle` is set. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether to render the inline page header VISIBLY. Defaults to true:\n\t\t * unlike CnIndexPage (which surfaces the title in its sidebar header\n\t\t * when this is false), a logs page has no sidebar fallback, so a false\n\t\t * default left the page with no heading at all. Setting it to false is\n\t\t * now safe for accessibility either way — the `<h1>` is still rendered\n\t\t * visually-hidden, so the `<main>` landmark keeps its accessible\n\t\t * heading (WCAG 2.4.6 / 1.3.1).\n\t\t */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** MDI icon name for the header. */\n\t\ticon: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Required (with `schema`) for store-backed\n\t\t * mode. Changing it after mount requires a remount — CnPageRenderer keys\n\t\t * the dispatched page on register+schema, so a manifest page swap already\n\t\t * remounts. A direct consumer that rebinds it instead gets a development\n\t\t * console warning, since the bound data source does not follow.\n\t\t */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Required (with `register`) for store-backed\n\t\t * mode. Changing it after mount requires a remount (see `register`).\n\t\t */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Custom log source URL — used when `register`+`schema` is not set. */\n\t\tsource: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Column definitions for the table. When omitted, a store-backed page\n\t\t * derives its columns from the loaded schema; a `source`-backed page\n\t\t * (or one whose schema failed to load) falls back to\n\t\t * `[timestamp, actor, action, target, details]`.\n\t\t * Pass either an array of strings (treated as keys + auto-labels)\n\t\t * OR an array of `{ key, label, sortable, width }` objects.\n\t\t *\n\t\t * @type {Array<string|{key: string, label: string}>}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Fixed filter map merged into every fetch, ABOVE the `$route.query`\n\t\t * deep-link filters so the page's own scoping wins on a key collision.\n\t\t * Values support the shared token grammar: `@route.<param>` / `:<param>`\n\t\t * (route params), `@me`, `@today±Nd`, `@workspace.<key>`. Null = no fixed\n\t\t * filter. Store mode only. Reactive: changing the map — or changing what a\n\t\t * token in it resolves to — refetches from page 1.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tfilter: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Pagination config; only `limit` is read, as the page size sent as\n\t\t * `_limit`. Null = the store default of 20. Store mode only.\n\t\t *\n\t\t * @type {{limit?: number}|null}\n\t\t */\n\t\tpagination: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Initial sort column. Null (the default) sends no `_order`, leaving the\n\t\t * server's own ordering in place.\n\t\t */\n\t\tsortKey: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Initial sort direction. Inert while `sortKey` is null. */\n\t\tsortOrder: {\n\t\t\ttype: String,\n\t\t\tdefault: 'asc',\n\t\t\tvalidator: (v) => v === null || ['asc', 'desc'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Initial MULTI-column sort as an ordered priority list. Takes precedence\n\t\t * over `sortKey`/`sortOrder` when non-empty; empty (the default) is inert.\n\t\t * A `?_order=` param on the URL (the JSON-encoded list CnIndexPage\n\t\t * persists when a header is clicked) outranks both, so a shared or\n\t\t * reloaded logs link opens on the sort it carried.\n\t\t *\n\t\t * @type {Array<{key: string, order: 'asc'|'desc'}>}\n\t\t */\n\t\tsortKeys: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Make the columns' declared `width` authoritative (`table-layout: fixed`)\n\t\t * instead of a hint the browser may override from cell content. Worth\n\t\t * setting on a log table: message text and long unbreakable values like a\n\t\t * PHP FQCN otherwise dictate the column widths between them. Long values\n\t\t * then wrap inside their cell rather than overflowing it. Default false\n\t\t * keeps the previous auto layout.\n\t\t */\n\t\tfixedLayout: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Open a read-only detail dialog when a row is clicked, rendering the\n\t\t * entry's fields — including nested bags like a stack trace or an\n\t\t * argument map that a table cell can only summarise. Default false\n\t\t * keeps a row click inert, as before. Ignored when `rowRoute` is set:\n\t\t * a purpose-built page beats a generic dialog.\n\t\t */\n\t\trowDetail: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Manifest page id (route name) to open on a row click, pushed as\n\t\t * `{ name: rowRoute, params: { id: row[rowKey] } }` — the same shape\n\t\t * CnIndexPage's `open-page` actions use. For a log whose detail surface\n\t\t * is a real page (a step timeline, a replay action) rather than a\n\t\t * read-only dump. Takes precedence over `rowDetail`.\n\t\t */\n\t\trowRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Row identifier property. Defaults to `id` (matches OR + most custom log shapes). */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/** Text shown when there are no log entries. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No log entries to show'),\n\t\t},\n\n\t\t/** Text shown when the fetch fails. */\n\t\terrorText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Could not load log entries'),\n\t\t},\n\n\t\t/** Label for the row-detail dialog's close button. */\n\t\tcloseLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Close'),\n\t\t},\n\n\t\t/**\n\t\t * Override the object store. Useful when the consuming app calls\n\t\t * `createObjectStore` with a custom ID. When null, the default\n\t\t * `useObjectStore()` is used. The component skips store wiring\n\t\t * entirely when in `source` mode.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * @event action Declared for hosts that dispatch log-row actions through\n\t\t * the `#row-actions` slot. Never emitted by this component itself; kept\n\t\t * in the surface because consumers may already listen for it.\n\t\t */\n\t\t'action',\n\t\t/**\n\t\t * @event row-click Emitted when a log row's body is clicked or\n\t\t * middle-clicked, whether or not `rowDetail` opened the detail dialog —\n\t\t * so a host can navigate instead of (or as well as) showing the dialog.\n\t\t * Payload: `(row, event)`, the second being the native click/auxclick\n\t\t * event.\n\t\t * @type {object}\n\t\t */\n\t\t'row-click',\n\t],\n\n\tsetup(props) {\n\t\t// Always returned, even when the composable is not used: Vue warns when\n\t\t// a computed reads a key setup() omitted, and `source` mode reads these.\n\t\t// `resolveFixedFilters` is included for the same reason — the\n\t\t// `filterSignature` computed calls it by name, and `source` mode (which\n\t\t// has no fixed filters at all) takes this early return.\n\t\tconst NO_LIST = {\n\t\t\tlist: null,\n\t\t\tobjectType: '',\n\t\t\tlistStore: null,\n\t\t\tresolveFixedFilters: () => ({}),\n\t\t}\n\t\tif (!props.register || !props.schema) {\n\t\t\treturn NO_LIST\n\t\t}\n\n\t\tconst store = props.store || useObjectStore()\n\t\t// The `store` prop has always accepted a hand-rolled object, but\n\t\t// useListView assumes a full OpenRegister store (pagination/errors/\n\t\t// fetchSchema). Duck-type it and fall back to the legacy no-params\n\t\t// fetch rather than throwing on a partial store.\n\t\tif (typeof store.fetchCollection !== 'function' || !store.pagination) {\n\t\t\treturn NO_LIST\n\t\t}\n\n\t\tconst objectType = `${props.register}-${props.schema}`\n\t\t// Must run synchronously: useListView's onMounted calls fetchSchema(),\n\t\t// which throws when the type is not registered yet. Positional\n\t\t// signature: (slug, schemaId, registerId).\n\t\tif (typeof store.registerObjectType === 'function') {\n\t\t\tstore.registerObjectType(objectType, props.schema, props.register)\n\t\t}\n\n\t\tconst instance = getCurrentInstance()\n\n\t\t// Token-resolution context for the `filter` prop's `@workspace.<key>` /\n\t\t// `@config.<key>` / `@object.<field>` tokens. Same injects + unwrap shape\n\t\t// as useSelfFetchList's `tokenCtx`. Without it `resolveFilterMap` was\n\t\t// called with no ctx, and `resolveFilterValue` returns an unresolvable\n\t\t// token VERBATIM — so a page declaring `filter: { client:\n\t\t// '@workspace.selectedClient' }` sent the literal string\n\t\t// \"@workspace.selectedClient\" to OpenRegister as a property filter and got\n\t\t// an empty table with no error, despite the prop doc naming that token.\n\t\t// All three default to absent, so an app that never provides them is\n\t\t// unaffected.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst workspaceCtxRaw = inject('cnWorkspaceContext', null)\n\t\tconst appConfigRaw = inject('cnAppConfig', null)\n\t\tconst unwrapCtx = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\t\tconst tokenCtx = () => {\n\t\t\tconst objCtx = unwrapCtx(objectCtxRaw)\n\t\t\tconst base = (objCtx && typeof objCtx === 'object') ? { ...objCtx } : {}\n\t\t\tbase.workspace = unwrapCtx(workspaceCtxRaw) || {}\n\t\t\tbase.config = unwrapCtx(appConfigRaw) || {}\n\t\t\treturn base\n\t\t}\n\t\t// The complete fetch scope: the deep-link query filters plus the `filter`\n\t\t// prop's route/`@`-token resolution. A getter, not a computed — it is\n\t\t// re-read on every fetch, so a `?jobId=` change re-scopes the list\n\t\t// without re-creating it, and `tokenCtx()` reflects the latest bags.\n\t\tconst resolveFixedFilters = () => {\n\t\t\tconst route = instance && instance.proxy && instance.proxy.$route\n\t\t\tconst ctx = tokenCtx()\n\t\t\treturn {\n\t\t\t\t...resolveQueryFilters(route && route.query, ctx),\n\t\t\t\t...resolveFilterMap(props.filter, (route && route.params) || {}, ctx),\n\t\t\t}\n\t\t}\n\n\t\tconst list = useListView(objectType, {\n\t\t\tobjectStore: store,\n\t\t\tdefaultPageSize: (props.pagination && props.pagination.limit) || undefined,\n\t\t\t// A `?_order=` deep link wins over the configured default, so a\n\t\t\t// reloaded or shared logs URL reproduces the sort the link carried —\n\t\t\t// the same precedence CnIndexPage's useSelfFetchList applies.\n\t\t\tdefaultSort: props.sortKey ? { key: props.sortKey, order: props.sortOrder || 'asc' } : undefined,\n\t\t\tdefaultSortKeys: parseSortKeysFromQuery(instance && instance.proxy && instance.proxy.$route)\n\t\t\t\t|| (props.sortKeys.length > 0 ? props.sortKeys : undefined),\n\t\t\tfixedFilters: resolveFixedFilters,\n\t\t})\n\n\t\treturn { list, objectType, listStore: store, resolveFixedFilters }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tlocalRows: [],\n\t\t\tlocalLoading: false,\n\t\t\tlocalError: null,\n\t\t\tlocalSortKeys: [],\n\t\t\tdetailRow: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Whether the component fetches via the object store + useListView. */\n\t\tusesStore() {\n\t\t\treturn !!this.list\n\t\t},\n\n\t\t/**\n\t\t * Whether the component should fetch via axios. Store mode needs BOTH\n\t\t * `register` and `schema`, so a half-configured pair with a `source` set\n\t\t * still falls back to the URL rather than the empty state.\n\t\t */\n\t\tusesSource() {\n\t\t\treturn !(this.register && this.schema) && !!this.source\n\t\t},\n\n\t\t/** Loading state of the active fetch mode. */\n\t\tloading() {\n\t\t\t// `list` is a bag of refs returned from setup(); only TOP-LEVEL setup\n\t\t\t// refs are auto-unwrapped, so every read below goes through `.value`.\n\t\t\treturn this.list ? this.list.loading.value : this.localLoading\n\t\t},\n\n\t\t/**\n\t\t * Fetch error. `fetchCollection` records failures on the store rather\n\t\t * than throwing, so store mode reads them from there — without this the\n\t\t * `#error` slot never rendered for a store-backed page.\n\t\t */\n\t\terror() {\n\t\t\tif (this.localError) {\n\t\t\t\treturn this.localError\n\t\t\t}\n\t\t\tif (this.list && this.listStore) {\n\t\t\t\treturn this.listStore.errors[this.objectType] || null\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/** Rows to render: the store collection, or locally sorted axios rows. */\n\t\trows() {\n\t\t\tif (this.list) {\n\t\t\t\treturn this.list.objects.value\n\t\t\t}\n\t\t\t// Legacy-store fallback: read the store's collection LIVE rather than\n\t\t\t// the snapshot `fetch()` took. `rows` used to reach the collection\n\t\t\t// through a computed; snapshotting into `localRows` meant a store that\n\t\t\t// REPLACES the array (rather than mutating it in place) no longer\n\t\t\t// reached the table. Only `this.store` is consulted — in this path it is\n\t\t\t// the partial store prop that made setup() bail — so `source` mode,\n\t\t\t// which has no store, still falls through to localRows.\n\t\t\tconst live = (this.register && this.schema && this.store)\n\t\t\t\t? this.store.collections?.[`${this.register}-${this.schema}`]\n\t\t\t\t: null\n\t\t\tconst base = Array.isArray(live) ? live : this.localRows\n\t\t\tif (this.localSortKeys.length === 0) {\n\t\t\t\treturn base\n\t\t\t}\n\t\t\t// CnDataTable is presentational — it never sorts its own rows — so\n\t\t\t// `source` mode has to apply the header sort itself.\n\t\t\treturn multiKeySort(base, this.localSortKeys.map((k) => ({ field: k.key, order: k.order })))\n\t\t},\n\n\t\t/**\n\t\t * Server pagination STATE (`{ total, page, pages, limit }`); null in\n\t\t * source mode, which has no paging. Distinct from the `pagination`\n\t\t * prop, which is the manifest's page-size config.\n\t\t */\n\t\tpaginationState() {\n\t\t\treturn this.list ? this.list.pagination.value : null\n\t\t},\n\n\t\t/**\n\t\t * The LOADED schema object (not the `schema` slug prop), forwarded to\n\t\t * CnDataTable for type-aware cell rendering and schema-derived columns.\n\t\t */\n\t\ttableSchema() {\n\t\t\treturn (this.list && this.list.schema.value) || null\n\t\t},\n\n\t\t/** Active ordered sort keys, from the composable or local source state. */\n\t\teffectiveSortKeys() {\n\t\t\treturn this.list ? this.list.sortKeys.value : this.localSortKeys\n\t\t},\n\n\t\t/** Primary sort key (mirrors `effectiveSortKeys[0]`) for CnDataTable. */\n\t\teffectiveSortKey() {\n\t\t\treturn this.effectiveSortKeys[0]?.key ?? null\n\t\t},\n\n\t\t/** Primary sort direction (mirrors `effectiveSortKeys[0]`). */\n\t\teffectiveSortOrder() {\n\t\t\treturn this.effectiveSortKeys[0]?.order ?? 'asc'\n\t\t},\n\n\t\t/**\n\t\t * Resolved columns. A consumer-provided list wins. Otherwise, when a\n\t\t * schema is loaded, return `[]` so CnDataTable derives the columns from\n\t\t * it — the legacy `timestamp/actor/action/target/details` default matched\n\t\t * no OpenRegister log schema and rendered five blank cells. `source` mode\n\t\t * and a failed schema load still get that legacy default.\n\t\t */\n\t\tresolvedColumns() {\n\t\t\tif (this.columns.length > 0) {\n\t\t\t\treturn this.columns.map((c) => (typeof c === 'string' ? { key: c, label: this.humanise(c) } : c))\n\t\t\t}\n\t\t\tif (this.tableSchema) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn legacyDefaultColumns()\n\t\t},\n\n\t\t/** Column slot names that the parent has provided (for pass-through). */\n\t\tslotColumns() {\n\t\t\treturn Object.keys(this.$slots || {})\n\t\t\t\t.filter((name) => name.startsWith('column-'))\n\t\t\t\t.map((name) => name.replace('column-', ''))\n\t\t},\n\n\t\t/** Dialog heading: the entry's message when it has one, else its id. */\n\t\tdetailTitle() {\n\t\t\tif (!this.detailRow) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst message = this.detailRow.message\n\t\t\tif (typeof message === 'string' && message !== '') {\n\t\t\t\treturn message\n\t\t\t}\n\t\t\tconst id = this.detailRow[this.rowKey]\n\t\t\treturn id ? String(id) : t('nextcloud-vue', 'Log entry')\n\t\t},\n\n\t\t/** The clicked row's primitive fields, as CnDetailGrid items. */\n\t\tdetailScalarItems() {\n\t\t\treturn this.detailEntries\n\t\t\t\t.filter(([, value]) => !this.isBag(value))\n\t\t\t\t.map(([key, value]) => ({ label: this.propertyLabel(key), value: String(value) }))\n\t\t},\n\n\t\t/**\n\t\t * The clicked row's nested fields. A bag of primitives (a stack trace's\n\t\t * frames, an argument map) renders as its own labelled grid; anything\n\t\t * deeper falls back to pretty-printed JSON.\n\t\t */\n\t\tdetailObjectBlocks() {\n\t\t\treturn this.detailEntries\n\t\t\t\t.filter(([, value]) => this.isBag(value))\n\t\t\t\t.map(([key, value]) => {\n\t\t\t\t\tconst entries = Object.entries(value)\n\t\t\t\t\tconst flat = entries.every(([, v]) => v === null || typeof v !== 'object')\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tlabel: this.propertyLabel(key),\n\t\t\t\t\t\titems: flat ? entries.map(([k, v]) => ({ label: this.humanise(k), value: String(v) })) : null,\n\t\t\t\t\t\tjson: flat ? null : JSON.stringify(value, null, 2),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Stable signature of the RESOLVED fetch scope — the query deep-link\n\t\t * filters plus the `filter` prop after route-param and `@`-token\n\t\t * resolution. Reading through the same getter the fetch uses is what\n\t\t * makes it exact: it tracks `$route`, `props.filter` and the injected\n\t\t * workspace / app-config bags, and it changes only when the request the\n\t\t * list would issue actually changes. Stringified so the watcher fires on\n\t\t * content, not identity. Empty string in `source` mode (no list).\n\t\t *\n\t\t * @return {string} The serialized filter map.\n\t\t */\n\t\tfilterSignature() {\n\t\t\tif (!this.list) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn JSON.stringify(this.resolveFixedFilters())\n\t\t},\n\n\t\t/** The clicked row's renderable entries — `@self` and empties dropped. */\n\t\tdetailEntries() {\n\t\t\tif (!this.detailRow) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn Object.entries(this.detailRow)\n\t\t\t\t.filter(([key, value]) => key !== '@self' && value !== null && value !== undefined && value !== '')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// ONE watcher over the whole fetch scope, rather than one per reactive\n\t\t// source that feeds it. `fixedFilters` is a plain getter re-read at fetch\n\t\t// time, so a refresh is all any of them needs — but watching them\n\t\t// individually got three things wrong:\n\t\t//\n\t\t// - `$route.query` and `$route.params` both fire on a single navigation,\n\t\t// so one route change issued TWO identical concurrent fetches, which\n\t\t// `fetchCollection` does not dedup and whose responses can land in\n\t\t// either order;\n\t\t// - they fired on navigation AWAY as well, because the route updates\n\t\t// before this component tears down. The refetch was then scoped by the\n\t\t// DESTINATION route's query (`/cases?status=open` → `GET\n\t\t// …/job_log?status=open`), overwriting the shared store collection that\n\t\t// any other mounted widget on the same objectType reads;\n\t\t// - a workspace / app-config bag mutation refetched even when no\n\t\t// `@workspace.`/`@config.` token was in play, so a volatile bag reset a\n\t\t// token-free page to page 1 on every write.\n\t\t//\n\t\t// The signature is the RESOLVED filter map, so it changes only when the\n\t\t// scope actually changes, and the route-name guard drops the fire that\n\t\t// belongs to the page being navigated to. No `immediate` — useListView\n\t\t// owns the initial fetch. This also covers a reactive `filter` prop\n\t\t// change, which had no watcher of its own at all.\n\t\tfilterSignature(next, prev) {\n\t\t\tif (!this.list || next === prev) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Guarded only when this page HAS a route name to compare against —\n\t\t\t// a router-less host, or one whose routes are unnamed, keeps the\n\t\t\t// unguarded behaviour rather than losing refetches to a comparison\n\t\t\t// that can never match.\n\t\t\tif (this._ownRouteName !== undefined && this.$route?.name !== this._ownRouteName) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.list.refresh(1)\n\t\t},\n\n\t\tregister(value, previous) {\n\t\t\tthis.onSourcePropChange('register', value, previous)\n\t\t},\n\n\t\tschema(value, previous) {\n\t\t\tthis.onSourcePropChange('schema', value, previous)\n\t\t},\n\n\t\tsource(value, previous) {\n\t\t\tthis.onSourcePropChange('source', value, previous)\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// The route this page belongs to, captured BEFORE any navigation can\n\t\t// change it — the `filterSignature` watcher's guard. CnPageRenderer mounts\n\t\t// a page when `$route.name === page.id`, so this is the page's own id.\n\t\tthis._ownRouteName = this.$route ? this.$route.name : undefined\n\t},\n\n\tmounted() {\n\t\t// In store mode useListView's own onMounted owns the initial fetch;\n\t\t// calling fetch() here too would double-request on every page load.\n\t\tif (!this.list) {\n\t\t\tthis.fetch()\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Capitalise + space a snake_case / camelCase key for a default column label.\n\t\t *\n\t\t * @param {string} key A log-entry property name, e.g. `'created_at'` or `'userAgent'`.\n\t\t * @return {string} The sentence-cased label, e.g. `'Created at'` / `'User agent'`.\n\t\t */\n\t\thumanise(key) {\n\t\t\tconst spaced = String(key).replace(/([A-Z])/g, ' $1').replace(/_/g, ' ').toLowerCase()\n\t\t\treturn spaced.charAt(0).toUpperCase() + spaced.slice(1)\n\t\t},\n\n\t\t/**\n\t\t * Label for a row property — the schema's own title when one is loaded.\n\t\t *\n\t\t * @param {string} key The property name.\n\t\t * @return {string} The display label.\n\t\t */\n\t\tpropertyLabel(key) {\n\t\t\tconst title = this.tableSchema?.properties?.[key]?.title\n\t\t\treturn (typeof title === 'string' && title !== '') ? title : this.humanise(key)\n\t\t},\n\n\t\t/**\n\t\t * Whether a value is a nested bag (object/array) rather than a scalar.\n\t\t *\n\t\t * @param {unknown} value The value to test.\n\t\t * @return {boolean} True for non-null objects and arrays.\n\t\t */\n\t\tisBag(value) {\n\t\t\treturn value !== null && typeof value === 'object'\n\t\t},\n\n\t\t/**\n\t\t * Fetch log entries from the resolved data source.\n\t\t *\n\t\t * Only used in `source` mode and in the legacy store fallback — the\n\t\t * composable owns fetching whenever it is active.\n\t\t */\n\t\tasync fetch() {\n\t\t\tif (this.register && this.schema) {\n\t\t\t\t// Legacy fallback: a partial `store` prop that useListView\n\t\t\t\t// cannot drive (see setup). Unfiltered + unpaginated, as before.\n\t\t\t\tconst store = this.store || useObjectStore()\n\t\t\t\tthis.localLoading = true\n\t\t\t\tthis.localError = null\n\t\t\t\ttry {\n\t\t\t\t\tif (typeof store.registerObjectType === 'function') {\n\t\t\t\t\t\tstore.registerObjectType(`${this.register}-${this.schema}`, this.schema, this.register)\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof store.fetchCollection === 'function') {\n\t\t\t\t\t\tawait store.fetchCollection(`${this.register}-${this.schema}`)\n\t\t\t\t\t}\n\t\t\t\t\tthis.localRows = store.collections?.[`${this.register}-${this.schema}`] ?? []\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.localError = err\n\t\t\t\t} finally {\n\t\t\t\t\tthis.localLoading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.usesSource) {\n\t\t\t\tthis.localLoading = true\n\t\t\t\tthis.localError = null\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await axios.get(prefixUrl(this.source))\n\t\t\t\t\tconst body = response?.data\n\t\t\t\t\tif (Array.isArray(body)) {\n\t\t\t\t\t\tthis.localRows = body\n\t\t\t\t\t} else if (body && Array.isArray(body.results)) {\n\t\t\t\t\t\tthis.localRows = body.results\n\t\t\t\t\t} else if (body && Array.isArray(body.entries)) {\n\t\t\t\t\t\tthis.localRows = body.entries\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.localRows = []\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.localError = err\n\t\t\t\t\tthis.localRows = []\n\t\t\t\t} finally {\n\t\t\t\t\tthis.localLoading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Misconfigured — surface a console warning so a developer notices.\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn('[CnLogsPage] Neither register+schema nor source configured; rendering empty state.')\n\t\t},\n\n\t\t/**\n\t\t * React to a data-source prop changing on a mounted page.\n\t\t *\n\t\t * Only the legacy / `source` paths can act on it, by refetching. Store\n\t\t * mode cannot: the object type, its store registration and the\n\t\t * `useListView` instance are all built once in `setup()` from the\n\t\t * initial pair, so a new `register`/`schema` needs a remount to take\n\t\t * effect (which CnPageRenderer does on a manifest page swap). That is\n\t\t * documented on the props, but it used to happen in complete silence —\n\t\t * a direct consumer rebinding the prop saw the old collection and no\n\t\t * indication why. Warns in development only, once per change.\n\t\t *\n\t\t * @param {string} name The prop that changed.\n\t\t * @param {unknown} value The new value.\n\t\t * @param {unknown} previous The previous value.\n\t\t * @return {void}\n\t\t */\n\t\tonSourcePropChange(name, value, previous) {\n\t\t\tif (!this.list) {\n\t\t\t\tthis.fetch()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(`[CnLogsPage] \\`${name}\\` changed from \"${previous}\" to \"${value}\" on a mounted store-backed page. `\n\t\t\t\t\t+ 'The data source is bound once at setup, so this has no effect — remount the page (e.g. give it a '\n\t\t\t\t\t+ ':key) to pick up the new register/schema.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Apply a header sort. Server-side in store mode, client-side otherwise.\n\t\t *\n\t\t * @param {{key: string|null, order: string|null, keys?: Array<{key: string, order: string}>}} sort CnDataTable's sort payload.\n\t\t */\n\t\tonSort(sort) {\n\t\t\tif (this.list) {\n\t\t\t\tthis.list.onSort(sort)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.localSortKeys = Array.isArray(sort.keys)\n\t\t\t\t? sort.keys\n\t\t\t\t: (sort.key ? [{ key: sort.key, order: sort.order || 'asc' }] : [])\n\t\t},\n\n\t\t/**\n\t\t * Navigate to a page of results.\n\t\t *\n\t\t * @param {number} page The 1-based page number.\n\t\t */\n\t\tonPageChange(page) {\n\t\t\tthis.list?.onPageChange(page)\n\t\t},\n\n\t\t/**\n\t\t * Change the page size, returning to page 1.\n\t\t *\n\t\t * @param {number} size The new page size.\n\t\t */\n\t\tonPageSizeChange(size) {\n\t\t\tthis.list?.onPageSizeChange(size)\n\t\t},\n\n\t\t/**\n\t\t * Handle a row-body click: navigate to `rowRoute` when one is declared,\n\t\t * else open the detail dialog when `rowDetail` is set. Always re-emits\n\t\t * so a host can do its own thing regardless. A ctrl/cmd/shift or middle\n\t\t * click opens `rowRoute` in a new tab; a middle click never opens the\n\t\t * dialog.\n\t\t *\n\t\t * @param {object} row The clicked log entry.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t */\n\t\tonRowClick(row, event) {\n\t\t\tif (this.rowRoute) {\n\t\t\t\tmarkNewTabHandled(event, openRowTarget(event, { name: this.rowRoute, params: { id: row?.[this.rowKey] } }, this.$router))\n\t\t\t} else if (this.rowDetail && !(event && event.button === 1)) {\n\t\t\t\tthis.detailRow = row\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event row-click Emitted when a log row's body is clicked or middle-clicked. Payload: `(row, event)` — the log entry and the native click/auxclick event.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('row-click', row, event)\n\t\t},\n\n\t\t/** Close the row-detail dialog. */\n\t\tcloseDetail() {\n\t\t\tthis.detailRow = null\n\t\t},\n\n\t\t/**\n\t\t * Re-fetch from the source. Exposed so refresh buttons in\n\t\t * actionsComponent can call `$parent.refresh()`.\n\t\t *\n\t\t * @public\n\t\t */\n\t\trefresh() {\n\t\t\tif (this.list) {\n\t\t\t\tthis.list.refresh(1)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetch()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* Padding matches CnIndexPage / CnDetailPage (see css/index-page.css and\n css/detail-page.css) — a logs page had none at all, so its table ran flush\n into both edges of the app content area. Gaps are expressed in the same\n grid-baseline unit as the rest of the fleet rather than raw pixels. */\n.cn-logs-page {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(4 * var(--default-grid-baseline));\n\tpadding: calc(5 * var(--default-grid-baseline));\n\tbox-sizing: border-box;\n}\n\n.cn-logs-page__actions {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__loading,\n.cn-logs-page__empty,\n.cn-logs-page__error {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-height: 200px;\n}\n\n.cn-logs-page__pagination {\n\tmargin-top: calc(3 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__detail {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tpadding: 0 calc(3 * var(--default-grid-baseline)) calc(3 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__detail-heading {\n\tmargin: calc(3 * var(--default-grid-baseline)) 0 0;\n\tfont-weight: bold;\n}\n\n.cn-logs-page__detail-json {\n\tmargin: 0;\n\tpadding: calc(2 * var(--default-grid-baseline));\n\toverflow-x: auto;\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-dark);\n\tfont-family: monospace;\n\twhite-space: pre;\n}\n\n/* Same breakpoint + step-down as CnDetailPage, so the three page types stay in\n sync on narrow viewports. */\n@media (max-width: 768px) {\n\t.cn-logs-page {\n\t\tpadding: calc(3 * var(--default-grid-baseline));\n\t}\n}\n</style>\n"],"names":["_renderSlot","_openBlock","_createElementBlock","_createVNode","_Fragment","_renderList","_withCtx","_createElementVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnLogsPage.vue3.js","sources":["../../../../src/components/CnLogsPage/CnLogsPage.vue?vue&type=template&id=18d3ce6f&lang.js"],"sourcesContent":["<!--\n CnLogsPage — Read-only audit-trail / activity-log surface.\n\n Wraps CnDataTable with server-side sorting, pagination and filtering.\n Reads its data source from `config`:\n - { register, schema } → fetched via the OpenRegister object store\n - { source: '/api/url' } → fetched via axios.get(source)\n\n In store mode the list state (params, paging, sort, filters) is owned by\n the shared `useListView` composable, so a `?jobId=<uuid>`-style deep link\n lands the page pre-filtered — the same behaviour CnIndexPage has.\n\n Mounted by CnPageRenderer when a manifest page declares\n `type: \"logs\"`. Honours `headerComponent`, `actionsComponent`, and\n the generic `slots` map from `pages[]` (forwarded by the renderer\n through scoped slots; this component just exposes `#header`,\n `#actions`, and `#row-actions` slots which the renderer fills).\n\n Backwards-compat: when neither `register+schema` nor `source` is\n set, the component renders an empty-state with a console warning\n rather than throwing — this keeps a misconfigured manifest from\n breaking the app shell.\n-->\n<template>\n\t<div class=\"cn-logs-page\" data-testid=\"cn-logs-page\">\n\t\t<!-- @slot Replaces the default CnPageHeader. CnPageRenderer fills it from `pages[].headerComponent` / `slots.header`. -->\n\t\t<!-- @binding {string} title The page title. -->\n\t\t<!-- @binding {string} description The page description. -->\n\t\t<!-- @binding {string} icon The header's MDI icon name. -->\n\t\t<slot\n\t\t\tname=\"header\"\n\t\t\t:title=\"title\"\n\t\t\t:description=\"description\"\n\t\t\t:icon=\"icon\">\n\t\t\t<CnPageHeader\n\t\t\t\tv-if=\"title\"\n\t\t\t\t:title=\"title\"\n\t\t\t\t:description=\"description\"\n\t\t\t\t:icon=\"icon\"\n\t\t\t\t:visuallyHidden=\"!showTitle\" />\n\t\t</slot>\n\n\t\t<div v-if=\"$slots.actions\" class=\"cn-logs-page__actions\">\n\t\t\t<!-- @slot Right-aligned action area (refresh, export, …). CnPageRenderer fills it from `pages[].actionsComponent`. -->\n\t\t\t<slot name=\"actions\" />\n\t\t</div>\n\n\t\t<!-- Body -->\n\t\t<div class=\"cn-logs-page__body\">\n\t\t\t<!-- Loading state -->\n\t\t\t<div v-if=\"loading\" class=\"cn-logs-page__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t\t</div>\n\n\t\t\t<!-- The error block below is a SIBLING of this chain, not part of it, so\n\t\t\t BOTH guards here are load-bearing and neither is sufficient alone.\n\t\t\t A failed fetch leaves the collection empty, so:\n\t\t\t - without `&& !error`, this branch renders \"no log entries\"\n\t\t\t stacked above \"could not load log entries\";\n\t\t\t - without the `rows.length > 0` guard on the table branch, the\n\t\t\t `v-else` catches that same state instead and mounts CnDataTable\n\t\t\t with zero rows, which renders its OWN empty row from the same\n\t\t\t `emptyText` — the identical contradiction, just inside a table.\n\t\t\t So: error + no rows shows the error alone; no error + no rows shows\n\t\t\t this block; rows present shows the table, with the error beneath it\n\t\t\t when a refresh failed over a still-populated collection. -->\n\t\t\t<div v-else-if=\"rows.length === 0 && !error\" class=\"cn-logs-page__empty\">\n\t\t\t\t<!-- @slot Replaces the empty-state block shown when there are no log entries. -->\n\t\t\t\t<slot name=\"empty\">\n\t\t\t\t\t<NcEmptyContent :name=\"emptyText\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<HistoryIcon :size=\"64\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcEmptyContent>\n\t\t\t\t</slot>\n\t\t\t</div>\n\n\t\t\t<!-- Table. `v-else-if=\"rows.length > 0\"` rather than a bare `v-else` —\n\t\t\t see the guard note above. -->\n\t\t\t<template v-else-if=\"rows.length > 0\">\n\t\t\t\t<CnDataTable\n\t\t\t\t\t:schema=\"tableSchema\"\n\t\t\t\t\t:columns=\"resolvedColumns\"\n\t\t\t\t\t:rows=\"rows\"\n\t\t\t\t\t:rowKey=\"rowKey\"\n\t\t\t\t\t:fixedLayout=\"fixedLayout\"\n\t\t\t\t\t:sortKey=\"effectiveSortKey\"\n\t\t\t\t\t:sortOrder=\"effectiveSortOrder\"\n\t\t\t\t\t:sortKeys=\"effectiveSortKeys\"\n\t\t\t\t\t:emptyText=\"emptyText\"\n\t\t\t\t\t@sort=\"onSort\"\n\t\t\t\t\t@rowClick=\"onRowClick\"\n\t\t\t\t\t@rowAuxClick=\"onRowAuxClick\">\n\t\t\t\t\t<template\n\t\t\t\t\t\tv-for=\"col in slotColumns\"\n\t\t\t\t\t\t#[`column-${col}`]=\"{ row, value }\">\n\t\t\t\t\t\t<!-- @slot Per-column cell renderer (`#column-<key>`), one per configured column key. -->\n\t\t\t\t\t\t<!-- @binding {object} row The log entry being rendered. -->\n\t\t\t\t\t\t<!-- @binding {*} value The cell's resolved value. -->\n\t\t\t\t\t\t<slot :name=\"'column-' + col\" :row=\"row\" :value=\"value\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"$slots['row-actions']\" #row-actions=\"{ row }\">\n\t\t\t\t\t\t<!-- @slot Per-row action menu cell. Supplying it adds the trailing actions column. -->\n\t\t\t\t\t\t<!-- @binding {object} row The log entry for this row. -->\n\t\t\t\t\t\t<slot name=\"row-actions\" :row=\"row\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnDataTable>\n\n\t\t\t\t<CnPagination\n\t\t\t\t\tv-if=\"paginationState && paginationState.pages > 1\"\n\t\t\t\t\tclass=\"cn-logs-page__pagination\"\n\t\t\t\t\t:currentPage=\"paginationState.page || 1\"\n\t\t\t\t\t:totalPages=\"paginationState.pages || 1\"\n\t\t\t\t\t:totalItems=\"paginationState.total || 0\"\n\t\t\t\t\t:currentPageSize=\"paginationState.limit || 20\"\n\t\t\t\t\t@pageChanged=\"onPageChange\"\n\t\t\t\t\t@pageSizeChanged=\"onPageSizeChange\" />\n\t\t\t</template>\n\n\t\t\t<div v-if=\"error\" class=\"cn-logs-page__error\">\n\t\t\t\t<!-- @slot Replaces the error block shown when the fetch fails. -->\n\t\t\t\t<!-- @binding {*} error The recorded fetch error. -->\n\t\t\t\t<slot name=\"error\" :error=\"error\">\n\t\t\t\t\t<NcEmptyContent :name=\"errorText\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<AlertCircleOutline :size=\"64\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcEmptyContent>\n\t\t\t\t</slot>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Row-detail dialog (opt-in via `rowDetail`). A log row's interesting\n\t\t payload is usually a nested object (a stack trace, an argument bag)\n\t\t that a table cell can only summarise. -->\n\t\t<NcDialog\n\t\t\tv-if=\"detailRow\"\n\t\t\t:open=\"true\"\n\t\t\t:name=\"detailTitle\"\n\t\t\tsize=\"large\"\n\t\t\t@update:open=\"closeDetail\">\n\t\t\t<!-- @slot Replaces the built-in row-detail dialog body. -->\n\t\t\t<!-- @binding {object} row The clicked log row. -->\n\t\t\t<slot name=\"row-detail\" :row=\"detailRow\">\n\t\t\t\t<div class=\"cn-logs-page__detail\">\n\t\t\t\t\t<CnDetailGrid\n\t\t\t\t\t\tv-if=\"detailScalarItems.length > 0\"\n\t\t\t\t\t\tlayout=\"horizontal\"\n\t\t\t\t\t\t:items=\"detailScalarItems\" />\n\t\t\t\t\t<template v-for=\"block in detailObjectBlocks\" :key=\"block.key\">\n\t\t\t\t\t\t<h4 class=\"cn-logs-page__detail-heading\">\n\t\t\t\t\t\t\t{{ block.label }}\n\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t<CnDetailGrid\n\t\t\t\t\t\t\tv-if=\"block.items\"\n\t\t\t\t\t\t\tlayout=\"horizontal\"\n\t\t\t\t\t\t\t:items=\"block.items\" />\n\t\t\t\t\t\t<pre v-else class=\"cn-logs-page__detail-json\">{{ block.json }}</pre>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</slot>\n\t\t\t<template #actions>\n\t\t\t\t<NcButton @click=\"closeDetail\">\n\t\t\t\t\t{{ closeLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</NcDialog>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport { getCurrentInstance, inject } from 'vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport HistoryIcon from 'vue-material-design-icons/History.vue'\nimport { useListView } from '../../composables/index.js'\nimport { useObjectStore } from '../../store/index.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { multiKeySort } from '../../utils/multiKeySort.js'\nimport { parseSortKeysFromQuery, resolveFilterMap, resolveQueryFilters } from '../../utils/routeFilters.js'\nimport { markNewTabHandled } from '../../utils/rowAuxClick.js'\nimport { CnDataTable } from '../CnDataTable/index.js'\nimport { CnDetailGrid } from '../CnDetailGrid/index.js'\nimport { CnPageHeader } from '../CnPageHeader/index.js'\nimport { CnPagination } from '../CnPagination/index.js'\n\n/**\n * Legacy default columns. Retained ONLY for `source` mode and for a store\n * mode whose schema could not be loaded — see `resolvedColumns`. They match\n * no OpenRegister log schema, so a store-backed page derives its columns\n * from the schema instead.\n *\n * A function, not a constant: `t()` at module scope would resolve before the\n * consuming app calls `registerTranslations()`, pinning the labels to English.\n *\n * @return {Array<{key: string, label: string, sortable?: boolean}>} The legacy column set.\n */\nfunction legacyDefaultColumns() {\n\treturn [\n\t\t{ key: 'timestamp', label: t('nextcloud-vue', 'Timestamp'), sortable: true },\n\t\t{ key: 'actor', label: t('nextcloud-vue', 'Actor'), sortable: true },\n\t\t{ key: 'action', label: t('nextcloud-vue', 'Action'), sortable: true },\n\t\t{ key: 'target', label: t('nextcloud-vue', 'Target') },\n\t\t{ key: 'details', label: t('nextcloud-vue', 'Details') },\n\t]\n}\n\n/**\n * CnLogsPage — Read-only audit-trail / activity-log page.\n *\n * Renders a CnDataTable of log entries with server-side sorting,\n * pagination and filtering. Columns come from `columns` (manifest:\n * `pages[].config.columns`); when none are given, a store-backed page\n * derives them from the schema.\n *\n * Two data-fetch modes:\n * - `register` + `schema`: fetched via `useObjectStore` as a regular\n * OpenRegister collection. The store registers the type as\n * `${register}-${schema}` per existing convention. List state is\n * owned by `useListView`, so `_limit` / `_page` / `_order` and any\n * `$route.query` / `filter` entries are sent with every request.\n * - `source`: a custom URL fetched via `axios.get(source)`. The\n * response shape may be either `{ results: [...] }` or a bare\n * array; the component handles both. This escape hatch supports\n * apps whose log surface is not OR-backed (e.g. a flat file).\n * Sorting is applied client-side; there is no pagination.\n *\n * Filtering (store mode) merges two sources, `filter` winning a collision:\n * 1. `$route.query` — every non-`_`-prefixed entry becomes a filter, so\n * `/jobs/logs?jobId=<uuid>` lands the page scoped to one job.\n * 2. `filter` — the manifest's own scoping, with `@route.<param>` /\n * `:<param>` / `@me` / `@today±Nd` tokens resolved at fetch time.\n *\n * The merged result is watched, so anything that moves it — a same-path query\n * change, a route param, the `filter` prop, or the workspace / app-config bag a\n * token in it reads — refetches from page 1, and nothing else does.\n *\n * Slots:\n * - `#header` — Replaces the default CnPageHeader.\n * - `#actions` — Renders a right-aligned action area (refresh,\n * export, etc.). The CnPageRenderer wires this from\n * `pages[].actionsComponent` when present.\n * - `#empty` — Replaces the empty-state block.\n * - `#error` — Replaces the error block. Scope: `{ error }`.\n * - `#row-actions` — Per-row action menu in the table. Scope:\n * `{ row }`.\n * - `#row-detail` — Replaces the row-detail dialog body. Scope: `{ row }`.\n * - `#column-<key>` — Per-column custom cell renderer. Scope:\n * `{ row, value }`.\n *\n * Props are intentionally permissive — every default has a sensible\n * fallback, so a manifest entry as small as\n * `{ type: \"logs\", config: { source: \"/api/logs\" } }` renders.\n */\nexport default {\n\tname: 'CnLogsPage',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcDialog,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tHistoryIcon,\n\t\tAlertCircleOutline,\n\t\tCnDataTable,\n\t\tCnDetailGrid,\n\t\tCnPageHeader,\n\t\tCnPagination,\n\t},\n\n\tprops: {\n\t\t/** Page title. Defaults to \"Activity log\" so a bare manifest entry still renders meaningfully. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Activity log'),\n\t\t},\n\n\t\t/** Description shown under the title when `showTitle` is set. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether to render the inline page header VISIBLY. Defaults to true:\n\t\t * unlike CnIndexPage (which surfaces the title in its sidebar header\n\t\t * when this is false), a logs page has no sidebar fallback, so a false\n\t\t * default left the page with no heading at all. Setting it to false is\n\t\t * now safe for accessibility either way — the `<h1>` is still rendered\n\t\t * visually-hidden, so the `<main>` landmark keeps its accessible\n\t\t * heading (WCAG 2.4.6 / 1.3.1).\n\t\t */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** MDI icon name for the header. */\n\t\ticon: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Required (with `schema`) for store-backed\n\t\t * mode. Changing it after mount requires a remount — CnPageRenderer keys\n\t\t * the dispatched page on register+schema, so a manifest page swap already\n\t\t * remounts. A direct consumer that rebinds it instead gets a development\n\t\t * console warning, since the bound data source does not follow.\n\t\t */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Required (with `register`) for store-backed\n\t\t * mode. Changing it after mount requires a remount (see `register`).\n\t\t */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Custom log source URL — used when `register`+`schema` is not set. */\n\t\tsource: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Column definitions for the table. When omitted, a store-backed page\n\t\t * derives its columns from the loaded schema; a `source`-backed page\n\t\t * (or one whose schema failed to load) falls back to\n\t\t * `[timestamp, actor, action, target, details]`.\n\t\t * Pass either an array of strings (treated as keys + auto-labels)\n\t\t * OR an array of `{ key, label, sortable, width }` objects.\n\t\t *\n\t\t * @type {Array<string|{key: string, label: string}>}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Fixed filter map merged into every fetch, ABOVE the `$route.query`\n\t\t * deep-link filters so the page's own scoping wins on a key collision.\n\t\t * Values support the shared token grammar: `@route.<param>` / `:<param>`\n\t\t * (route params), `@me`, `@today±Nd`, `@workspace.<key>`. Null = no fixed\n\t\t * filter. Store mode only. Reactive: changing the map — or changing what a\n\t\t * token in it resolves to — refetches from page 1.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tfilter: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Pagination config; only `limit` is read, as the page size sent as\n\t\t * `_limit`. Null = the store default of 20. Store mode only.\n\t\t *\n\t\t * @type {{limit?: number}|null}\n\t\t */\n\t\tpagination: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Initial sort column. Null (the default) sends no `_order`, leaving the\n\t\t * server's own ordering in place.\n\t\t */\n\t\tsortKey: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Initial sort direction. Inert while `sortKey` is null. */\n\t\tsortOrder: {\n\t\t\ttype: String,\n\t\t\tdefault: 'asc',\n\t\t\tvalidator: (v) => v === null || ['asc', 'desc'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Initial MULTI-column sort as an ordered priority list. Takes precedence\n\t\t * over `sortKey`/`sortOrder` when non-empty; empty (the default) is inert.\n\t\t * A `?_order=` param on the URL (the JSON-encoded list CnIndexPage\n\t\t * persists when a header is clicked) outranks both, so a shared or\n\t\t * reloaded logs link opens on the sort it carried.\n\t\t *\n\t\t * @type {Array<{key: string, order: 'asc'|'desc'}>}\n\t\t */\n\t\tsortKeys: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Make the columns' declared `width` authoritative (`table-layout: fixed`)\n\t\t * instead of a hint the browser may override from cell content. Worth\n\t\t * setting on a log table: message text and long unbreakable values like a\n\t\t * PHP FQCN otherwise dictate the column widths between them. Long values\n\t\t * then wrap inside their cell rather than overflowing it. Default false\n\t\t * keeps the previous auto layout.\n\t\t */\n\t\tfixedLayout: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Open a read-only detail dialog when a row is clicked, rendering the\n\t\t * entry's fields — including nested bags like a stack trace or an\n\t\t * argument map that a table cell can only summarise. Default false\n\t\t * keeps a row click inert, as before. Ignored when `rowRoute` is set:\n\t\t * a purpose-built page beats a generic dialog.\n\t\t */\n\t\trowDetail: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Manifest page id (route name) to open on a row click, pushed as\n\t\t * `{ name: rowRoute, params: { id: row[rowKey] } }` — the same shape\n\t\t * CnIndexPage's `open-page` actions use. For a log whose detail surface\n\t\t * is a real page (a step timeline, a replay action) rather than a\n\t\t * read-only dump. Takes precedence over `rowDetail`.\n\t\t */\n\t\trowRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Row identifier property. Defaults to `id` (matches OR + most custom log shapes). */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/** Text shown when there are no log entries. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No log entries to show'),\n\t\t},\n\n\t\t/** Text shown when the fetch fails. */\n\t\terrorText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Could not load log entries'),\n\t\t},\n\n\t\t/** Label for the row-detail dialog's close button. */\n\t\tcloseLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Close'),\n\t\t},\n\n\t\t/**\n\t\t * Override the object store. Useful when the consuming app calls\n\t\t * `createObjectStore` with a custom ID. When null, the default\n\t\t * `useObjectStore()` is used. The component skips store wiring\n\t\t * entirely when in `source` mode.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * @event action Declared for hosts that dispatch log-row actions through\n\t\t * the `#row-actions` slot. Never emitted by this component itself; kept\n\t\t * in the surface because consumers may already listen for it.\n\t\t */\n\t\t'action',\n\t\t/**\n\t\t * @event row-click Emitted when a log row's body is clicked, whether or\n\t\t * not `rowDetail` opened the detail dialog — so a host can navigate\n\t\t * instead of (or as well as) showing the dialog. Payload: `(row, event)`,\n\t\t * the second being the native click event. A middle click emits\n\t\t * `row-aux-click` instead.\n\t\t * @type {object}\n\t\t */\n\t\t'row-click',\n\t\t/**\n\t\t * @event row-aux-click Emitted when a log row's body is middle-clicked,\n\t\t * for opening it in a new tab. Payload: `(row, event)`, the second being\n\t\t * the native auxclick event.\n\t\t * @type {object}\n\t\t */\n\t\t'row-aux-click',\n\t],\n\n\tsetup(props) {\n\t\t// Always returned, even when the composable is not used: Vue warns when\n\t\t// a computed reads a key setup() omitted, and `source` mode reads these.\n\t\t// `resolveFixedFilters` is included for the same reason — the\n\t\t// `filterSignature` computed calls it by name, and `source` mode (which\n\t\t// has no fixed filters at all) takes this early return.\n\t\tconst NO_LIST = {\n\t\t\tlist: null,\n\t\t\tobjectType: '',\n\t\t\tlistStore: null,\n\t\t\tresolveFixedFilters: () => ({}),\n\t\t}\n\t\tif (!props.register || !props.schema) {\n\t\t\treturn NO_LIST\n\t\t}\n\n\t\tconst store = props.store || useObjectStore()\n\t\t// The `store` prop has always accepted a hand-rolled object, but\n\t\t// useListView assumes a full OpenRegister store (pagination/errors/\n\t\t// fetchSchema). Duck-type it and fall back to the legacy no-params\n\t\t// fetch rather than throwing on a partial store.\n\t\tif (typeof store.fetchCollection !== 'function' || !store.pagination) {\n\t\t\treturn NO_LIST\n\t\t}\n\n\t\tconst objectType = `${props.register}-${props.schema}`\n\t\t// Must run synchronously: useListView's onMounted calls fetchSchema(),\n\t\t// which throws when the type is not registered yet. Positional\n\t\t// signature: (slug, schemaId, registerId).\n\t\tif (typeof store.registerObjectType === 'function') {\n\t\t\tstore.registerObjectType(objectType, props.schema, props.register)\n\t\t}\n\n\t\tconst instance = getCurrentInstance()\n\n\t\t// Token-resolution context for the `filter` prop's `@workspace.<key>` /\n\t\t// `@config.<key>` / `@object.<field>` tokens. Same injects + unwrap shape\n\t\t// as useSelfFetchList's `tokenCtx`. Without it `resolveFilterMap` was\n\t\t// called with no ctx, and `resolveFilterValue` returns an unresolvable\n\t\t// token VERBATIM — so a page declaring `filter: { client:\n\t\t// '@workspace.selectedClient' }` sent the literal string\n\t\t// \"@workspace.selectedClient\" to OpenRegister as a property filter and got\n\t\t// an empty table with no error, despite the prop doc naming that token.\n\t\t// All three default to absent, so an app that never provides them is\n\t\t// unaffected.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst workspaceCtxRaw = inject('cnWorkspaceContext', null)\n\t\tconst appConfigRaw = inject('cnAppConfig', null)\n\t\tconst unwrapCtx = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\t\tconst tokenCtx = () => {\n\t\t\tconst objCtx = unwrapCtx(objectCtxRaw)\n\t\t\tconst base = (objCtx && typeof objCtx === 'object') ? { ...objCtx } : {}\n\t\t\tbase.workspace = unwrapCtx(workspaceCtxRaw) || {}\n\t\t\tbase.config = unwrapCtx(appConfigRaw) || {}\n\t\t\treturn base\n\t\t}\n\t\t// The complete fetch scope: the deep-link query filters plus the `filter`\n\t\t// prop's route/`@`-token resolution. A getter, not a computed — it is\n\t\t// re-read on every fetch, so a `?jobId=` change re-scopes the list\n\t\t// without re-creating it, and `tokenCtx()` reflects the latest bags.\n\t\tconst resolveFixedFilters = () => {\n\t\t\tconst route = instance && instance.proxy && instance.proxy.$route\n\t\t\tconst ctx = tokenCtx()\n\t\t\treturn {\n\t\t\t\t...resolveQueryFilters(route && route.query, ctx),\n\t\t\t\t...resolveFilterMap(props.filter, (route && route.params) || {}, ctx),\n\t\t\t}\n\t\t}\n\n\t\tconst list = useListView(objectType, {\n\t\t\tobjectStore: store,\n\t\t\tdefaultPageSize: (props.pagination && props.pagination.limit) || undefined,\n\t\t\t// A `?_order=` deep link wins over the configured default, so a\n\t\t\t// reloaded or shared logs URL reproduces the sort the link carried —\n\t\t\t// the same precedence CnIndexPage's useSelfFetchList applies.\n\t\t\tdefaultSort: props.sortKey ? { key: props.sortKey, order: props.sortOrder || 'asc' } : undefined,\n\t\t\tdefaultSortKeys: parseSortKeysFromQuery(instance && instance.proxy && instance.proxy.$route)\n\t\t\t\t|| (props.sortKeys.length > 0 ? props.sortKeys : undefined),\n\t\t\tfixedFilters: resolveFixedFilters,\n\t\t})\n\n\t\treturn { list, objectType, listStore: store, resolveFixedFilters }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tlocalRows: [],\n\t\t\tlocalLoading: false,\n\t\t\tlocalError: null,\n\t\t\tlocalSortKeys: [],\n\t\t\tdetailRow: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Whether the component fetches via the object store + useListView. */\n\t\tusesStore() {\n\t\t\treturn !!this.list\n\t\t},\n\n\t\t/**\n\t\t * Whether the component should fetch via axios. Store mode needs BOTH\n\t\t * `register` and `schema`, so a half-configured pair with a `source` set\n\t\t * still falls back to the URL rather than the empty state.\n\t\t */\n\t\tusesSource() {\n\t\t\treturn !(this.register && this.schema) && !!this.source\n\t\t},\n\n\t\t/** Loading state of the active fetch mode. */\n\t\tloading() {\n\t\t\t// `list` is a bag of refs returned from setup(); only TOP-LEVEL setup\n\t\t\t// refs are auto-unwrapped, so every read below goes through `.value`.\n\t\t\treturn this.list ? this.list.loading.value : this.localLoading\n\t\t},\n\n\t\t/**\n\t\t * Fetch error. `fetchCollection` records failures on the store rather\n\t\t * than throwing, so store mode reads them from there — without this the\n\t\t * `#error` slot never rendered for a store-backed page.\n\t\t */\n\t\terror() {\n\t\t\tif (this.localError) {\n\t\t\t\treturn this.localError\n\t\t\t}\n\t\t\tif (this.list && this.listStore) {\n\t\t\t\treturn this.listStore.errors[this.objectType] || null\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/** Rows to render: the store collection, or locally sorted axios rows. */\n\t\trows() {\n\t\t\tif (this.list) {\n\t\t\t\treturn this.list.objects.value\n\t\t\t}\n\t\t\t// Legacy-store fallback: read the store's collection LIVE rather than\n\t\t\t// the snapshot `fetch()` took. `rows` used to reach the collection\n\t\t\t// through a computed; snapshotting into `localRows` meant a store that\n\t\t\t// REPLACES the array (rather than mutating it in place) no longer\n\t\t\t// reached the table. Only `this.store` is consulted — in this path it is\n\t\t\t// the partial store prop that made setup() bail — so `source` mode,\n\t\t\t// which has no store, still falls through to localRows.\n\t\t\tconst live = (this.register && this.schema && this.store)\n\t\t\t\t? this.store.collections?.[`${this.register}-${this.schema}`]\n\t\t\t\t: null\n\t\t\tconst base = Array.isArray(live) ? live : this.localRows\n\t\t\tif (this.localSortKeys.length === 0) {\n\t\t\t\treturn base\n\t\t\t}\n\t\t\t// CnDataTable is presentational — it never sorts its own rows — so\n\t\t\t// `source` mode has to apply the header sort itself.\n\t\t\treturn multiKeySort(base, this.localSortKeys.map((k) => ({ field: k.key, order: k.order })))\n\t\t},\n\n\t\t/**\n\t\t * Server pagination STATE (`{ total, page, pages, limit }`); null in\n\t\t * source mode, which has no paging. Distinct from the `pagination`\n\t\t * prop, which is the manifest's page-size config.\n\t\t */\n\t\tpaginationState() {\n\t\t\treturn this.list ? this.list.pagination.value : null\n\t\t},\n\n\t\t/**\n\t\t * The LOADED schema object (not the `schema` slug prop), forwarded to\n\t\t * CnDataTable for type-aware cell rendering and schema-derived columns.\n\t\t */\n\t\ttableSchema() {\n\t\t\treturn (this.list && this.list.schema.value) || null\n\t\t},\n\n\t\t/** Active ordered sort keys, from the composable or local source state. */\n\t\teffectiveSortKeys() {\n\t\t\treturn this.list ? this.list.sortKeys.value : this.localSortKeys\n\t\t},\n\n\t\t/** Primary sort key (mirrors `effectiveSortKeys[0]`) for CnDataTable. */\n\t\teffectiveSortKey() {\n\t\t\treturn this.effectiveSortKeys[0]?.key ?? null\n\t\t},\n\n\t\t/** Primary sort direction (mirrors `effectiveSortKeys[0]`). */\n\t\teffectiveSortOrder() {\n\t\t\treturn this.effectiveSortKeys[0]?.order ?? 'asc'\n\t\t},\n\n\t\t/**\n\t\t * Resolved columns. A consumer-provided list wins. Otherwise, when a\n\t\t * schema is loaded, return `[]` so CnDataTable derives the columns from\n\t\t * it — the legacy `timestamp/actor/action/target/details` default matched\n\t\t * no OpenRegister log schema and rendered five blank cells. `source` mode\n\t\t * and a failed schema load still get that legacy default.\n\t\t */\n\t\tresolvedColumns() {\n\t\t\tif (this.columns.length > 0) {\n\t\t\t\treturn this.columns.map((c) => (typeof c === 'string' ? { key: c, label: this.humanise(c) } : c))\n\t\t\t}\n\t\t\tif (this.tableSchema) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn legacyDefaultColumns()\n\t\t},\n\n\t\t/** Column slot names that the parent has provided (for pass-through). */\n\t\tslotColumns() {\n\t\t\treturn Object.keys(this.$slots || {})\n\t\t\t\t.filter((name) => name.startsWith('column-'))\n\t\t\t\t.map((name) => name.replace('column-', ''))\n\t\t},\n\n\t\t/** Dialog heading: the entry's message when it has one, else its id. */\n\t\tdetailTitle() {\n\t\t\tif (!this.detailRow) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst message = this.detailRow.message\n\t\t\tif (typeof message === 'string' && message !== '') {\n\t\t\t\treturn message\n\t\t\t}\n\t\t\tconst id = this.detailRow[this.rowKey]\n\t\t\treturn id ? String(id) : t('nextcloud-vue', 'Log entry')\n\t\t},\n\n\t\t/** The clicked row's primitive fields, as CnDetailGrid items. */\n\t\tdetailScalarItems() {\n\t\t\treturn this.detailEntries\n\t\t\t\t.filter(([, value]) => !this.isBag(value))\n\t\t\t\t.map(([key, value]) => ({ label: this.propertyLabel(key), value: String(value) }))\n\t\t},\n\n\t\t/**\n\t\t * The clicked row's nested fields. A bag of primitives (a stack trace's\n\t\t * frames, an argument map) renders as its own labelled grid; anything\n\t\t * deeper falls back to pretty-printed JSON.\n\t\t */\n\t\tdetailObjectBlocks() {\n\t\t\treturn this.detailEntries\n\t\t\t\t.filter(([, value]) => this.isBag(value))\n\t\t\t\t.map(([key, value]) => {\n\t\t\t\t\tconst entries = Object.entries(value)\n\t\t\t\t\tconst flat = entries.every(([, v]) => v === null || typeof v !== 'object')\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tlabel: this.propertyLabel(key),\n\t\t\t\t\t\titems: flat ? entries.map(([k, v]) => ({ label: this.humanise(k), value: String(v) })) : null,\n\t\t\t\t\t\tjson: flat ? null : JSON.stringify(value, null, 2),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Stable signature of the RESOLVED fetch scope — the query deep-link\n\t\t * filters plus the `filter` prop after route-param and `@`-token\n\t\t * resolution. Reading through the same getter the fetch uses is what\n\t\t * makes it exact: it tracks `$route`, `props.filter` and the injected\n\t\t * workspace / app-config bags, and it changes only when the request the\n\t\t * list would issue actually changes. Stringified so the watcher fires on\n\t\t * content, not identity. Empty string in `source` mode (no list).\n\t\t *\n\t\t * @return {string} The serialized filter map.\n\t\t */\n\t\tfilterSignature() {\n\t\t\tif (!this.list) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn JSON.stringify(this.resolveFixedFilters())\n\t\t},\n\n\t\t/** The clicked row's renderable entries — `@self` and empties dropped. */\n\t\tdetailEntries() {\n\t\t\tif (!this.detailRow) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn Object.entries(this.detailRow)\n\t\t\t\t.filter(([key, value]) => key !== '@self' && value !== null && value !== undefined && value !== '')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// ONE watcher over the whole fetch scope, rather than one per reactive\n\t\t// source that feeds it. `fixedFilters` is a plain getter re-read at fetch\n\t\t// time, so a refresh is all any of them needs — but watching them\n\t\t// individually got three things wrong:\n\t\t//\n\t\t// - `$route.query` and `$route.params` both fire on a single navigation,\n\t\t// so one route change issued TWO identical concurrent fetches, which\n\t\t// `fetchCollection` does not dedup and whose responses can land in\n\t\t// either order;\n\t\t// - they fired on navigation AWAY as well, because the route updates\n\t\t// before this component tears down. The refetch was then scoped by the\n\t\t// DESTINATION route's query (`/cases?status=open` → `GET\n\t\t// …/job_log?status=open`), overwriting the shared store collection that\n\t\t// any other mounted widget on the same objectType reads;\n\t\t// - a workspace / app-config bag mutation refetched even when no\n\t\t// `@workspace.`/`@config.` token was in play, so a volatile bag reset a\n\t\t// token-free page to page 1 on every write.\n\t\t//\n\t\t// The signature is the RESOLVED filter map, so it changes only when the\n\t\t// scope actually changes, and the route-name guard drops the fire that\n\t\t// belongs to the page being navigated to. No `immediate` — useListView\n\t\t// owns the initial fetch. This also covers a reactive `filter` prop\n\t\t// change, which had no watcher of its own at all.\n\t\tfilterSignature(next, prev) {\n\t\t\tif (!this.list || next === prev) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Guarded only when this page HAS a route name to compare against —\n\t\t\t// a router-less host, or one whose routes are unnamed, keeps the\n\t\t\t// unguarded behaviour rather than losing refetches to a comparison\n\t\t\t// that can never match.\n\t\t\tif (this._ownRouteName !== undefined && this.$route?.name !== this._ownRouteName) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.list.refresh(1)\n\t\t},\n\n\t\tregister(value, previous) {\n\t\t\tthis.onSourcePropChange('register', value, previous)\n\t\t},\n\n\t\tschema(value, previous) {\n\t\t\tthis.onSourcePropChange('schema', value, previous)\n\t\t},\n\n\t\tsource(value, previous) {\n\t\t\tthis.onSourcePropChange('source', value, previous)\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// The route this page belongs to, captured BEFORE any navigation can\n\t\t// change it — the `filterSignature` watcher's guard. CnPageRenderer mounts\n\t\t// a page when `$route.name === page.id`, so this is the page's own id.\n\t\tthis._ownRouteName = this.$route ? this.$route.name : undefined\n\t},\n\n\tmounted() {\n\t\t// In store mode useListView's own onMounted owns the initial fetch;\n\t\t// calling fetch() here too would double-request on every page load.\n\t\tif (!this.list) {\n\t\t\tthis.fetch()\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Capitalise + space a snake_case / camelCase key for a default column label.\n\t\t *\n\t\t * @param {string} key A log-entry property name, e.g. `'created_at'` or `'userAgent'`.\n\t\t * @return {string} The sentence-cased label, e.g. `'Created at'` / `'User agent'`.\n\t\t */\n\t\thumanise(key) {\n\t\t\tconst spaced = String(key).replace(/([A-Z])/g, ' $1').replace(/_/g, ' ').toLowerCase()\n\t\t\treturn spaced.charAt(0).toUpperCase() + spaced.slice(1)\n\t\t},\n\n\t\t/**\n\t\t * Label for a row property — the schema's own title when one is loaded.\n\t\t *\n\t\t * @param {string} key The property name.\n\t\t * @return {string} The display label.\n\t\t */\n\t\tpropertyLabel(key) {\n\t\t\tconst title = this.tableSchema?.properties?.[key]?.title\n\t\t\treturn (typeof title === 'string' && title !== '') ? title : this.humanise(key)\n\t\t},\n\n\t\t/**\n\t\t * Whether a value is a nested bag (object/array) rather than a scalar.\n\t\t *\n\t\t * @param {unknown} value The value to test.\n\t\t * @return {boolean} True for non-null objects and arrays.\n\t\t */\n\t\tisBag(value) {\n\t\t\treturn value !== null && typeof value === 'object'\n\t\t},\n\n\t\t/**\n\t\t * Fetch log entries from the resolved data source.\n\t\t *\n\t\t * Only used in `source` mode and in the legacy store fallback — the\n\t\t * composable owns fetching whenever it is active.\n\t\t */\n\t\tasync fetch() {\n\t\t\tif (this.register && this.schema) {\n\t\t\t\t// Legacy fallback: a partial `store` prop that useListView\n\t\t\t\t// cannot drive (see setup). Unfiltered + unpaginated, as before.\n\t\t\t\tconst store = this.store || useObjectStore()\n\t\t\t\tthis.localLoading = true\n\t\t\t\tthis.localError = null\n\t\t\t\ttry {\n\t\t\t\t\tif (typeof store.registerObjectType === 'function') {\n\t\t\t\t\t\tstore.registerObjectType(`${this.register}-${this.schema}`, this.schema, this.register)\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof store.fetchCollection === 'function') {\n\t\t\t\t\t\tawait store.fetchCollection(`${this.register}-${this.schema}`)\n\t\t\t\t\t}\n\t\t\t\t\tthis.localRows = store.collections?.[`${this.register}-${this.schema}`] ?? []\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.localError = err\n\t\t\t\t} finally {\n\t\t\t\t\tthis.localLoading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.usesSource) {\n\t\t\t\tthis.localLoading = true\n\t\t\t\tthis.localError = null\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await axios.get(prefixUrl(this.source))\n\t\t\t\t\tconst body = response?.data\n\t\t\t\t\tif (Array.isArray(body)) {\n\t\t\t\t\t\tthis.localRows = body\n\t\t\t\t\t} else if (body && Array.isArray(body.results)) {\n\t\t\t\t\t\tthis.localRows = body.results\n\t\t\t\t\t} else if (body && Array.isArray(body.entries)) {\n\t\t\t\t\t\tthis.localRows = body.entries\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.localRows = []\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.localError = err\n\t\t\t\t\tthis.localRows = []\n\t\t\t\t} finally {\n\t\t\t\t\tthis.localLoading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Misconfigured — surface a console warning so a developer notices.\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn('[CnLogsPage] Neither register+schema nor source configured; rendering empty state.')\n\t\t},\n\n\t\t/**\n\t\t * React to a data-source prop changing on a mounted page.\n\t\t *\n\t\t * Only the legacy / `source` paths can act on it, by refetching. Store\n\t\t * mode cannot: the object type, its store registration and the\n\t\t * `useListView` instance are all built once in `setup()` from the\n\t\t * initial pair, so a new `register`/`schema` needs a remount to take\n\t\t * effect (which CnPageRenderer does on a manifest page swap). That is\n\t\t * documented on the props, but it used to happen in complete silence —\n\t\t * a direct consumer rebinding the prop saw the old collection and no\n\t\t * indication why. Warns in development only, once per change.\n\t\t *\n\t\t * @param {string} name The prop that changed.\n\t\t * @param {unknown} value The new value.\n\t\t * @param {unknown} previous The previous value.\n\t\t * @return {void}\n\t\t */\n\t\tonSourcePropChange(name, value, previous) {\n\t\t\tif (!this.list) {\n\t\t\t\tthis.fetch()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(`[CnLogsPage] \\`${name}\\` changed from \"${previous}\" to \"${value}\" on a mounted store-backed page. `\n\t\t\t\t\t+ 'The data source is bound once at setup, so this has no effect — remount the page (e.g. give it a '\n\t\t\t\t\t+ ':key) to pick up the new register/schema.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Apply a header sort. Server-side in store mode, client-side otherwise.\n\t\t *\n\t\t * @param {{key: string|null, order: string|null, keys?: Array<{key: string, order: string}>}} sort CnDataTable's sort payload.\n\t\t */\n\t\tonSort(sort) {\n\t\t\tif (this.list) {\n\t\t\t\tthis.list.onSort(sort)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.localSortKeys = Array.isArray(sort.keys)\n\t\t\t\t? sort.keys\n\t\t\t\t: (sort.key ? [{ key: sort.key, order: sort.order || 'asc' }] : [])\n\t\t},\n\n\t\t/**\n\t\t * Navigate to a page of results.\n\t\t *\n\t\t * @param {number} page The 1-based page number.\n\t\t */\n\t\tonPageChange(page) {\n\t\t\tthis.list?.onPageChange(page)\n\t\t},\n\n\t\t/**\n\t\t * Change the page size, returning to page 1.\n\t\t *\n\t\t * @param {number} size The new page size.\n\t\t */\n\t\tonPageSizeChange(size) {\n\t\t\tthis.list?.onPageSizeChange(size)\n\t\t},\n\n\t\t/**\n\t\t * Handle a row-body click: navigate to `rowRoute` when one is declared,\n\t\t * else open the detail dialog when `rowDetail` is set. Always re-emits\n\t\t * so a host can do its own thing regardless. A ctrl/cmd/shift click\n\t\t * opens `rowRoute` in a new tab.\n\t\t *\n\t\t * @param {object} row The clicked log entry.\n\t\t * @param {MouseEvent} [event] The originating click event.\n\t\t */\n\t\tonRowClick(row, event) {\n\t\t\tif (this.rowRoute) {\n\t\t\t\tmarkNewTabHandled(event, openRowTarget(event, { name: this.rowRoute, params: { id: row?.[this.rowKey] } }, this.$router))\n\t\t\t} else if (this.rowDetail) {\n\t\t\t\tthis.detailRow = row\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event row-click Emitted when a log row's body is clicked. Payload: `(row, event)` — the log entry and the native click event.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('row-click', row, event)\n\t\t},\n\n\t\t/**\n\t\t * Handle a row-body middle click: open `rowRoute` in a new tab, never\n\t\t * the detail dialog, and emit `row-aux-click` rather than `row-click`.\n\t\t *\n\t\t * @param {object} row The clicked log entry.\n\t\t * @param {MouseEvent} event The originating auxclick event.\n\t\t */\n\t\tonRowAuxClick(row, event) {\n\t\t\tif (this.rowRoute) {\n\t\t\t\tmarkNewTabHandled(event, openRowTarget(event, { name: this.rowRoute, params: { id: row?.[this.rowKey] } }, this.$router))\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event row-aux-click Emitted when a log row's body is middle-clicked. Payload: `(row, event)` — the log entry and the native auxclick event.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('row-aux-click', row, event)\n\t\t},\n\n\t\t/** Close the row-detail dialog. */\n\t\tcloseDetail() {\n\t\t\tthis.detailRow = null\n\t\t},\n\n\t\t/**\n\t\t * Re-fetch from the source. Exposed so refresh buttons in\n\t\t * actionsComponent can call `$parent.refresh()`.\n\t\t *\n\t\t * @public\n\t\t */\n\t\trefresh() {\n\t\t\tif (this.list) {\n\t\t\t\tthis.list.refresh(1)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetch()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* Padding matches CnIndexPage / CnDetailPage (see css/index-page.css and\n css/detail-page.css) — a logs page had none at all, so its table ran flush\n into both edges of the app content area. Gaps are expressed in the same\n grid-baseline unit as the rest of the fleet rather than raw pixels. */\n.cn-logs-page {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(4 * var(--default-grid-baseline));\n\tpadding: calc(5 * var(--default-grid-baseline));\n\tbox-sizing: border-box;\n}\n\n.cn-logs-page__actions {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__loading,\n.cn-logs-page__empty,\n.cn-logs-page__error {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-height: 200px;\n}\n\n.cn-logs-page__pagination {\n\tmargin-top: calc(3 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__detail {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tpadding: 0 calc(3 * var(--default-grid-baseline)) calc(3 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__detail-heading {\n\tmargin: calc(3 * var(--default-grid-baseline)) 0 0;\n\tfont-weight: bold;\n}\n\n.cn-logs-page__detail-json {\n\tmargin: 0;\n\tpadding: calc(2 * var(--default-grid-baseline));\n\toverflow-x: auto;\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-dark);\n\tfont-family: monospace;\n\twhite-space: pre;\n}\n\n/* Same breakpoint + step-down as CnDetailPage, so the three page types stay in\n sync on narrow viewports. */\n@media (max-width: 768px) {\n\t.cn-logs-page {\n\t\tpadding: calc(3 * var(--default-grid-baseline));\n\t}\n}\n</style>\n"],"names":["_renderSlot","_openBlock","_createElementBlock","_createVNode","_Fragment","_renderList","_withCtx","_createElementVNode"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import script from './CnNextStepCard.vue2.js';
|
|
2
|
+
import { render } from './CnNextStepCard.vue3.js';
|
|
3
|
+
|
|
4
|
+
script.render = render;
|
|
5
|
+
script.__scopeId = "data-v-3a9ce962";
|
|
6
|
+
script.__file = "src/components/CnNextStepCard/CnNextStepCard.vue";
|
|
7
|
+
const __sfc_main = script;
|
|
8
|
+
|
|
9
|
+
export { __sfc_main as default };
|
|
10
|
+
//# sourceMappingURL=CnNextStepCard.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnNextStepCard.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { translate } from '@nextcloud/l10n';
|
|
2
|
+
import { NcButton } from '@nextcloud/vue';
|
|
3
|
+
import Check from 'vue-material-design-icons/Check.vue';
|
|
4
|
+
|
|
5
|
+
let nextStepCardUid = 0;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CnNextStepCard: what this record needs now, next to the button that does it.
|
|
9
|
+
*
|
|
10
|
+
* A short checklist for the current step and one primary action. It answers
|
|
11
|
+
* "what do I do next?" on a record whose menu would otherwise make the handler
|
|
12
|
+
* guess. Renders nothing when there is no checklist, so a step that needs no
|
|
13
|
+
* explaining shows no empty card.
|
|
14
|
+
*
|
|
15
|
+
* ```vue
|
|
16
|
+
* <CnNextStepCard
|
|
17
|
+
* title="What now? Step 2: handling"
|
|
18
|
+
* :items="[
|
|
19
|
+
* { label: 'Receipt confirmed', done: true },
|
|
20
|
+
* { label: 'Review the open documents', hint: '2 of 5 done' },
|
|
21
|
+
* { label: 'Draft the decision' },
|
|
22
|
+
* ]"
|
|
23
|
+
* action-label="Continue reviewing"
|
|
24
|
+
* after="Then: step 3, decision"
|
|
25
|
+
* @action="onContinue" />
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* CnDetailPage renders this card from its `nextStep` config, and the
|
|
29
|
+
* `next-step` detail widget type renders it inside a grid.
|
|
30
|
+
*/
|
|
31
|
+
var script = {
|
|
32
|
+
name: 'CnNextStepCard',
|
|
33
|
+
|
|
34
|
+
components: {
|
|
35
|
+
Check,
|
|
36
|
+
NcButton,
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
props: {
|
|
40
|
+
/** Heading of the card, for example "What now? Step 2: handling". */
|
|
41
|
+
title: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: '',
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/** Element the heading renders as. Pick the level that fits the page outline. */
|
|
47
|
+
titleTag: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: 'h3',
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The checklist: `{ label, done?, hint? }` per item. The first item
|
|
54
|
+
* that is not done is emphasised as the one to pick up now.
|
|
55
|
+
*/
|
|
56
|
+
items: {
|
|
57
|
+
type: Array,
|
|
58
|
+
default: () => [],
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/** Label of the primary button. Empty renders no button. */
|
|
62
|
+
actionLabel: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: '',
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/** DOM id for the primary button, so a skip link can land on it. */
|
|
68
|
+
actionId: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: '',
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/** Disable the primary button, for example while its request is in flight. */
|
|
74
|
+
actionDisabled: {
|
|
75
|
+
type: Boolean,
|
|
76
|
+
default: false,
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/** One line under the button saying what follows, for example "Then: step 3, decision". */
|
|
80
|
+
after: {
|
|
81
|
+
type: String,
|
|
82
|
+
default: '',
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/** Text state of a done item, read by screen readers. */
|
|
86
|
+
doneLabel: {
|
|
87
|
+
type: String,
|
|
88
|
+
default: () => translate('nextcloud-vue', 'Done'),
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/** Text state of an open item, read by screen readers. */
|
|
92
|
+
todoLabel: {
|
|
93
|
+
type: String,
|
|
94
|
+
default: () => translate('nextcloud-vue', 'To do'),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
emits: [
|
|
99
|
+
/** The primary button was pressed. */
|
|
100
|
+
'action',
|
|
101
|
+
],
|
|
102
|
+
|
|
103
|
+
data() {
|
|
104
|
+
nextStepCardUid += 1;
|
|
105
|
+
return {
|
|
106
|
+
titleId: `cn-next-step-title-${nextStepCardUid}`,
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
computed: {
|
|
111
|
+
/**
|
|
112
|
+
* The items that can render: an object with a label.
|
|
113
|
+
*
|
|
114
|
+
* @return {Array<{label: string, done: boolean, hint: string}>} The checklist.
|
|
115
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
116
|
+
*/
|
|
117
|
+
normalisedItems() {
|
|
118
|
+
return (Array.isArray(this.items) ? this.items : [])
|
|
119
|
+
.filter((item) => item && typeof item.label === 'string' && item.label !== '')
|
|
120
|
+
.map((item) => ({
|
|
121
|
+
label: item.label,
|
|
122
|
+
done: item.done === true,
|
|
123
|
+
hint: typeof item.hint === 'string' ? item.hint : '',
|
|
124
|
+
}))
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Index of the first open item, the one to pick up now.
|
|
129
|
+
*
|
|
130
|
+
* @return {number} The index, or -1 when everything is done.
|
|
131
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
132
|
+
*/
|
|
133
|
+
currentIndex() {
|
|
134
|
+
return this.normalisedItems.findIndex((item) => !item.done)
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Accessible name for a card without a title.
|
|
139
|
+
*
|
|
140
|
+
* @return {string} The label.
|
|
141
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
142
|
+
*/
|
|
143
|
+
fallbackLabel() {
|
|
144
|
+
return translate('nextcloud-vue', 'Next step')
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export { script as default };
|
|
150
|
+
//# sourceMappingURL=CnNextStepCard.vue2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnNextStepCard.vue2.js","sources":["../../../../src/components/CnNextStepCard/CnNextStepCard.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tv-if=\"normalisedItems.length > 0\"\n\t\tclass=\"cn-next-step-card\"\n\t\tdata-testid=\"cn-next-step-card\"\n\t\t:aria-labelledby=\"title ? titleId : null\"\n\t\t:aria-label=\"title ? null : fallbackLabel\">\n\t\t<div class=\"cn-next-step-card__main\">\n\t\t\t<component\n\t\t\t\t:is=\"titleTag\"\n\t\t\t\tv-if=\"title\"\n\t\t\t\t:id=\"titleId\"\n\t\t\t\tclass=\"cn-next-step-card__title\">\n\t\t\t\t{{ title }}\n\t\t\t</component>\n\t\t\t<ul class=\"cn-next-step-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"(item, index) in normalisedItems\"\n\t\t\t\t\t:key=\"`${index}-${item.label}`\"\n\t\t\t\t\tclass=\"cn-next-step-card__item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-next-step-card__item--done': item.done,\n\t\t\t\t\t\t'cn-next-step-card__item--current': index === currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\tdata-testid=\"cn-next-step-item\">\n\t\t\t\t\t<span class=\"cn-next-step-card__marker\" aria-hidden=\"true\">\n\t\t\t\t\t\t<Check v-if=\"item.done\" :size=\"14\" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-next-step-card__label\">{{ item.label }}</span>\n\t\t\t\t\t<span v-if=\"item.hint\" class=\"cn-next-step-card__hint\">{{ item.hint }}</span>\n\t\t\t\t\t<!-- The state in words. The marker is a shape and a colour, and\n\t\t\t\t\t neither reaches a screen reader or a reader who cannot tell\n\t\t\t\t\t the two colours apart. -->\n\t\t\t\t\t<span class=\"cn-next-step-card__state\">{{ item.done ? doneLabel : todoLabel }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\t\t<div v-if=\"actionLabel || $slots.action || after\" class=\"cn-next-step-card__side\">\n\t\t\t<!-- @slot action Replace the primary button, for a host that renders its own control. -->\n\t\t\t<slot name=\"action\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"actionLabel\"\n\t\t\t\t\t:id=\"actionId || null\"\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"actionDisabled\"\n\t\t\t\t\tdata-testid=\"cn-next-step-action\"\n\t\t\t\t\t@click=\"$emit('action')\">\n\t\t\t\t\t{{ actionLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</slot>\n\t\t\t<span v-if=\"after\" class=\"cn-next-step-card__after\">{{ after }}</span>\n\t\t</div>\n\t</section>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport Check from 'vue-material-design-icons/Check.vue'\n\nlet nextStepCardUid = 0\n\n/**\n * CnNextStepCard: what this record needs now, next to the button that does it.\n *\n * A short checklist for the current step and one primary action. It answers\n * \"what do I do next?\" on a record whose menu would otherwise make the handler\n * guess. Renders nothing when there is no checklist, so a step that needs no\n * explaining shows no empty card.\n *\n * ```vue\n * <CnNextStepCard\n * title=\"What now? Step 2: handling\"\n * :items=\"[\n * { label: 'Receipt confirmed', done: true },\n * { label: 'Review the open documents', hint: '2 of 5 done' },\n * { label: 'Draft the decision' },\n * ]\"\n * action-label=\"Continue reviewing\"\n * after=\"Then: step 3, decision\"\n * @action=\"onContinue\" />\n * ```\n *\n * CnDetailPage renders this card from its `nextStep` config, and the\n * `next-step` detail widget type renders it inside a grid.\n */\nexport default {\n\tname: 'CnNextStepCard',\n\n\tcomponents: {\n\t\tCheck,\n\t\tNcButton,\n\t},\n\n\tprops: {\n\t\t/** Heading of the card, for example \"What now? Step 2: handling\". */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Element the heading renders as. Pick the level that fits the page outline. */\n\t\ttitleTag: {\n\t\t\ttype: String,\n\t\t\tdefault: 'h3',\n\t\t},\n\n\t\t/**\n\t\t * The checklist: `{ label, done?, hint? }` per item. The first item\n\t\t * that is not done is emphasised as the one to pick up now.\n\t\t */\n\t\titems: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Label of the primary button. Empty renders no button. */\n\t\tactionLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** DOM id for the primary button, so a skip link can land on it. */\n\t\tactionId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Disable the primary button, for example while its request is in flight. */\n\t\tactionDisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** One line under the button saying what follows, for example \"Then: step 3, decision\". */\n\t\tafter: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Text state of a done item, read by screen readers. */\n\t\tdoneLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Done'),\n\t\t},\n\n\t\t/** Text state of an open item, read by screen readers. */\n\t\ttodoLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'To do'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The primary button was pressed. */\n\t\t'action',\n\t],\n\n\tdata() {\n\t\tnextStepCardUid += 1\n\t\treturn {\n\t\t\ttitleId: `cn-next-step-title-${nextStepCardUid}`,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The items that can render: an object with a label.\n\t\t *\n\t\t * @return {Array<{label: string, done: boolean, hint: string}>} The checklist.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n\t\t */\n\t\tnormalisedItems() {\n\t\t\treturn (Array.isArray(this.items) ? this.items : [])\n\t\t\t\t.filter((item) => item && typeof item.label === 'string' && item.label !== '')\n\t\t\t\t.map((item) => ({\n\t\t\t\t\tlabel: item.label,\n\t\t\t\t\tdone: item.done === true,\n\t\t\t\t\thint: typeof item.hint === 'string' ? item.hint : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Index of the first open item, the one to pick up now.\n\t\t *\n\t\t * @return {number} The index, or -1 when everything is done.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n\t\t */\n\t\tcurrentIndex() {\n\t\t\treturn this.normalisedItems.findIndex((item) => !item.done)\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for a card without a title.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n\t\t */\n\t\tfallbackLabel() {\n\t\t\treturn t('nextcloud-vue', 'Next step')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-next-step-card {\n\talign-items: center;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\t/* The edge says \"this is the thing to act on\". An inset shadow, so it\n\t follows the card's rounded corners instead of squaring them off. */\n\tbox-shadow: inset 4px 0 0 var(--color-primary-element);\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(4 * var(--default-grid-baseline)) calc(8 * var(--default-grid-baseline));\n\tpadding: calc(5 * var(--default-grid-baseline)) calc(6 * var(--default-grid-baseline));\n\tpadding-inline-start: calc(7 * var(--default-grid-baseline));\n}\n\n.cn-next-step-card__main {\n\tdisplay: flex;\n\tflex: 1 1 420px;\n\tflex-direction: column;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tmin-width: 0;\n}\n\n.cn-next-step-card__title {\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n\tletter-spacing: 0.06em;\n\tmargin: 0;\n\ttext-transform: uppercase;\n}\n\n.cn-next-step-card__list {\n\tdisplay: grid;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-next-step-card__item {\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n}\n\n.cn-next-step-card__item--current {\n\tcolor: var(--color-main-text);\n}\n\n.cn-next-step-card__item--current .cn-next-step-card__label {\n\tfont-weight: 700;\n}\n\n.cn-next-step-card__marker {\n\talign-items: center;\n\tborder: 2px solid var(--color-border-maxcontrast);\n\tborder-radius: 50%;\n\tbox-sizing: border-box;\n\tdisplay: inline-flex;\n\tflex: none;\n\theight: 20px;\n\tjustify-content: center;\n\twidth: 20px;\n}\n\n.cn-next-step-card__item--current .cn-next-step-card__marker {\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-next-step-card__item--done .cn-next-step-card__marker {\n\tbackground: var(--color-primary-element);\n\tborder-color: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-next-step-card__hint {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n/* Read, not seen. */\n.cn-next-step-card__state {\n\tborder: 0;\n\tclip-path: inset(50%);\n\theight: 1px;\n\tmargin: -1px;\n\toverflow: hidden;\n\tpadding: 0;\n\tposition: absolute;\n\twhite-space: nowrap;\n\twidth: 1px;\n}\n\n.cn-next-step-card__side {\n\talign-items: flex-start;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-next-step-card__after {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n</style>\n"],"names":["t"],"mappings":";;;;AAiEA,IAAI,kBAAkB,EAAA;;AAEtB;AACC;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;;;AAGA;AACA;;;;AAID,aAAe;AACd,CAAA,IAAI,EAAE,gBAAgB;;CAEtB,UAAU,EAAE;EACX,KAAK;AACL,EAAA,QAAQ;EACR;;AAED,CAAA,KAAK,EAAE;;AAEN,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;AACA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;AAEC;;AAED,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;;AAGD,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;AACA,EAAA,cAAc,EAAE;GACf,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;AACA,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;EACA,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAC;GACzC;;;EAGD,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAC;GAC1C;EACD;;AAED,CAAA,KAAK,EAAE;AACN;AACA,EAAA,QAAQ;EACR;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,eAAc,IAAK,EAAA;AACnB,EAAA,OAAO;GACN,OAAO,EAAE,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAAC;AACjD,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;AACC;AACA;AACA;;;AAGD,EAAA,eAAe,GAAG;GACjB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAA,GAAI,IAAI,CAAC,KAAM,GAAE,EAAE;AAClD,KAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAG,IAAK,OAAO,IAAI,CAAC,KAAM,KAAI,QAAS,IAAG,IAAI,CAAC,KAAM,KAAI,EAAE,CAAA;AAC7E,KAAC,GAAG,CAAC,CAAC,IAAI,MAAM;AACf,KAAA,KAAK,EAAE,IAAI,CAAC,KAAK;AACjB,KAAA,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI;AACxB,KAAA,IAAI,EAAE,OAAO,IAAI,CAAC,SAAS,WAAW,IAAI,CAAC,IAAK,GAAE,EAAE;AACrD,KAAC,CAAC,CAAA;GACH;;AAED;AACC;AACA;;;;AAID,EAAA,YAAY,GAAG;GACd,OAAO,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAA;GAC1D;;AAED;AACC;AACA;;;;AAID,EAAA,aAAa,GAAG;AACf,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,WAAW,CAAA;GACrC;EACD;AACF;;;;"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, createElementVNode, createBlock, resolveDynamicComponent, withCtx, createTextVNode, toDisplayString, createCommentVNode, Fragment, renderList, normalizeClass, renderSlot } from 'vue';
|
|
2
|
+
|
|
3
|
+
const _hoisted_1 = ["aria-labelledby", "aria-label"];
|
|
4
|
+
const _hoisted_2 = { class: "cn-next-step-card__main" };
|
|
5
|
+
const _hoisted_3 = { class: "cn-next-step-card__list" };
|
|
6
|
+
const _hoisted_4 = {
|
|
7
|
+
class: "cn-next-step-card__marker",
|
|
8
|
+
"aria-hidden": "true"
|
|
9
|
+
};
|
|
10
|
+
const _hoisted_5 = { class: "cn-next-step-card__label" };
|
|
11
|
+
const _hoisted_6 = {
|
|
12
|
+
key: 0,
|
|
13
|
+
class: "cn-next-step-card__hint"
|
|
14
|
+
};
|
|
15
|
+
const _hoisted_7 = { class: "cn-next-step-card__state" };
|
|
16
|
+
const _hoisted_8 = {
|
|
17
|
+
key: 0,
|
|
18
|
+
class: "cn-next-step-card__side"
|
|
19
|
+
};
|
|
20
|
+
const _hoisted_9 = {
|
|
21
|
+
key: 0,
|
|
22
|
+
class: "cn-next-step-card__after"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
26
|
+
const _component_Check = resolveComponent("Check");
|
|
27
|
+
const _component_NcButton = resolveComponent("NcButton");
|
|
28
|
+
|
|
29
|
+
return ($options.normalisedItems.length > 0)
|
|
30
|
+
? (openBlock(), createElementBlock("section", {
|
|
31
|
+
key: 0,
|
|
32
|
+
class: "cn-next-step-card",
|
|
33
|
+
"data-testid": "cn-next-step-card",
|
|
34
|
+
"aria-labelledby": $props.title ? $data.titleId : null,
|
|
35
|
+
"aria-label": $props.title ? null : $options.fallbackLabel
|
|
36
|
+
}, [
|
|
37
|
+
createElementVNode("div", _hoisted_2, [
|
|
38
|
+
($props.title)
|
|
39
|
+
? (openBlock(), createBlock(resolveDynamicComponent($props.titleTag), {
|
|
40
|
+
key: 0,
|
|
41
|
+
id: $data.titleId,
|
|
42
|
+
class: "cn-next-step-card__title"
|
|
43
|
+
}, {
|
|
44
|
+
default: withCtx(() => [
|
|
45
|
+
createTextVNode(toDisplayString($props.title), 1 /* TEXT */)
|
|
46
|
+
]),
|
|
47
|
+
_: 1 /* STABLE */
|
|
48
|
+
}, 8 /* PROPS */, ["id"]))
|
|
49
|
+
: createCommentVNode("v-if", true),
|
|
50
|
+
createElementVNode("ul", _hoisted_3, [
|
|
51
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.normalisedItems, (item, index) => {
|
|
52
|
+
return (openBlock(), createElementBlock("li", {
|
|
53
|
+
key: `${index}-${item.label}`,
|
|
54
|
+
class: normalizeClass(["cn-next-step-card__item", {
|
|
55
|
+
'cn-next-step-card__item--done': item.done,
|
|
56
|
+
'cn-next-step-card__item--current': index === $options.currentIndex,
|
|
57
|
+
}]),
|
|
58
|
+
"data-testid": "cn-next-step-item"
|
|
59
|
+
}, [
|
|
60
|
+
createElementVNode("span", _hoisted_4, [
|
|
61
|
+
(item.done)
|
|
62
|
+
? (openBlock(), createBlock(_component_Check, {
|
|
63
|
+
key: 0,
|
|
64
|
+
size: 14
|
|
65
|
+
}))
|
|
66
|
+
: createCommentVNode("v-if", true)
|
|
67
|
+
]),
|
|
68
|
+
createElementVNode("span", _hoisted_5, toDisplayString(item.label), 1 /* TEXT */),
|
|
69
|
+
(item.hint)
|
|
70
|
+
? (openBlock(), createElementBlock("span", _hoisted_6, toDisplayString(item.hint), 1 /* TEXT */))
|
|
71
|
+
: createCommentVNode("v-if", true),
|
|
72
|
+
createElementVNode("span", _hoisted_7, toDisplayString(item.done ? $props.doneLabel : $props.todoLabel), 1 /* TEXT */)
|
|
73
|
+
], 2 /* CLASS */))
|
|
74
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
75
|
+
])
|
|
76
|
+
]),
|
|
77
|
+
($props.actionLabel || _ctx.$slots.action || $props.after)
|
|
78
|
+
? (openBlock(), createElementBlock("div", _hoisted_8, [
|
|
79
|
+
renderSlot(_ctx.$slots, "action", {}, () => [
|
|
80
|
+
($props.actionLabel)
|
|
81
|
+
? (openBlock(), createBlock(_component_NcButton, {
|
|
82
|
+
key: 0,
|
|
83
|
+
id: $props.actionId || null,
|
|
84
|
+
variant: "primary",
|
|
85
|
+
disabled: $props.actionDisabled,
|
|
86
|
+
"data-testid": "cn-next-step-action",
|
|
87
|
+
onClick: _cache[0] || (_cache[0] = $event => (_ctx.$emit('action')))
|
|
88
|
+
}, {
|
|
89
|
+
default: withCtx(() => [
|
|
90
|
+
createTextVNode(toDisplayString($props.actionLabel), 1 /* TEXT */)
|
|
91
|
+
]),
|
|
92
|
+
_: 1 /* STABLE */
|
|
93
|
+
}, 8 /* PROPS */, ["id", "disabled"]))
|
|
94
|
+
: createCommentVNode("v-if", true)
|
|
95
|
+
]),
|
|
96
|
+
($props.after)
|
|
97
|
+
? (openBlock(), createElementBlock("span", _hoisted_9, toDisplayString($props.after), 1 /* TEXT */))
|
|
98
|
+
: createCommentVNode("v-if", true)
|
|
99
|
+
]))
|
|
100
|
+
: createCommentVNode("v-if", true)
|
|
101
|
+
], 8 /* PROPS */, _hoisted_1))
|
|
102
|
+
: createCommentVNode("v-if", true)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { render };
|
|
106
|
+
//# sourceMappingURL=CnNextStepCard.vue3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnNextStepCard.vue3.js","sources":["../../../../src/components/CnNextStepCard/CnNextStepCard.vue?vue&type=template&id=3a9ce962&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<section\n\t\tv-if=\"normalisedItems.length > 0\"\n\t\tclass=\"cn-next-step-card\"\n\t\tdata-testid=\"cn-next-step-card\"\n\t\t:aria-labelledby=\"title ? titleId : null\"\n\t\t:aria-label=\"title ? null : fallbackLabel\">\n\t\t<div class=\"cn-next-step-card__main\">\n\t\t\t<component\n\t\t\t\t:is=\"titleTag\"\n\t\t\t\tv-if=\"title\"\n\t\t\t\t:id=\"titleId\"\n\t\t\t\tclass=\"cn-next-step-card__title\">\n\t\t\t\t{{ title }}\n\t\t\t</component>\n\t\t\t<ul class=\"cn-next-step-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"(item, index) in normalisedItems\"\n\t\t\t\t\t:key=\"`${index}-${item.label}`\"\n\t\t\t\t\tclass=\"cn-next-step-card__item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-next-step-card__item--done': item.done,\n\t\t\t\t\t\t'cn-next-step-card__item--current': index === currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\tdata-testid=\"cn-next-step-item\">\n\t\t\t\t\t<span class=\"cn-next-step-card__marker\" aria-hidden=\"true\">\n\t\t\t\t\t\t<Check v-if=\"item.done\" :size=\"14\" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-next-step-card__label\">{{ item.label }}</span>\n\t\t\t\t\t<span v-if=\"item.hint\" class=\"cn-next-step-card__hint\">{{ item.hint }}</span>\n\t\t\t\t\t<!-- The state in words. The marker is a shape and a colour, and\n\t\t\t\t\t neither reaches a screen reader or a reader who cannot tell\n\t\t\t\t\t the two colours apart. -->\n\t\t\t\t\t<span class=\"cn-next-step-card__state\">{{ item.done ? doneLabel : todoLabel }}</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\t\t<div v-if=\"actionLabel || $slots.action || after\" class=\"cn-next-step-card__side\">\n\t\t\t<!-- @slot action Replace the primary button, for a host that renders its own control. -->\n\t\t\t<slot name=\"action\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"actionLabel\"\n\t\t\t\t\t:id=\"actionId || null\"\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"actionDisabled\"\n\t\t\t\t\tdata-testid=\"cn-next-step-action\"\n\t\t\t\t\t@click=\"$emit('action')\">\n\t\t\t\t\t{{ actionLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</slot>\n\t\t\t<span v-if=\"after\" class=\"cn-next-step-card__after\">{{ after }}</span>\n\t\t</div>\n\t</section>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport Check from 'vue-material-design-icons/Check.vue'\n\nlet nextStepCardUid = 0\n\n/**\n * CnNextStepCard: what this record needs now, next to the button that does it.\n *\n * A short checklist for the current step and one primary action. It answers\n * \"what do I do next?\" on a record whose menu would otherwise make the handler\n * guess. Renders nothing when there is no checklist, so a step that needs no\n * explaining shows no empty card.\n *\n * ```vue\n * <CnNextStepCard\n * title=\"What now? Step 2: handling\"\n * :items=\"[\n * { label: 'Receipt confirmed', done: true },\n * { label: 'Review the open documents', hint: '2 of 5 done' },\n * { label: 'Draft the decision' },\n * ]\"\n * action-label=\"Continue reviewing\"\n * after=\"Then: step 3, decision\"\n * @action=\"onContinue\" />\n * ```\n *\n * CnDetailPage renders this card from its `nextStep` config, and the\n * `next-step` detail widget type renders it inside a grid.\n */\nexport default {\n\tname: 'CnNextStepCard',\n\n\tcomponents: {\n\t\tCheck,\n\t\tNcButton,\n\t},\n\n\tprops: {\n\t\t/** Heading of the card, for example \"What now? Step 2: handling\". */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Element the heading renders as. Pick the level that fits the page outline. */\n\t\ttitleTag: {\n\t\t\ttype: String,\n\t\t\tdefault: 'h3',\n\t\t},\n\n\t\t/**\n\t\t * The checklist: `{ label, done?, hint? }` per item. The first item\n\t\t * that is not done is emphasised as the one to pick up now.\n\t\t */\n\t\titems: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Label of the primary button. Empty renders no button. */\n\t\tactionLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** DOM id for the primary button, so a skip link can land on it. */\n\t\tactionId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Disable the primary button, for example while its request is in flight. */\n\t\tactionDisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** One line under the button saying what follows, for example \"Then: step 3, decision\". */\n\t\tafter: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Text state of a done item, read by screen readers. */\n\t\tdoneLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Done'),\n\t\t},\n\n\t\t/** Text state of an open item, read by screen readers. */\n\t\ttodoLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'To do'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The primary button was pressed. */\n\t\t'action',\n\t],\n\n\tdata() {\n\t\tnextStepCardUid += 1\n\t\treturn {\n\t\t\ttitleId: `cn-next-step-title-${nextStepCardUid}`,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The items that can render: an object with a label.\n\t\t *\n\t\t * @return {Array<{label: string, done: boolean, hint: string}>} The checklist.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n\t\t */\n\t\tnormalisedItems() {\n\t\t\treturn (Array.isArray(this.items) ? this.items : [])\n\t\t\t\t.filter((item) => item && typeof item.label === 'string' && item.label !== '')\n\t\t\t\t.map((item) => ({\n\t\t\t\t\tlabel: item.label,\n\t\t\t\t\tdone: item.done === true,\n\t\t\t\t\thint: typeof item.hint === 'string' ? item.hint : '',\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Index of the first open item, the one to pick up now.\n\t\t *\n\t\t * @return {number} The index, or -1 when everything is done.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n\t\t */\n\t\tcurrentIndex() {\n\t\t\treturn this.normalisedItems.findIndex((item) => !item.done)\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for a card without a title.\n\t\t *\n\t\t * @return {string} The label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card\n\t\t */\n\t\tfallbackLabel() {\n\t\t\treturn t('nextcloud-vue', 'Next step')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-next-step-card {\n\talign-items: center;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\t/* The edge says \"this is the thing to act on\". An inset shadow, so it\n\t follows the card's rounded corners instead of squaring them off. */\n\tbox-shadow: inset 4px 0 0 var(--color-primary-element);\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(4 * var(--default-grid-baseline)) calc(8 * var(--default-grid-baseline));\n\tpadding: calc(5 * var(--default-grid-baseline)) calc(6 * var(--default-grid-baseline));\n\tpadding-inline-start: calc(7 * var(--default-grid-baseline));\n}\n\n.cn-next-step-card__main {\n\tdisplay: flex;\n\tflex: 1 1 420px;\n\tflex-direction: column;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n\tmin-width: 0;\n}\n\n.cn-next-step-card__title {\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n\tfont-weight: 700;\n\tletter-spacing: 0.06em;\n\tmargin: 0;\n\ttext-transform: uppercase;\n}\n\n.cn-next-step-card__list {\n\tdisplay: grid;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-next-step-card__item {\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: calc(2.5 * var(--default-grid-baseline));\n}\n\n.cn-next-step-card__item--current {\n\tcolor: var(--color-main-text);\n}\n\n.cn-next-step-card__item--current .cn-next-step-card__label {\n\tfont-weight: 700;\n}\n\n.cn-next-step-card__marker {\n\talign-items: center;\n\tborder: 2px solid var(--color-border-maxcontrast);\n\tborder-radius: 50%;\n\tbox-sizing: border-box;\n\tdisplay: inline-flex;\n\tflex: none;\n\theight: 20px;\n\tjustify-content: center;\n\twidth: 20px;\n}\n\n.cn-next-step-card__item--current .cn-next-step-card__marker {\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-next-step-card__item--done .cn-next-step-card__marker {\n\tbackground: var(--color-primary-element);\n\tborder-color: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n}\n\n.cn-next-step-card__hint {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n/* Read, not seen. */\n.cn-next-step-card__state {\n\tborder: 0;\n\tclip-path: inset(50%);\n\theight: 1px;\n\tmargin: -1px;\n\toverflow: hidden;\n\tpadding: 0;\n\tposition: absolute;\n\twhite-space: nowrap;\n\twidth: 1px;\n}\n\n.cn-next-step-card__side {\n\talign-items: flex-start;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-next-step-card__after {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_openBlock","_createBlock","_resolveDynamicComponent","_Fragment","_renderList","_toDisplayString"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import script from './CnNextStepWidget.vue2.js';
|
|
2
|
+
import { render } from './CnNextStepWidget.vue3.js';
|
|
3
|
+
|
|
4
|
+
script.render = render;
|
|
5
|
+
script.__file = "src/components/CnNextStepCard/CnNextStepWidget.vue";
|
|
6
|
+
const __sfc_main = script;
|
|
7
|
+
|
|
8
|
+
export { __sfc_main as default };
|
|
9
|
+
//# sourceMappingURL=CnNextStepWidget.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnNextStepWidget.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|