@conduction/nextcloud-vue 2.56.0-beta.37 → 2.56.0-beta.39
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/CnAdvancedFormDialog/CnMetadataTab.vue.js +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js +3 -2
- package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js.map +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue3.js.map +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue.js +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js +5 -4
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue3.js.map +1 -1
- package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue.js +1 -1
- package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js +2 -1
- package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +2 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +156 -14
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +105 -74
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue.js +1 -1
- package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js +2 -1
- package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue.js +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +2 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +6 -5
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue.js +1 -1
- package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js +2 -1
- package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js.map +1 -1
- package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +2 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +2 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +2 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue.js +1 -1
- package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js +3 -1
- package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js.map +1 -1
- package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js +2 -1
- package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue3.js.map +1 -1
- package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue.js +1 -1
- package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js +2 -1
- package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue.js +1 -1
- package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js +3 -1
- package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js.map +1 -1
- package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue3.js.map +1 -1
- package/dist/esm/components/CnFileManager/CnFileManager.vue.js +1 -1
- package/dist/esm/components/CnFileManager/CnFileManager.vue2.js +3 -1
- package/dist/esm/components/CnFileManager/CnFileManager.vue2.js.map +1 -1
- package/dist/esm/components/CnFileManager/CnFileManager.vue3.js.map +1 -1
- package/dist/esm/components/CnFilePreview/CnFilePreview.vue.js +1 -1
- package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js +2 -1
- package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js.map +1 -1
- package/dist/esm/components/CnFilePreview/CnFilePreview.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +49 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +3 -3
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue.js +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +2 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
- package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -5
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +2 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue.js +1 -1
- package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js +3 -2
- package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +82 -75
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js +5 -4
- package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue.js +1 -1
- package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js +2 -1
- package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js.map +1 -1
- package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue3.js.map +1 -1
- package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue.js +1 -1
- package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js +2 -1
- package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js.map +1 -1
- package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue3.js.map +1 -1
- package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js +2 -0
- package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js +2 -2
- package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue.js +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +2 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue.js +1 -1
- package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js +2 -1
- package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js.map +1 -1
- package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +36 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +55 -40
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksCard/CnTasksCard.vue.js +1 -1
- package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js +2 -1
- package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksCard/CnTasksCard.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +2 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue.js +1 -1
- package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js +2 -1
- package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue.js +1 -1
- package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js +2 -1
- package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +3 -26
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/dialogs/CnNoteHistoryDialog.vue.js +1 -1
- package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js +2 -1
- package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnNoteHistoryDialog.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js +3 -2
- package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js +4 -3
- package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/activity/CnActivityTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -2
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +6 -5
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue.js +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.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +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.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js +2 -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/field-inspection/CnFieldInspectionCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue.js +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.map +1 -1
- package/dist/esm/l10n/en.json.js +35 -2
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +38 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1068 -962
- package/dist/esm/store/useObjectStore.js +7 -2
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/boardColumns.js +50 -1
- package/dist/esm/utils/boardColumns.js.map +1 -1
- package/dist/esm/utils/boardReferenceColumns.js +115 -4
- package/dist/esm/utils/boardReferenceColumns.js.map +1 -1
- package/dist/esm/utils/contentHeaderLink.js +48 -1
- package/dist/esm/utils/contentHeaderLink.js.map +1 -1
- package/dist/esm/utils/schema.js +5 -4
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/userLocale.js +30 -0
- package/dist/esm/utils/userLocale.js.map +1 -0
- 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 +1088 -476
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1068 -962
- package/l10n/en.json +35 -2
- package/l10n/nl.json +38 -2
- package/package.json +1 -1
- package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +3 -2
- package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +5 -4
- package/src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue +2 -1
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +2 -1
- package/src/components/CnBoardView/CnBoardView.vue +189 -24
- package/src/components/CnCalendarEventPicker/CnCalendarEventPicker.vue +2 -1
- package/src/components/CnCalendarWidget/CnCalendarWidget.vue +2 -1
- package/src/components/CnChartWidget/CnChartWidget.vue +6 -5
- package/src/components/CnCollectivePagePicker/CnCollectivePagePicker.vue +2 -1
- package/src/components/CnConversationThread/CnConversationThread.vue +2 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +2 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -1
- package/src/components/CnDataMatrix/CnDataMatrix.vue +2 -1
- package/src/components/CnDetailPage/CnDetailHeaderChips.vue +2 -1
- package/src/components/CnEmailPicker/CnEmailPicker.vue +2 -1
- package/src/components/CnFederationStatus/CnFederationStatus.vue +3 -1
- package/src/components/CnFileManager/CnFileManager.vue +2 -1
- package/src/components/CnFilePreview/CnFilePreview.vue +2 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -6
- package/src/components/CnFollowToggle/CnFollowToggle.vue +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +30 -4
- package/src/components/CnNotesCard/CnNotesCard.vue +2 -1
- package/src/components/CnObjectCalendar/CnObjectCalendar.vue +3 -2
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +36 -0
- package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +5 -4
- package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +2 -1
- package/src/components/CnObjectSidebar/CnFlowTasksPanel.vue +2 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +2 -1
- package/src/components/CnObjectSidebar/CnTasksTab.vue +2 -1
- package/src/components/CnPropertySourceField/CnPropertySourceField.vue +2 -1
- package/src/components/CnRoadmapItem/CnRoadmapItem.vue +2 -1
- package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +4 -2
- package/src/components/CnStatsBlock/CnStatsBlock.vue +2 -1
- package/src/components/CnSummaryAggregates/CnSummaryAggregates.vue +2 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +71 -0
- package/src/components/CnTasksCard/CnTasksCard.vue +2 -1
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +2 -1
- package/src/components/CnTranslatedBadge/CnTranslatedBadge.vue +2 -1
- package/src/components/CnVersionHistory/CnVersionHistory.vue +2 -1
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +3 -26
- package/src/css/board.css +40 -0
- package/src/dialogs/CnNoteHistoryDialog.vue +2 -1
- package/src/integrations/builtin/activity/CnActivityCard.vue +3 -2
- package/src/integrations/builtin/activity/CnActivityTab.vue +4 -3
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +3 -2
- package/src/integrations/builtin/calendar/CnCalendarCard.vue +2 -1
- package/src/integrations/builtin/calendar/CnCalendarTab.vue +6 -5
- package/src/integrations/builtin/contactmoment/CnContactmomentCard.vue +2 -1
- package/src/integrations/builtin/cospend/CnCospendTab.vue +2 -1
- package/src/integrations/builtin/deck/CnDeckTab.vue +2 -1
- package/src/integrations/builtin/email/CnEmailCard.vue +2 -1
- package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +2 -1
- package/src/integrations/builtin/field-inspection/CnFieldInspectionTab.vue +2 -1
- package/src/integrations/builtin/photos/CnPhotosTab.vue +2 -1
- package/src/schemas/app-manifest-v2.schema.json +4 -1
- package/src/store/useObjectStore.js +7 -2
- package/src/utils/boardColumns.js +49 -0
- package/src/utils/boardReferenceColumns.js +114 -3
- package/src/utils/contentHeaderLink.js +47 -0
- package/src/utils/schema.js +5 -4
- package/src/utils/userLocale.js +26 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnActivityTab.vue3.js","sources":["../../../../../src/integrations/builtin/activity/CnActivityTab.vue?vue&type=template&id=9e1507d2&lang.js"],"sourcesContent":["<!--\n CnActivityTab — bespoke sidebar tab for the `activity` integration.\n\n Reads OpenRegister's MERGED activity feed (audit trail, NC Activity, files,\n notes, mail) for the object, paged on the feed's time cursor, with a chip\n per kind (a kind with no rows still shows, reading 0), reads off by default\n (the reader's choice is remembered per user), a from/until date range and\n an export of what is on screen. The wire names are in `activityFeedWire.js`.\n A server without the merged feed (404/405/501) makes the tab fall back to\n the single-source mode described below, which `legacyFeed` also selects.\n\n Single-source mode: renders a chronological timeline of NC Activity events grouped by day,\n with a type icon + actor + subject text + relative timestamp per row.\n\n Tier-2 surface (read-only — NC Activity entries are core-generated):\n - Filter bar: type dropdown, actor dropdown, date-range segmented\n control (24h / 7d / 30d / all).\n - Cursor-based \"load more\" pagination.\n - NO picker, NO inline create (entries are not user-authored here).\n\n Talks to the OpenRegister Tier-2 activity endpoints:\n GET /api/objects/{register}/{schema}/{objectId}/activity\n ?type=&actor=&after=&limit=&cursor=\n GET /api/integrations/activity/types?object={r}/{s}/{id}\n GET /api/integrations/activity/actors?object={r}/{s}/{id}\n backed by `OCA\\OpenRegister\\Service\\ActivityFilterService`, which wraps\n the wave-5.3 `MarkerLookupTrait` carve-out (NC Activity's single string\n `subject` column is the `[or:{uuid}]` marker target) with the filters\n and cursor pagination above. The carve-out is intentionally preserved.\n\n Surface behaviour:\n - Empty state with neutral copy when no activity matches the filters.\n - Loading + 501/503 \"unavailable\" + generic error banner all match\n CnIntegrationTab's degradation patterns (AD-23).\n\n See `openregister/openspec/changes/integration-activity/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-activity-tab\" :class=\"{ 'cn-activity-tab--board': isBoard }\">\n\t\t<div v-if=\"degraded\" class=\"cn-activity-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<!-- The History panel's header in the board look: the title, what the\n\t\t list is, and the two buttons at its end. -->\n\t\t<div v-if=\"isBoard && !degraded\" class=\"cn-activity-tab__board-head\" data-testid=\"cn-activity-board-head\">\n\t\t\t<div class=\"cn-activity-tab__board-titles\">\n\t\t\t\t<h2 class=\"cn-activity-tab__board-title\">\n\t\t\t\t\t{{ historyTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<p class=\"cn-activity-tab__board-subtitle\">\n\t\t\t\t\t{{ historySubtitle }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-activity-tab__board-actions\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showAddNote\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\tdata-testid=\"cn-activity-add-note\"\n\t\t\t\t\t@click=\"$emit('add-note')\">\n\t\t\t\t\t{{ addNoteLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"!legacy\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"visibleEntries.length === 0 || exporting\"\n\t\t\t\t\tdata-testid=\"cn-activity-export\"\n\t\t\t\t\t@click=\"exportFeed\">\n\t\t\t\t\t{{ exportLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-if=\"!degraded\" class=\"cn-activity-tab__filters\">\n\t\t\t<!-- Merged feed: one chip per kind. A kind with no rows keeps its chip, reading 0: gone would say the kind does not exist on this object. -->\n\t\t\t<div\n\t\t\t\tv-if=\"!legacy\"\n\t\t\t\tclass=\"cn-activity-tab__kinds\"\n\t\t\t\trole=\"group\"\n\t\t\t\t:aria-label=\"kindsGroupLabel\"\n\t\t\t\tdata-testid=\"cn-activity-kinds\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"kind in feedKinds\"\n\t\t\t\t\t:key=\"kind\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-activity-tab__kind\"\n\t\t\t\t\t:class=\"{ 'cn-activity-tab__kind--active': selectedKinds.includes(kind) }\"\n\t\t\t\t\t:aria-pressed=\"String(selectedKinds.includes(kind))\"\n\t\t\t\t\t:data-testid=\"`cn-activity-kind-${kind}`\"\n\t\t\t\t\t@click=\"toggleKind(kind)\">\n\t\t\t\t\t{{ kindLabel(kind) }}\n\t\t\t\t\t<span class=\"cn-activity-tab__kind-count\" :data-testid=\"`cn-activity-kind-count-${kind}`\">{{ kindCount(kind) }}</span>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div class=\"cn-activity-tab__filter-row\">\n\t\t\t\t<label v-if=\"legacy\" class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ typeLabel }}</span>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-model=\"selectedType\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\t@change=\"resetAndFetch\">\n\t\t\t\t\t\t<option value=\"\">{{ allTypesLabel }}</option>\n\t\t\t\t\t\t<option v-for=\"ty in types\" :key=\"ty\" :value=\"ty\">{{ ty }}</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t\t<label class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ actorLabel }}</span>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-model=\"selectedActor\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\t@change=\"resetAndFetch\">\n\t\t\t\t\t\t<option value=\"\">{{ allActorsLabel }}</option>\n\t\t\t\t\t\t<option v-for=\"ac in actors\" :key=\"ac\" :value=\"ac\">{{ ac }}</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<!-- Visibility filter (showVisibility): all, internal, public. A caller served the\n\t\t\t public view sees it fixed at public, with the reason. -->\n\t\t\t<div v-if=\"showVisibility\" class=\"cn-activity-tab__visibility\" data-testid=\"cn-activity-visibility\">\n\t\t\t\t<p v-if=\"publicViewOnly\" class=\"cn-activity-tab__visibility-fixed\" data-testid=\"cn-activity-visibility-fixed\">\n\t\t\t\t\t<CnVisibilityChip visibility=\"public\" />\n\t\t\t\t\t{{ publicOnlyLabel }}\n\t\t\t\t</p>\n\t\t\t\t<label v-else class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ visibilityLabel }}</span>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-model=\"selectedVisibility\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\tdata-testid=\"cn-activity-visibility-select\"\n\t\t\t\t\t\t@change=\"resetAndFetch\">\n\t\t\t\t\t\t<option value=\"\">{{ allVisibilityLabel }}</option>\n\t\t\t\t\t\t<option value=\"internal\">{{ internalLabel }}</option>\n\t\t\t\t\t\t<option value=\"public\">{{ publicLabel }}</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<label v-if=\"!legacy\" class=\"cn-activity-tab__reads\">\n\t\t\t\t<input\n\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t:checked=\"showReads\"\n\t\t\t\t\tdata-testid=\"cn-activity-reads\"\n\t\t\t\t\t@change=\"setShowReads($event.target.checked)\">\n\t\t\t\t{{ showReadsLabel }}\n\t\t\t</label>\n\t\t\t<div class=\"cn-activity-tab__range\" role=\"group\" :aria-label=\"rangeGroupLabel\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"range in ranges\"\n\t\t\t\t\t:key=\"range.key\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-activity-tab__range-btn\"\n\t\t\t\t\t:class=\"{ 'cn-activity-tab__range-btn--active': selectedRange === range.key }\"\n\t\t\t\t\t@click=\"selectRange(range.key)\">\n\t\t\t\t\t{{ range.label }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<!-- The range kept as a range: from and until, which is what the feed takes. -->\n\t\t\t<div v-if=\"!legacy\" class=\"cn-activity-tab__dates\">\n\t\t\t\t<label class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ fromLabel }}</span>\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\t:value=\"fromDate\"\n\t\t\t\t\t\tdata-testid=\"cn-activity-from\"\n\t\t\t\t\t\t@change=\"setDate('fromDate', $event.target.value)\">\n\t\t\t\t</label>\n\t\t\t\t<label class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ untilLabel }}</span>\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\t:value=\"untilDate\"\n\t\t\t\t\t\tdata-testid=\"cn-activity-until\"\n\t\t\t\t\t\t@change=\"setDate('untilDate', $event.target.value)\">\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<!-- The export is what is on screen: these rows and these filters, nothing re-queried. -->\n\t\t\t<NcButton\n\t\t\t\tv-if=\"!legacy && !isBoard\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:disabled=\"visibleEntries.length === 0 || exporting\"\n\t\t\t\tdata-testid=\"cn-activity-export\"\n\t\t\t\t@click=\"exportFeed\">\n\t\t\t\t{{ exportLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading && entries.length === 0\" />\n\t\t<div v-else-if=\"error\" class=\"cn-activity-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"visibleEntries.length === 0\" class=\"cn-sidebar-tab__empty cn-activity-tab__empty\">\n\t\t\t<Timeline :size=\"32\" class=\"cn-activity-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t</div>\n\t\t<!-- The board look draws the events as one rail: a 34px icon on a tint\n\t\t of its kind, a connector to the next event, the verb in bold, and a\n\t\t meta line of kind, who and when. -->\n\t\t<div v-else-if=\"isBoard\" class=\"cn-activity-tab__timeline cn-activity-tab__timeline--rail\">\n\t\t\t<ol class=\"cn-activity-tab__rail\" data-testid=\"cn-activity-rail\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t\t:key=\"entryKey(entry)\"\n\t\t\t\t\tclass=\"cn-activity-tab__event\"\n\t\t\t\t\t:class=\"`cn-activity-tab__event--${entry.kind || 'activity'}`\"\n\t\t\t\t\tdata-testid=\"cn-activity-event\">\n\t\t\t\t\t<span class=\"cn-activity-tab__event-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t<component :is=\"iconFor(entry)\" :size=\"18\" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<div class=\"cn-activity-tab__event-body\">\n\t\t\t\t\t\t<p class=\"cn-activity-tab__event-line\">\n\t\t\t\t\t\t\t<strong>{{ splitSubject(entry).verb }}</strong>{{ splitSubject(entry).rest }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p class=\"cn-activity-tab__event-meta\">\n\t\t\t\t\t\t\t{{ eventMeta(entry) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<CnVisibilityChip v-if=\"showVisibility\" :visibility=\"entry.visibility\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t\t<div v-if=\"hasMore\" class=\"cn-activity-tab__footer\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:wide=\"true\"\n\t\t\t\t\t:disabled=\"loadingMore\"\n\t\t\t\t\t@click=\"loadMore\">\n\t\t\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ loadMoreLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t\t<div v-else class=\"cn-activity-tab__timeline\">\n\t\t\t<section\n\t\t\t\tv-for=\"day in groupedByDay\"\n\t\t\t\t:key=\"day.key\"\n\t\t\t\tclass=\"cn-activity-tab__day\">\n\t\t\t\t<header class=\"cn-activity-tab__day-header\">\n\t\t\t\t\t<CalendarOutline :size=\"16\" />\n\t\t\t\t\t<span class=\"cn-activity-tab__day-label\">{{ day.label }}</span>\n\t\t\t\t\t<span class=\"cn-activity-tab__day-count\">{{ day.rows.length }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-activity-tab__list\">\n\t\t\t\t\t<NcListItem\n\t\t\t\t\t\tv-for=\"entry in day.rows\"\n\t\t\t\t\t\t:key=\"entryKey(entry)\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__row\"\n\t\t\t\t\t\t:name=\"subjectFor(entry)\"\n\t\t\t\t\t\t:bold=\"false\"\n\t\t\t\t\t\t:compact=\"true\"\n\t\t\t\t\t\t:forceDisplayActions=\"false\">\n\t\t\t\t\t\t<template #name>\n\t\t\t\t\t\t\t<span class=\"cn-activity-tab__subject\">{{ subjectFor(entry) }}</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-activity-tab__avatar-wrap\">\n\t\t\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t\t\t:user=\"avatarUser(entry)\"\n\t\t\t\t\t\t\t\t\t:displayName=\"actorFor(entry)\"\n\t\t\t\t\t\t\t\t\t:size=\"36\"\n\t\t\t\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t\t\t\t:disableTooltip=\"true\"\n\t\t\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t\t\t<span class=\"cn-activity-tab__type-badge\" :class=\"typeBadgeClass(entry)\">\n\t\t\t\t\t\t\t\t\t<component :is=\"iconFor(entry)\" :size=\"12\" />\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t\t<span class=\"cn-activity-tab__subname\">\n\t\t\t\t\t\t\t\t<span class=\"cn-activity-tab__actor\">{{ actorFor(entry) }}</span>\n\t\t\t\t\t\t\t\t<CnVisibilityChip v-if=\"showVisibility\" :visibility=\"entry.visibility\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"timestampMillis(entry)\" #details>\n\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\tclass=\"cn-activity-tab__time\"\n\t\t\t\t\t\t\t\t:timestamp=\"timestampMillis(entry)\"\n\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-else #details>\n\t\t\t\t\t\t\t<span class=\"cn-activity-tab__time\">{{ relativeTime(entry) }}</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcListItem>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t\t<div v-if=\"hasMore\" class=\"cn-activity-tab__footer\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:wide=\"true\"\n\t\t\t\t\t:disabled=\"loadingMore\"\n\t\t\t\t\t@click=\"loadMore\">\n\t\t\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ loadMoreLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AccountOutline from 'vue-material-design-icons/AccountOutline.vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CalendarClockOutline from 'vue-material-design-icons/CalendarClockOutline.vue'\nimport CalendarOutline from 'vue-material-design-icons/CalendarOutline.vue'\nimport CommentTextOutline from 'vue-material-design-icons/CommentTextOutline.vue'\nimport EmailOutline from 'vue-material-design-icons/EmailOutline.vue'\nimport FileOutline from 'vue-material-design-icons/FileOutline.vue'\nimport ShareVariantOutline from 'vue-material-design-icons/ShareVariantOutline.vue'\nimport TagOutline from 'vue-material-design-icons/TagOutline.vue'\nimport Timeline from 'vue-material-design-icons/Timeline.vue'\nimport CnVisibilityChip from '../../../components/CnVisibilityChip/CnVisibilityChip.vue'\nimport { normalizeLook } from '../../../composables/useLook.js'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { ACTIVITY_FEED_KINDS, exportBody, feedPath, feedQuery, isReadRow, parseFeed } from './activityFeedWire.js'\n\nconst DEFAULT_PAGE_SIZE = 25\nconst READS_PREFERENCE_KEY = 'activity.showReads'\nconst SECONDS_PER_DAY = 86400\n\n/**\n * CnActivityTab — bespoke chronological timeline for the `activity`\n * integration. Reads the merged feed (audit trail, NC Activity, files, notes,\n * mail) with a chip per kind, a reads toggle that is remembered per user, a\n * from/until range, an export of what is on screen, and cursor pagination on\n * the feed's time cursor. In `legacyFeed` mode (or against a server without\n * the merged feed) it reads the single-source Activity endpoint with type,\n * actor and date filters, as it did before.\n *\n * Renders activity entries grouped by day (today / yesterday / older\n * dates), with type icons, actor name, and a relative timestamp. Reads\n * from the OR Tier-2 activity endpoints; filters drive a fresh fetch,\n * \"load more\" walks the cursor returned by the backend.\n */\nexport default {\n\tname: 'CnActivityTab',\n\n\tcomponents: {\n\t\tCnVisibilityChip,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tCalendarOutline,\n\t\tEmailOutline,\n\t\tTimeline,\n\t\tFileOutline,\n\t\tAccountOutline,\n\t\tCommentTextOutline,\n\t\tShareVariantOutline,\n\t\tTagOutline,\n\t\tCalendarClockOutline,\n\t},\n\n\tinject: {\n\t\t/** The per-user preference group from CnAppRoot, so \"show reads\" is remembered. */\n\t\tcnUserPreferences: { default: null },\n\t\t/** The app's look, provided by CnAppRoot or CnPageRenderer (`nextcloud` or `board`). */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\n\t\t/** Stable integration id (forwarded from the registry — always `'activity'`). */\n\t\tintegrationId: { type: String, default: 'activity' },\n\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Number of entries per fetch. */\n\t\tpageSize: { type: Number, default: DEFAULT_PAGE_SIZE },\n\t\t/**\n\t\t * Read the single-source NC Activity endpoint (type, actor, date-range\n\t\t * filters, no kinds, reads toggle or export) instead of the merged feed.\n\t\t * The tab also falls back to it by itself when the server has no merged feed.\n\t\t */\n\t\tlegacyFeed: { type: Boolean, default: false },\n\t\t/**\n\t\t * Show a visibility chip on each row and a visibility filter (all,\n\t\t * internal, public) that sends `visibility` on the fetch. Off by default,\n\t\t * so a host that passes nothing renders the feed as before.\n\t\t */\n\t\tshowVisibility: { type: Boolean, default: false },\n\t\t/**\n\t\t * The server serves this caller the public view only (no `update` on the\n\t\t * object): the filter is then fixed at public and says why, instead of\n\t\t * offering options that change nothing. The host passes it.\n\t\t */\n\t\tpublicViewOnly: { type: Boolean, default: false },\n\t\t/**\n\t\t * Offer an \"Add note\" button in the board look's History header. It\n\t\t * emits `add-note`; the host opens whatever composes the note. Off by\n\t\t * default, so no button points at nothing.\n\t\t */\n\t\tshowAddNote: { type: Boolean, default: false },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No activity yet for this object') },\n\t\t/** Pre-translated unavailable banner. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Activity is currently unavailable.') },\n\t\t/** Pre-translated load-more button label. */\n\t\tloadMoreLabel: { type: String, default: () => t('nextcloud-vue', 'Load more') },\n\t},\n\n\temits: ['exported', 'add-note'],\n\n\tdata() {\n\t\treturn {\n\t\t\tentries: [],\n\t\t\ttypes: [],\n\t\t\tactors: [],\n\t\t\tselectedType: '',\n\t\t\tselectedActor: '',\n\t\t\tselectedVisibility: '',\n\t\t\tselectedRange: 'all',\n\t\t\t/** The single-source endpoint is in use: chosen by `legacyFeed`, or after the server had no merged feed. */\n\t\t\tlegacy: this.legacyFeed,\n\t\t\tfeedKinds: ACTIVITY_FEED_KINDS,\n\t\t\t/** Kinds the reader narrowed to; empty = all. */\n\t\t\tselectedKinds: [],\n\t\t\t/** Count per kind, as the feed returned it. */\n\t\t\tkindCounts: {},\n\t\t\t/** Read entries are shown. Off by default; the reader's choice is remembered per user. */\n\t\t\tshowReads: false,\n\t\t\tfromDate: '',\n\t\t\tuntilDate: '',\n\t\t\texporting: false,\n\t\t\tcursor: null,\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\ttotal: 0,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tvisibilityLabel: t('nextcloud-vue', 'Visibility'),\n\t\t\tallVisibilityLabel: t('nextcloud-vue', 'All entries'),\n\t\t\tinternalLabel: t('nextcloud-vue', 'Internal'),\n\t\t\tpublicLabel: t('nextcloud-vue', 'Public'),\n\t\t\tpublicOnlyLabel: t('nextcloud-vue', 'You see the public entries only.'),\n\t\t\ttypeLabel: t('nextcloud-vue', 'Type'),\n\t\t\tactorLabel: t('nextcloud-vue', 'Actor'),\n\t\t\tallTypesLabel: t('nextcloud-vue', 'All types'),\n\t\t\tallActorsLabel: t('nextcloud-vue', 'All actors'),\n\t\t\trangeGroupLabel: t('nextcloud-vue', 'Date range'),\n\t\t\tkindsGroupLabel: t('nextcloud-vue', 'Kinds of activity'),\n\t\t\tshowReadsLabel: t('nextcloud-vue', 'Show reads'),\n\t\t\tfromLabel: t('nextcloud-vue', 'From'),\n\t\t\tuntilLabel: t('nextcloud-vue', 'Until'),\n\t\t\texportLabel: t('nextcloud-vue', 'Export'),\n\t\t\taddNoteLabel: t('nextcloud-vue', 'Add note'),\n\t\t\thistoryTitle: t('nextcloud-vue', 'History'),\n\t\t\thistorySubtitle: t('nextcloud-vue', 'What happened, who did it and when, newest first'),\n\t\t\tranges: [\n\t\t\t\t{ key: '24h', label: t('nextcloud-vue', '24h') },\n\t\t\t\t{ key: '7d', label: t('nextcloud-vue', '7d') },\n\t\t\t\t{ key: '30d', label: t('nextcloud-vue', '30d') },\n\t\t\t\t{ key: 'all', label: t('nextcloud-vue', 'All') },\n\t\t\t],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tab is drawn in the board look: the History header, the\n\t\t * filled kind chips and the event rail.\n\t\t *\n\t\t * @return {boolean} True in the board look.\n\t\t * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-history-is-the-last-tab\n\t\t */\n\t\tisBoard() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasMore() {\n\t\t\treturn this.cursor !== null\n\t\t},\n\n\t\t/**\n\t\t * Unix-epoch-seconds lower bound for the selected date range, or\n\t\t * null for \"all\".\n\t\t *\n\t\t * @return {?number} Lower-bound timestamp or null.\n\t\t */\n\t\tafterTimestamp() {\n\t\t\tconst days = { '24h': 1, '7d': 7, '30d': 30 }[this.selectedRange]\n\t\t\tif (!days) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn Math.floor(Date.now() / 1000) - (days * SECONDS_PER_DAY)\n\t\t},\n\n\t\t/**\n\t\t * The rows on screen: reads are left out unless the reader turned them on.\n\t\t * Only an audit row can be a read.\n\t\t *\n\t\t * @return {object[]} The rows.\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (this.legacy || this.showReads) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.filter((entry) => !isReadRow(entry))\n\t\t},\n\n\t\tgroupedByDay() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const entry of this.visibleEntries) {\n\t\t\t\tconst ts = this.timestampFor(entry)\n\t\t\t\tconst key = this.dayKey(ts)\n\t\t\t\tif (groups.has(key) === false) {\n\t\t\t\t\tgroups.set(key, { key, label: this.dayLabel(ts), rows: [] })\n\t\t\t\t}\n\t\t\t\tgroups.get(key).rows.push(entry)\n\t\t\t}\n\t\t\t// Preserve insertion order (entries assumed pre-sorted DESC by\n\t\t\t// the provider). Each day's rows likewise stay in the order\n\t\t\t// they were appended.\n\t\t\treturn [...groups.values()]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.bootstrap()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.bootstrap()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.bootstrap()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\tif (!this.legacy) {\n\t\t\t\treturn prefixUrl(feedPath(this.objectAddress()))\n\t\t\t}\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/activity`)\n\t\t},\n\n\t\tobjectAddress() {\n\t\t\treturn { apiBase: this.apiBase, register: this.register, schema: this.schema, objectId: this.objectId }\n\t\t},\n\n\t\t/** @return {object} The filters the tab is showing, in the feed's terms. */\n\t\tfeedFilters() {\n\t\t\treturn {\n\t\t\t\tkinds: this.selectedKinds,\n\t\t\t\tfrom: this.fromDate ? new Date(`${this.fromDate}T00:00:00`).toISOString() : '',\n\t\t\t\tuntil: this.untilDate ? new Date(`${this.untilDate}T23:59:59.999`).toISOString() : '',\n\t\t\t\treads: this.showReads,\n\t\t\t\tactor: this.selectedActor,\n\t\t\t\tvisibility: this.showVisibility ? (this.publicViewOnly ? 'public' : this.selectedVisibility) : '',\n\t\t\t\tlimit: this.pageSize,\n\t\t\t}\n\t\t},\n\n\t\tdropdownUrl(kind) {\n\t\t\tconst object = `${this.register}/${this.schema}/${this.objectId}`\n\t\t\treturn prefixUrl(`${this.apiBase}/integrations/activity/${kind}?object=${encodeURIComponent(object)}`)\n\t\t},\n\n\t\tbuildQuery() {\n\t\t\tif (!this.legacy) {\n\t\t\t\treturn feedQuery({ ...this.feedFilters(), cursor: this.cursor })\n\t\t\t}\n\t\t\tconst params = new URLSearchParams()\n\t\t\tparams.set('limit', String(this.pageSize))\n\t\t\tif (this.selectedType) {\n\t\t\t\tparams.set('type', this.selectedType)\n\t\t\t}\n\t\t\tif (this.selectedActor) {\n\t\t\t\tparams.set('actor', this.selectedActor)\n\t\t\t}\n\t\t\tif (this.showVisibility) {\n\t\t\t\tconst visibility = this.publicViewOnly ? 'public' : this.selectedVisibility\n\t\t\t\tif (visibility) {\n\t\t\t\t\tparams.set('visibility', visibility)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.afterTimestamp !== null) {\n\t\t\t\tparams.set('after', String(this.afterTimestamp))\n\t\t\t}\n\t\t\tif (this.cursor !== null) {\n\t\t\t\tparams.set('cursor', String(this.cursor))\n\t\t\t}\n\t\t\treturn params.toString()\n\t\t},\n\n\t\tentryKey(entry) {\n\t\t\treturn entry.id ?? entry.activity_id ?? entry.activityId ?? ''\n\t\t},\n\n\t\ttimestampFor(entry) {\n\t\t\tconst raw = entry.timestamp ?? entry.datetime ?? entry.time ?? entry.at ?? entry.created ?? entry.creationDateTime ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// NC Activity exposes Unix epoch seconds in `timestamp`.\n\t\t\tif (typeof raw === 'number' && raw < 1e12) {\n\t\t\t\treturn new Date(raw * 1000)\n\t\t\t}\n\t\t\tconst parsed = new Date(raw)\n\t\t\treturn Number.isNaN(parsed.getTime()) ? null : parsed\n\t\t},\n\n\t\tdayKey(date) {\n\t\t\tif (date === null) {\n\t\t\t\treturn 'unknown'\n\t\t\t}\n\t\t\treturn `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`\n\t\t},\n\n\t\tdayLabel(date) {\n\t\t\tif (date === null) {\n\t\t\t\treturn t('nextcloud-vue', 'Earlier')\n\t\t\t}\n\t\t\tconst now = new Date()\n\t\t\tconst today = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n\t\t\tconst yesterday = new Date(today.getTime() - 86400000)\n\t\t\tconst start = new Date(date.getFullYear(), date.getMonth(), date.getDate())\n\t\t\tif (start.getTime() === today.getTime()) {\n\t\t\t\treturn t('nextcloud-vue', 'Today')\n\t\t\t}\n\t\t\tif (start.getTime() === yesterday.getTime()) {\n\t\t\t\treturn t('nextcloud-vue', 'Yesterday')\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn date.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' })\n\t\t\t} catch {\n\t\t\t\treturn date.toISOString().split('T')[0]\n\t\t\t}\n\t\t},\n\n\t\trelativeTime(entry) {\n\t\t\tconst ts = this.timestampFor(entry)\n\t\t\tif (ts === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn ts.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n\t\t\t} catch {\n\t\t\t\treturn ts.toISOString().split('T')[1].slice(0, 5)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the entry timestamp as epoch-millisecond value for\n\t\t * NcDateTime, or 0 when unknown (callers branch on truthiness).\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {number} Epoch milliseconds, or 0.\n\t\t */\n\t\ttimestampMillis(entry) {\n\t\t\tconst ts = this.timestampFor(entry)\n\t\t\treturn ts === null ? 0 : ts.getTime()\n\t\t},\n\n\t\t/**\n\t\t * Split an event's subject at its first colon: the verb (\"Document\n\t\t * reviewed\") is drawn bold, the rest as written. A subject without a\n\t\t * colon is all verb.\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {{verb: string, rest: string}} The two parts.\n\t\t * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-history-is-the-last-tab\n\t\t */\n\t\tsplitSubject(entry) {\n\t\t\tconst subject = String(this.subjectFor(entry))\n\t\t\tconst at = subject.indexOf(':')\n\t\t\tif (at <= 0) {\n\t\t\t\treturn { verb: subject, rest: '' }\n\t\t\t}\n\t\t\treturn { verb: subject.slice(0, at + 1), rest: subject.slice(at + 1) }\n\t\t},\n\n\t\t/**\n\t\t * The meta line under an event: kind, who, when.\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {string} For example \"Document · Pieter Jansen · 6 Oct, 15:40\".\n\t\t * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-history-is-the-last-tab\n\t\t */\n\t\teventMeta(entry) {\n\t\t\tconst ts = this.timestampFor(entry)\n\t\t\tlet when = ''\n\t\t\tif (ts !== null) {\n\t\t\t\ttry {\n\t\t\t\t\twhen = ts.toLocaleString(undefined, { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })\n\t\t\t\t} catch {\n\t\t\t\t\twhen = ts.toISOString().replace('T', ' ').slice(0, 16)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn [this.kindLabel(entry.kind || 'activity'), this.actorFor(entry), when].filter((part) => part !== '').join(' \\u00b7 ')\n\t\t},\n\n\t\tsubjectFor(entry) {\n\t\t\treturn entry.subject_rich ?? entry.subjectRich ?? entry.subject ?? entry.summary ?? entry.title ?? ''\n\t\t},\n\n\t\tactorFor(entry) {\n\t\t\treturn entry.actor_id ?? entry.actorDisplayName ?? entry.actor ?? entry.user ?? entry.affecteduser ?? t('nextcloud-vue', 'System')\n\t\t},\n\n\t\t/**\n\t\t * NC user id to seed the actor avatar, when the entry carries a\n\t\t * real account id. Falls back to '' so NcAvatar renders initials\n\t\t * from the display name (e.g. the System pseudo-actor).\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {string} NC user id, or ''.\n\t\t */\n\t\tavatarUser(entry) {\n\t\t\tconst id = entry.actor_id ?? entry.actor ?? entry.user ?? entry.affecteduser ?? ''\n\t\t\treturn typeof id === 'string' ? id : ''\n\t\t},\n\n\t\t/**\n\t\t * Modifier class colouring the small activity-type badge that\n\t\t * overlaps the actor avatar, grouping verbs into NC-Activity-like\n\t\t * families (file / comment / share / tag).\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {string} BEM modifier class.\n\t\t */\n\t\ttypeBadgeClass(entry) {\n\t\t\tconst type = String(entry.type ?? '').toLowerCase()\n\t\t\tif (type.includes('comment')) {\n\t\t\t\treturn 'cn-activity-tab__type-badge--comment'\n\t\t\t}\n\t\t\tif (type.includes('share')) {\n\t\t\t\treturn 'cn-activity-tab__type-badge--share'\n\t\t\t}\n\t\t\tif (type.includes('tag')) {\n\t\t\t\treturn 'cn-activity-tab__type-badge--tag'\n\t\t\t}\n\t\t\treturn 'cn-activity-tab__type-badge--file'\n\t\t},\n\n\t\ticonFor(entry) {\n\t\t\tconst byKind = { file: 'FileOutline', note: 'CommentTextOutline', mail: 'EmailOutline', audit: 'Timeline', activity: 'Timeline' }[entry.kind]\n\t\t\tif (byKind) {\n\t\t\t\treturn byKind\n\t\t\t}\n\t\t\tconst type = String(entry.type ?? '').toLowerCase()\n\t\t\tif (type.includes('file') || type.includes('upload') || type.includes('change')) {\n\t\t\t\treturn 'FileOutline'\n\t\t\t}\n\t\t\tif (type.includes('comment')) {\n\t\t\t\treturn 'CommentTextOutline'\n\t\t\t}\n\t\t\tif (type.includes('share')) {\n\t\t\t\treturn 'ShareVariantOutline'\n\t\t\t}\n\t\t\tif (type.includes('tag')) {\n\t\t\t\treturn 'TagOutline'\n\t\t\t}\n\t\t\tif (type.includes('calendar') || type.includes('event')) {\n\t\t\t\treturn 'CalendarClockOutline'\n\t\t\t}\n\t\t\tif (type.includes('user') || type.includes('account')) {\n\t\t\t\treturn 'AccountOutline'\n\t\t\t}\n\t\t\treturn 'Timeline'\n\t\t},\n\n\t\tselectRange(key) {\n\t\t\tif (this.selectedRange === key) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.selectedRange = key\n\t\t\tif (!this.legacy) {\n\t\t\t\t// A preset is a from date; until stays open.\n\t\t\t\tconst days = { '24h': 1, '7d': 7, '30d': 30 }[key]\n\t\t\t\tthis.fromDate = days ? this.localDate(new Date(Date.now() - (days * SECONDS_PER_DAY * 1000))) : ''\n\t\t\t\tthis.untilDate = ''\n\t\t\t}\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\t/**\n\t\t * @param {Date} date A date.\n\t\t * @return {string} `YYYY-MM-DD` in local time.\n\t\t */\n\t\tlocalDate(date) {\n\t\t\tconst pad = (n) => String(n).padStart(2, '0')\n\t\t\treturn `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n\t\t},\n\n\t\t/**\n\t\t * @param {'fromDate'|'untilDate'} field Which end of the range.\n\t\t * @param {string} value `YYYY-MM-DD`, or '' to leave it open.\n\t\t * @return {void}\n\t\t */\n\t\tsetDate(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.selectedRange = 'custom'\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\t/**\n\t\t * @param {string} kind A feed kind.\n\t\t * @return {string} Its label.\n\t\t */\n\t\tkindLabel(kind) {\n\t\t\treturn {\n\t\t\t\taudit: t('nextcloud-vue', 'Changes'),\n\t\t\t\tactivity: t('nextcloud-vue', 'Activity'),\n\t\t\t\tfile: t('nextcloud-vue', 'Files'),\n\t\t\t\tnote: t('nextcloud-vue', 'Notes'),\n\t\t\t\tmail: t('nextcloud-vue', 'Mail'),\n\t\t\t}[kind] || kind\n\t\t},\n\n\t\t/**\n\t\t * The count the feed returned for a kind. A kind with no rows reads 0\n\t\t * and keeps its chip.\n\t\t *\n\t\t * @param {string} kind A feed kind.\n\t\t * @return {number} The count.\n\t\t */\n\t\tkindCount(kind) {\n\t\t\treturn this.kindCounts[kind] ?? 0\n\t\t},\n\n\t\ttoggleKind(kind) {\n\t\t\tthis.selectedKinds = this.selectedKinds.includes(kind)\n\t\t\t\t? this.selectedKinds.filter((k) => k !== kind)\n\t\t\t\t: [...this.selectedKinds, kind]\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\t/**\n\t\t * Turn read entries on or off, and remember the choice for this user.\n\t\t *\n\t\t * @param {boolean} on Whether reads are shown.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync setShowReads(on) {\n\t\t\tthis.showReads = !!on\n\t\t\tconst write = this.cnUserPreferences && this.cnUserPreferences.write\n\t\t\ttry {\n\t\t\t\tif (typeof write === 'function') {\n\t\t\t\t\tawait write(READS_PREFERENCE_KEY, this.showReads)\n\t\t\t\t} else if (typeof localStorage !== 'undefined') {\n\t\t\t\t\tlocalStorage.setItem(`cn-pref:${READS_PREFERENCE_KEY}`, JSON.stringify(this.showReads))\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Not remembered; the toggle still works for this session.\n\t\t\t}\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\t/**\n\t\t * Read the reader's remembered choice for the reads toggle.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadShowReads() {\n\t\t\ttry {\n\t\t\t\tconst read = this.cnUserPreferences && this.cnUserPreferences.read\n\t\t\t\tif (typeof read === 'function') {\n\t\t\t\t\tthis.showReads = (await read(READS_PREFERENCE_KEY, false)) === true\n\t\t\t\t} else if (typeof localStorage !== 'undefined') {\n\t\t\t\t\tthis.showReads = JSON.parse(localStorage.getItem(`cn-pref:${READS_PREFERENCE_KEY}`) || 'false') === true\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tthis.showReads = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Export what is on screen: the rows shown and the filters that produced\n\t\t * them are sent to the feed's export; nothing is re-queried.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync exportFeed() {\n\t\t\tthis.exporting = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/export`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t\tbody: JSON.stringify(exportBody(this.visibleEntries, this.feedFilters())),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`export failed (${response.status})`)\n\t\t\t\t}\n\t\t\t\tconst blob = new Blob([await response.text()], { type: 'text/csv;charset=utf-8' })\n\t\t\t\tconst url = URL.createObjectURL(blob)\n\t\t\t\tconst link = document.createElement('a')\n\t\t\t\tlink.href = url\n\t\t\t\tlink.download = `activity-${this.objectId}.csv`\n\t\t\t\tdocument.body.appendChild(link)\n\t\t\t\tlink.click()\n\t\t\t\tlink.remove()\n\t\t\t\tURL.revokeObjectURL(url)\n\t\t\t\t/**\n\t\t\t\t * @event exported Emitted after the feed on screen was exported. Payload: the number of rows.\n\t\t\t\t * @type {number}\n\t\t\t\t */\n\t\t\t\tthis.$emit('exported', this.visibleEntries.length)\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnActivityTab] export failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not export activity.')\n\t\t\t} finally {\n\t\t\t\tthis.exporting = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Full (re)load: refresh the dropdown sources, then fetch the\n\t\t * first filtered page.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tbootstrap() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchDropdowns()\n\t\t\tif (!this.legacy) {\n\t\t\t\tthis.loadShowReads().then(() => this.resetAndFetch())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\tresetAndFetch() {\n\t\t\tthis.entries = []\n\t\t\tthis.cursor = null\n\t\t\tthis.total = 0\n\t\t\tthis.fetchEntries()\n\t\t},\n\n\t\t/** The server has no merged feed: read the single-source endpoint, as before. */\n\t\tfallBackToLegacy() {\n\t\t\tthis.legacy = true\n\t\t\tthis.selectedRange = 'all'\n\t\t\tthis.fromDate = ''\n\t\t\tthis.untilDate = ''\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\tasync fetchDropdowns() {\n\t\t\ttry {\n\t\t\t\tconst [typesRes, actorsRes] = await Promise.all([\n\t\t\t\t\tfetch(this.dropdownUrl('types'), { headers: buildHeaders() }),\n\t\t\t\t\tfetch(this.dropdownUrl('actors'), { headers: buildHeaders() }),\n\t\t\t\t])\n\t\t\t\tif (typesRes.ok) {\n\t\t\t\t\tconst data = await typesRes.json()\n\t\t\t\t\tthis.types = data.results || []\n\t\t\t\t}\n\t\t\t\tif (actorsRes.ok) {\n\t\t\t\t\tconst data = await actorsRes.json()\n\t\t\t\t\tthis.actors = data.results || []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// Dropdown failure is non-fatal — filters just stay empty.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnActivityTab] failed to fetch filter options', err)\n\t\t\t}\n\t\t},\n\n\t\tasync fetchEntries() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst isFirstPage = this.cursor === null\n\t\t\tif (isFirstPage) {\n\t\t\t\tthis.loading = true\n\t\t\t} else {\n\t\t\t\tthis.loadingMore = true\n\t\t\t}\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\tlet noFeed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}?${this.buildQuery()}`, { headers: buildHeaders() })\n\t\t\t\tif (!this.legacy && [404, 405, 501].includes(response.status)) {\n\t\t\t\t\t// No merged feed on this server: read the single-source endpoint instead.\n\t\t\t\t\tnoFeed = true\n\t\t\t\t} else if (response.ok && !this.legacy) {\n\t\t\t\t\tconst feed = parseFeed(await response.json())\n\t\t\t\t\tthis.entries = isFirstPage ? feed.rows : [...this.entries, ...feed.rows]\n\t\t\t\t\tthis.kindCounts = feed.counts\n\t\t\t\t\tthis.total = this.entries.length\n\t\t\t\t\tthis.cursor = feed.cursor\n\t\t\t\t} else if (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.entries = isFirstPage ? rows : [...this.entries, ...rows]\n\t\t\t\t\tthis.total = Number(data.total ?? 0)\n\t\t\t\t\tconst next = data.nextCursor ?? null\n\t\t\t\t\tthis.cursor = (next === null || next === undefined) ? null : Number(next)\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tif (isFirstPage) {\n\t\t\t\t\t\tthis.entries = []\n\t\t\t\t\t}\n\t\t\t\t\tthis.cursor = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tif (isFirstPage) {\n\t\t\t\t\t\tthis.entries = []\n\t\t\t\t\t}\n\t\t\t\t\tthis.cursor = null\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load activity.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnActivityTab] failed to fetch activity', err)\n\t\t\t\tif (isFirstPage) {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t\tthis.cursor = null\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load activity.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t\tif (noFeed) {\n\t\t\t\tthis.fallBackToLegacy()\n\t\t\t}\n\t\t},\n\n\t\tloadMore() {\n\t\t\tif (this.cursor === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchEntries()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-activity-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-activity-tab__filters {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin-bottom: 12px;\n}\n\n.cn-activity-tab__filter-row {\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n.cn-activity-tab__filter {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-activity-tab__filter-label {\n\tfont-size: 0.78em;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-activity-tab__select {\n\twidth: 100%;\n\tmin-height: 34px;\n\tpadding: 4px 6px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-activity-tab__range {\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-activity-tab__range-btn {\n\tflex: 1;\n\tpadding: 4px 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.82em;\n\tcursor: pointer;\n}\n\n.cn-activity-tab__range-btn--active {\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-activity-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-activity-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-activity-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-activity-tab__day {\n\tmargin-bottom: 14px;\n}\n\n.cn-activity-tab__day-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 0;\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-activity-tab__day-label {\n\tflex: 1;\n}\n\n.cn-activity-tab__day-count {\n\tfont-weight: normal;\n\tfont-size: 0.85em;\n\tpadding: 1px 6px;\n\tborder-radius: 9px;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-activity-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Actor avatar with an overlapping activity-type badge, mirroring the\n NC Activity feed's per-event glyph. */\n.cn-activity-tab__avatar-wrap {\n\tposition: relative;\n\tdisplay: inline-flex;\n\twidth: 36px;\n\theight: 36px;\n}\n\n.cn-activity-tab__type-badge {\n\tposition: absolute;\n\tright: -2px;\n\tbottom: -2px;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 16px;\n\theight: 16px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-main-background);\n\tcolor: var(--color-primary-element-text);\n\tbackground: var(--color-primary-element);\n}\n\n.cn-activity-tab__type-badge--comment {\n\tbackground: var(--color-success, #46ba61);\n}\n\n.cn-activity-tab__type-badge--share {\n\tbackground: var(--color-primary-element);\n}\n\n.cn-activity-tab__type-badge--tag {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-activity-tab__type-badge--file {\n\tbackground: var(--color-text-maxcontrast);\n}\n\n.cn-activity-tab__subject {\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-activity-tab__subname {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-activity-tab__actor {\n\tfont-weight: 500;\n}\n\n.cn-activity-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n\n.cn-activity-tab__footer {\n\tmargin-top: 8px;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_toDisplayString","_createElementVNode","_createBlock","_openBlock","_Fragment","_renderList","_createVNode","_createTextVNode","_resolveDynamicComponent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnActivityTab.vue3.js","sources":["../../../../../src/integrations/builtin/activity/CnActivityTab.vue?vue&type=template&id=bf3b0e7c&lang.js"],"sourcesContent":["<!--\n CnActivityTab — bespoke sidebar tab for the `activity` integration.\n\n Reads OpenRegister's MERGED activity feed (audit trail, NC Activity, files,\n notes, mail) for the object, paged on the feed's time cursor, with a chip\n per kind (a kind with no rows still shows, reading 0), reads off by default\n (the reader's choice is remembered per user), a from/until date range and\n an export of what is on screen. The wire names are in `activityFeedWire.js`.\n A server without the merged feed (404/405/501) makes the tab fall back to\n the single-source mode described below, which `legacyFeed` also selects.\n\n Single-source mode: renders a chronological timeline of NC Activity events grouped by day,\n with a type icon + actor + subject text + relative timestamp per row.\n\n Tier-2 surface (read-only — NC Activity entries are core-generated):\n - Filter bar: type dropdown, actor dropdown, date-range segmented\n control (24h / 7d / 30d / all).\n - Cursor-based \"load more\" pagination.\n - NO picker, NO inline create (entries are not user-authored here).\n\n Talks to the OpenRegister Tier-2 activity endpoints:\n GET /api/objects/{register}/{schema}/{objectId}/activity\n ?type=&actor=&after=&limit=&cursor=\n GET /api/integrations/activity/types?object={r}/{s}/{id}\n GET /api/integrations/activity/actors?object={r}/{s}/{id}\n backed by `OCA\\OpenRegister\\Service\\ActivityFilterService`, which wraps\n the wave-5.3 `MarkerLookupTrait` carve-out (NC Activity's single string\n `subject` column is the `[or:{uuid}]` marker target) with the filters\n and cursor pagination above. The carve-out is intentionally preserved.\n\n Surface behaviour:\n - Empty state with neutral copy when no activity matches the filters.\n - Loading + 501/503 \"unavailable\" + generic error banner all match\n CnIntegrationTab's degradation patterns (AD-23).\n\n See `openregister/openspec/changes/integration-activity/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-activity-tab\" :class=\"{ 'cn-activity-tab--board': isBoard }\">\n\t\t<div v-if=\"degraded\" class=\"cn-activity-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<!-- The History panel's header in the board look: the title, what the\n\t\t list is, and the two buttons at its end. -->\n\t\t<div v-if=\"isBoard && !degraded\" class=\"cn-activity-tab__board-head\" data-testid=\"cn-activity-board-head\">\n\t\t\t<div class=\"cn-activity-tab__board-titles\">\n\t\t\t\t<h2 class=\"cn-activity-tab__board-title\">\n\t\t\t\t\t{{ historyTitle }}\n\t\t\t\t</h2>\n\t\t\t\t<p class=\"cn-activity-tab__board-subtitle\">\n\t\t\t\t\t{{ historySubtitle }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-activity-tab__board-actions\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showAddNote\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\tdata-testid=\"cn-activity-add-note\"\n\t\t\t\t\t@click=\"$emit('add-note')\">\n\t\t\t\t\t{{ addNoteLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"!legacy\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"visibleEntries.length === 0 || exporting\"\n\t\t\t\t\tdata-testid=\"cn-activity-export\"\n\t\t\t\t\t@click=\"exportFeed\">\n\t\t\t\t\t{{ exportLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-if=\"!degraded\" class=\"cn-activity-tab__filters\">\n\t\t\t<!-- Merged feed: one chip per kind. A kind with no rows keeps its chip, reading 0: gone would say the kind does not exist on this object. -->\n\t\t\t<div\n\t\t\t\tv-if=\"!legacy\"\n\t\t\t\tclass=\"cn-activity-tab__kinds\"\n\t\t\t\trole=\"group\"\n\t\t\t\t:aria-label=\"kindsGroupLabel\"\n\t\t\t\tdata-testid=\"cn-activity-kinds\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"kind in feedKinds\"\n\t\t\t\t\t:key=\"kind\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-activity-tab__kind\"\n\t\t\t\t\t:class=\"{ 'cn-activity-tab__kind--active': selectedKinds.includes(kind) }\"\n\t\t\t\t\t:aria-pressed=\"String(selectedKinds.includes(kind))\"\n\t\t\t\t\t:data-testid=\"`cn-activity-kind-${kind}`\"\n\t\t\t\t\t@click=\"toggleKind(kind)\">\n\t\t\t\t\t{{ kindLabel(kind) }}\n\t\t\t\t\t<span class=\"cn-activity-tab__kind-count\" :data-testid=\"`cn-activity-kind-count-${kind}`\">{{ kindCount(kind) }}</span>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div class=\"cn-activity-tab__filter-row\">\n\t\t\t\t<label v-if=\"legacy\" class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ typeLabel }}</span>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-model=\"selectedType\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\t@change=\"resetAndFetch\">\n\t\t\t\t\t\t<option value=\"\">{{ allTypesLabel }}</option>\n\t\t\t\t\t\t<option v-for=\"ty in types\" :key=\"ty\" :value=\"ty\">{{ ty }}</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t\t<label class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ actorLabel }}</span>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-model=\"selectedActor\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\t@change=\"resetAndFetch\">\n\t\t\t\t\t\t<option value=\"\">{{ allActorsLabel }}</option>\n\t\t\t\t\t\t<option v-for=\"ac in actors\" :key=\"ac\" :value=\"ac\">{{ ac }}</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<!-- Visibility filter (showVisibility): all, internal, public. A caller served the\n\t\t\t public view sees it fixed at public, with the reason. -->\n\t\t\t<div v-if=\"showVisibility\" class=\"cn-activity-tab__visibility\" data-testid=\"cn-activity-visibility\">\n\t\t\t\t<p v-if=\"publicViewOnly\" class=\"cn-activity-tab__visibility-fixed\" data-testid=\"cn-activity-visibility-fixed\">\n\t\t\t\t\t<CnVisibilityChip visibility=\"public\" />\n\t\t\t\t\t{{ publicOnlyLabel }}\n\t\t\t\t</p>\n\t\t\t\t<label v-else class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ visibilityLabel }}</span>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-model=\"selectedVisibility\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\tdata-testid=\"cn-activity-visibility-select\"\n\t\t\t\t\t\t@change=\"resetAndFetch\">\n\t\t\t\t\t\t<option value=\"\">{{ allVisibilityLabel }}</option>\n\t\t\t\t\t\t<option value=\"internal\">{{ internalLabel }}</option>\n\t\t\t\t\t\t<option value=\"public\">{{ publicLabel }}</option>\n\t\t\t\t\t</select>\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<label v-if=\"!legacy\" class=\"cn-activity-tab__reads\">\n\t\t\t\t<input\n\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t:checked=\"showReads\"\n\t\t\t\t\tdata-testid=\"cn-activity-reads\"\n\t\t\t\t\t@change=\"setShowReads($event.target.checked)\">\n\t\t\t\t{{ showReadsLabel }}\n\t\t\t</label>\n\t\t\t<div class=\"cn-activity-tab__range\" role=\"group\" :aria-label=\"rangeGroupLabel\">\n\t\t\t\t<button\n\t\t\t\t\tv-for=\"range in ranges\"\n\t\t\t\t\t:key=\"range.key\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-activity-tab__range-btn\"\n\t\t\t\t\t:class=\"{ 'cn-activity-tab__range-btn--active': selectedRange === range.key }\"\n\t\t\t\t\t@click=\"selectRange(range.key)\">\n\t\t\t\t\t{{ range.label }}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<!-- The range kept as a range: from and until, which is what the feed takes. -->\n\t\t\t<div v-if=\"!legacy\" class=\"cn-activity-tab__dates\">\n\t\t\t\t<label class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ fromLabel }}</span>\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\t:value=\"fromDate\"\n\t\t\t\t\t\tdata-testid=\"cn-activity-from\"\n\t\t\t\t\t\t@change=\"setDate('fromDate', $event.target.value)\">\n\t\t\t\t</label>\n\t\t\t\t<label class=\"cn-activity-tab__filter\">\n\t\t\t\t\t<span class=\"cn-activity-tab__filter-label\">{{ untilLabel }}</span>\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"date\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__select\"\n\t\t\t\t\t\t:value=\"untilDate\"\n\t\t\t\t\t\tdata-testid=\"cn-activity-until\"\n\t\t\t\t\t\t@change=\"setDate('untilDate', $event.target.value)\">\n\t\t\t\t</label>\n\t\t\t</div>\n\t\t\t<!-- The export is what is on screen: these rows and these filters, nothing re-queried. -->\n\t\t\t<NcButton\n\t\t\t\tv-if=\"!legacy && !isBoard\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:disabled=\"visibleEntries.length === 0 || exporting\"\n\t\t\t\tdata-testid=\"cn-activity-export\"\n\t\t\t\t@click=\"exportFeed\">\n\t\t\t\t{{ exportLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading && entries.length === 0\" />\n\t\t<div v-else-if=\"error\" class=\"cn-activity-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"visibleEntries.length === 0\" class=\"cn-sidebar-tab__empty cn-activity-tab__empty\">\n\t\t\t<Timeline :size=\"32\" class=\"cn-activity-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t</div>\n\t\t<!-- The board look draws the events as one rail: a 34px icon on a tint\n\t\t of its kind, a connector to the next event, the verb in bold, and a\n\t\t meta line of kind, who and when. -->\n\t\t<div v-else-if=\"isBoard\" class=\"cn-activity-tab__timeline cn-activity-tab__timeline--rail\">\n\t\t\t<ol class=\"cn-activity-tab__rail\" data-testid=\"cn-activity-rail\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t\t:key=\"entryKey(entry)\"\n\t\t\t\t\tclass=\"cn-activity-tab__event\"\n\t\t\t\t\t:class=\"`cn-activity-tab__event--${entry.kind || 'activity'}`\"\n\t\t\t\t\tdata-testid=\"cn-activity-event\">\n\t\t\t\t\t<span class=\"cn-activity-tab__event-icon\" aria-hidden=\"true\">\n\t\t\t\t\t\t<component :is=\"iconFor(entry)\" :size=\"18\" />\n\t\t\t\t\t</span>\n\t\t\t\t\t<div class=\"cn-activity-tab__event-body\">\n\t\t\t\t\t\t<p class=\"cn-activity-tab__event-line\">\n\t\t\t\t\t\t\t<strong>{{ splitSubject(entry).verb }}</strong>{{ splitSubject(entry).rest }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p class=\"cn-activity-tab__event-meta\">\n\t\t\t\t\t\t\t{{ eventMeta(entry) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<CnVisibilityChip v-if=\"showVisibility\" :visibility=\"entry.visibility\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t\t<div v-if=\"hasMore\" class=\"cn-activity-tab__footer\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:wide=\"true\"\n\t\t\t\t\t:disabled=\"loadingMore\"\n\t\t\t\t\t@click=\"loadMore\">\n\t\t\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ loadMoreLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t\t<div v-else class=\"cn-activity-tab__timeline\">\n\t\t\t<section\n\t\t\t\tv-for=\"day in groupedByDay\"\n\t\t\t\t:key=\"day.key\"\n\t\t\t\tclass=\"cn-activity-tab__day\">\n\t\t\t\t<header class=\"cn-activity-tab__day-header\">\n\t\t\t\t\t<CalendarOutline :size=\"16\" />\n\t\t\t\t\t<span class=\"cn-activity-tab__day-label\">{{ day.label }}</span>\n\t\t\t\t\t<span class=\"cn-activity-tab__day-count\">{{ day.rows.length }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-activity-tab__list\">\n\t\t\t\t\t<NcListItem\n\t\t\t\t\t\tv-for=\"entry in day.rows\"\n\t\t\t\t\t\t:key=\"entryKey(entry)\"\n\t\t\t\t\t\tclass=\"cn-activity-tab__row\"\n\t\t\t\t\t\t:name=\"subjectFor(entry)\"\n\t\t\t\t\t\t:bold=\"false\"\n\t\t\t\t\t\t:compact=\"true\"\n\t\t\t\t\t\t:forceDisplayActions=\"false\">\n\t\t\t\t\t\t<template #name>\n\t\t\t\t\t\t\t<span class=\"cn-activity-tab__subject\">{{ subjectFor(entry) }}</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-activity-tab__avatar-wrap\">\n\t\t\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t\t\t:user=\"avatarUser(entry)\"\n\t\t\t\t\t\t\t\t\t:displayName=\"actorFor(entry)\"\n\t\t\t\t\t\t\t\t\t:size=\"36\"\n\t\t\t\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t\t\t\t:disableTooltip=\"true\"\n\t\t\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t\t\t<span class=\"cn-activity-tab__type-badge\" :class=\"typeBadgeClass(entry)\">\n\t\t\t\t\t\t\t\t\t<component :is=\"iconFor(entry)\" :size=\"12\" />\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t\t<span class=\"cn-activity-tab__subname\">\n\t\t\t\t\t\t\t\t<span class=\"cn-activity-tab__actor\">{{ actorFor(entry) }}</span>\n\t\t\t\t\t\t\t\t<CnVisibilityChip v-if=\"showVisibility\" :visibility=\"entry.visibility\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"timestampMillis(entry)\" #details>\n\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\tclass=\"cn-activity-tab__time\"\n\t\t\t\t\t\t\t\t:timestamp=\"timestampMillis(entry)\"\n\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-else #details>\n\t\t\t\t\t\t\t<span class=\"cn-activity-tab__time\">{{ relativeTime(entry) }}</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcListItem>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t\t<div v-if=\"hasMore\" class=\"cn-activity-tab__footer\">\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t:wide=\"true\"\n\t\t\t\t\t:disabled=\"loadingMore\"\n\t\t\t\t\t@click=\"loadMore\">\n\t\t\t\t\t<template v-if=\"loadingMore\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ loadMoreLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AccountOutline from 'vue-material-design-icons/AccountOutline.vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CalendarClockOutline from 'vue-material-design-icons/CalendarClockOutline.vue'\nimport CalendarOutline from 'vue-material-design-icons/CalendarOutline.vue'\nimport CommentTextOutline from 'vue-material-design-icons/CommentTextOutline.vue'\nimport EmailOutline from 'vue-material-design-icons/EmailOutline.vue'\nimport FileOutline from 'vue-material-design-icons/FileOutline.vue'\nimport ShareVariantOutline from 'vue-material-design-icons/ShareVariantOutline.vue'\nimport TagOutline from 'vue-material-design-icons/TagOutline.vue'\nimport Timeline from 'vue-material-design-icons/Timeline.vue'\nimport CnVisibilityChip from '../../../components/CnVisibilityChip/CnVisibilityChip.vue'\nimport { normalizeLook } from '../../../composables/useLook.js'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\nimport { ACTIVITY_FEED_KINDS, exportBody, feedPath, feedQuery, isReadRow, parseFeed } from './activityFeedWire.js'\n\nconst DEFAULT_PAGE_SIZE = 25\nconst READS_PREFERENCE_KEY = 'activity.showReads'\nconst SECONDS_PER_DAY = 86400\n\n/**\n * CnActivityTab — bespoke chronological timeline for the `activity`\n * integration. Reads the merged feed (audit trail, NC Activity, files, notes,\n * mail) with a chip per kind, a reads toggle that is remembered per user, a\n * from/until range, an export of what is on screen, and cursor pagination on\n * the feed's time cursor. In `legacyFeed` mode (or against a server without\n * the merged feed) it reads the single-source Activity endpoint with type,\n * actor and date filters, as it did before.\n *\n * Renders activity entries grouped by day (today / yesterday / older\n * dates), with type icons, actor name, and a relative timestamp. Reads\n * from the OR Tier-2 activity endpoints; filters drive a fresh fetch,\n * \"load more\" walks the cursor returned by the backend.\n */\nexport default {\n\tname: 'CnActivityTab',\n\n\tcomponents: {\n\t\tCnVisibilityChip,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tCalendarOutline,\n\t\tEmailOutline,\n\t\tTimeline,\n\t\tFileOutline,\n\t\tAccountOutline,\n\t\tCommentTextOutline,\n\t\tShareVariantOutline,\n\t\tTagOutline,\n\t\tCalendarClockOutline,\n\t},\n\n\tinject: {\n\t\t/** The per-user preference group from CnAppRoot, so \"show reads\" is remembered. */\n\t\tcnUserPreferences: { default: null },\n\t\t/** The app's look, provided by CnAppRoot or CnPageRenderer (`nextcloud` or `board`). */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\n\t\t/** Stable integration id (forwarded from the registry — always `'activity'`). */\n\t\tintegrationId: { type: String, default: 'activity' },\n\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Number of entries per fetch. */\n\t\tpageSize: { type: Number, default: DEFAULT_PAGE_SIZE },\n\t\t/**\n\t\t * Read the single-source NC Activity endpoint (type, actor, date-range\n\t\t * filters, no kinds, reads toggle or export) instead of the merged feed.\n\t\t * The tab also falls back to it by itself when the server has no merged feed.\n\t\t */\n\t\tlegacyFeed: { type: Boolean, default: false },\n\t\t/**\n\t\t * Show a visibility chip on each row and a visibility filter (all,\n\t\t * internal, public) that sends `visibility` on the fetch. Off by default,\n\t\t * so a host that passes nothing renders the feed as before.\n\t\t */\n\t\tshowVisibility: { type: Boolean, default: false },\n\t\t/**\n\t\t * The server serves this caller the public view only (no `update` on the\n\t\t * object): the filter is then fixed at public and says why, instead of\n\t\t * offering options that change nothing. The host passes it.\n\t\t */\n\t\tpublicViewOnly: { type: Boolean, default: false },\n\t\t/**\n\t\t * Offer an \"Add note\" button in the board look's History header. It\n\t\t * emits `add-note`; the host opens whatever composes the note. Off by\n\t\t * default, so no button points at nothing.\n\t\t */\n\t\tshowAddNote: { type: Boolean, default: false },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No activity yet for this object') },\n\t\t/** Pre-translated unavailable banner. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Activity is currently unavailable.') },\n\t\t/** Pre-translated load-more button label. */\n\t\tloadMoreLabel: { type: String, default: () => t('nextcloud-vue', 'Load more') },\n\t},\n\n\temits: ['exported', 'add-note'],\n\n\tdata() {\n\t\treturn {\n\t\t\tentries: [],\n\t\t\ttypes: [],\n\t\t\tactors: [],\n\t\t\tselectedType: '',\n\t\t\tselectedActor: '',\n\t\t\tselectedVisibility: '',\n\t\t\tselectedRange: 'all',\n\t\t\t/** The single-source endpoint is in use: chosen by `legacyFeed`, or after the server had no merged feed. */\n\t\t\tlegacy: this.legacyFeed,\n\t\t\tfeedKinds: ACTIVITY_FEED_KINDS,\n\t\t\t/** Kinds the reader narrowed to; empty = all. */\n\t\t\tselectedKinds: [],\n\t\t\t/** Count per kind, as the feed returned it. */\n\t\t\tkindCounts: {},\n\t\t\t/** Read entries are shown. Off by default; the reader's choice is remembered per user. */\n\t\t\tshowReads: false,\n\t\t\tfromDate: '',\n\t\t\tuntilDate: '',\n\t\t\texporting: false,\n\t\t\tcursor: null,\n\t\t\tloading: false,\n\t\t\tloadingMore: false,\n\t\t\ttotal: 0,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tvisibilityLabel: t('nextcloud-vue', 'Visibility'),\n\t\t\tallVisibilityLabel: t('nextcloud-vue', 'All entries'),\n\t\t\tinternalLabel: t('nextcloud-vue', 'Internal'),\n\t\t\tpublicLabel: t('nextcloud-vue', 'Public'),\n\t\t\tpublicOnlyLabel: t('nextcloud-vue', 'You see the public entries only.'),\n\t\t\ttypeLabel: t('nextcloud-vue', 'Type'),\n\t\t\tactorLabel: t('nextcloud-vue', 'Actor'),\n\t\t\tallTypesLabel: t('nextcloud-vue', 'All types'),\n\t\t\tallActorsLabel: t('nextcloud-vue', 'All actors'),\n\t\t\trangeGroupLabel: t('nextcloud-vue', 'Date range'),\n\t\t\tkindsGroupLabel: t('nextcloud-vue', 'Kinds of activity'),\n\t\t\tshowReadsLabel: t('nextcloud-vue', 'Show reads'),\n\t\t\tfromLabel: t('nextcloud-vue', 'From'),\n\t\t\tuntilLabel: t('nextcloud-vue', 'Until'),\n\t\t\texportLabel: t('nextcloud-vue', 'Export'),\n\t\t\taddNoteLabel: t('nextcloud-vue', 'Add note'),\n\t\t\thistoryTitle: t('nextcloud-vue', 'History'),\n\t\t\thistorySubtitle: t('nextcloud-vue', 'What happened, who did it and when, newest first'),\n\t\t\tranges: [\n\t\t\t\t{ key: '24h', label: t('nextcloud-vue', '24h') },\n\t\t\t\t{ key: '7d', label: t('nextcloud-vue', '7d') },\n\t\t\t\t{ key: '30d', label: t('nextcloud-vue', '30d') },\n\t\t\t\t{ key: 'all', label: t('nextcloud-vue', 'All') },\n\t\t\t],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tab is drawn in the board look: the History header, the\n\t\t * filled kind chips and the event rail.\n\t\t *\n\t\t * @return {boolean} True in the board look.\n\t\t * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-history-is-the-last-tab\n\t\t */\n\t\tisBoard() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasMore() {\n\t\t\treturn this.cursor !== null\n\t\t},\n\n\t\t/**\n\t\t * Unix-epoch-seconds lower bound for the selected date range, or\n\t\t * null for \"all\".\n\t\t *\n\t\t * @return {?number} Lower-bound timestamp or null.\n\t\t */\n\t\tafterTimestamp() {\n\t\t\tconst days = { '24h': 1, '7d': 7, '30d': 30 }[this.selectedRange]\n\t\t\tif (!days) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn Math.floor(Date.now() / 1000) - (days * SECONDS_PER_DAY)\n\t\t},\n\n\t\t/**\n\t\t * The rows on screen: reads are left out unless the reader turned them on.\n\t\t * Only an audit row can be a read.\n\t\t *\n\t\t * @return {object[]} The rows.\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (this.legacy || this.showReads) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.filter((entry) => !isReadRow(entry))\n\t\t},\n\n\t\tgroupedByDay() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const entry of this.visibleEntries) {\n\t\t\t\tconst ts = this.timestampFor(entry)\n\t\t\t\tconst key = this.dayKey(ts)\n\t\t\t\tif (groups.has(key) === false) {\n\t\t\t\t\tgroups.set(key, { key, label: this.dayLabel(ts), rows: [] })\n\t\t\t\t}\n\t\t\t\tgroups.get(key).rows.push(entry)\n\t\t\t}\n\t\t\t// Preserve insertion order (entries assumed pre-sorted DESC by\n\t\t\t// the provider). Each day's rows likewise stay in the order\n\t\t\t// they were appended.\n\t\t\treturn [...groups.values()]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.bootstrap()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.bootstrap()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.bootstrap()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\tif (!this.legacy) {\n\t\t\t\treturn prefixUrl(feedPath(this.objectAddress()))\n\t\t\t}\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/activity`)\n\t\t},\n\n\t\tobjectAddress() {\n\t\t\treturn { apiBase: this.apiBase, register: this.register, schema: this.schema, objectId: this.objectId }\n\t\t},\n\n\t\t/** @return {object} The filters the tab is showing, in the feed's terms. */\n\t\tfeedFilters() {\n\t\t\treturn {\n\t\t\t\tkinds: this.selectedKinds,\n\t\t\t\tfrom: this.fromDate ? new Date(`${this.fromDate}T00:00:00`).toISOString() : '',\n\t\t\t\tuntil: this.untilDate ? new Date(`${this.untilDate}T23:59:59.999`).toISOString() : '',\n\t\t\t\treads: this.showReads,\n\t\t\t\tactor: this.selectedActor,\n\t\t\t\tvisibility: this.showVisibility ? (this.publicViewOnly ? 'public' : this.selectedVisibility) : '',\n\t\t\t\tlimit: this.pageSize,\n\t\t\t}\n\t\t},\n\n\t\tdropdownUrl(kind) {\n\t\t\tconst object = `${this.register}/${this.schema}/${this.objectId}`\n\t\t\treturn prefixUrl(`${this.apiBase}/integrations/activity/${kind}?object=${encodeURIComponent(object)}`)\n\t\t},\n\n\t\tbuildQuery() {\n\t\t\tif (!this.legacy) {\n\t\t\t\treturn feedQuery({ ...this.feedFilters(), cursor: this.cursor })\n\t\t\t}\n\t\t\tconst params = new URLSearchParams()\n\t\t\tparams.set('limit', String(this.pageSize))\n\t\t\tif (this.selectedType) {\n\t\t\t\tparams.set('type', this.selectedType)\n\t\t\t}\n\t\t\tif (this.selectedActor) {\n\t\t\t\tparams.set('actor', this.selectedActor)\n\t\t\t}\n\t\t\tif (this.showVisibility) {\n\t\t\t\tconst visibility = this.publicViewOnly ? 'public' : this.selectedVisibility\n\t\t\t\tif (visibility) {\n\t\t\t\t\tparams.set('visibility', visibility)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.afterTimestamp !== null) {\n\t\t\t\tparams.set('after', String(this.afterTimestamp))\n\t\t\t}\n\t\t\tif (this.cursor !== null) {\n\t\t\t\tparams.set('cursor', String(this.cursor))\n\t\t\t}\n\t\t\treturn params.toString()\n\t\t},\n\n\t\tentryKey(entry) {\n\t\t\treturn entry.id ?? entry.activity_id ?? entry.activityId ?? ''\n\t\t},\n\n\t\ttimestampFor(entry) {\n\t\t\tconst raw = entry.timestamp ?? entry.datetime ?? entry.time ?? entry.at ?? entry.created ?? entry.creationDateTime ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// NC Activity exposes Unix epoch seconds in `timestamp`.\n\t\t\tif (typeof raw === 'number' && raw < 1e12) {\n\t\t\t\treturn new Date(raw * 1000)\n\t\t\t}\n\t\t\tconst parsed = new Date(raw)\n\t\t\treturn Number.isNaN(parsed.getTime()) ? null : parsed\n\t\t},\n\n\t\tdayKey(date) {\n\t\t\tif (date === null) {\n\t\t\t\treturn 'unknown'\n\t\t\t}\n\t\t\treturn `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`\n\t\t},\n\n\t\tdayLabel(date) {\n\t\t\tif (date === null) {\n\t\t\t\treturn t('nextcloud-vue', 'Earlier')\n\t\t\t}\n\t\t\tconst now = new Date()\n\t\t\tconst today = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n\t\t\tconst yesterday = new Date(today.getTime() - 86400000)\n\t\t\tconst start = new Date(date.getFullYear(), date.getMonth(), date.getDate())\n\t\t\tif (start.getTime() === today.getTime()) {\n\t\t\t\treturn t('nextcloud-vue', 'Today')\n\t\t\t}\n\t\t\tif (start.getTime() === yesterday.getTime()) {\n\t\t\t\treturn t('nextcloud-vue', 'Yesterday')\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn date.toLocaleDateString(userLocale(), { weekday: 'short', day: 'numeric', month: 'short' })\n\t\t\t} catch {\n\t\t\t\treturn date.toISOString().split('T')[0]\n\t\t\t}\n\t\t},\n\n\t\trelativeTime(entry) {\n\t\t\tconst ts = this.timestampFor(entry)\n\t\t\tif (ts === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn ts.toLocaleTimeString(userLocale(), { hour: '2-digit', minute: '2-digit' })\n\t\t\t} catch {\n\t\t\t\treturn ts.toISOString().split('T')[1].slice(0, 5)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Resolve the entry timestamp as epoch-millisecond value for\n\t\t * NcDateTime, or 0 when unknown (callers branch on truthiness).\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {number} Epoch milliseconds, or 0.\n\t\t */\n\t\ttimestampMillis(entry) {\n\t\t\tconst ts = this.timestampFor(entry)\n\t\t\treturn ts === null ? 0 : ts.getTime()\n\t\t},\n\n\t\t/**\n\t\t * Split an event's subject at its first colon: the verb (\"Document\n\t\t * reviewed\") is drawn bold, the rest as written. A subject without a\n\t\t * colon is all verb.\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {{verb: string, rest: string}} The two parts.\n\t\t * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-history-is-the-last-tab\n\t\t */\n\t\tsplitSubject(entry) {\n\t\t\tconst subject = String(this.subjectFor(entry))\n\t\t\tconst at = subject.indexOf(':')\n\t\t\tif (at <= 0) {\n\t\t\t\treturn { verb: subject, rest: '' }\n\t\t\t}\n\t\t\treturn { verb: subject.slice(0, at + 1), rest: subject.slice(at + 1) }\n\t\t},\n\n\t\t/**\n\t\t * The meta line under an event: kind, who, when.\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {string} For example \"Document · Pieter Jansen · 6 Oct, 15:40\".\n\t\t * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-history-is-the-last-tab\n\t\t */\n\t\teventMeta(entry) {\n\t\t\tconst ts = this.timestampFor(entry)\n\t\t\tlet when = ''\n\t\t\tif (ts !== null) {\n\t\t\t\ttry {\n\t\t\t\t\twhen = ts.toLocaleString(userLocale(), { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })\n\t\t\t\t} catch {\n\t\t\t\t\twhen = ts.toISOString().replace('T', ' ').slice(0, 16)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn [this.kindLabel(entry.kind || 'activity'), this.actorFor(entry), when].filter((part) => part !== '').join(' \\u00b7 ')\n\t\t},\n\n\t\tsubjectFor(entry) {\n\t\t\treturn entry.subject_rich ?? entry.subjectRich ?? entry.subject ?? entry.summary ?? entry.title ?? ''\n\t\t},\n\n\t\tactorFor(entry) {\n\t\t\treturn entry.actor_id ?? entry.actorDisplayName ?? entry.actor ?? entry.user ?? entry.affecteduser ?? t('nextcloud-vue', 'System')\n\t\t},\n\n\t\t/**\n\t\t * NC user id to seed the actor avatar, when the entry carries a\n\t\t * real account id. Falls back to '' so NcAvatar renders initials\n\t\t * from the display name (e.g. the System pseudo-actor).\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {string} NC user id, or ''.\n\t\t */\n\t\tavatarUser(entry) {\n\t\t\tconst id = entry.actor_id ?? entry.actor ?? entry.user ?? entry.affecteduser ?? ''\n\t\t\treturn typeof id === 'string' ? id : ''\n\t\t},\n\n\t\t/**\n\t\t * Modifier class colouring the small activity-type badge that\n\t\t * overlaps the actor avatar, grouping verbs into NC-Activity-like\n\t\t * families (file / comment / share / tag).\n\t\t *\n\t\t * @param {object} entry Activity row.\n\t\t * @return {string} BEM modifier class.\n\t\t */\n\t\ttypeBadgeClass(entry) {\n\t\t\tconst type = String(entry.type ?? '').toLowerCase()\n\t\t\tif (type.includes('comment')) {\n\t\t\t\treturn 'cn-activity-tab__type-badge--comment'\n\t\t\t}\n\t\t\tif (type.includes('share')) {\n\t\t\t\treturn 'cn-activity-tab__type-badge--share'\n\t\t\t}\n\t\t\tif (type.includes('tag')) {\n\t\t\t\treturn 'cn-activity-tab__type-badge--tag'\n\t\t\t}\n\t\t\treturn 'cn-activity-tab__type-badge--file'\n\t\t},\n\n\t\ticonFor(entry) {\n\t\t\tconst byKind = { file: 'FileOutline', note: 'CommentTextOutline', mail: 'EmailOutline', audit: 'Timeline', activity: 'Timeline' }[entry.kind]\n\t\t\tif (byKind) {\n\t\t\t\treturn byKind\n\t\t\t}\n\t\t\tconst type = String(entry.type ?? '').toLowerCase()\n\t\t\tif (type.includes('file') || type.includes('upload') || type.includes('change')) {\n\t\t\t\treturn 'FileOutline'\n\t\t\t}\n\t\t\tif (type.includes('comment')) {\n\t\t\t\treturn 'CommentTextOutline'\n\t\t\t}\n\t\t\tif (type.includes('share')) {\n\t\t\t\treturn 'ShareVariantOutline'\n\t\t\t}\n\t\t\tif (type.includes('tag')) {\n\t\t\t\treturn 'TagOutline'\n\t\t\t}\n\t\t\tif (type.includes('calendar') || type.includes('event')) {\n\t\t\t\treturn 'CalendarClockOutline'\n\t\t\t}\n\t\t\tif (type.includes('user') || type.includes('account')) {\n\t\t\t\treturn 'AccountOutline'\n\t\t\t}\n\t\t\treturn 'Timeline'\n\t\t},\n\n\t\tselectRange(key) {\n\t\t\tif (this.selectedRange === key) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.selectedRange = key\n\t\t\tif (!this.legacy) {\n\t\t\t\t// A preset is a from date; until stays open.\n\t\t\t\tconst days = { '24h': 1, '7d': 7, '30d': 30 }[key]\n\t\t\t\tthis.fromDate = days ? this.localDate(new Date(Date.now() - (days * SECONDS_PER_DAY * 1000))) : ''\n\t\t\t\tthis.untilDate = ''\n\t\t\t}\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\t/**\n\t\t * @param {Date} date A date.\n\t\t * @return {string} `YYYY-MM-DD` in local time.\n\t\t */\n\t\tlocalDate(date) {\n\t\t\tconst pad = (n) => String(n).padStart(2, '0')\n\t\t\treturn `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n\t\t},\n\n\t\t/**\n\t\t * @param {'fromDate'|'untilDate'} field Which end of the range.\n\t\t * @param {string} value `YYYY-MM-DD`, or '' to leave it open.\n\t\t * @return {void}\n\t\t */\n\t\tsetDate(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.selectedRange = 'custom'\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\t/**\n\t\t * @param {string} kind A feed kind.\n\t\t * @return {string} Its label.\n\t\t */\n\t\tkindLabel(kind) {\n\t\t\treturn {\n\t\t\t\taudit: t('nextcloud-vue', 'Changes'),\n\t\t\t\tactivity: t('nextcloud-vue', 'Activity'),\n\t\t\t\tfile: t('nextcloud-vue', 'Files'),\n\t\t\t\tnote: t('nextcloud-vue', 'Notes'),\n\t\t\t\tmail: t('nextcloud-vue', 'Mail'),\n\t\t\t}[kind] || kind\n\t\t},\n\n\t\t/**\n\t\t * The count the feed returned for a kind. A kind with no rows reads 0\n\t\t * and keeps its chip.\n\t\t *\n\t\t * @param {string} kind A feed kind.\n\t\t * @return {number} The count.\n\t\t */\n\t\tkindCount(kind) {\n\t\t\treturn this.kindCounts[kind] ?? 0\n\t\t},\n\n\t\ttoggleKind(kind) {\n\t\t\tthis.selectedKinds = this.selectedKinds.includes(kind)\n\t\t\t\t? this.selectedKinds.filter((k) => k !== kind)\n\t\t\t\t: [...this.selectedKinds, kind]\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\t/**\n\t\t * Turn read entries on or off, and remember the choice for this user.\n\t\t *\n\t\t * @param {boolean} on Whether reads are shown.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync setShowReads(on) {\n\t\t\tthis.showReads = !!on\n\t\t\tconst write = this.cnUserPreferences && this.cnUserPreferences.write\n\t\t\ttry {\n\t\t\t\tif (typeof write === 'function') {\n\t\t\t\t\tawait write(READS_PREFERENCE_KEY, this.showReads)\n\t\t\t\t} else if (typeof localStorage !== 'undefined') {\n\t\t\t\t\tlocalStorage.setItem(`cn-pref:${READS_PREFERENCE_KEY}`, JSON.stringify(this.showReads))\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Not remembered; the toggle still works for this session.\n\t\t\t}\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\t/**\n\t\t * Read the reader's remembered choice for the reads toggle.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadShowReads() {\n\t\t\ttry {\n\t\t\t\tconst read = this.cnUserPreferences && this.cnUserPreferences.read\n\t\t\t\tif (typeof read === 'function') {\n\t\t\t\t\tthis.showReads = (await read(READS_PREFERENCE_KEY, false)) === true\n\t\t\t\t} else if (typeof localStorage !== 'undefined') {\n\t\t\t\t\tthis.showReads = JSON.parse(localStorage.getItem(`cn-pref:${READS_PREFERENCE_KEY}`) || 'false') === true\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tthis.showReads = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Export what is on screen: the rows shown and the filters that produced\n\t\t * them are sent to the feed's export; nothing is re-queried.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync exportFeed() {\n\t\t\tthis.exporting = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/export`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t\tbody: JSON.stringify(exportBody(this.visibleEntries, this.feedFilters())),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`export failed (${response.status})`)\n\t\t\t\t}\n\t\t\t\tconst blob = new Blob([await response.text()], { type: 'text/csv;charset=utf-8' })\n\t\t\t\tconst url = URL.createObjectURL(blob)\n\t\t\t\tconst link = document.createElement('a')\n\t\t\t\tlink.href = url\n\t\t\t\tlink.download = `activity-${this.objectId}.csv`\n\t\t\t\tdocument.body.appendChild(link)\n\t\t\t\tlink.click()\n\t\t\t\tlink.remove()\n\t\t\t\tURL.revokeObjectURL(url)\n\t\t\t\t/**\n\t\t\t\t * @event exported Emitted after the feed on screen was exported. Payload: the number of rows.\n\t\t\t\t * @type {number}\n\t\t\t\t */\n\t\t\t\tthis.$emit('exported', this.visibleEntries.length)\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnActivityTab] export failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not export activity.')\n\t\t\t} finally {\n\t\t\t\tthis.exporting = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Full (re)load: refresh the dropdown sources, then fetch the\n\t\t * first filtered page.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tbootstrap() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchDropdowns()\n\t\t\tif (!this.legacy) {\n\t\t\t\tthis.loadShowReads().then(() => this.resetAndFetch())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\tresetAndFetch() {\n\t\t\tthis.entries = []\n\t\t\tthis.cursor = null\n\t\t\tthis.total = 0\n\t\t\tthis.fetchEntries()\n\t\t},\n\n\t\t/** The server has no merged feed: read the single-source endpoint, as before. */\n\t\tfallBackToLegacy() {\n\t\t\tthis.legacy = true\n\t\t\tthis.selectedRange = 'all'\n\t\t\tthis.fromDate = ''\n\t\t\tthis.untilDate = ''\n\t\t\tthis.resetAndFetch()\n\t\t},\n\n\t\tasync fetchDropdowns() {\n\t\t\ttry {\n\t\t\t\tconst [typesRes, actorsRes] = await Promise.all([\n\t\t\t\t\tfetch(this.dropdownUrl('types'), { headers: buildHeaders() }),\n\t\t\t\t\tfetch(this.dropdownUrl('actors'), { headers: buildHeaders() }),\n\t\t\t\t])\n\t\t\t\tif (typesRes.ok) {\n\t\t\t\t\tconst data = await typesRes.json()\n\t\t\t\t\tthis.types = data.results || []\n\t\t\t\t}\n\t\t\t\tif (actorsRes.ok) {\n\t\t\t\t\tconst data = await actorsRes.json()\n\t\t\t\t\tthis.actors = data.results || []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// Dropdown failure is non-fatal — filters just stay empty.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnActivityTab] failed to fetch filter options', err)\n\t\t\t}\n\t\t},\n\n\t\tasync fetchEntries() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst isFirstPage = this.cursor === null\n\t\t\tif (isFirstPage) {\n\t\t\t\tthis.loading = true\n\t\t\t} else {\n\t\t\t\tthis.loadingMore = true\n\t\t\t}\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\tlet noFeed = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}?${this.buildQuery()}`, { headers: buildHeaders() })\n\t\t\t\tif (!this.legacy && [404, 405, 501].includes(response.status)) {\n\t\t\t\t\t// No merged feed on this server: read the single-source endpoint instead.\n\t\t\t\t\tnoFeed = true\n\t\t\t\t} else if (response.ok && !this.legacy) {\n\t\t\t\t\tconst feed = parseFeed(await response.json())\n\t\t\t\t\tthis.entries = isFirstPage ? feed.rows : [...this.entries, ...feed.rows]\n\t\t\t\t\tthis.kindCounts = feed.counts\n\t\t\t\t\tthis.total = this.entries.length\n\t\t\t\t\tthis.cursor = feed.cursor\n\t\t\t\t} else if (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.entries = isFirstPage ? rows : [...this.entries, ...rows]\n\t\t\t\t\tthis.total = Number(data.total ?? 0)\n\t\t\t\t\tconst next = data.nextCursor ?? null\n\t\t\t\t\tthis.cursor = (next === null || next === undefined) ? null : Number(next)\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tif (isFirstPage) {\n\t\t\t\t\t\tthis.entries = []\n\t\t\t\t\t}\n\t\t\t\t\tthis.cursor = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tif (isFirstPage) {\n\t\t\t\t\t\tthis.entries = []\n\t\t\t\t\t}\n\t\t\t\t\tthis.cursor = null\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load activity.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnActivityTab] failed to fetch activity', err)\n\t\t\t\tif (isFirstPage) {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t\tthis.cursor = null\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load activity.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.loadingMore = false\n\t\t\t}\n\t\t\tif (noFeed) {\n\t\t\t\tthis.fallBackToLegacy()\n\t\t\t}\n\t\t},\n\n\t\tloadMore() {\n\t\t\tif (this.cursor === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchEntries()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-activity-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-activity-tab__filters {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin-bottom: 12px;\n}\n\n.cn-activity-tab__filter-row {\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n.cn-activity-tab__filter {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-activity-tab__filter-label {\n\tfont-size: 0.78em;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-activity-tab__select {\n\twidth: 100%;\n\tmin-height: 34px;\n\tpadding: 4px 6px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-activity-tab__range {\n\tdisplay: flex;\n\tgap: 4px;\n}\n\n.cn-activity-tab__range-btn {\n\tflex: 1;\n\tpadding: 4px 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.82em;\n\tcursor: pointer;\n}\n\n.cn-activity-tab__range-btn--active {\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-activity-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-activity-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-activity-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-activity-tab__day {\n\tmargin-bottom: 14px;\n}\n\n.cn-activity-tab__day-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 0;\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-activity-tab__day-label {\n\tflex: 1;\n}\n\n.cn-activity-tab__day-count {\n\tfont-weight: normal;\n\tfont-size: 0.85em;\n\tpadding: 1px 6px;\n\tborder-radius: 9px;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-activity-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Actor avatar with an overlapping activity-type badge, mirroring the\n NC Activity feed's per-event glyph. */\n.cn-activity-tab__avatar-wrap {\n\tposition: relative;\n\tdisplay: inline-flex;\n\twidth: 36px;\n\theight: 36px;\n}\n\n.cn-activity-tab__type-badge {\n\tposition: absolute;\n\tright: -2px;\n\tbottom: -2px;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 16px;\n\theight: 16px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-main-background);\n\tcolor: var(--color-primary-element-text);\n\tbackground: var(--color-primary-element);\n}\n\n.cn-activity-tab__type-badge--comment {\n\tbackground: var(--color-success, #46ba61);\n}\n\n.cn-activity-tab__type-badge--share {\n\tbackground: var(--color-primary-element);\n}\n\n.cn-activity-tab__type-badge--tag {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-activity-tab__type-badge--file {\n\tbackground: var(--color-text-maxcontrast);\n}\n\n.cn-activity-tab__subject {\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-activity-tab__subname {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-activity-tab__actor {\n\tfont-weight: 500;\n}\n\n.cn-activity-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n\n.cn-activity-tab__footer {\n\tmargin-top: 8px;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_toDisplayString","_createElementVNode","_createBlock","_openBlock","_Fragment","_renderList","_createVNode","_createTextVNode","_resolveDynamicComponent"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnAnalyticsTab.vue2.js';
|
|
|
2
2
|
import { render } from './CnAnalyticsTab.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-58d5577f";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -14,6 +14,7 @@ import ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue';
|
|
|
14
14
|
import __sfc_main$2 from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue.js';
|
|
15
15
|
import __sfc_main$1 from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue.js';
|
|
16
16
|
import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
|
|
17
|
+
import { userLocale } from '../../../utils/userLocale.js';
|
|
17
18
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
18
19
|
import { safeHref } from '../../../utils/safeHref.js';
|
|
19
20
|
|
|
@@ -233,7 +234,7 @@ var script = {
|
|
|
233
234
|
const num = Number(raw);
|
|
234
235
|
if (Number.isFinite(num) && String(raw).trim() !== '') {
|
|
235
236
|
try {
|
|
236
|
-
return num.toLocaleString()
|
|
237
|
+
return num.toLocaleString(userLocale())
|
|
237
238
|
} catch {
|
|
238
239
|
return String(num)
|
|
239
240
|
}
|
|
@@ -275,7 +276,7 @@ var script = {
|
|
|
275
276
|
if (Number.isNaN(d.getTime())) {
|
|
276
277
|
return String(value)
|
|
277
278
|
}
|
|
278
|
-
return d.toLocaleDateString(
|
|
279
|
+
return d.toLocaleDateString(userLocale(), { dateStyle: 'medium' })
|
|
279
280
|
} catch {
|
|
280
281
|
return String(value)
|
|
281
282
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnAnalyticsTab.vue2.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsTab.vue"],"sourcesContent":["<!--\n CnAnalyticsTab — bespoke sidebar tab for the `analytics` integration\n leaf.\n\n Replaces the generic `CnIntegrationTab` for the `analytics` leaf:\n renders the linked NC Analytics reports / datasets so the tab reads\n like the real Analytics app — each row is an `NcListItem` showing the\n chart-type icon (bar / line / pie / dataset), the bold report name, a\n data-source / type subtitle, an optional headline KPI value, and a\n colour-coded type badge (CnStatusBadge). Clicking a row deep-links\n into the NC Analytics app at `/index.php/apps/analytics/#/r/{id}`\n (`#/r/` is NC Analytics 6.x's report route). Per-row \"Open in\n Analytics\" + \"Unlink\" live in the NcListItem action menu.\n\n Tier-2 surface (this commit):\n - \"Link existing report\" → opens CnAnalyticsReportPicker (modal)\n - \"Create new report\" → opens CnAnalyticsReportCreate (modal)\n - Per-row unlink → DELETE …/analytics/{reportId}\n\n Talks to the OpenRegister Tier-2 analytics-link endpoints\n GET /api/objects/{r}/{s}/{id}/analytics — list\n POST /api/objects/{r}/{s}/{id}/analytics — link existing\n POST /api/objects/{r}/{s}/{id}/analytics/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/analytics/{reportId} — unlink\n GET /api/integrations/analytics/available — picker source\n served by `OCA\\OpenRegister\\Controller\\AnalyticsLinksController`.\n\n The endpoint returns rows shaped:\n { id, reportId, reportTitle, reportType, subheader, value, kpi,\n createdAt, modifiedAt, url, … }\n\n Empty + loading + error + 501/503 unavailable states follow ADR-017\n and AD-23 graceful degradation. All UI strings pass through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n The headline KPI (`value` / `kpi`) is rendered only when the provider\n supplies it — there is no extra HTTP call per row. A type-coded icon\n communicates the chart kind without fetching chart data; inline\n mini-charts remain a follow-up enhancement.\n\n Bespoke-vs-generic rationale: LaunchPad needs to surface \"what kind of\n report is this + its headline number\" without opening it; the generic\n link list strips the chart type, the data source and the KPI.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-analytics-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-analytics-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-analytics-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing report') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new report') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-analytics-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-sidebar-tab__empty cn-analytics-tab__empty\">\n\t\t\t<ChartBar :size=\"32\" class=\"cn-analytics-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openAnalyticsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Report list — NcListItem rows mirror the NC Analytics dataset list -->\n\t\t<ul v-else class=\"cn-analytics-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"report in reports\"\n\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\tclass=\"cn-analytics-tab__row\"\n\t\t\t\t:name=\"reportTitle(report)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-analytics-tab__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"22\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-analytics-tab__sub\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-analytics-tab__badge\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:variant=\"badgeVariant(report)\"\n\t\t\t\t\t\t\t:label=\"reportTypeLabel(report)\" />\n\t\t\t\t\t\t<span v-if=\"reportSubheader(report)\" class=\"cn-analytics-tab__subheader\">\n\t\t\t\t\t\t\t{{ reportSubheader(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"modifiedAt(report)\" class=\"cn-analytics-tab__when\">\n\t\t\t\t\t\t\t· {{ formatWhen(modifiedAt(report)) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"reportValue(report)\" #details>\n\t\t\t\t\t<span class=\"cn-analytics-tab__kpi\" :title=\"kpiTitle(report)\">\n\t\t\t\t\t\t{{ reportValue(report) }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Analytics') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkReport(report)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink report') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnAnalyticsReportPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnAnalyticsReportCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'\nimport CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.\n *\n * Renders linked NC Analytics reports / datasets as NcListItem rows\n * styled to mirror the Analytics app: chart-type icon, bold report\n * name, data-source / type subtitle, type badge and an optional\n * headline KPI value. Clicking a row opens the report in NC Analytics\n * in a new tab. Tier-2: includes link / create modals and per-row\n * unlink via the row action menu. See the file-level docblock for\n * design notes.\n */\nexport default {\n\tname: 'CnAnalyticsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAlertCircleOutline,\n\t\tChartBar,\n\t\tChartLine,\n\t\tChartPie,\n\t\tDatabaseOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tTableIcon,\n\t\tViewDashboard,\n\t\tCnAnalyticsReportPicker,\n\t\tCnAnalyticsReportCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated \"Open Analytics\" CTA label. */\n\t\topenAnalyticsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Analytics') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treports: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchReports()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchReports()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchReports()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 analytics endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tanalyticsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/analytics`)\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.reportId ?? report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable report title, stripping the\n\t\t * `[or:{uuid}]` marker (pre-Tier-2 reports embedded the marker\n\t\t * inside `name`).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.reportTitle || report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\t/**\n\t\t * Resolve the secondary \"subheader\" line, stripping any leaked\n\t\t * marker defensively.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportSubheader(report) {\n\t\t\tconst raw = report?.subheader ?? report?.dataSource ?? report?.data?.subheader ?? ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim()\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type. Falls back\n\t\t * to \"Report\" when the type code is unknown. Type codes mirror\n\t\t * NC Analytics 6.x (datasource types).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge colour variant for the report type. Mirrors NC\n\t\t * Analytics' datasource-type accenting without inventing colours\n\t\t * (uses the shared variant palette).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbadgeVariant(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return 'primary'\n\t\t\t\tcase 2: return 'success'\n\t\t\t\tcase 4: return 'warning'\n\t\t\t\tcase 6: return 'info'\n\t\t\t\tdefault: return 'default'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * MDI component for the report type. Tries to express the\n\t\t * \"kind\" without fetching chart data.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {object} Vue component.\n\t\t */\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return TableIcon\n\t\t\t\tcase 0: return ViewDashboard\n\t\t\t\tcase 2: return DatabaseOutline\n\t\t\t\tcase 4: return ChartLine\n\t\t\t\tcase 6: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Headline KPI / value for the report, when the provider supplies\n\t\t * one. Numbers are locale-formatted; strings pass through.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportValue(report) {\n\t\t\tconst raw = report?.value ?? report?.kpi ?? report?.data?.value ?? ''\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isFinite(num) && String(raw).trim() !== '') {\n\t\t\t\ttry {\n\t\t\t\t\treturn num.toLocaleString()\n\t\t\t\t} catch {\n\t\t\t\t\treturn String(num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(raw)\n\t\t},\n\n\t\tkpiTitle(_report) {\n\t\t\treturn t('nextcloud-vue', 'Latest value')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tmodifiedAt(report) {\n\t\t\treturn report?.modifiedAt\n\t\t\t\t|| report?.data?.modifiedAt\n\t\t\t\t|| report?.data?.modified\n\t\t\t\t|| report?.createdAt\n\t\t\t\t|| report?.data?.createdAt\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This report is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkReport(report) {\n\t\t\tconst reportId = this.reportKey(report)\n\t\t\tif (!reportId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/${reportId}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchReports() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-analytics-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-analytics-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-analytics-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-analytics-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-analytics-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Chart-type icon avatar — tinted with the descriptor accent (or NC primary). */\n.cn-analytics-tab__row-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tcolor: var(--cn-iw-accent, var(--color-primary-element, #0082c9));\n}\n\n/* Subtitle row: type badge + data source + modified date, single muted line. */\n.cn-analytics-tab__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-analytics-tab__badge {\n\tflex-shrink: 0;\n}\n\n.cn-analytics-tab__subheader {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmax-width: 100%;\n}\n\n.cn-analytics-tab__when {\n\twhite-space: nowrap;\n}\n\n/* Headline KPI value, right-aligned in the details slot — Analytics style. */\n.cn-analytics-tab__kpi {\n\tfont-weight: 700;\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnStatusBadge","CnAnalyticsReportPicker","CnAnalyticsReportCreate","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnAnalyticsTab.vue2.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsTab.vue"],"sourcesContent":["<!--\n CnAnalyticsTab — bespoke sidebar tab for the `analytics` integration\n leaf.\n\n Replaces the generic `CnIntegrationTab` for the `analytics` leaf:\n renders the linked NC Analytics reports / datasets so the tab reads\n like the real Analytics app — each row is an `NcListItem` showing the\n chart-type icon (bar / line / pie / dataset), the bold report name, a\n data-source / type subtitle, an optional headline KPI value, and a\n colour-coded type badge (CnStatusBadge). Clicking a row deep-links\n into the NC Analytics app at `/index.php/apps/analytics/#/r/{id}`\n (`#/r/` is NC Analytics 6.x's report route). Per-row \"Open in\n Analytics\" + \"Unlink\" live in the NcListItem action menu.\n\n Tier-2 surface (this commit):\n - \"Link existing report\" → opens CnAnalyticsReportPicker (modal)\n - \"Create new report\" → opens CnAnalyticsReportCreate (modal)\n - Per-row unlink → DELETE …/analytics/{reportId}\n\n Talks to the OpenRegister Tier-2 analytics-link endpoints\n GET /api/objects/{r}/{s}/{id}/analytics — list\n POST /api/objects/{r}/{s}/{id}/analytics — link existing\n POST /api/objects/{r}/{s}/{id}/analytics/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/analytics/{reportId} — unlink\n GET /api/integrations/analytics/available — picker source\n served by `OCA\\OpenRegister\\Controller\\AnalyticsLinksController`.\n\n The endpoint returns rows shaped:\n { id, reportId, reportTitle, reportType, subheader, value, kpi,\n createdAt, modifiedAt, url, … }\n\n Empty + loading + error + 501/503 unavailable states follow ADR-017\n and AD-23 graceful degradation. All UI strings pass through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n The headline KPI (`value` / `kpi`) is rendered only when the provider\n supplies it — there is no extra HTTP call per row. A type-coded icon\n communicates the chart kind without fetching chart data; inline\n mini-charts remain a follow-up enhancement.\n\n Bespoke-vs-generic rationale: LaunchPad needs to surface \"what kind of\n report is this + its headline number\" without opening it; the generic\n link list strips the chart type, the data source and the KPI.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-analytics-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-analytics-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-analytics-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing report') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new report') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-analytics-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-sidebar-tab__empty cn-analytics-tab__empty\">\n\t\t\t<ChartBar :size=\"32\" class=\"cn-analytics-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openAnalyticsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Report list — NcListItem rows mirror the NC Analytics dataset list -->\n\t\t<ul v-else class=\"cn-analytics-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"report in reports\"\n\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\tclass=\"cn-analytics-tab__row\"\n\t\t\t\t:name=\"reportTitle(report)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-analytics-tab__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"22\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-analytics-tab__sub\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-analytics-tab__badge\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:variant=\"badgeVariant(report)\"\n\t\t\t\t\t\t\t:label=\"reportTypeLabel(report)\" />\n\t\t\t\t\t\t<span v-if=\"reportSubheader(report)\" class=\"cn-analytics-tab__subheader\">\n\t\t\t\t\t\t\t{{ reportSubheader(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"modifiedAt(report)\" class=\"cn-analytics-tab__when\">\n\t\t\t\t\t\t\t· {{ formatWhen(modifiedAt(report)) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"reportValue(report)\" #details>\n\t\t\t\t\t<span class=\"cn-analytics-tab__kpi\" :title=\"kpiTitle(report)\">\n\t\t\t\t\t\t{{ reportValue(report) }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Analytics') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkReport(report)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink report') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnAnalyticsReportPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnAnalyticsReportCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'\nimport CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\n/**\n * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.\n *\n * Renders linked NC Analytics reports / datasets as NcListItem rows\n * styled to mirror the Analytics app: chart-type icon, bold report\n * name, data-source / type subtitle, type badge and an optional\n * headline KPI value. Clicking a row opens the report in NC Analytics\n * in a new tab. Tier-2: includes link / create modals and per-row\n * unlink via the row action menu. See the file-level docblock for\n * design notes.\n */\nexport default {\n\tname: 'CnAnalyticsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAlertCircleOutline,\n\t\tChartBar,\n\t\tChartLine,\n\t\tChartPie,\n\t\tDatabaseOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tTableIcon,\n\t\tViewDashboard,\n\t\tCnAnalyticsReportPicker,\n\t\tCnAnalyticsReportCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated \"Open Analytics\" CTA label. */\n\t\topenAnalyticsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Analytics') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treports: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchReports()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchReports()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchReports()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 analytics endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tanalyticsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/analytics`)\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.reportId ?? report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable report title, stripping the\n\t\t * `[or:{uuid}]` marker (pre-Tier-2 reports embedded the marker\n\t\t * inside `name`).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.reportTitle || report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\t/**\n\t\t * Resolve the secondary \"subheader\" line, stripping any leaked\n\t\t * marker defensively.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportSubheader(report) {\n\t\t\tconst raw = report?.subheader ?? report?.dataSource ?? report?.data?.subheader ?? ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim()\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type. Falls back\n\t\t * to \"Report\" when the type code is unknown. Type codes mirror\n\t\t * NC Analytics 6.x (datasource types).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge colour variant for the report type. Mirrors NC\n\t\t * Analytics' datasource-type accenting without inventing colours\n\t\t * (uses the shared variant palette).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbadgeVariant(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return 'primary'\n\t\t\t\tcase 2: return 'success'\n\t\t\t\tcase 4: return 'warning'\n\t\t\t\tcase 6: return 'info'\n\t\t\t\tdefault: return 'default'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * MDI component for the report type. Tries to express the\n\t\t * \"kind\" without fetching chart data.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {object} Vue component.\n\t\t */\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return TableIcon\n\t\t\t\tcase 0: return ViewDashboard\n\t\t\t\tcase 2: return DatabaseOutline\n\t\t\t\tcase 4: return ChartLine\n\t\t\t\tcase 6: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Headline KPI / value for the report, when the provider supplies\n\t\t * one. Numbers are locale-formatted; strings pass through.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportValue(report) {\n\t\t\tconst raw = report?.value ?? report?.kpi ?? report?.data?.value ?? ''\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isFinite(num) && String(raw).trim() !== '') {\n\t\t\t\ttry {\n\t\t\t\t\treturn num.toLocaleString(userLocale())\n\t\t\t\t} catch {\n\t\t\t\t\treturn String(num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(raw)\n\t\t},\n\n\t\tkpiTitle(_report) {\n\t\t\treturn t('nextcloud-vue', 'Latest value')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tmodifiedAt(report) {\n\t\t\treturn report?.modifiedAt\n\t\t\t\t|| report?.data?.modifiedAt\n\t\t\t\t|| report?.data?.modified\n\t\t\t\t|| report?.createdAt\n\t\t\t\t|| report?.data?.createdAt\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(userLocale(), { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This report is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkReport(report) {\n\t\t\tconst reportId = this.reportKey(report)\n\t\t\tif (!reportId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/${reportId}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchReports() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-analytics-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-analytics-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-analytics-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-analytics-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-analytics-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Chart-type icon avatar — tinted with the descriptor accent (or NC primary). */\n.cn-analytics-tab__row-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tcolor: var(--cn-iw-accent, var(--color-primary-element, #0082c9));\n}\n\n/* Subtitle row: type badge + data source + modified date, single muted line. */\n.cn-analytics-tab__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-analytics-tab__badge {\n\tflex-shrink: 0;\n}\n\n.cn-analytics-tab__subheader {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmax-width: 100%;\n}\n\n.cn-analytics-tab__when {\n\twhite-space: nowrap;\n}\n\n/* Headline KPI value, right-aligned in the details slot — Analytics style. */\n.cn-analytics-tab__kpi {\n\tfont-weight: 700;\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnStatusBadge","CnAnalyticsReportPicker","CnAnalyticsReportCreate","t"],"mappings":"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"}
|