@conduction/nextcloud-vue 2.85.0 → 2.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/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":"CnWidgetWrapper.vue3.js","sources":["../../../../src/components/CnWidgetWrapper/CnWidgetWrapper.vue?vue&type=template&id=b19b1aa6&lang.js"],"sourcesContent":["<!--\n CnWidgetWrapper — Container shell around a dashboard widget.\n\n Provides header (icon + title), scrollable content area, and optional\n footer with action buttons. Applies style configuration for borders,\n backgrounds, and padding. The built-in overflow Actions menu (Refresh /\n Documentation / Request a feature) is delegated to the shared\n CnActionsMenu so widgets and page surfaces stay in lockstep.\n-->\n<template>\n\t<div\n\t\tclass=\"cn-widget-wrapper\"\n\t\t:class=\"{\n\t\t\t'cn-widget-wrapper--borderless': noBorder,\n\t\t\t'cn-widget-wrapper--flush': noPadding,\n\t\t\t'cn-widget-wrapper--chromeless': chromeless,\n\t\t\t'cn-widget-wrapper--nc-dashboard': chrome === 'nc-dashboard',\n\t\t\t'cn-widget-wrapper--high-contrast': highContrast,\n\t\t}\"\n\t\t:data-cn-high-contrast=\"highContrast ? 'true' : undefined\"\n\t\t:style=\"wrapperStyles\">\n\t\t<!-- Header.\n\t\t Rendered when a title earns it, or when controls that are actually\n\t\t on screen need a home. Three conditions rather than one, and each of\n\t\t the three is a bug this component has already shipped: gating on the\n\t\t title alone took the Save button for an inline edit away along with\n\t\t the doubled title; reading `showActions`, which defaults to true,\n\t\t handed a header to every headerless KPI tile; and reading the\n\t\t `actions` slot's mere PRESENCE gave a chromeless panel a 59px band\n\t\t holding nothing, because `CnObjectDataWidget` provides that template\n\t\t always and fills it only while an edit is unsaved.\n\t\t `headerIsWorthIt()` carries the reasoning. -->\n\t\t<div v-if=\"headerIsWorthIt()\"\n\t\t\tclass=\"cn-widget-wrapper__header\"\n\t\t\t:class=\"{ 'cn-widget-wrapper__header--actions-only': !titleVisible }\"\n\t\t\t:style=\"[headerStyles, titleIconStyle]\">\n\t\t\t<!-- Title icon — left: rendered before the title group. Moved\n\t\t\t INSIDE header-left so the header's `space-between` cannot pull\n\t\t\t it away from the title it belongs to; header-left's `gap` is\n\t\t\t what now separates icon from title (they used to touch). -->\n\t\t\t<div v-if=\"titleVisible\" class=\"cn-widget-wrapper__header-left\">\n\t\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'left'\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t\t</div>\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"iconUrl\"\n\t\t\t\t\t:src=\"iconUrl\"\n\t\t\t\t\t:alt=\"resolvedTitle\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\">\n\t\t\t\t<span\n\t\t\t\t\tv-else-if=\"iconClass\"\n\t\t\t\t\t:class=\"iconClass\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\" />\n\t\t\t\t<!-- An eyebrow (`eyebrow`) is a muted line above the title:\n\t\t\t\t \"Saldo per week\" above \"Prognose 13 weken\". -->\n\t\t\t\t<div v-if=\"resolvedEyebrow\" class=\"cn-widget-wrapper__title-stack\">\n\t\t\t\t\t<span class=\"cn-widget-wrapper__eyebrow\" data-testid=\"cn-widget-wrapper-eyebrow\">{{ resolvedEyebrow }}</span>\n\t\t\t\t\t<h3 :id=\"titleId\" class=\"cn-widget-wrapper__title\">\n\t\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t\t</h3>\n\t\t\t\t</div>\n\t\t\t\t<h3 v-else :id=\"titleId\" class=\"cn-widget-wrapper__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h3>\n\t\t\t\t<!-- @slot title-meta Rendered inside the header's left group,\n\t\t\t\t after the title and before the spacer. Use for small\n\t\t\t\t contextual chips (e.g. a date-range badge on chart\n\t\t\t\t widgets). Kept inside header-left so the chip sits next\n\t\t\t\t to the title rather than floating right next to the\n\t\t\t\t actions menu. -->\n\t\t\t\t<div v-if=\"$slots['title-meta']\" class=\"cn-widget-wrapper__title-meta\">\n\t\t\t\t\t<slot name=\"title-meta\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- `showActions` gates the overflow MENU, not the slot beside it.\n\t\t\t It used to gate both, so a caller who switched the menu off\n\t\t\t also lost the Save button for an inline edit, silently. -->\n\t\t\t<div v-if=\"actionsAreVisible()\" class=\"cn-widget-wrapper__actions\">\n\t\t\t\t<!-- One text link in the header (`headerLink`): \"All deadlines\",\n\t\t\t\t \"To the board\". A route is a router link, an href a plain\n\t\t\t\t anchor. Sits before the slot and the overflow menu. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"headerLinkTag\"\n\t\t\t\t\tv-if=\"headerLinkTag\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__header-link\"\n\t\t\t\t\tdata-testid=\"cn-widget-wrapper-header-link\"\n\t\t\t\t\tv-bind=\"headerLinkAttrs\">\n\t\t\t\t\t{{ headerLinkLabel }}\n\t\t\t\t</component>\n\t\t\t\t<!-- @slot actions Custom action buttons rendered before the\n\t\t\t\t built-in overflow menu. -->\n\t\t\t\t<slot name=\"actions\" />\n\t\t\t\t<CnActionsMenu\n\t\t\t\t\tv-if=\"showActions\"\n\t\t\t\t\t:showRefresh=\"effectiveShowRefresh\"\n\t\t\t\t\t:showRequestFeature=\"effectiveShowRequestFeature\"\n\t\t\t\t\t:showReportBug=\"showReportBug\"\n\t\t\t\t\t:showDocumentation=\"showDocumentation\"\n\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t:docsAnchor=\"docsAnchor\"\n\t\t\t\t\t:reportBugUrl=\"reportBugUrl\"\n\t\t\t\t\t:documentationLabel=\"documentationLabel\"\n\t\t\t\t\t:refreshLabel=\"refreshLabel\"\n\t\t\t\t\t:requestFeatureLabel=\"requestFeatureLabel\"\n\t\t\t\t\t:actionsMenuLabel=\"actionsMenuLabel\"\n\t\t\t\t\t:refreshing=\"refreshing\"\n\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t:title=\"displayTitle\"\n\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t:specRef=\"specRef\"\n\t\t\t\t\trefreshChannel=\"cn:widget:refresh\"\n\t\t\t\t\ttestidBase=\"cn-widget-wrapper\"\n\t\t\t\t\t@refresh=\"onActionsRefresh\"\n\t\t\t\t\t@requestFeature=\"onActionsRequestFeature\">\n\t\t\t\t\t<!-- @slot action-items Additional NcActionButton-family\n\t\t\t\t\t items rendered inside the overflow menu, after the\n\t\t\t\t\t built-in Refresh / Documentation / Request-a-feature\n\t\t\t\t\t group. -->\n\t\t\t\t\t<template v-if=\"hasActionItemsSlot\" #action-items>\n\t\t\t\t\t\t<slot name=\"action-items\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnActionsMenu>\n\t\t\t</div>\n\t\t\t<!-- Title icon — right: rendered after actions, far right -->\n\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'right'\"\n\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Headerless meta (e.g. a KPI date chip on a flush card). When the\n\t\t header is hidden but a `title-meta` chip is provided, float it in the\n\t\t top-right corner over the content instead of dropping it — so a\n\t\t compact flush KPI tile can carry a date-range chip without growing a\n\t\t full header bar. -->\n\t\t<div\n\t\t\tv-if=\"!titleVisible && $slots['title-meta']\"\n\t\t\tclass=\"cn-widget-wrapper__floating-meta\">\n\t\t\t<slot name=\"title-meta\" />\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<!--\n\t\t The content area is `overflow: auto`, so it can become a scrollable\n\t\t region. WCAG 2.1.1 (Keyboard) requires scrollable regions to be\n\t\t keyboard-focusable so keyboard-only users can scroll them, and 4.1.2\n\t\t requires an accessible name. We expose it as a labelled region and\n\t\t give it tabindex=\"0\". When the header (and its title) is hidden we\n\t\t fall back to an explicit aria-label from the widget title so the\n\t\t region is never anonymous.\n\t\t-->\n\t\t<div\n\t\t\tclass=\"cn-widget-wrapper__content\"\n\t\t\ttabindex=\"0\"\n\t\t\trole=\"region\"\n\t\t\t:aria-labelledby=\"titleVisible ? titleId : null\"\n\t\t\t:aria-label=\"titleVisible ? null : resolvedTitle\">\n\t\t\t<slot />\n\t\t</div>\n\n\t\t<!-- Footer -->\n\t\t<div v-if=\"$slots.footer || (buttons && buttons.length > 0)\" class=\"cn-widget-wrapper__footer\">\n\t\t\t<slot name=\"footer\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"button in buttons\"\n\t\t\t\t\t:key=\"button.link\"\n\t\t\t\t\t:href=\"button.link\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__footer-link\">\n\t\t\t\t\t{{ button.text }}\n\t\t\t\t</a>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { slotRenders } from '../../utils/slotContent.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\n\n/**\n * CnWidgetWrapper — Widget container with header, content, and footer.\n *\n * ```vue\n * <CnWidgetWrapper title=\"My Cases\" :icon-url=\"casesIconUrl\">\n * <MyCasesChart :data=\"chartData\" />\n * </CnWidgetWrapper>\n * ```\n *\n * With NC widget object\n * ```vue\n * <CnWidgetWrapper\n * :title=\"widget.title\"\n * :icon-url=\"widget.iconUrl\"\n * :icon-class=\"widget.iconClass\"\n * :documentation-url=\"widget.documentationUrl\"\n * :buttons=\"widget.buttons\">\n * <CnWidgetRenderer :widget=\"widget\" />\n * </CnWidgetWrapper>\n * ```\n */\n/**\n * Semantic variant → CSS colour token for the widget header icon. Tokens\n * only — the nldesign app themes by overriding the Nextcloud variables, so a\n * literal hex here would ignore government theming entirely (ADR-062 / NL\n * Design System).\n *\n * @type {Record<string, string>}\n */\nconst TITLE_ICON_VARIANT_COLORS = {\n\tprimary: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n\tinfo: 'var(--color-info, var(--color-primary-element))',\n\tneutral: 'var(--color-text-maxcontrast)',\n}\n\nexport default {\n\tname: 'CnWidgetWrapper',\n\n\tcomponents: {\n\t\tCnActionsMenu,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored widget title is run through it so the card\n\t\t * heading follows the user's language. Defaults to an identity\n\t\t * function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Widget title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Widget'),\n\t\t},\n\n\t\t/**\n\t\t * A short muted line above the title (\"Saldo per week\" above\n\t\t * \"Prognose 13 weken\"), through the host translate. Empty draws none.\n\t\t */\n\t\teyebrow: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Whether to show the header with title */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Chrome variant for the wrapper card.\n\t\t * - `'default'` — the library's own card chrome (opaque background,\n\t\t * 1px border, compact header).\n\t\t * - `'nc-dashboard'` — matches the native Nextcloud Dashboard panel\n\t\t * exactly using the same design tokens: translucent blurred\n\t\t * background (`--color-main-background-blur` + `--filter-background-blur`),\n\t\t * `--border-radius-container-large` corners, no border/shadow, a 16px\n\t\t * header with a 20px/700 title and 32px leading icon, and content\n\t\t * inset 16px on the sides + bottom. `styleConfig` overrides still\n\t\t * layer on top so a user can customise any token.\n\t\t *\n\t\t * @type {'default'|'nc-dashboard'}\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'nc-dashboard'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Remove border and background — makes the wrapper transparent.\n\t\t * Useful for widgets that are self-contained cards (e.g. CnStatsBlock).\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Remove content padding — allows content to go edge-to-edge.\n\t\t * Useful for list-style widgets where items should span the full width.\n\t\t */\n\t\tflush: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw NO card of this widget's own: no border, no background, no\n\t\t * content padding, no title row and no header divider. For a surface\n\t\t * that already supplies the card and the heading — a tab panel being\n\t\t * the case that matters, where a nested card repeats both.\n\t\t *\n\t\t * The wrapper ELEMENT still renders. That is the contract, not an\n\t\t * oversight: `CnObjectDataWidget` measures its overflow against\n\t\t * `.cn-widget-wrapper__content` (`closest()` plus a ResizeObserver on\n\t\t * that node), `src/css/table.css` gives a table inside it its\n\t\t * edge-to-edge treatment, and `src/css/detail-page.css` and\n\t\t * `dashboard.css` size their content areas through it. Dropping the\n\t\t * node would take all of that out silently — `closest()` returning\n\t\t * null reads as \"nothing overflows\", never as an error.\n\t\t *\n\t\t * Controls in the `actions` slot are NOT chrome and keep their header:\n\t\t * the Save button that commits an inline edit lives there, and a panel\n\t\t * that hides it is silently unsaveable.\n\t\t *\n\t\t * `chromeless` is what an integration provider already asks for through\n\t\t * `CnDetailWidgetHost` (`bareWidget`), so this is the same word for the\n\t\t * same thing on every path into the wrapper.\n\t\t */\n\t\tchromeless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Icon URL (image) */\n\t\ticonUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon CSS class (e.g., Nextcloud icon class) */\n\t\ticonClass: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Position of the title-icon slot in the header.\n\t\t * 'left' places it before the title; 'right' places it after the actions.\n\t\t */\n\t\ttitleIconPosition: {\n\t\t\ttype: String,\n\t\t\tdefault: 'right',\n\t\t\tvalidator: (v) => ['left', 'right'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Explicit CSS colour for the header icon. Overrides `titleIconVariant`.\n\t\t * Must be a CSS custom property or a theme token — never a literal hex\n\t\t * (NL Design System: the nldesign app re-themes by overriding the\n\t\t * Nextcloud variables, and a hardcoded colour ignores that).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttitleIconColor: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Semantic colour for the header icon. Every widget's icon is\n\t\t * coloured — `primary` (the theme colour) is the default, and a\n\t\t * widget whose subject already carries a semantic meaning names it\n\t\t * here so the icon says so at a glance (a Depublished list is\n\t\t * `error`, a Concepts list `warning`, a Published list `success`).\n\t\t *\n\t\t * @type {'primary'|'success'|'warning'|'error'|'info'|'neutral'}\n\t\t */\n\t\ttitleIconVariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'primary',\n\t\t\tvalidator: (v) => ['primary', 'success', 'warning', 'error', 'info', 'neutral'].includes(v),\n\t\t},\n\n\t\t/** Footer action buttons: [{ text, link }] */\n\t\tbuttons: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * One text link in the header's actions group, before the overflow\n\t\t * menu: `{ label, route?, params?, query?, href? }`. A `route` is a\n\t\t * named router link (needs a router), an `href` a plain anchor. The\n\t\t * label goes through the host translate function. Null (the\n\t\t * default) draws nothing. Manifest: `headerLink` on the widget\n\t\t * definition or its layout entry.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @type {{label: string, route?: string, params?: object, query?: object, href?: string}|null}\n\t\t */\n\t\theaderLink: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the header's overflow action menu (Refresh / Documentation /\n\t\t * Request-a-feature + any `#action-items`) renders. Shown by default;\n\t\t * set `false` for compact surfaces — e.g. a KPI tile whose only header\n\t\t * affordance is a date chip — to drop the menu and free header width.\n\t\t */\n\t\tshowActions: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Style configuration for the wrapper.\n\t\t *\n\t\t * @type {{ backgroundColor: string, borderStyle: string, borderWidth: number, borderColor: string, borderRadius: number, padding: { top: number, right: number, bottom: number, left: number } }}\n\t\t */\n\t\tstyleConfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Refresh item from the overflow action menu.\n\t\t * The Refresh item is shown by default — set this when the widget\n\t\t * has no refreshable data source (e.g. a static tile). Alias for\n\t\t * the inverse `:show-refresh=\"false\"`; either form opts out.\n\t\t */\n\t\thideRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Request-a-feature item from the overflow\n\t\t * action menu. Shown by default; set when the consuming app has\n\t\t * no public issue tracker to link out to. Alias for the inverse\n\t\t * `:show-request-feature=\"false\"`; either form opts out.\n\t\t */\n\t\thideRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether to show the built-in Refresh item. Tri-state:\n\t\t * - `true` / `false` — force the action on or off.\n\t\t * - `null` (the default) — **auto**: show the action only when a\n\t\t * parent has attached an `@refresh` listener (i.e. something will\n\t\t * actually handle the refresh). This keeps widgets that can't\n\t\t * refresh — e.g. a prop-driven `CnObjectDataWidget` — from showing\n\t\t * a dead button. Widgets that refresh themselves via the\n\t\t * `cn:widget:refresh` event bus (with no `@refresh` listener)\n\t\t * should set `:show-refresh=\"true\"` explicitly.\n\t\t *\n\t\t * `hideRefresh` (or `:show-refresh=\"false\"`) always wins.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Inverse of `hideRequestFeature`. Defaults to `true` so the\n\t\t * action renders. Set `:show-request-feature=\"false\"` to hide it.\n\t\t * Either flag hides the action.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Report a bug\" item renders. On by default —\n\t\t * the trio Request a feature / Report a bug / Documentation is the\n\t\t * contract for every widget; this exists for the rare surface that\n\t\t * must suppress one deliberately.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowReportBug: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Documentation\" item renders. On by default,\n\t\t * for the same reason as `showReportBug`. The item's target is\n\t\t * resolved by the shared menu (see `docsAnchor`), so leaving it on\n\t\t * costs the host nothing.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowDocumentation: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Explicit documentation link for this widget, opened in a new tab.\n\t\t * Usually unnecessary: leave it empty and set `docsAnchor` instead,\n\t\t * so the link is built from the app-wide documentation base and\n\t\t * lands on this widget's own section.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Documentation action.\n\t\t * Defaults to the lib's translation of \"Documentation\".\n\t\t */\n\t\tdocumentationLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Documentation'),\n\t\t},\n\n\t\t/**\n\t\t * Widget id for the built-in default Refresh / Request-a-feature\n\t\t * handlers (B2). Forwarded as the `surface: \"widget:<id>\"` value\n\t\t * on the Actions menu (the English headline fallback for its forge\n\t\t * deep-links) AND as the `widgetId` field on the\n\t\t * `cn:widget:refresh` event-bus payload. When unset, the wrapper\n\t\t * falls back to a slugified `displayTitle`, which works but is\n\t\t * less stable than an explicit id.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * This widget's own section in the app's documentation, appended to\n\t\t * the app-wide documentation base URL (provided by CnAppRoot) to build\n\t\t * the Actions menu's Documentation deep-link. A bare slug becomes a\n\t\t * `#fragment`. Supply it per widget type — without it the item still\n\t\t * renders, but lands on the docs homepage rather than this widget.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocsAnchor: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Explicit \"Report a bug\" target for the Actions menu. Empty (the\n\t\t * default) builds a new-issue deep-link on the app's own forge.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\treportBugUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Declares that this widget wants a high contrast treatment. The flag\n\t\t * reaches the theme as `data-cn-high-contrast` and a `--high-contrast`\n\t\t * class; the component picks no colour of its own (ADR-003). A theme\n\t\t * that says nothing about the flag renders the widget unchanged.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\thighContrast: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional `specRef` slug, forwarded to the Actions menu. Accepted\n\t\t * for backward compatibility with the removed in-product suggestion\n\t\t * modal; the forge issue form asks for its own context.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tspecRef: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether a refresh is currently in flight. When bound by the host\n\t\t * (e.g. `:refreshing=\"loading\"` around its refetch), the Refresh\n\t\t * item is disabled and shows a loading spinner for exactly as long\n\t\t * as this stays true — so the spinner reflects the real refresh time.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\trefreshing: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Refresh action. Defaults\n\t\t * to the lib's translation of \"Refresh\" so callers usually don't\n\t\t * need to set this.\n\t\t */\n\t\trefreshLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Refresh'),\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Request-a-feature action.\n\t\t * Defaults to the lib's translation of \"Request a feature\".\n\t\t */\n\t\trequestFeatureLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Request a feature'),\n\t\t},\n\n\t\t/**\n\t\t * Pre-translated aria-label / tooltip for the overflow menu\n\t\t * trigger. Defaults to \"Actions\".\n\t\t */\n\t\tactionsMenuLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Actions'),\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`,\n\t\t * which itself defaults to an identity function.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['refresh', 'request-feature'],\n\n\tcomputed: {\n\t\tdisplayTitle() {\n\t\t\treturn this.title || 'Widget'\n\t\t},\n\n\t\t/**\n\t\t * The header's icon colour, published as ONE custom property on the\n\t\t * header rather than an inline `color` repeated on each icon element.\n\t\t * Everything under the header — the icon-class span, the `title-icon`\n\t\t * slot's content, an SVG inside it — inherits from that single\n\t\t * declaration, so the icon and any decoration around it cannot end up\n\t\t * different colours.\n\t\t *\n\t\t * `titleIconColor` wins when the host set one; otherwise the\n\t\t * `titleIconVariant` token does, which is why EVERY widget icon is\n\t\t * coloured and not just the ones an app remembered to configure. Every\n\t\t * value is a CSS variable, so the nldesign app's overrides re-theme\n\t\t * them for free (NL Design System).\n\t\t *\n\t\t * @return {object} A style object declaring `--cn-widget-icon-color`.\n\t\t */\n\t\ttitleIconStyle() {\n\t\t\treturn {\n\t\t\t\t'--cn-widget-icon-color': this.titleIconColor\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS[this.titleIconVariant]\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS.primary,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the explicit `translate` prop when\n\t\t * given, else the injected `cnTranslate` (identity by default).\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The header link's tag: `router-link` for a route (when a router is\n\t\t * mounted), `a` for an href, '' when there is no usable link.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {string}\n\t\t */\n\t\theaderLinkTag() {\n\t\t\tconst link = this.headerLink\n\t\t\tif (!link || typeof link.label !== 'string' || link.label === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (link.route && this.$router) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (typeof link.href === 'string' && link.href !== '') {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * The header link's attributes for its tag.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {object}\n\t\t */\n\t\theaderLinkAttrs() {\n\t\t\tconst link = this.headerLink || {}\n\t\t\tif (this.headerLinkTag === 'router-link') {\n\t\t\t\tconst to = { name: link.route }\n\t\t\t\tif (link.params && typeof link.params === 'object') {\n\t\t\t\t\tto.params = link.params\n\t\t\t\t}\n\t\t\t\tif (link.query && typeof link.query === 'object') {\n\t\t\t\t\tto.query = link.query\n\t\t\t\t}\n\t\t\t\treturn { to }\n\t\t\t}\n\t\t\tif (this.headerLinkTag === 'a') {\n\t\t\t\treturn { href: link.href }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * The header link's text, through the host translate function.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {string}\n\t\t */\n\t\theaderLinkLabel() {\n\t\t\tconst label = this.headerLink && this.headerLink.label\n\t\t\treturn label ? this.effectiveTranslate(label) : ''\n\t\t},\n\n\t\t/**\n\t\t * The eyebrow through the host translate, or ''.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/widget-title-eyebrow/specs/dashboard-page/spec.md#requirement-a-widget-card-can-carry-a-line-above-its-title\n\t\t */\n\t\tresolvedEyebrow() {\n\t\t\treturn this.eyebrow ? this.effectiveTranslate(this.eyebrow) : ''\n\t\t},\n\n\t\t/**\n\t\t * The RENDERED widget title — `displayTitle` run through the host\n\t\t * translate function. Kept separate from `displayTitle` on purpose:\n\t\t * `resolvedWidgetId` slugifies the raw title, so identifiers (DOM\n\t\t * ids, refresh channels, action payloads) stay locale-independent\n\t\t * while only the visible/AT-facing text localises.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title ? this.effectiveTranslate(this.title) : this.displayTitle\n\t\t},\n\n\t\t/**\n\t\t * Stable DOM id for the header title `<h3>`, used as the\n\t\t * `aria-labelledby` target of the scrollable content region so the\n\t\t * region gets an accessible name from the widget title (WCAG 4.1.2).\n\t\t * Derived from `resolvedWidgetId` so it is stable and unique per\n\t\t * widget instance on the page.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttitleId() {\n\t\t\treturn `cn-widget-wrapper-title-${this.resolvedWidgetId}`\n\t\t},\n\n\t\t/**\n\t\t * Effective Refresh visibility. `hideRefresh: true` (or\n\t\t * `:show-refresh=\"false\"`) always hides it. When `showRefresh` is\n\t\t * left unset (`null`), auto-detect: show the action only when a\n\t\t * parent attached an `@refresh` listener — otherwise the refresh\n\t\t * would do nothing. `hideRefresh` remains a back-compat alias.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRefresh() {\n\t\t\tif (this.hideRefresh) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.showRefresh !== null) {\n\t\t\t\treturn this.showRefresh\n\t\t\t}\n\t\t\t// `$.vnode.props`, not `$attrs`: `refresh` is a declared emit, and\n\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\treturn Boolean(this.$.vnode.props?.onRefresh)\n\t\t},\n\n\t\t/**\n\t\t * Effective Request-a-feature visibility — same OR-of-opt-outs\n\t\t * pattern as `effectiveShowRefresh`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRequestFeature() {\n\t\t\treturn this.showRequestFeature && !this.hideRequestFeature\n\t\t},\n\n\t\t/**\n\t\t * Stable id used in event-bus payloads and the\n\t\t * `surface: \"widget:<id>\"` field on the auto-mounted feature\n\t\t * modal. Prefers the explicit `widgetId` prop; falls back to a\n\t\t * slugified `displayTitle` so widgets without an explicit id\n\t\t * still get a usable identifier.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedWidgetId() {\n\t\t\tif (this.widgetId) {\n\t\t\t\treturn this.widgetId\n\t\t\t}\n\t\t\treturn this.displayTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller supplied an `action-items` slot. Forwarded\n\t\t * conditionally so the shared CnActionsMenu doesn't treat an\n\t\t * always-present pass-through template as content (which would\n\t\t * keep the overflow menu visible even when every built-in is\n\t\t * opted out).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasActionItemsSlot() {\n\t\t\treturn Boolean(this.$slots['action-items']) || Boolean(this.$slots && this.$slots['action-items'])\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller put controls in the `actions` slot.\n\t\t *\n\t\t * Gates the header together with `showTitle`, so a surface that wants no\n\t\t * title still keeps controls that have nowhere else to go — the Save\n\t\t * button `CnObjectDataWidget` renders there being the case that matters.\n\t\t *\n\t\t * Deliberately NOT `showActions`: that prop defaults to true and governs\n\t\t * the overflow menu, so reading it here would give a header to every\n\t\t * headerless KPI tile that never had one.\n\t\t *\n\t\t * @return {boolean} true when the slot is filled.\n\t\t */\n\t\thasActionsSlot() {\n\t\t\treturn Boolean(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the card outline is drawn. `chromeless` implies `borderless`\n\t\t * so a caller names the intent once instead of remembering the three\n\t\t * props it decomposes into.\n\t\t *\n\t\t * @return {boolean} true when border and background are suppressed.\n\t\t */\n\t\tnoBorder() {\n\t\t\treturn this.borderless || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the content area goes edge to edge. `chromeless` implies\n\t\t * `flush` for the same reason as `noBorder`.\n\t\t *\n\t\t * @return {boolean} true when content padding is suppressed.\n\t\t */\n\t\tnoPadding() {\n\t\t\treturn this.flush || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the title row renders. `chromeless` overrides `showTitle`,\n\t\t * which defaults to true: a surface that already names the widget must\n\t\t * not have to also remember to turn the title off.\n\t\t *\n\t\t * Read everywhere the template used to read `showTitle` directly,\n\t\t * including the two ARIA bindings on the content region — a hidden\n\t\t * title cannot be the region's `aria-labelledby` target.\n\t\t *\n\t\t * @return {boolean} true when the header's title group renders.\n\t\t */\n\t\ttitleVisible() {\n\t\t\treturn this.showTitle && !this.chromeless\n\t\t},\n\n\t\twrapperStyles() {\n\t\t\tconst styles = {}\n\n\t\t\tif (this.styleConfig.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = this.styleConfig.backgroundColor\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderStyle && this.styleConfig.borderStyle !== 'none') {\n\t\t\t\tstyles.border = `${this.styleConfig.borderWidth || 1}px ${this.styleConfig.borderStyle} ${this.styleConfig.borderColor || 'var(--color-border)'}`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderRadius !== undefined) {\n\t\t\t\tstyles.borderRadius = `${this.styleConfig.borderRadius}px`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.padding) {\n\t\t\t\tconst p = this.styleConfig.padding\n\t\t\t\tstyles.padding = `${p.top || 0}px ${p.right || 0}px ${p.bottom || 0}px ${p.left || 0}px`\n\t\t\t}\n\n\t\t\treturn styles\n\t\t},\n\n\t\t/**\n\t\t * Inline styles for the header bar, derived from\n\t\t * `styleConfig.headerStyle.{backgroundColor, textColor}`. Lets a host\n\t\t * give individual widgets a custom header colour without a per-app CSS\n\t\t * workaround. Empty object when no header style is configured.\n\t\t *\n\t\t * @return {object} the header style bindings.\n\t\t */\n\t\theaderStyles() {\n\t\t\tconst hs = this.styleConfig.headerStyle\n\t\t\tif (!hs || typeof hs !== 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst styles = {}\n\t\t\tif (hs.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = hs.backgroundColor\n\t\t\t}\n\t\t\tif (hs.textColor) {\n\t\t\t\tstyles.color = hs.textColor\n\t\t\t}\n\t\t\treturn styles\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the `actions` slot renders anything a reader can see.\n\t\t *\n\t\t * `CnObjectDataWidget` always PROVIDES the template and fills it only\n\t\t * while an edit is unsaved, so the slot has to be called and its vnodes\n\t\t * inspected. Reading `$slots.actions` as a boolean counts the template\n\t\t * itself as content, which is what gave a titleless tab panel a 59px\n\t\t * band holding nothing but a second Actions menu.\n\t\t *\n\t\t * A method rather than a computed on purpose. The answer changes when the\n\t\t * PARENT's state changes, and a computed reading `$slots` does not\n\t\t * re-track that; a method re-runs on every render, and a slot whose\n\t\t * content depends on parent state already forces this component to\n\t\t * re-render.\n\t\t *\n\t\t * @return {boolean} True when the slot yields visible content.\n\t\t */\n\t\tactionsSlotRenders() {\n\t\t\treturn slotRenders(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the header band is worth the vertical space it costs.\n\t\t *\n\t\t * A VISIBLE title always earns it, which `chromeless` rules out. Otherwise\n\t\t * it is earned by controls that are actually on screen: content in the\n\t\t * `actions` slot, or the overflow menu when a caller both asked for it and\n\t\t * provided the slot it sits beside.\n\t\t *\n\t\t * That last clause looks redundant and is not. `showActions` defaults to\n\t\t * TRUE, so reading it alone hands a header to every headerless KPI tile\n\t\t * in the fleet, which is what the floating title-meta test caught when\n\t\t * this was first written the short way.\n\t\t *\n\t\t * @return {boolean} True when the header should render.\n\t\t */\n\t\theaderIsWorthIt() {\n\t\t\tif (this.titleVisible) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (this.actionsSlotRenders()) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.showActions && this.hasActionsSlot\n\t\t},\n\n\t\t/**\n\t\t * Whether the actions group holds anything.\n\t\t *\n\t\t * The overflow menu and the slot beside it are separate conditions now.\n\t\t * `showActions` used to gate both, so a caller who only wanted the menu\n\t\t * gone also lost the button that commits an inline edit.\n\t\t *\n\t\t * @return {boolean} True when the group would hold a visible control.\n\t\t */\n\t\tactionsAreVisible() {\n\t\t\treturn this.showActions || this.actionsSlotRenders() || Boolean(this.headerLinkTag)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@refresh` to the host, passing\n\t\t * the synthetic event through unchanged so a host listener can\n\t\t * still `preventDefault()` the built-in default (event-bus emit on\n\t\t * `cn:widget:refresh`).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRefresh(payload, ev) {\n\t\t\t/**\n\t\t\t * @event refresh User clicked the Refresh item in the overflow\n\t\t\t * action menu. Payload: `{ widgetId, title }`. Handlers may call\n\t\t\t * the second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (event-bus emit on `cn:widget:refresh`).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('refresh', payload, ev)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@request-feature` to the host,\n\t\t * passing the synthetic event through so a host listener can\n\t\t * `preventDefault()` the built-in default (opening the forge's\n\t\t * feature-request issue form).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRequestFeature(payload, ev) {\n\t\t\t/**\n\t\t\t * @event request-feature User clicked the Request a feature\n\t\t\t * item. Payload: `{ widgetId, title }`. Handlers may call the\n\t\t\t * second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (opening the forge's feature-request issue form).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('request-feature', payload, ev)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-wrapper {\n\tposition: relative;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\t/* NC design system: every content card is rounded — same token as\n\t CnDetailCard so detail pages read as ONE card family (ADR-062). */\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n/* Headerless date chip — floated over the top-right of a flush card. */\n.cn-widget-wrapper__floating-meta {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 10px;\n\tz-index: 2;\n}\n\n.cn-widget-wrapper__content {\n\tflex: 1;\n\toverflow: auto;\n\tmin-height: 0;\n\tpadding: 16px;\n}\n\n/* A TABLE GOES EDGE TO EDGE. ALWAYS.\n A table drawn inside 16px of padding renders a border inside a border — two\n rectangles a few pixels apart, which reads as a mistake because it is one.\n The wrapper already has a `flush` prop that zeroes this padding, but a prop\n is something an app has to remember on every table it ever adds, and the\n apps did not: the padding was visible in pipelinq's table widgets.\n So it is structural instead. `:has()` keys on the table component's own root\n class, which means a table is flush by construction and no caller can\n reintroduce the gap by forgetting a prop. */\n.cn-widget-wrapper__content:has(> .cn-table-container),\n.cn-widget-wrapper__content:has(> .cn-widget-object-table) {\n\tpadding: 0;\n}\n\n/* Keyboard focus ring for the now-focusable scrollable content region. */\n.cn-widget-wrapper__content:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n\n.cn-widget-wrapper--borderless {\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-widget-wrapper--borderless .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n.cn-widget-wrapper--flush .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n/* A form or a search list keeps its padding, the reverse of the table rule\n above. A dashboard renders every widget `flush`, and without this their\n fields touch the card's edges. Borderless and chromeless cards, and the\n Nextcloud dashboard variant, keep their own padding. */\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-form-widget),\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-kb-search-widget) {\n\tpadding: 16px;\n}\n\n/* `chromeless` — border and background are already gone through borderless,\n and the content padding through flush. What is left on the root is the\n corner radius, which still clips: with `overflow: hidden` above, a child\n that paints its own background (a table header) gets rounded corners inside\n a square panel, which is the last visible piece of a card that is supposed\n to be absent. `CnDetailCard.cn-detail-card--chromeless` zeroes the same\n thing for the same reason. */\n.cn-widget-wrapper--chromeless {\n\tborder-radius: 0;\n}\n\n/*\n * `chrome=\"nc-dashboard\"` — reproduce the native Nextcloud Dashboard panel\n * (apps/dashboard) exactly, using the same design tokens so an un-customised\n * widget is pixel-identical to a core dashboard panel. `styleConfig` inline\n * overrides (wrapperStyles / headerStyles) still win over these class rules,\n * so users can override any token. Combined selector raises specificity above\n * the base `.cn-widget-wrapper` rules it supersedes.\n */\n.cn-widget-wrapper__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\t/* The header is `space-between`, so without an explicit gap a right-hand\n\t title-icon sits flush against whatever precedes it. The left-hand icon's\n\t spacing comes from header-left's own gap. */\n\tgap: 8px;\n\tpadding: 12px 16px;\n\tborder-bottom: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n/* Actions with no title beside them. `header-left` is not rendered at all in\n this mode, and the header is `space-between`, so a single child would sit\n flush LEFT where every other surface puts its actions on the right. */\n.cn-widget-wrapper__header--actions-only {\n\tjustify-content: flex-end;\n}\n\n/* `chromeless` — the surface around this widget already drew the card, so\n nothing here may draw a second one. Border, background and content padding\n are already gone (chromeless implies borderless + flush); what is left is\n the header, which still renders when there are controls to hold. Its bottom\n rule is the card's own divider and reads as a second card edge inside the\n panel, and its 16px side inset leaves those controls hanging off the grid\n that now starts at the panel's edge. Both go.\n\n Placed AFTER the rules it overrides, which is what stylelint's\n no-descending-specificity asks for. The cascade did not need it (this\n selector is more specific either way), a reader does: an override written\n above the thing it overrides has to be read twice. */\n.cn-widget-wrapper--chromeless .cn-widget-wrapper__header {\n\tborder-bottom: none;\n\tpadding-inline: 0;\n}\n\n.cn-widget-wrapper__header-left {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-width: 0;\n\t/* Grow so the title hugs the (optional) title-icon on the left instead\n\t of centering between icon and actions (header is space-between). */\n\tflex: 1 1 auto;\n}\n\n.cn-widget-wrapper__icon {\n\twidth: 24px;\n\theight: 24px;\n\tflex-shrink: 0;\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n}\n\n.cn-widget-wrapper__title-stack {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-widget-wrapper__eyebrow {\n\tfont-size: 13px;\n\tline-height: 1.3;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-widget-wrapper__title {\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tmargin: 0;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-widget-wrapper.cn-widget-wrapper--nc-dashboard {\n\tbackground: var(--color-main-background-blur, var(--color-main-background));\n\t-webkit-backdrop-filter: var(--filter-background-blur, none);\n\tbackdrop-filter: var(--filter-background-blur, none);\n\tborder: none;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header {\n\tpadding: 16px;\n\tborder-bottom: none;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header-left {\n\tgap: 16px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__icon {\n\twidth: 32px;\n\theight: 32px;\n\tbackground-size: 32px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__title {\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 24px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__content {\n\tpadding: 0 16px 16px;\n}\n\n.cn-widget-wrapper__title-meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__actions {\n\tdisplay: flex;\n\tgap: 4px;\n\tflex-shrink: 0;\n}\n\n/* The header's text link (`headerLink`). Theme hooks: --cn-widget-header-link-size\n and --cn-widget-header-link-weight. */\n.cn-widget-wrapper__header-link {\n\talign-self: center;\n\tpadding: 0 calc(1 * var(--default-grid-baseline));\n\tcolor: var(--color-primary-element);\n\tfont-size: var(--cn-widget-header-link-size, 14px);\n\tfont-weight: var(--cn-widget-header-link-weight, 600);\n\ttext-decoration: none;\n\twhite-space: nowrap;\n}\n\n.cn-widget-wrapper__header-link:hover,\n.cn-widget-wrapper__header-link:focus-visible {\n\ttext-decoration: underline;\n}\n\n.cn-widget-wrapper__header-link:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n\tborder-radius: var(--border-radius-small, 4px);\n}\n\n/* Every widget's header icon is coloured — `--cn-widget-icon-color` is set on\n the header from titleIconStyle, and defaults to the theme colour if a\n wrapper is ever rendered without it. Material-design-icon components paint\n with `fill: currentColor`, so this reaches them; the :deep rule below makes\n it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-shrink: 0;\n\t/* COLOURED BY DEFAULT. `--cn-widget-icon-color` is set on the header from\n\t titleIconStyle; the theme colour is the fallback for a wrapper rendered\n\t without it. Before this, the prop defaulted to null and the icon inherited\n\t body text colour, which is why every widget header looked the same. */\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n\n\t/* SPACE BETWEEN THE ICON AND THE TITLE.\n\t This element is a sibling of `__header-left` inside a space-between\n\t header, so it never received that element's `gap: 8px` — the icon sat\n\t flush against the title text. Matching the header-left gap rather than\n\t inventing a value keeps the two icon positions spaced identically. */\n\tmargin-inline-end: 8px;\n}\n\n/* Right-positioned title icons sit AFTER the actions, so the margin belongs on\n the other side or it pushes the icon off the header's right edge. */\n.cn-widget-wrapper__title-icon:last-child {\n\tmargin-inline-end: 0;\n\tmargin-inline-start: 8px;\n}\n\n/* Material-design-icon components paint with `fill: currentColor`, so the\n colour above reaches them; this makes it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon :deep(svg) {\n\tfill: currentcolor;\n}\n\n.cn-widget-wrapper__footer {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tpadding: 8px 16px;\n\tborder-top: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__footer-link {\n\tfont-size: 13px;\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-widget-wrapper__footer-link:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_normalizeClass","_normalizeStyle","_openBlock","_createElementBlock","_renderSlot","_createElementVNode","_toDisplayString","_resolveDynamicComponent","_createBlock"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnWidgetWrapper.vue3.js","sources":["../../../../src/components/CnWidgetWrapper/CnWidgetWrapper.vue?vue&type=template&id=4f6c55f6&lang.js"],"sourcesContent":["<!--\n CnWidgetWrapper — Container shell around a dashboard widget.\n\n Provides header (icon + title), scrollable content area, and optional\n footer with action buttons. Applies style configuration for borders,\n backgrounds, and padding. The built-in overflow Actions menu (Refresh /\n Documentation / Request a feature) is delegated to the shared\n CnActionsMenu so widgets and page surfaces stay in lockstep.\n-->\n<template>\n\t<div\n\t\tclass=\"cn-widget-wrapper\"\n\t\t:class=\"{\n\t\t\t'cn-widget-wrapper--borderless': noBorder,\n\t\t\t'cn-widget-wrapper--flush': noPadding,\n\t\t\t'cn-widget-wrapper--chromeless': chromeless,\n\t\t\t'cn-widget-wrapper--nc-dashboard': chrome === 'nc-dashboard',\n\t\t\t'cn-widget-wrapper--high-contrast': highContrast,\n\t\t}\"\n\t\t:data-cn-high-contrast=\"highContrast ? 'true' : undefined\"\n\t\t:style=\"wrapperStyles\">\n\t\t<!-- Header.\n\t\t Rendered when a title earns it, or when controls that are actually\n\t\t on screen need a home. Three conditions rather than one, and each of\n\t\t the three is a bug this component has already shipped: gating on the\n\t\t title alone took the Save button for an inline edit away along with\n\t\t the doubled title; reading `showActions`, which defaults to true,\n\t\t handed a header to every headerless KPI tile; and reading the\n\t\t `actions` slot's mere PRESENCE gave a chromeless panel a 59px band\n\t\t holding nothing, because `CnObjectDataWidget` provides that template\n\t\t always and fills it only while an edit is unsaved.\n\t\t `headerIsWorthIt()` carries the reasoning. -->\n\t\t<div v-if=\"headerIsWorthIt()\"\n\t\t\tclass=\"cn-widget-wrapper__header\"\n\t\t\t:class=\"{ 'cn-widget-wrapper__header--actions-only': !titleVisible }\"\n\t\t\t:style=\"[headerStyles, titleIconStyle]\">\n\t\t\t<!-- Title icon — left: rendered before the title group. Moved\n\t\t\t INSIDE header-left so the header's `space-between` cannot pull\n\t\t\t it away from the title it belongs to; header-left's `gap` is\n\t\t\t what now separates icon from title (they used to touch). -->\n\t\t\t<div v-if=\"titleVisible\" class=\"cn-widget-wrapper__header-left\">\n\t\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'left'\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t\t</div>\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"iconUrl\"\n\t\t\t\t\t:src=\"iconUrl\"\n\t\t\t\t\t:alt=\"resolvedTitle\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\">\n\t\t\t\t<span\n\t\t\t\t\tv-else-if=\"iconClass\"\n\t\t\t\t\t:class=\"iconClass\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\" />\n\t\t\t\t<!-- An eyebrow (`eyebrow`) is a muted line above the title:\n\t\t\t\t \"Saldo per week\" above \"Prognose 13 weken\". -->\n\t\t\t\t<div v-if=\"resolvedEyebrow\" class=\"cn-widget-wrapper__title-stack\">\n\t\t\t\t\t<span class=\"cn-widget-wrapper__eyebrow\" data-testid=\"cn-widget-wrapper-eyebrow\">{{ resolvedEyebrow }}</span>\n\t\t\t\t\t<h3 :id=\"titleId\" class=\"cn-widget-wrapper__title\">\n\t\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t\t</h3>\n\t\t\t\t</div>\n\t\t\t\t<h3 v-else :id=\"titleId\" class=\"cn-widget-wrapper__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h3>\n\t\t\t\t<!-- @slot title-meta Rendered inside the header's left group,\n\t\t\t\t after the title and before the spacer. Use for small\n\t\t\t\t contextual chips (e.g. a date-range badge on chart\n\t\t\t\t widgets). Kept inside header-left so the chip sits next\n\t\t\t\t to the title rather than floating right next to the\n\t\t\t\t actions menu. -->\n\t\t\t\t<div v-if=\"$slots['title-meta']\" class=\"cn-widget-wrapper__title-meta\">\n\t\t\t\t\t<slot name=\"title-meta\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- `showActions` gates the overflow MENU, not the slot beside it.\n\t\t\t It used to gate both, so a caller who switched the menu off\n\t\t\t also lost the Save button for an inline edit, silently. -->\n\t\t\t<div v-if=\"actionsAreVisible()\" class=\"cn-widget-wrapper__actions\">\n\t\t\t\t<!-- One text link in the header (`headerLink`): \"All deadlines\",\n\t\t\t\t \"To the board\". A route is a router link, an href a plain\n\t\t\t\t anchor. Sits before the slot and the overflow menu. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"headerLinkTag\"\n\t\t\t\t\tv-if=\"headerLinkTag\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__header-link\"\n\t\t\t\t\tdata-testid=\"cn-widget-wrapper-header-link\"\n\t\t\t\t\tv-bind=\"headerLinkAttrs\">\n\t\t\t\t\t{{ headerLinkLabel }}\n\t\t\t\t</component>\n\t\t\t\t<!-- @slot actions Custom action buttons rendered before the\n\t\t\t\t built-in overflow menu. -->\n\t\t\t\t<slot name=\"actions\" />\n\t\t\t\t<CnActionsMenu\n\t\t\t\t\tv-if=\"showActions\"\n\t\t\t\t\t:showRefresh=\"effectiveShowRefresh\"\n\t\t\t\t\t:showRequestFeature=\"effectiveShowRequestFeature\"\n\t\t\t\t\t:showReportBug=\"showReportBug\"\n\t\t\t\t\t:showDocumentation=\"showDocumentation\"\n\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t:docsAnchor=\"docsAnchor\"\n\t\t\t\t\t:reportBugUrl=\"reportBugUrl\"\n\t\t\t\t\t:documentationLabel=\"documentationLabel\"\n\t\t\t\t\t:refreshLabel=\"refreshLabel\"\n\t\t\t\t\t:requestFeatureLabel=\"requestFeatureLabel\"\n\t\t\t\t\t:actionsMenuLabel=\"actionsMenuLabel\"\n\t\t\t\t\t:refreshing=\"refreshing\"\n\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t:title=\"displayTitle\"\n\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t:specRef=\"specRef\"\n\t\t\t\t\trefreshChannel=\"cn:widget:refresh\"\n\t\t\t\t\ttestidBase=\"cn-widget-wrapper\"\n\t\t\t\t\t@refresh=\"onActionsRefresh\"\n\t\t\t\t\t@requestFeature=\"onActionsRequestFeature\">\n\t\t\t\t\t<!-- @slot action-items Additional NcActionButton-family\n\t\t\t\t\t items rendered inside the overflow menu, after the\n\t\t\t\t\t built-in Refresh / Documentation / Request-a-feature\n\t\t\t\t\t group. -->\n\t\t\t\t\t<template v-if=\"hasActionItemsSlot\" #action-items>\n\t\t\t\t\t\t<slot name=\"action-items\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnActionsMenu>\n\t\t\t</div>\n\t\t\t<!-- Title icon — right: rendered after actions, far right -->\n\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'right'\"\n\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Headerless meta (e.g. a KPI date chip on a flush card). When the\n\t\t header is hidden but a `title-meta` chip is provided, float it in the\n\t\t top-right corner over the content instead of dropping it — so a\n\t\t compact flush KPI tile can carry a date-range chip without growing a\n\t\t full header bar. -->\n\t\t<div\n\t\t\tv-if=\"!titleVisible && $slots['title-meta']\"\n\t\t\tclass=\"cn-widget-wrapper__floating-meta\">\n\t\t\t<slot name=\"title-meta\" />\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<!--\n\t\t The content area is `overflow: auto`, so it can become a scrollable\n\t\t region. WCAG 2.1.1 (Keyboard) requires scrollable regions to be\n\t\t keyboard-focusable so keyboard-only users can scroll them, and 4.1.2\n\t\t requires an accessible name. We expose it as a labelled region and\n\t\t give it tabindex=\"0\". When the header (and its title) is hidden we\n\t\t fall back to an explicit aria-label from the widget title so the\n\t\t region is never anonymous.\n\t\t-->\n\t\t<div\n\t\t\tclass=\"cn-widget-wrapper__content\"\n\t\t\ttabindex=\"0\"\n\t\t\trole=\"region\"\n\t\t\t:aria-labelledby=\"titleVisible ? titleId : null\"\n\t\t\t:aria-label=\"titleVisible ? null : resolvedTitle\">\n\t\t\t<slot />\n\t\t</div>\n\n\t\t<!-- Footer -->\n\t\t<div v-if=\"$slots.footer || (buttons && buttons.length > 0)\" class=\"cn-widget-wrapper__footer\">\n\t\t\t<slot name=\"footer\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"button in buttons\"\n\t\t\t\t\t:key=\"button.link\"\n\t\t\t\t\t:href=\"button.link\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__footer-link\">\n\t\t\t\t\t{{ button.text }}\n\t\t\t\t</a>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { headerLinkTarget } from '../../utils/contentHeaderLink.js'\nimport { slotRenders } from '../../utils/slotContent.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\n\n/**\n * CnWidgetWrapper — Widget container with header, content, and footer.\n *\n * ```vue\n * <CnWidgetWrapper title=\"My Cases\" :icon-url=\"casesIconUrl\">\n * <MyCasesChart :data=\"chartData\" />\n * </CnWidgetWrapper>\n * ```\n *\n * With NC widget object\n * ```vue\n * <CnWidgetWrapper\n * :title=\"widget.title\"\n * :icon-url=\"widget.iconUrl\"\n * :icon-class=\"widget.iconClass\"\n * :documentation-url=\"widget.documentationUrl\"\n * :buttons=\"widget.buttons\">\n * <CnWidgetRenderer :widget=\"widget\" />\n * </CnWidgetWrapper>\n * ```\n */\n/**\n * Semantic variant → CSS colour token for the widget header icon. Tokens\n * only — the nldesign app themes by overriding the Nextcloud variables, so a\n * literal hex here would ignore government theming entirely (ADR-062 / NL\n * Design System).\n *\n * @type {Record<string, string>}\n */\nconst TITLE_ICON_VARIANT_COLORS = {\n\tprimary: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n\tinfo: 'var(--color-info, var(--color-primary-element))',\n\tneutral: 'var(--color-text-maxcontrast)',\n}\n\nexport default {\n\tname: 'CnWidgetWrapper',\n\n\tcomponents: {\n\t\tCnActionsMenu,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored widget title is run through it so the card\n\t\t * heading follows the user's language. Defaults to an identity\n\t\t * function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Widget title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Widget'),\n\t\t},\n\n\t\t/**\n\t\t * A short muted line above the title (\"Saldo per week\" above\n\t\t * \"Prognose 13 weken\"), through the host translate. Empty draws none.\n\t\t */\n\t\teyebrow: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Whether to show the header with title */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Chrome variant for the wrapper card.\n\t\t * - `'default'` — the library's own card chrome (opaque background,\n\t\t * 1px border, compact header).\n\t\t * - `'nc-dashboard'` — matches the native Nextcloud Dashboard panel\n\t\t * exactly using the same design tokens: translucent blurred\n\t\t * background (`--color-main-background-blur` + `--filter-background-blur`),\n\t\t * `--border-radius-container-large` corners, no border/shadow, a 16px\n\t\t * header with a 20px/700 title and 32px leading icon, and content\n\t\t * inset 16px on the sides + bottom. `styleConfig` overrides still\n\t\t * layer on top so a user can customise any token.\n\t\t *\n\t\t * @type {'default'|'nc-dashboard'}\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'nc-dashboard'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Remove border and background — makes the wrapper transparent.\n\t\t * Useful for widgets that are self-contained cards (e.g. CnStatsBlock).\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Remove content padding — allows content to go edge-to-edge.\n\t\t * Useful for list-style widgets where items should span the full width.\n\t\t */\n\t\tflush: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw NO card of this widget's own: no border, no background, no\n\t\t * content padding, no title row and no header divider. For a surface\n\t\t * that already supplies the card and the heading — a tab panel being\n\t\t * the case that matters, where a nested card repeats both.\n\t\t *\n\t\t * The wrapper ELEMENT still renders. That is the contract, not an\n\t\t * oversight: `CnObjectDataWidget` measures its overflow against\n\t\t * `.cn-widget-wrapper__content` (`closest()` plus a ResizeObserver on\n\t\t * that node), `src/css/table.css` gives a table inside it its\n\t\t * edge-to-edge treatment, and `src/css/detail-page.css` and\n\t\t * `dashboard.css` size their content areas through it. Dropping the\n\t\t * node would take all of that out silently — `closest()` returning\n\t\t * null reads as \"nothing overflows\", never as an error.\n\t\t *\n\t\t * Controls in the `actions` slot are NOT chrome and keep their header:\n\t\t * the Save button that commits an inline edit lives there, and a panel\n\t\t * that hides it is silently unsaveable.\n\t\t *\n\t\t * `chromeless` is what an integration provider already asks for through\n\t\t * `CnDetailWidgetHost` (`bareWidget`), so this is the same word for the\n\t\t * same thing on every path into the wrapper.\n\t\t */\n\t\tchromeless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Icon URL (image) */\n\t\ticonUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon CSS class (e.g., Nextcloud icon class) */\n\t\ticonClass: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Position of the title-icon slot in the header.\n\t\t * 'left' places it before the title; 'right' places it after the actions.\n\t\t */\n\t\ttitleIconPosition: {\n\t\t\ttype: String,\n\t\t\tdefault: 'right',\n\t\t\tvalidator: (v) => ['left', 'right'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Explicit CSS colour for the header icon. Overrides `titleIconVariant`.\n\t\t * Must be a CSS custom property or a theme token — never a literal hex\n\t\t * (NL Design System: the nldesign app re-themes by overriding the\n\t\t * Nextcloud variables, and a hardcoded colour ignores that).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttitleIconColor: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Semantic colour for the header icon. Every widget's icon is\n\t\t * coloured — `primary` (the theme colour) is the default, and a\n\t\t * widget whose subject already carries a semantic meaning names it\n\t\t * here so the icon says so at a glance (a Depublished list is\n\t\t * `error`, a Concepts list `warning`, a Published list `success`).\n\t\t *\n\t\t * @type {'primary'|'success'|'warning'|'error'|'info'|'neutral'}\n\t\t */\n\t\ttitleIconVariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'primary',\n\t\t\tvalidator: (v) => ['primary', 'success', 'warning', 'error', 'info', 'neutral'].includes(v),\n\t\t},\n\n\t\t/** Footer action buttons: [{ text, link }] */\n\t\tbuttons: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * One text link in the header's actions group, before the overflow\n\t\t * menu: `{ label, route?, params?, query?, href? }`. A `route` is a\n\t\t * named router link (needs a router), an `href` a plain anchor. The\n\t\t * label goes through the host translate function. Null (the\n\t\t * default) draws nothing. Manifest: `headerLink` on the widget\n\t\t * definition or its layout entry.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @type {{label: string, route?: string, params?: object, query?: object, href?: string}|null}\n\t\t */\n\t\theaderLink: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the header's overflow action menu (Refresh / Documentation /\n\t\t * Request-a-feature + any `#action-items`) renders. Shown by default;\n\t\t * set `false` for compact surfaces — e.g. a KPI tile whose only header\n\t\t * affordance is a date chip — to drop the menu and free header width.\n\t\t */\n\t\tshowActions: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Style configuration for the wrapper.\n\t\t *\n\t\t * @type {{ backgroundColor: string, borderStyle: string, borderWidth: number, borderColor: string, borderRadius: number, padding: { top: number, right: number, bottom: number, left: number } }}\n\t\t */\n\t\tstyleConfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Refresh item from the overflow action menu.\n\t\t * The Refresh item is shown by default — set this when the widget\n\t\t * has no refreshable data source (e.g. a static tile). Alias for\n\t\t * the inverse `:show-refresh=\"false\"`; either form opts out.\n\t\t */\n\t\thideRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Request-a-feature item from the overflow\n\t\t * action menu. Shown by default; set when the consuming app has\n\t\t * no public issue tracker to link out to. Alias for the inverse\n\t\t * `:show-request-feature=\"false\"`; either form opts out.\n\t\t */\n\t\thideRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether to show the built-in Refresh item. Tri-state:\n\t\t * - `true` / `false` — force the action on or off.\n\t\t * - `null` (the default) — **auto**: show the action only when a\n\t\t * parent has attached an `@refresh` listener (i.e. something will\n\t\t * actually handle the refresh). This keeps widgets that can't\n\t\t * refresh — e.g. a prop-driven `CnObjectDataWidget` — from showing\n\t\t * a dead button. Widgets that refresh themselves via the\n\t\t * `cn:widget:refresh` event bus (with no `@refresh` listener)\n\t\t * should set `:show-refresh=\"true\"` explicitly.\n\t\t *\n\t\t * `hideRefresh` (or `:show-refresh=\"false\"`) always wins.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Inverse of `hideRequestFeature`. Defaults to `true` so the\n\t\t * action renders. Set `:show-request-feature=\"false\"` to hide it.\n\t\t * Either flag hides the action.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Report a bug\" item renders. On by default —\n\t\t * the trio Request a feature / Report a bug / Documentation is the\n\t\t * contract for every widget; this exists for the rare surface that\n\t\t * must suppress one deliberately.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowReportBug: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Documentation\" item renders. On by default,\n\t\t * for the same reason as `showReportBug`. The item's target is\n\t\t * resolved by the shared menu (see `docsAnchor`), so leaving it on\n\t\t * costs the host nothing.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowDocumentation: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Explicit documentation link for this widget, opened in a new tab.\n\t\t * Usually unnecessary: leave it empty and set `docsAnchor` instead,\n\t\t * so the link is built from the app-wide documentation base and\n\t\t * lands on this widget's own section.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Documentation action.\n\t\t * Defaults to the lib's translation of \"Documentation\".\n\t\t */\n\t\tdocumentationLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Documentation'),\n\t\t},\n\n\t\t/**\n\t\t * Widget id for the built-in default Refresh / Request-a-feature\n\t\t * handlers (B2). Forwarded as the `surface: \"widget:<id>\"` value\n\t\t * on the Actions menu (the English headline fallback for its forge\n\t\t * deep-links) AND as the `widgetId` field on the\n\t\t * `cn:widget:refresh` event-bus payload. When unset, the wrapper\n\t\t * falls back to a slugified `displayTitle`, which works but is\n\t\t * less stable than an explicit id.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * This widget's own section in the app's documentation, appended to\n\t\t * the app-wide documentation base URL (provided by CnAppRoot) to build\n\t\t * the Actions menu's Documentation deep-link. A bare slug becomes a\n\t\t * `#fragment`. Supply it per widget type — without it the item still\n\t\t * renders, but lands on the docs homepage rather than this widget.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocsAnchor: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Explicit \"Report a bug\" target for the Actions menu. Empty (the\n\t\t * default) builds a new-issue deep-link on the app's own forge.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\treportBugUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Declares that this widget wants a high contrast treatment. The flag\n\t\t * reaches the theme as `data-cn-high-contrast` and a `--high-contrast`\n\t\t * class; the component picks no colour of its own (ADR-003). A theme\n\t\t * that says nothing about the flag renders the widget unchanged.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\thighContrast: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional `specRef` slug, forwarded to the Actions menu. Accepted\n\t\t * for backward compatibility with the removed in-product suggestion\n\t\t * modal; the forge issue form asks for its own context.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tspecRef: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether a refresh is currently in flight. When bound by the host\n\t\t * (e.g. `:refreshing=\"loading\"` around its refetch), the Refresh\n\t\t * item is disabled and shows a loading spinner for exactly as long\n\t\t * as this stays true — so the spinner reflects the real refresh time.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\trefreshing: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Refresh action. Defaults\n\t\t * to the lib's translation of \"Refresh\" so callers usually don't\n\t\t * need to set this.\n\t\t */\n\t\trefreshLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Refresh'),\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Request-a-feature action.\n\t\t * Defaults to the lib's translation of \"Request a feature\".\n\t\t */\n\t\trequestFeatureLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Request a feature'),\n\t\t},\n\n\t\t/**\n\t\t * Pre-translated aria-label / tooltip for the overflow menu\n\t\t * trigger. Defaults to \"Actions\".\n\t\t */\n\t\tactionsMenuLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Actions'),\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`,\n\t\t * which itself defaults to an identity function.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['refresh', 'request-feature'],\n\n\tcomputed: {\n\t\tdisplayTitle() {\n\t\t\treturn this.title || 'Widget'\n\t\t},\n\n\t\t/**\n\t\t * The header's icon colour, published as ONE custom property on the\n\t\t * header rather than an inline `color` repeated on each icon element.\n\t\t * Everything under the header — the icon-class span, the `title-icon`\n\t\t * slot's content, an SVG inside it — inherits from that single\n\t\t * declaration, so the icon and any decoration around it cannot end up\n\t\t * different colours.\n\t\t *\n\t\t * `titleIconColor` wins when the host set one; otherwise the\n\t\t * `titleIconVariant` token does, which is why EVERY widget icon is\n\t\t * coloured and not just the ones an app remembered to configure. Every\n\t\t * value is a CSS variable, so the nldesign app's overrides re-theme\n\t\t * them for free (NL Design System).\n\t\t *\n\t\t * @return {object} A style object declaring `--cn-widget-icon-color`.\n\t\t */\n\t\ttitleIconStyle() {\n\t\t\treturn {\n\t\t\t\t'--cn-widget-icon-color': this.titleIconColor\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS[this.titleIconVariant]\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS.primary,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the explicit `translate` prop when\n\t\t * given, else the injected `cnTranslate` (identity by default).\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The header link's tag: `router-link` for a route (when a router is\n\t\t * mounted), `a` for an href, '' when there is no usable link.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {string}\n\t\t */\n\t\theaderLinkTag() {\n\t\t\treturn headerLinkTarget(this.headerLink, Boolean(this.$router)).tag\n\t\t},\n\n\t\t/**\n\t\t * The header link's attributes for its tag.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {object}\n\t\t */\n\t\theaderLinkAttrs() {\n\t\t\treturn headerLinkTarget(this.headerLink, Boolean(this.$router)).attrs\n\t\t},\n\n\t\t/**\n\t\t * The header link's text, through the host translate function.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {string}\n\t\t */\n\t\theaderLinkLabel() {\n\t\t\tconst label = this.headerLink && this.headerLink.label\n\t\t\treturn label ? this.effectiveTranslate(label) : ''\n\t\t},\n\n\t\t/**\n\t\t * The eyebrow through the host translate, or ''.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/widget-title-eyebrow/specs/dashboard-page/spec.md#requirement-a-widget-card-can-carry-a-line-above-its-title\n\t\t */\n\t\tresolvedEyebrow() {\n\t\t\treturn this.eyebrow ? this.effectiveTranslate(this.eyebrow) : ''\n\t\t},\n\n\t\t/**\n\t\t * The RENDERED widget title — `displayTitle` run through the host\n\t\t * translate function. Kept separate from `displayTitle` on purpose:\n\t\t * `resolvedWidgetId` slugifies the raw title, so identifiers (DOM\n\t\t * ids, refresh channels, action payloads) stay locale-independent\n\t\t * while only the visible/AT-facing text localises.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title ? this.effectiveTranslate(this.title) : this.displayTitle\n\t\t},\n\n\t\t/**\n\t\t * Stable DOM id for the header title `<h3>`, used as the\n\t\t * `aria-labelledby` target of the scrollable content region so the\n\t\t * region gets an accessible name from the widget title (WCAG 4.1.2).\n\t\t * Derived from `resolvedWidgetId` so it is stable and unique per\n\t\t * widget instance on the page.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttitleId() {\n\t\t\treturn `cn-widget-wrapper-title-${this.resolvedWidgetId}`\n\t\t},\n\n\t\t/**\n\t\t * Effective Refresh visibility. `hideRefresh: true` (or\n\t\t * `:show-refresh=\"false\"`) always hides it. When `showRefresh` is\n\t\t * left unset (`null`), auto-detect: show the action only when a\n\t\t * parent attached an `@refresh` listener — otherwise the refresh\n\t\t * would do nothing. `hideRefresh` remains a back-compat alias.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRefresh() {\n\t\t\tif (this.hideRefresh) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.showRefresh !== null) {\n\t\t\t\treturn this.showRefresh\n\t\t\t}\n\t\t\t// `$.vnode.props`, not `$attrs`: `refresh` is a declared emit, and\n\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\treturn Boolean(this.$.vnode.props?.onRefresh)\n\t\t},\n\n\t\t/**\n\t\t * Effective Request-a-feature visibility — same OR-of-opt-outs\n\t\t * pattern as `effectiveShowRefresh`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRequestFeature() {\n\t\t\treturn this.showRequestFeature && !this.hideRequestFeature\n\t\t},\n\n\t\t/**\n\t\t * Stable id used in event-bus payloads and the\n\t\t * `surface: \"widget:<id>\"` field on the auto-mounted feature\n\t\t * modal. Prefers the explicit `widgetId` prop; falls back to a\n\t\t * slugified `displayTitle` so widgets without an explicit id\n\t\t * still get a usable identifier.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedWidgetId() {\n\t\t\tif (this.widgetId) {\n\t\t\t\treturn this.widgetId\n\t\t\t}\n\t\t\treturn this.displayTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller supplied an `action-items` slot. Forwarded\n\t\t * conditionally so the shared CnActionsMenu doesn't treat an\n\t\t * always-present pass-through template as content (which would\n\t\t * keep the overflow menu visible even when every built-in is\n\t\t * opted out).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasActionItemsSlot() {\n\t\t\treturn Boolean(this.$slots['action-items']) || Boolean(this.$slots && this.$slots['action-items'])\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller put controls in the `actions` slot.\n\t\t *\n\t\t * Gates the header together with `showTitle`, so a surface that wants no\n\t\t * title still keeps controls that have nowhere else to go — the Save\n\t\t * button `CnObjectDataWidget` renders there being the case that matters.\n\t\t *\n\t\t * Deliberately NOT `showActions`: that prop defaults to true and governs\n\t\t * the overflow menu, so reading it here would give a header to every\n\t\t * headerless KPI tile that never had one.\n\t\t *\n\t\t * @return {boolean} true when the slot is filled.\n\t\t */\n\t\thasActionsSlot() {\n\t\t\treturn Boolean(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the card outline is drawn. `chromeless` implies `borderless`\n\t\t * so a caller names the intent once instead of remembering the three\n\t\t * props it decomposes into.\n\t\t *\n\t\t * @return {boolean} true when border and background are suppressed.\n\t\t */\n\t\tnoBorder() {\n\t\t\treturn this.borderless || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the content area goes edge to edge. `chromeless` implies\n\t\t * `flush` for the same reason as `noBorder`.\n\t\t *\n\t\t * @return {boolean} true when content padding is suppressed.\n\t\t */\n\t\tnoPadding() {\n\t\t\treturn this.flush || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the title row renders. `chromeless` overrides `showTitle`,\n\t\t * which defaults to true: a surface that already names the widget must\n\t\t * not have to also remember to turn the title off.\n\t\t *\n\t\t * Read everywhere the template used to read `showTitle` directly,\n\t\t * including the two ARIA bindings on the content region — a hidden\n\t\t * title cannot be the region's `aria-labelledby` target.\n\t\t *\n\t\t * @return {boolean} true when the header's title group renders.\n\t\t */\n\t\ttitleVisible() {\n\t\t\treturn this.showTitle && !this.chromeless\n\t\t},\n\n\t\twrapperStyles() {\n\t\t\tconst styles = {}\n\n\t\t\tif (this.styleConfig.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = this.styleConfig.backgroundColor\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderStyle && this.styleConfig.borderStyle !== 'none') {\n\t\t\t\tstyles.border = `${this.styleConfig.borderWidth || 1}px ${this.styleConfig.borderStyle} ${this.styleConfig.borderColor || 'var(--color-border)'}`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderRadius !== undefined) {\n\t\t\t\tstyles.borderRadius = `${this.styleConfig.borderRadius}px`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.padding) {\n\t\t\t\tconst p = this.styleConfig.padding\n\t\t\t\tstyles.padding = `${p.top || 0}px ${p.right || 0}px ${p.bottom || 0}px ${p.left || 0}px`\n\t\t\t}\n\n\t\t\treturn styles\n\t\t},\n\n\t\t/**\n\t\t * Inline styles for the header bar, derived from\n\t\t * `styleConfig.headerStyle.{backgroundColor, textColor}`. Lets a host\n\t\t * give individual widgets a custom header colour without a per-app CSS\n\t\t * workaround. Empty object when no header style is configured.\n\t\t *\n\t\t * @return {object} the header style bindings.\n\t\t */\n\t\theaderStyles() {\n\t\t\tconst hs = this.styleConfig.headerStyle\n\t\t\tif (!hs || typeof hs !== 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst styles = {}\n\t\t\tif (hs.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = hs.backgroundColor\n\t\t\t}\n\t\t\tif (hs.textColor) {\n\t\t\t\tstyles.color = hs.textColor\n\t\t\t}\n\t\t\treturn styles\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the `actions` slot renders anything a reader can see.\n\t\t *\n\t\t * `CnObjectDataWidget` always PROVIDES the template and fills it only\n\t\t * while an edit is unsaved, so the slot has to be called and its vnodes\n\t\t * inspected. Reading `$slots.actions` as a boolean counts the template\n\t\t * itself as content, which is what gave a titleless tab panel a 59px\n\t\t * band holding nothing but a second Actions menu.\n\t\t *\n\t\t * A method rather than a computed on purpose. The answer changes when the\n\t\t * PARENT's state changes, and a computed reading `$slots` does not\n\t\t * re-track that; a method re-runs on every render, and a slot whose\n\t\t * content depends on parent state already forces this component to\n\t\t * re-render.\n\t\t *\n\t\t * @return {boolean} True when the slot yields visible content.\n\t\t */\n\t\tactionsSlotRenders() {\n\t\t\treturn slotRenders(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the header band is worth the vertical space it costs.\n\t\t *\n\t\t * A VISIBLE title always earns it, which `chromeless` rules out. Otherwise\n\t\t * it is earned by controls that are actually on screen: content in the\n\t\t * `actions` slot, or the overflow menu when a caller both asked for it and\n\t\t * provided the slot it sits beside.\n\t\t *\n\t\t * That last clause looks redundant and is not. `showActions` defaults to\n\t\t * TRUE, so reading it alone hands a header to every headerless KPI tile\n\t\t * in the fleet, which is what the floating title-meta test caught when\n\t\t * this was first written the short way.\n\t\t *\n\t\t * @return {boolean} True when the header should render.\n\t\t */\n\t\theaderIsWorthIt() {\n\t\t\tif (this.titleVisible) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (this.actionsSlotRenders()) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.showActions && this.hasActionsSlot\n\t\t},\n\n\t\t/**\n\t\t * Whether the actions group holds anything.\n\t\t *\n\t\t * The overflow menu and the slot beside it are separate conditions now.\n\t\t * `showActions` used to gate both, so a caller who only wanted the menu\n\t\t * gone also lost the button that commits an inline edit.\n\t\t *\n\t\t * @return {boolean} True when the group would hold a visible control.\n\t\t */\n\t\tactionsAreVisible() {\n\t\t\treturn this.showActions || this.actionsSlotRenders() || Boolean(this.headerLinkTag)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@refresh` to the host, passing\n\t\t * the synthetic event through unchanged so a host listener can\n\t\t * still `preventDefault()` the built-in default (event-bus emit on\n\t\t * `cn:widget:refresh`).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRefresh(payload, ev) {\n\t\t\t/**\n\t\t\t * @event refresh User clicked the Refresh item in the overflow\n\t\t\t * action menu. Payload: `{ widgetId, title }`. Handlers may call\n\t\t\t * the second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (event-bus emit on `cn:widget:refresh`).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('refresh', payload, ev)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@request-feature` to the host,\n\t\t * passing the synthetic event through so a host listener can\n\t\t * `preventDefault()` the built-in default (opening the forge's\n\t\t * feature-request issue form).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRequestFeature(payload, ev) {\n\t\t\t/**\n\t\t\t * @event request-feature User clicked the Request a feature\n\t\t\t * item. Payload: `{ widgetId, title }`. Handlers may call the\n\t\t\t * second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (opening the forge's feature-request issue form).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('request-feature', payload, ev)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-wrapper {\n\tposition: relative;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\t/* NC design system: every content card is rounded — same token as\n\t CnDetailCard so detail pages read as ONE card family (ADR-062). */\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n/* Headerless date chip — floated over the top-right of a flush card. */\n.cn-widget-wrapper__floating-meta {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 10px;\n\tz-index: 2;\n}\n\n.cn-widget-wrapper__content {\n\tflex: 1;\n\toverflow: auto;\n\tmin-height: 0;\n\tpadding: 16px;\n}\n\n/* A TABLE GOES EDGE TO EDGE. ALWAYS.\n A table drawn inside 16px of padding renders a border inside a border — two\n rectangles a few pixels apart, which reads as a mistake because it is one.\n The wrapper already has a `flush` prop that zeroes this padding, but a prop\n is something an app has to remember on every table it ever adds, and the\n apps did not: the padding was visible in pipelinq's table widgets.\n So it is structural instead. `:has()` keys on the table component's own root\n class, which means a table is flush by construction and no caller can\n reintroduce the gap by forgetting a prop. */\n.cn-widget-wrapper__content:has(> .cn-table-container),\n.cn-widget-wrapper__content:has(> .cn-widget-object-table) {\n\tpadding: 0;\n}\n\n/* Keyboard focus ring for the now-focusable scrollable content region. */\n.cn-widget-wrapper__content:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n\n.cn-widget-wrapper--borderless {\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-widget-wrapper--borderless .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n.cn-widget-wrapper--flush .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n/* A form or a search list keeps its padding, the reverse of the table rule\n above. A dashboard renders every widget `flush`, and without this their\n fields touch the card's edges. Borderless and chromeless cards, and the\n Nextcloud dashboard variant, keep their own padding. */\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-form-widget),\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-kb-search-widget) {\n\tpadding: 16px;\n}\n\n/* `chromeless` — border and background are already gone through borderless,\n and the content padding through flush. What is left on the root is the\n corner radius, which still clips: with `overflow: hidden` above, a child\n that paints its own background (a table header) gets rounded corners inside\n a square panel, which is the last visible piece of a card that is supposed\n to be absent. `CnDetailCard.cn-detail-card--chromeless` zeroes the same\n thing for the same reason. */\n.cn-widget-wrapper--chromeless {\n\tborder-radius: 0;\n}\n\n/*\n * `chrome=\"nc-dashboard\"` — reproduce the native Nextcloud Dashboard panel\n * (apps/dashboard) exactly, using the same design tokens so an un-customised\n * widget is pixel-identical to a core dashboard panel. `styleConfig` inline\n * overrides (wrapperStyles / headerStyles) still win over these class rules,\n * so users can override any token. Combined selector raises specificity above\n * the base `.cn-widget-wrapper` rules it supersedes.\n */\n.cn-widget-wrapper__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\t/* The header is `space-between`, so without an explicit gap a right-hand\n\t title-icon sits flush against whatever precedes it. The left-hand icon's\n\t spacing comes from header-left's own gap. */\n\tgap: 8px;\n\tpadding: 12px 16px;\n\tborder-bottom: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n/* Actions with no title beside them. `header-left` is not rendered at all in\n this mode, and the header is `space-between`, so a single child would sit\n flush LEFT where every other surface puts its actions on the right. */\n.cn-widget-wrapper__header--actions-only {\n\tjustify-content: flex-end;\n}\n\n/* `chromeless` — the surface around this widget already drew the card, so\n nothing here may draw a second one. Border, background and content padding\n are already gone (chromeless implies borderless + flush); what is left is\n the header, which still renders when there are controls to hold. Its bottom\n rule is the card's own divider and reads as a second card edge inside the\n panel, and its 16px side inset leaves those controls hanging off the grid\n that now starts at the panel's edge. Both go.\n\n Placed AFTER the rules it overrides, which is what stylelint's\n no-descending-specificity asks for. The cascade did not need it (this\n selector is more specific either way), a reader does: an override written\n above the thing it overrides has to be read twice. */\n.cn-widget-wrapper--chromeless .cn-widget-wrapper__header {\n\tborder-bottom: none;\n\tpadding-inline: 0;\n}\n\n.cn-widget-wrapper__header-left {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-width: 0;\n\t/* Grow so the title hugs the (optional) title-icon on the left instead\n\t of centering between icon and actions (header is space-between). */\n\tflex: 1 1 auto;\n}\n\n.cn-widget-wrapper__icon {\n\twidth: 24px;\n\theight: 24px;\n\tflex-shrink: 0;\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n}\n\n.cn-widget-wrapper__title-stack {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-widget-wrapper__eyebrow {\n\tfont-size: 13px;\n\tline-height: 1.3;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-widget-wrapper__title {\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tmargin: 0;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-widget-wrapper.cn-widget-wrapper--nc-dashboard {\n\tbackground: var(--color-main-background-blur, var(--color-main-background));\n\t-webkit-backdrop-filter: var(--filter-background-blur, none);\n\tbackdrop-filter: var(--filter-background-blur, none);\n\tborder: none;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header {\n\tpadding: 16px;\n\tborder-bottom: none;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header-left {\n\tgap: 16px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__icon {\n\twidth: 32px;\n\theight: 32px;\n\tbackground-size: 32px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__title {\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 24px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__content {\n\tpadding: 0 16px 16px;\n}\n\n.cn-widget-wrapper__title-meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__actions {\n\tdisplay: flex;\n\tgap: 4px;\n\tflex-shrink: 0;\n}\n\n/* The header's text link (`headerLink`). Theme hooks: --cn-widget-header-link-size\n and --cn-widget-header-link-weight. */\n.cn-widget-wrapper__header-link {\n\talign-self: center;\n\tpadding: 0 calc(1 * var(--default-grid-baseline));\n\tcolor: var(--color-primary-element);\n\tfont-size: var(--cn-widget-header-link-size, 14px);\n\tfont-weight: var(--cn-widget-header-link-weight, 600);\n\ttext-decoration: none;\n\twhite-space: nowrap;\n}\n\n.cn-widget-wrapper__header-link:hover,\n.cn-widget-wrapper__header-link:focus-visible {\n\ttext-decoration: underline;\n}\n\n.cn-widget-wrapper__header-link:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n\tborder-radius: var(--border-radius-small, 4px);\n}\n\n/* Every widget's header icon is coloured — `--cn-widget-icon-color` is set on\n the header from titleIconStyle, and defaults to the theme colour if a\n wrapper is ever rendered without it. Material-design-icon components paint\n with `fill: currentColor`, so this reaches them; the :deep rule below makes\n it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-shrink: 0;\n\t/* COLOURED BY DEFAULT. `--cn-widget-icon-color` is set on the header from\n\t titleIconStyle; the theme colour is the fallback for a wrapper rendered\n\t without it. Before this, the prop defaulted to null and the icon inherited\n\t body text colour, which is why every widget header looked the same. */\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n\n\t/* SPACE BETWEEN THE ICON AND THE TITLE.\n\t This element is a sibling of `__header-left` inside a space-between\n\t header, so it never received that element's `gap: 8px` — the icon sat\n\t flush against the title text. Matching the header-left gap rather than\n\t inventing a value keeps the two icon positions spaced identically. */\n\tmargin-inline-end: 8px;\n}\n\n/* Right-positioned title icons sit AFTER the actions, so the margin belongs on\n the other side or it pushes the icon off the header's right edge. */\n.cn-widget-wrapper__title-icon:last-child {\n\tmargin-inline-end: 0;\n\tmargin-inline-start: 8px;\n}\n\n/* Material-design-icon components paint with `fill: currentColor`, so the\n colour above reaches them; this makes it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon :deep(svg) {\n\tfill: currentcolor;\n}\n\n.cn-widget-wrapper__footer {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tpadding: 8px 16px;\n\tborder-top: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__footer-link {\n\tfont-size: 13px;\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-widget-wrapper__footer-link:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_normalizeClass","_normalizeStyle","_openBlock","_createElementBlock","_renderSlot","_createElementVNode","_toDisplayString","_resolveDynamicComponent","_createBlock"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnNoteHistoryDialog.vue2.js';
|
|
|
2
2
|
import { render } from './CnNoteHistoryDialog.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-0dcefccf";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -4,6 +4,7 @@ import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue
|
|
|
4
4
|
import History from 'vue-material-design-icons/History.vue';
|
|
5
5
|
import __sfc_main from '../components/CnDialog/CnDialog.vue.js';
|
|
6
6
|
import { dialogBoardMixin } from '../mixins/dialogBoard.js';
|
|
7
|
+
import { userLocale } from '../utils/userLocale.js';
|
|
7
8
|
import { prefixUrl, buildHeaders } from '../utils/headers.js';
|
|
8
9
|
|
|
9
10
|
var script = {
|
|
@@ -120,7 +121,7 @@ var script = {
|
|
|
120
121
|
return ''
|
|
121
122
|
}
|
|
122
123
|
try {
|
|
123
|
-
return new Date(dateStr).toLocaleString(
|
|
124
|
+
return new Date(dateStr).toLocaleString(userLocale(), {
|
|
124
125
|
year: 'numeric',
|
|
125
126
|
month: 'short',
|
|
126
127
|
day: 'numeric',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnNoteHistoryDialog.vue2.js","sources":["../../../src/dialogs/CnNoteHistoryDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnNoteHistoryDialog — what a note said before it was edited.\n\n Lives in src/dialogs/ per the ADR-004 modal/dialog file-isolation rule:\n NcDialog markup never sits inline in the component that opens it.\n\n A note is a Nextcloud comment and an edit overwrites its message, so\n OpenRegister keeps every prior text beside the comment. On open this fetches\n GET /apps/openregister/api/objects/{register}/{schema}/{objectId}/notes/{noteId}/versions\n which answers `{ results, total }` with the versions newest first. Each row\n carries the text that was replaced, the author it was attributed to, who\n replaced it and when.\n\n The list is read-only. Restoring an old text is a new note that references\n the old one, not a rewrite of the record.\n\n Emits:\n - update:open (false) — when the dialog is closed\n-->\n<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:open=\"open\"\n\t\t:name=\"dialogName\"\n\t\t:closeOnClickOutside=\"true\"\n\t\tsize=\"normal\"\n\t\t@update:open=\"$emit('update:open', $event)\">\n\t\t<div\n\t\t\tclass=\"cn-note-history\"\n\t\t\tdata-testid=\"cn-modal\"\n\t\t\tdata-testid-modal=\"cn-note-history-dialog\">\n\t\t\t<NcLoadingIcon v-if=\"loading\" :name=\"loadingLabel\" />\n\n\t\t\t<NcEmptyContent v-else-if=\"failed\" :name=\"errorLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<AlertCircleOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\n\t\t\t<NcEmptyContent v-else-if=\"versions.length === 0\" :name=\"emptyLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<HistoryIcon :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\n\t\t\t<ol v-else class=\"cn-note-history__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"version in versions\"\n\t\t\t\t\t:key=\"version.id || version.uuid\"\n\t\t\t\t\tclass=\"cn-note-history__item\"\n\t\t\t\t\tdata-testid=\"cn-note-history-version\">\n\t\t\t\t\t<p class=\"cn-note-history__meta\">\n\t\t\t\t\t\t{{ metaLine(version) }}\n\t\t\t\t\t</p>\n\t\t\t\t\t<p class=\"cn-note-history__message\">\n\t\t\t\t\t\t{{ version.message }}\n\t\t\t\t\t</p>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</div>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport HistoryIcon from 'vue-material-design-icons/History.vue'\nimport CnDialog from '../components/CnDialog/CnDialog.vue'\nimport { dialogBoardMixin } from '../mixins/dialogBoard.js'\nimport { buildHeaders, prefixUrl } from '../utils/index.js'\n\nexport default {\n\tname: 'CnNoteHistoryDialog',\n\n\tcomponents: { CnDialog, NcEmptyContent, NcLoadingIcon, AlertCircleOutline, HistoryIcon },\n\n\tmixins: [dialogBoardMixin],\n\n\tprops: {\n\t\t/** Whether the dialog is open */\n\t\topen: { type: Boolean, default: false },\n\t\t/** The note whose earlier versions are listed */\n\t\tnoteId: { type: [String, Number], default: '' },\n\t\t/** ID of the object the note hangs on */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register slug */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema slug */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base URL for the OpenRegister API */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Title of the dialog */\n\t\tdialogName: { type: String, default: () => t('nextcloud-vue', 'Earlier versions') },\n\t\t/** Text shown while the versions are being fetched */\n\t\tloadingLabel: { type: String, default: () => t('nextcloud-vue', 'Loading earlier versions…') },\n\t\t/** Text shown when this note was never edited */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'Nobody has changed this note') },\n\t\t/** Text shown when the versions could not be read */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'The earlier versions could not be loaded') },\n\t},\n\n\temits: ['update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tversions: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\topen: {\n\t\t\timmediate: true,\n\t\t\thandler(isOpen) {\n\t\t\t\tif (isOpen) {\n\t\t\t\t\tthis.fetchVersions()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the note's earlier versions, newest first.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchVersions() {\n\t\t\tif (!this.register || !this.schema || !this.noteId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\tthis.versions = []\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/notes/${this.noteId}/versions`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// A failed read says so. Rendering an empty list here would\n\t\t\t\t\t// claim the note was never edited, which is a different\n\t\t\t\t\t// statement and the one the reader would believe.\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tthis.versions = data.results || []\n\t\t\t} catch (err) {\n\t\t\t\tthis.failed = true\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnNoteHistoryDialog: Failed to fetch note versions', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The line above a prior text: who wrote it, who replaced it, when.\n\t\t *\n\t\t * @param {object} version One row from the versions endpoint.\n\t\t * @return {string} The rendered line.\n\t\t */\n\t\tmetaLine(version) {\n\t\t\tconst author = version.authorDisplayName || version.author || t('nextcloud-vue', 'Unknown')\n\t\t\tconst editor = version.editedByDisplayName || version.editedBy || t('nextcloud-vue', 'Unknown')\n\t\t\treturn t('nextcloud-vue', 'Written by {author}, replaced by {editor} on {date}', {\n\t\t\t\tauthor,\n\t\t\t\teditor,\n\t\t\t\tdate: this.formatDate(version.editedAt),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Render a moment in the reader's own locale.\n\t\t *\n\t\t * @param {string} dateStr An ISO-8601 moment.\n\t\t * @return {string} The formatted moment, or the raw string.\n\t\t */\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleString(undefined, {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t\thour: '2-digit',\n\t\t\t\t\tminute: '2-digit',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-note-history { padding: 4px 0; }\n\n.cn-note-history__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-note-history__item {\n\tborder-inline-start: 2px solid var(--color-border);\n\tpadding-inline-start: 12px;\n}\n\n.cn-note-history__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n\tmargin: 0 0 2px;\n}\n\n.cn-note-history__message {\n\tmargin: 0;\n\twhite-space: pre-wrap;\n\toverflow-wrap: anywhere;\n}\n</style>\n"],"names":["CnDialog","HistoryIcon","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnNoteHistoryDialog.vue2.js","sources":["../../../src/dialogs/CnNoteHistoryDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnNoteHistoryDialog — what a note said before it was edited.\n\n Lives in src/dialogs/ per the ADR-004 modal/dialog file-isolation rule:\n NcDialog markup never sits inline in the component that opens it.\n\n A note is a Nextcloud comment and an edit overwrites its message, so\n OpenRegister keeps every prior text beside the comment. On open this fetches\n GET /apps/openregister/api/objects/{register}/{schema}/{objectId}/notes/{noteId}/versions\n which answers `{ results, total }` with the versions newest first. Each row\n carries the text that was replaced, the author it was attributed to, who\n replaced it and when.\n\n The list is read-only. Restoring an old text is a new note that references\n the old one, not a rewrite of the record.\n\n Emits:\n - update:open (false) — when the dialog is closed\n-->\n<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:open=\"open\"\n\t\t:name=\"dialogName\"\n\t\t:closeOnClickOutside=\"true\"\n\t\tsize=\"normal\"\n\t\t@update:open=\"$emit('update:open', $event)\">\n\t\t<div\n\t\t\tclass=\"cn-note-history\"\n\t\t\tdata-testid=\"cn-modal\"\n\t\t\tdata-testid-modal=\"cn-note-history-dialog\">\n\t\t\t<NcLoadingIcon v-if=\"loading\" :name=\"loadingLabel\" />\n\n\t\t\t<NcEmptyContent v-else-if=\"failed\" :name=\"errorLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<AlertCircleOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\n\t\t\t<NcEmptyContent v-else-if=\"versions.length === 0\" :name=\"emptyLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<HistoryIcon :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\n\t\t\t<ol v-else class=\"cn-note-history__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"version in versions\"\n\t\t\t\t\t:key=\"version.id || version.uuid\"\n\t\t\t\t\tclass=\"cn-note-history__item\"\n\t\t\t\t\tdata-testid=\"cn-note-history-version\">\n\t\t\t\t\t<p class=\"cn-note-history__meta\">\n\t\t\t\t\t\t{{ metaLine(version) }}\n\t\t\t\t\t</p>\n\t\t\t\t\t<p class=\"cn-note-history__message\">\n\t\t\t\t\t\t{{ version.message }}\n\t\t\t\t\t</p>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</div>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport HistoryIcon from 'vue-material-design-icons/History.vue'\nimport CnDialog from '../components/CnDialog/CnDialog.vue'\nimport { dialogBoardMixin } from '../mixins/dialogBoard.js'\nimport { buildHeaders, prefixUrl } from '../utils/index.js'\nimport { userLocale } from '../utils/userLocale.js'\n\nexport default {\n\tname: 'CnNoteHistoryDialog',\n\n\tcomponents: { CnDialog, NcEmptyContent, NcLoadingIcon, AlertCircleOutline, HistoryIcon },\n\n\tmixins: [dialogBoardMixin],\n\n\tprops: {\n\t\t/** Whether the dialog is open */\n\t\topen: { type: Boolean, default: false },\n\t\t/** The note whose earlier versions are listed */\n\t\tnoteId: { type: [String, Number], default: '' },\n\t\t/** ID of the object the note hangs on */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register slug */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema slug */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base URL for the OpenRegister API */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Title of the dialog */\n\t\tdialogName: { type: String, default: () => t('nextcloud-vue', 'Earlier versions') },\n\t\t/** Text shown while the versions are being fetched */\n\t\tloadingLabel: { type: String, default: () => t('nextcloud-vue', 'Loading earlier versions…') },\n\t\t/** Text shown when this note was never edited */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'Nobody has changed this note') },\n\t\t/** Text shown when the versions could not be read */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'The earlier versions could not be loaded') },\n\t},\n\n\temits: ['update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tversions: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\topen: {\n\t\t\timmediate: true,\n\t\t\thandler(isOpen) {\n\t\t\t\tif (isOpen) {\n\t\t\t\t\tthis.fetchVersions()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the note's earlier versions, newest first.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchVersions() {\n\t\t\tif (!this.register || !this.schema || !this.noteId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\tthis.versions = []\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/notes/${this.noteId}/versions`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// A failed read says so. Rendering an empty list here would\n\t\t\t\t\t// claim the note was never edited, which is a different\n\t\t\t\t\t// statement and the one the reader would believe.\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tthis.versions = data.results || []\n\t\t\t} catch (err) {\n\t\t\t\tthis.failed = true\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnNoteHistoryDialog: Failed to fetch note versions', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The line above a prior text: who wrote it, who replaced it, when.\n\t\t *\n\t\t * @param {object} version One row from the versions endpoint.\n\t\t * @return {string} The rendered line.\n\t\t */\n\t\tmetaLine(version) {\n\t\t\tconst author = version.authorDisplayName || version.author || t('nextcloud-vue', 'Unknown')\n\t\t\tconst editor = version.editedByDisplayName || version.editedBy || t('nextcloud-vue', 'Unknown')\n\t\t\treturn t('nextcloud-vue', 'Written by {author}, replaced by {editor} on {date}', {\n\t\t\t\tauthor,\n\t\t\t\teditor,\n\t\t\t\tdate: this.formatDate(version.editedAt),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Render a moment in the reader's own locale.\n\t\t *\n\t\t * @param {string} dateStr An ISO-8601 moment.\n\t\t * @return {string} The formatted moment, or the raw string.\n\t\t */\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleString(userLocale(), {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t\thour: '2-digit',\n\t\t\t\t\tminute: '2-digit',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-note-history { padding: 4px 0; }\n\n.cn-note-history__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-note-history__item {\n\tborder-inline-start: 2px solid var(--color-border);\n\tpadding-inline-start: 12px;\n}\n\n.cn-note-history__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n\tmargin: 0 0 2px;\n}\n\n.cn-note-history__message {\n\tmargin: 0;\n\twhite-space: pre-wrap;\n\toverflow-wrap: anywhere;\n}\n</style>\n"],"names":["CnDialog","HistoryIcon","t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnNoteHistoryDialog.vue3.js","sources":["../../../src/dialogs/CnNoteHistoryDialog.vue?vue&type=template&id=
|
|
1
|
+
{"version":3,"file":"CnNoteHistoryDialog.vue3.js","sources":["../../../src/dialogs/CnNoteHistoryDialog.vue?vue&type=template&id=0dcefccf&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<!--\n CnNoteHistoryDialog — what a note said before it was edited.\n\n Lives in src/dialogs/ per the ADR-004 modal/dialog file-isolation rule:\n NcDialog markup never sits inline in the component that opens it.\n\n A note is a Nextcloud comment and an edit overwrites its message, so\n OpenRegister keeps every prior text beside the comment. On open this fetches\n GET /apps/openregister/api/objects/{register}/{schema}/{objectId}/notes/{noteId}/versions\n which answers `{ results, total }` with the versions newest first. Each row\n carries the text that was replaced, the author it was attributed to, who\n replaced it and when.\n\n The list is read-only. Restoring an old text is a new note that references\n the old one, not a rewrite of the record.\n\n Emits:\n - update:open (false) — when the dialog is closed\n-->\n<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:open=\"open\"\n\t\t:name=\"dialogName\"\n\t\t:closeOnClickOutside=\"true\"\n\t\tsize=\"normal\"\n\t\t@update:open=\"$emit('update:open', $event)\">\n\t\t<div\n\t\t\tclass=\"cn-note-history\"\n\t\t\tdata-testid=\"cn-modal\"\n\t\t\tdata-testid-modal=\"cn-note-history-dialog\">\n\t\t\t<NcLoadingIcon v-if=\"loading\" :name=\"loadingLabel\" />\n\n\t\t\t<NcEmptyContent v-else-if=\"failed\" :name=\"errorLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<AlertCircleOutline :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\n\t\t\t<NcEmptyContent v-else-if=\"versions.length === 0\" :name=\"emptyLabel\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<HistoryIcon :size=\"48\" />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\n\t\t\t<ol v-else class=\"cn-note-history__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"version in versions\"\n\t\t\t\t\t:key=\"version.id || version.uuid\"\n\t\t\t\t\tclass=\"cn-note-history__item\"\n\t\t\t\t\tdata-testid=\"cn-note-history-version\">\n\t\t\t\t\t<p class=\"cn-note-history__meta\">\n\t\t\t\t\t\t{{ metaLine(version) }}\n\t\t\t\t\t</p>\n\t\t\t\t\t<p class=\"cn-note-history__message\">\n\t\t\t\t\t\t{{ version.message }}\n\t\t\t\t\t</p>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</div>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport HistoryIcon from 'vue-material-design-icons/History.vue'\nimport CnDialog from '../components/CnDialog/CnDialog.vue'\nimport { dialogBoardMixin } from '../mixins/dialogBoard.js'\nimport { buildHeaders, prefixUrl } from '../utils/index.js'\nimport { userLocale } from '../utils/userLocale.js'\n\nexport default {\n\tname: 'CnNoteHistoryDialog',\n\n\tcomponents: { CnDialog, NcEmptyContent, NcLoadingIcon, AlertCircleOutline, HistoryIcon },\n\n\tmixins: [dialogBoardMixin],\n\n\tprops: {\n\t\t/** Whether the dialog is open */\n\t\topen: { type: Boolean, default: false },\n\t\t/** The note whose earlier versions are listed */\n\t\tnoteId: { type: [String, Number], default: '' },\n\t\t/** ID of the object the note hangs on */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register slug */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema slug */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base URL for the OpenRegister API */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Title of the dialog */\n\t\tdialogName: { type: String, default: () => t('nextcloud-vue', 'Earlier versions') },\n\t\t/** Text shown while the versions are being fetched */\n\t\tloadingLabel: { type: String, default: () => t('nextcloud-vue', 'Loading earlier versions…') },\n\t\t/** Text shown when this note was never edited */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'Nobody has changed this note') },\n\t\t/** Text shown when the versions could not be read */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'The earlier versions could not be loaded') },\n\t},\n\n\temits: ['update:open'],\n\n\tdata() {\n\t\treturn {\n\t\t\tversions: [],\n\t\t\tloading: false,\n\t\t\tfailed: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\topen: {\n\t\t\timmediate: true,\n\t\t\thandler(isOpen) {\n\t\t\t\tif (isOpen) {\n\t\t\t\t\tthis.fetchVersions()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the note's earlier versions, newest first.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchVersions() {\n\t\t\tif (!this.register || !this.schema || !this.noteId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.failed = false\n\t\t\tthis.versions = []\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\tprefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/notes/${this.noteId}/versions`),\n\t\t\t\t\t{ headers: buildHeaders() },\n\t\t\t\t)\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// A failed read says so. Rendering an empty list here would\n\t\t\t\t\t// claim the note was never edited, which is a different\n\t\t\t\t\t// statement and the one the reader would believe.\n\t\t\t\t\tthis.failed = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tthis.versions = data.results || []\n\t\t\t} catch (err) {\n\t\t\t\tthis.failed = true\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('CnNoteHistoryDialog: Failed to fetch note versions', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The line above a prior text: who wrote it, who replaced it, when.\n\t\t *\n\t\t * @param {object} version One row from the versions endpoint.\n\t\t * @return {string} The rendered line.\n\t\t */\n\t\tmetaLine(version) {\n\t\t\tconst author = version.authorDisplayName || version.author || t('nextcloud-vue', 'Unknown')\n\t\t\tconst editor = version.editedByDisplayName || version.editedBy || t('nextcloud-vue', 'Unknown')\n\t\t\treturn t('nextcloud-vue', 'Written by {author}, replaced by {editor} on {date}', {\n\t\t\t\tauthor,\n\t\t\t\teditor,\n\t\t\t\tdate: this.formatDate(version.editedAt),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Render a moment in the reader's own locale.\n\t\t *\n\t\t * @param {string} dateStr An ISO-8601 moment.\n\t\t * @return {string} The formatted moment, or the raw string.\n\t\t */\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleString(userLocale(), {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t\thour: '2-digit',\n\t\t\t\t\tminute: '2-digit',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-note-history { padding: 4px 0; }\n\n.cn-note-history__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-note-history__item {\n\tborder-inline-start: 2px solid var(--color-border);\n\tpadding-inline-start: 12px;\n}\n\n.cn-note-history__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n\tmargin: 0 0 2px;\n}\n\n.cn-note-history__message {\n\tmargin: 0;\n\twhite-space: pre-wrap;\n\toverflow-wrap: anywhere;\n}\n</style>\n"],"names":["_createBlock","_createElementVNode","_createVNode"],"mappings":";;;AA6DM,EAAA,OAAG;AACJ,EAAA,aAAG,EAAA,UAAA;AACH,EAAA,mBAAe,EAAC,wBAAmB;;;;;;;;;;;;;;;;uBAdpCA;QACG,EAAE,IAAI,CAAA,IAAA;SACL,EAAE,IAAI,CAAA,KAAA;AACX,IAAA,YAAW,EAAC,QAAI;;AAEhB,IAAA,QAAQ,EAAC,IAAS,CAAA,QAAA;AACjB,IAAA,IAAE,EAAA,MAAA,CAAA,IAAA;QACC,EAAE,MAAW,CAAA,UAAA;AACf,IAAA,mBAAiB,EAAG,IAAG;QACtB,EAAC,QAAQ;IACV,eAAS,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,IAAK,CAAA,KAAA,CAAA,aAAA,EAAgB,MAAK,CAAA,CAAA,CAAA;;qBACrC;AAAA,MAAAC,kBAAA,QAAA;cAIqB,CAAA,OAAA;0BAApBD,WAAoB,CAAA,wBAAA,EAAA;;AAAA,cAAA,IAAA,EAAA,MAAA,CAAA,YAAA;;aAElB,KAAA,CAAA,MAAA;;;AAAA,gBAAA,IAAA,EAAA,MAAA,CAAA,UAAA;;AACD,gBAAA,IAAA,UACF,MAAE;kBAAFE,WAAE,CAAA,6BAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -2,7 +2,7 @@ import script from './CnActivityCard.vue2.js';
|
|
|
2
2
|
import { render } from './CnActivityCard.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-287c17fa";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -10,6 +10,7 @@ import TagOutline from 'vue-material-design-icons/TagOutline.vue';
|
|
|
10
10
|
import Timeline from 'vue-material-design-icons/Timeline.vue';
|
|
11
11
|
import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
|
|
12
12
|
import __sfc_main$1 from '../../../components/CnVisibilityChip/CnVisibilityChip.vue.js';
|
|
13
|
+
import { userLocale } from '../../../utils/userLocale.js';
|
|
13
14
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
14
15
|
|
|
15
16
|
const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
|
|
@@ -196,7 +197,7 @@ var script = {
|
|
|
196
197
|
return translate('nextcloud-vue', 'Yesterday')
|
|
197
198
|
}
|
|
198
199
|
try {
|
|
199
|
-
return date.toLocaleDateString(
|
|
200
|
+
return date.toLocaleDateString(userLocale(), { weekday: 'short', day: 'numeric', month: 'short' })
|
|
200
201
|
} catch {
|
|
201
202
|
return date.toISOString().split('T')[0]
|
|
202
203
|
}
|
|
@@ -208,7 +209,7 @@ var script = {
|
|
|
208
209
|
return ''
|
|
209
210
|
}
|
|
210
211
|
try {
|
|
211
|
-
return ts.toLocaleTimeString(
|
|
212
|
+
return ts.toLocaleTimeString(userLocale(), { hour: '2-digit', minute: '2-digit' })
|
|
212
213
|
} catch {
|
|
213
214
|
return ts.toISOString().split('T')[1].slice(0, 5)
|
|
214
215
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnActivityCard.vue2.js","sources":["../../../../../src/integrations/builtin/activity/CnActivityCard.vue"],"sourcesContent":["<!--\n CnActivityCard — bespoke surface-aware widget for the `activity` integration.\n\n Replaces the generic CnIntegrationCard for the `activity` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline count of activity events\n (with a \"today\" sub-count) + most-recent line summarising the\n latest event (actor + verb + time).\n - detail-page : compact chronological feed grouped\n by day (mirrors the tab layout at a smaller scale).\n - single-entity : chip with type icon + actor +\n subject (referenceType: 'activity').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnActivityTab; for `single-entity` the optional `value` prop addresses\n a single event by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-activity/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-activity-card__chip\" :title=\"subjectFor(entity)\">\n\t\t\t\t<component :is=\"iconFor(entity)\" :size=\"14\" />\n\t\t\t\t<span class=\"cn-activity-card__chip-label\">\n\t\t\t\t\t<span class=\"cn-activity-card__chip-actor\">{{ actorFor(entity) }}</span>\n\t\t\t\t\t<span class=\"cn-activity-card__chip-sep\">·</span>\n\t\t\t\t\t<span class=\"cn-activity-card__chip-subject\">{{ subjectFor(entity) }}</span>\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-activity-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline count + most-recent line -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-activity-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-activity-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-activity-card__headline\">\n\t\t\t\t<div class=\"cn-activity-card__headline-line\">\n\t\t\t\t\t<strong>{{ headlineLabel }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-activity-card__headline-recent\">\n\t\t\t\t\t<component :is=\"iconFor(mostRecent)\" :size=\"14\" />\n\t\t\t\t\t<span class=\"cn-activity-card__headline-actor\">{{ actorFor(mostRecent) }}</span>\n\t\t\t\t\t<span class=\"cn-activity-card__chip-sep\">·</span>\n\t\t\t\t\t<span class=\"cn-activity-card__headline-subject\">{{ subjectFor(mostRecent) }}</span>\n\t\t\t\t\t<span class=\"cn-activity-card__chip-sep\">·</span>\n\t\t\t\t\t<span class=\"cn-activity-card__headline-time\">{{ relativeTime(mostRecent) }}</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact timeline -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-activity-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-activity-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-activity-card__timeline\">\n\t\t\t\t<section\n\t\t\t\t\tv-for=\"day in groupedByDay\"\n\t\t\t\t\t:key=\"day.key\"\n\t\t\t\t\tclass=\"cn-activity-card__day\">\n\t\t\t\t\t<header class=\"cn-activity-card__day-header\">\n\t\t\t\t\t\t<CalendarOutline :size=\"14\" />\n\t\t\t\t\t\t<span>{{ day.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-activity-card__day-count\">{{ day.rows.length }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul class=\"cn-activity-card__list\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"entry in day.rows.slice(0, COMPACT_LIMIT)\"\n\t\t\t\t\t\t\t:key=\"entryKey(entry)\"\n\t\t\t\t\t\t\tclass=\"cn-activity-card__row\">\n\t\t\t\t\t\t\t<component :is=\"iconFor(entry)\" :size=\"14\" />\n\t\t\t\t\t\t\t<div class=\"cn-activity-card__row-main\">\n\t\t\t\t\t\t\t\t<span class=\"cn-activity-card__row-subject\">{{ subjectFor(entry) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-activity-card__row-meta\">\n\t\t\t\t\t\t\t\t\t{{ actorFor(entry) }} · {{ relativeTime(entry) }}\n\t\t\t\t\t\t\t\t</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</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</section>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport AccountOutline from 'vue-material-design-icons/AccountOutline.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 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 CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnVisibilityChip from '../../../components/CnVisibilityChip/CnVisibilityChip.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 4\n\n/**\n * CnActivityCard — bespoke surface-aware widget for the `activity` integration.\n *\n * Renders activity events across all four surfaces. See the file-level\n * docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnActivityCard',\n\n\tcomponents: {\n\t\tCnDetailCard,\n\t\tCnVisibilityChip,\n\t\tNcLoadingIcon,\n\t\tCalendarOutline,\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\tprops: {\n\t\t/** Show a visibility chip (internal or public) on each row of the detail-page feed. Off by default. */\n\t\tshowVisibility: { type: Boolean, default: false },\n\t\t/** Stable integration id (forwarded from the registry — always `'activity'`). */\n\t\tintegrationId: { type: String, default: 'activity' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (event id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Activity') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Timeline },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Compact-feed page size for dashboard / detail surfaces. */\n\t\tpageSize: { type: Number, default: 12 },\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 label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Activity is currently unavailable.') },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tentries: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t\ttotal: 0,\n\t\t\tCOMPACT_LIMIT,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\ttodayCount() {\n\t\t\tconst now = new Date()\n\t\t\tconst start = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()\n\t\t\tlet count = 0\n\t\t\tfor (const entry of this.entries) {\n\t\t\t\tconst ts = this.timestampFor(entry)\n\t\t\t\tif (ts !== null && ts.getTime() >= start) {\n\t\t\t\t\tcount += 1\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn count\n\t\t},\n\n\t\theadlineLabel() {\n\t\t\tif (this.todayCount > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} today', { n: this.todayCount })\n\t\t\t}\n\t\t\tconst total = this.total > 0 ? this.total : this.entries.length\n\t\t\treturn t('nextcloud-vue', '{n} events', { n: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.entries.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Provider sorts DESC; rely on first row.\n\t\t\treturn this.entries[0]\n\t\t},\n\n\t\tgroupedByDay() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const entry of this.entries) {\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\treturn [...groups.values()]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\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.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\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\tsubjectFor(entry) {\n\t\t\treturn entry.subject_rich ?? entry.subjectRich ?? entry.subject ?? entry.title ?? ''\n\t\t},\n\n\t\tactorFor(entry) {\n\t\t\treturn entry.actor_id ?? entry.actorDisplayName ?? entry.user ?? entry.affecteduser ?? t('nextcloud-vue', 'System')\n\t\t},\n\n\t\ticonFor(entry) {\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\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\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.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ limit: String(this.pageSize) })\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}?${params.toString()}`), { 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.entries = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.total = Number(data.total ?? 0)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\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('[CnActivityCard] failed to fetch activity', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === null || this.value === undefined\n\t\t\t\t|| !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(String(this.value))}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\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('[CnActivityCard] failed to fetch single event', err)\n\t\t\t\tthis.entity = null\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-activity-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-activity-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-activity-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-activity-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-activity-card__headline-actor {\n\tfont-weight: 500;\n}\n\n.cn-activity-card__headline-subject {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-activity-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-activity-card__chip-label {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-activity-card__chip-actor {\n\tfont-weight: 500;\n}\n\n.cn-activity-card__chip-sep {\n\topacity: 0.6;\n}\n\n.cn-activity-card__chip-subject {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-activity-card__day {\n\tmargin-bottom: 10px;\n}\n\n.cn-activity-card__day-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 0;\n\tfont-size: 0.8em;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-activity-card__day-count {\n\tmargin-left: auto;\n\tfont-weight: normal;\n\tfont-size: 0.8em;\n\tpadding: 1px 6px;\n\tborder-radius: 9px;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-activity-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-activity-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 6px;\n\tpadding: 6px 0;\n}\n\n.cn-activity-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-activity-card__row-subject {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.95em;\n}\n\n.cn-activity-card__row-meta {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnDetailCard","CnVisibilityChip","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnActivityCard.vue2.js","sources":["../../../../../src/integrations/builtin/activity/CnActivityCard.vue"],"sourcesContent":["<!--\n CnActivityCard — bespoke surface-aware widget for the `activity` integration.\n\n Replaces the generic CnIntegrationCard for the `activity` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline count of activity events\n (with a \"today\" sub-count) + most-recent line summarising the\n latest event (actor + verb + time).\n - detail-page : compact chronological feed grouped\n by day (mirrors the tab layout at a smaller scale).\n - single-entity : chip with type icon + actor +\n subject (referenceType: 'activity').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnActivityTab; for `single-entity` the optional `value` prop addresses\n a single event by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-activity/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-activity-card__chip\" :title=\"subjectFor(entity)\">\n\t\t\t\t<component :is=\"iconFor(entity)\" :size=\"14\" />\n\t\t\t\t<span class=\"cn-activity-card__chip-label\">\n\t\t\t\t\t<span class=\"cn-activity-card__chip-actor\">{{ actorFor(entity) }}</span>\n\t\t\t\t\t<span class=\"cn-activity-card__chip-sep\">·</span>\n\t\t\t\t\t<span class=\"cn-activity-card__chip-subject\">{{ subjectFor(entity) }}</span>\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-activity-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline count + most-recent line -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-activity-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-activity-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-activity-card__headline\">\n\t\t\t\t<div class=\"cn-activity-card__headline-line\">\n\t\t\t\t\t<strong>{{ headlineLabel }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-activity-card__headline-recent\">\n\t\t\t\t\t<component :is=\"iconFor(mostRecent)\" :size=\"14\" />\n\t\t\t\t\t<span class=\"cn-activity-card__headline-actor\">{{ actorFor(mostRecent) }}</span>\n\t\t\t\t\t<span class=\"cn-activity-card__chip-sep\">·</span>\n\t\t\t\t\t<span class=\"cn-activity-card__headline-subject\">{{ subjectFor(mostRecent) }}</span>\n\t\t\t\t\t<span class=\"cn-activity-card__chip-sep\">·</span>\n\t\t\t\t\t<span class=\"cn-activity-card__headline-time\">{{ relativeTime(mostRecent) }}</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact timeline -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-activity-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-activity-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-activity-card__timeline\">\n\t\t\t\t<section\n\t\t\t\t\tv-for=\"day in groupedByDay\"\n\t\t\t\t\t:key=\"day.key\"\n\t\t\t\t\tclass=\"cn-activity-card__day\">\n\t\t\t\t\t<header class=\"cn-activity-card__day-header\">\n\t\t\t\t\t\t<CalendarOutline :size=\"14\" />\n\t\t\t\t\t\t<span>{{ day.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-activity-card__day-count\">{{ day.rows.length }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul class=\"cn-activity-card__list\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"entry in day.rows.slice(0, COMPACT_LIMIT)\"\n\t\t\t\t\t\t\t:key=\"entryKey(entry)\"\n\t\t\t\t\t\t\tclass=\"cn-activity-card__row\">\n\t\t\t\t\t\t\t<component :is=\"iconFor(entry)\" :size=\"14\" />\n\t\t\t\t\t\t\t<div class=\"cn-activity-card__row-main\">\n\t\t\t\t\t\t\t\t<span class=\"cn-activity-card__row-subject\">{{ subjectFor(entry) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-activity-card__row-meta\">\n\t\t\t\t\t\t\t\t\t{{ actorFor(entry) }} · {{ relativeTime(entry) }}\n\t\t\t\t\t\t\t\t</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</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</section>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport AccountOutline from 'vue-material-design-icons/AccountOutline.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 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 CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnVisibilityChip from '../../../components/CnVisibilityChip/CnVisibilityChip.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 4\n\n/**\n * CnActivityCard — bespoke surface-aware widget for the `activity` integration.\n *\n * Renders activity events across all four surfaces. See the file-level\n * docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnActivityCard',\n\n\tcomponents: {\n\t\tCnDetailCard,\n\t\tCnVisibilityChip,\n\t\tNcLoadingIcon,\n\t\tCalendarOutline,\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\tprops: {\n\t\t/** Show a visibility chip (internal or public) on each row of the detail-page feed. Off by default. */\n\t\tshowVisibility: { type: Boolean, default: false },\n\t\t/** Stable integration id (forwarded from the registry — always `'activity'`). */\n\t\tintegrationId: { type: String, default: 'activity' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (event id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Activity') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Timeline },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Compact-feed page size for dashboard / detail surfaces. */\n\t\tpageSize: { type: Number, default: 12 },\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 label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Activity is currently unavailable.') },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tentries: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t\ttotal: 0,\n\t\t\tCOMPACT_LIMIT,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\ttodayCount() {\n\t\t\tconst now = new Date()\n\t\t\tconst start = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()\n\t\t\tlet count = 0\n\t\t\tfor (const entry of this.entries) {\n\t\t\t\tconst ts = this.timestampFor(entry)\n\t\t\t\tif (ts !== null && ts.getTime() >= start) {\n\t\t\t\t\tcount += 1\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn count\n\t\t},\n\n\t\theadlineLabel() {\n\t\t\tif (this.todayCount > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} today', { n: this.todayCount })\n\t\t\t}\n\t\t\tconst total = this.total > 0 ? this.total : this.entries.length\n\t\t\treturn t('nextcloud-vue', '{n} events', { n: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.entries.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Provider sorts DESC; rely on first row.\n\t\t\treturn this.entries[0]\n\t\t},\n\n\t\tgroupedByDay() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const entry of this.entries) {\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\treturn [...groups.values()]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\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.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\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\tsubjectFor(entry) {\n\t\t\treturn entry.subject_rich ?? entry.subjectRich ?? entry.subject ?? entry.title ?? ''\n\t\t},\n\n\t\tactorFor(entry) {\n\t\t\treturn entry.actor_id ?? entry.actorDisplayName ?? entry.user ?? entry.affecteduser ?? t('nextcloud-vue', 'System')\n\t\t},\n\n\t\ticonFor(entry) {\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\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\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.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst params = new URLSearchParams({ limit: String(this.pageSize) })\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}?${params.toString()}`), { 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.entries = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.total = Number(data.total ?? 0)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\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('[CnActivityCard] failed to fetch activity', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === null || this.value === undefined\n\t\t\t\t|| !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(String(this.value))}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\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('[CnActivityCard] failed to fetch single event', err)\n\t\t\t\tthis.entity = null\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-activity-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-activity-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-activity-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-activity-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-activity-card__headline-actor {\n\tfont-weight: 500;\n}\n\n.cn-activity-card__headline-subject {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-activity-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-activity-card__chip-label {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-activity-card__chip-actor {\n\tfont-weight: 500;\n}\n\n.cn-activity-card__chip-sep {\n\topacity: 0.6;\n}\n\n.cn-activity-card__chip-subject {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-activity-card__day {\n\tmargin-bottom: 10px;\n}\n\n.cn-activity-card__day-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 0;\n\tfont-size: 0.8em;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-activity-card__day-count {\n\tmargin-left: auto;\n\tfont-weight: normal;\n\tfont-size: 0.8em;\n\tpadding: 1px 6px;\n\tborder-radius: 9px;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-activity-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-activity-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 6px;\n\tpadding: 6px 0;\n}\n\n.cn-activity-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-activity-card__row-subject {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.95em;\n}\n\n.cn-activity-card__row-meta {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnDetailCard","CnVisibilityChip","t"],"mappings":"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"}
|