@conduction/nextcloud-vue 2.58.0 → 2.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +39 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +224 -9
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
- package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +58 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +13 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +532 -29
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +300 -142
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
- package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +169 -12
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
- package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +199 -16
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +20 -10
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
- package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
- package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +100 -3
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
- package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
- package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
- package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +55 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +55 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +998 -7
- package/dist/esm/utils/actionLink.js +5 -0
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +16 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/dateVariant.js +124 -0
- package/dist/esm/utils/dateVariant.js.map +1 -0
- package/dist/esm/utils/detailActionModel.js +260 -0
- package/dist/esm/utils/detailActionModel.js.map +1 -0
- package/dist/esm/utils/dueRule.js +84 -0
- package/dist/esm/utils/dueRule.js.map +1 -0
- package/dist/esm/utils/fetchAggregate.js +37 -1
- package/dist/esm/utils/fetchAggregate.js.map +1 -1
- package/dist/esm/utils/fetchFilterCounts.js +186 -0
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +5 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/linkNavigation.js +6 -3
- package/dist/esm/utils/linkNavigation.js.map +1 -1
- package/dist/esm/utils/readPath.js +30 -0
- package/dist/esm/utils/readPath.js.map +1 -0
- package/dist/esm/utils/rowAuxClick.js +30 -5
- package/dist/esm/utils/rowAuxClick.js.map +1 -1
- package/dist/esm/utils/safeHref.js +18 -3
- package/dist/esm/utils/safeHref.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +15855 -9353
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +998 -7
- package/l10n/en.json +55 -1
- package/l10n/nl.json +55 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +3 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
- package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -4
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
- package/src/components/CnAppNav/CnAppNav.md +26 -0
- package/src/components/CnAppNav/CnAppNav.vue +104 -1
- package/src/components/CnBannerWidget/CnBannerWidget.vue +365 -10
- package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
- package/src/components/CnBoardView/CnBoardView.vue +90 -5
- package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
- package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
- package/src/components/CnBrandStripe/index.js +4 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
- package/src/components/CnConversationThread/CnConversationThread.md +51 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
- package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
- package/src/components/CnConversationThread/index.js +4 -0
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +131 -17
- package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
- package/src/components/CnDetailPage/CnDetailPage.md +17 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +712 -49
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
- package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
- package/src/components/CnDocumentReviewList/index.js +4 -0
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
- package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
- package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +189 -14
- package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +210 -17
- package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
- package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
- package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
- package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
- package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
- package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
- package/src/components/CnNextStepCard/index.js +4 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
- package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
- package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
- package/src/components/CnObjectList/CnObjectList.vue +19 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
- package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
- package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
- package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +100 -3
- package/src/components/CnRowActions/CnRowActions.vue +7 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
- package/src/components/CnSegmentedControl/index.js +4 -0
- package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
- package/src/components/CnStackedBarWidget/index.js +14 -0
- package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
- package/src/components/CnStackedBarWidgetForm/index.js +4 -0
- package/src/components/CnTabs/CnTab.vue +35 -0
- package/src/components/CnTabs/CnTabs.vue +251 -13
- package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
- package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
- package/src/components/CnWeekStripWidget/index.js +14 -0
- package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
- package/src/components/CnWeekStripWidgetForm/index.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
- package/src/components/index.js +11 -0
- package/src/css/detail-page.css +47 -0
- package/src/css/table.css +26 -0
- package/src/index.js +9 -0
- package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
- package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
- package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
- package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
- package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
- package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
- package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
- package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
- package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
- package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
- package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
- package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
- package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
- package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
- package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
- package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
- package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
- package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
- package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
- package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
- package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
- package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
- package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
- package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
- package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
- package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
- package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
- package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
- package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
- package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
- package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
- package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
- package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
- package/src/schemas/app-manifest-v2.schema.json +389 -1
- package/src/schemas/app-manifest.schema.json +2 -1
- package/src/utils/actionLink.js +5 -0
- package/src/utils/actionsDispatcher.js +15 -0
- package/src/utils/dateVariant.js +120 -0
- package/src/utils/detailActionModel.js +256 -0
- package/src/utils/dueRule.js +94 -0
- package/src/utils/fetchAggregate.js +36 -0
- package/src/utils/fetchFilterCounts.js +182 -0
- package/src/utils/index.js +1 -0
- package/src/utils/libraryWidgetKeys.js +5 -0
- package/src/utils/linkNavigation.js +6 -3
- package/src/utils/readPath.js +27 -0
- package/src/utils/rowAuxClick.js +29 -4
- package/src/utils/safeHref.js +18 -3
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getCurrentUser } from '@nextcloud/auth';
|
|
2
|
+
import { translate, getCanonicalLocale } from '@nextcloud/l10n';
|
|
2
3
|
import { resolveImageUrl } from '../../utils/resolveImageUrl.js';
|
|
3
4
|
|
|
4
5
|
const ALLOWED_OVERLAY_MODES = ['none', 'tint', 'gradient-bottom'];
|
|
@@ -35,6 +36,16 @@ const VERTICAL_ALIGN_FLEX = Object.freeze({
|
|
|
35
36
|
* External CTA URLs (http/https) open in a new tab with
|
|
36
37
|
* `rel="noopener noreferrer"`; relative / anchor URLs stay in the current tab.
|
|
37
38
|
*
|
|
39
|
+
* Optional greeting: `greeting: true` makes the heading greet the signed-in
|
|
40
|
+
* user by time of day and first name ("Good afternoon, Pieter"; `"full"` uses
|
|
41
|
+
* the whole display name), and `showDate: true` adds a line with today's
|
|
42
|
+
* date above it. `plain: true` drops the coloured background and aligns the
|
|
43
|
+
* text to the start, which is how a greeting usually sits on a dashboard.
|
|
44
|
+
*
|
|
45
|
+
* ```json
|
|
46
|
+
* { "widgetKey": "header", "props": { "content": { "greeting": true, "showDate": true, "plain": true } } }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
38
49
|
* Registered as the `header` dashboard widget type via the renderer's
|
|
39
50
|
* `index.js`.
|
|
40
51
|
*/
|
|
@@ -56,9 +67,10 @@ var script = {
|
|
|
56
67
|
/**
|
|
57
68
|
* Persisted widget content: `{title, subtitle, backgroundImageUrl,
|
|
58
69
|
* backgroundImageFileId, backgroundColor, overlayMode, overlayColor,
|
|
59
|
-
* overlayOpacity, textColor, textAlign, verticalAlign, height, cta
|
|
60
|
-
* All fields are optional except `title
|
|
61
|
-
*
|
|
70
|
+
* overlayOpacity, textColor, textAlign, verticalAlign, height, cta,
|
|
71
|
+
* greeting, showDate, plain}`. All fields are optional except `title`
|
|
72
|
+
* (or `greeting`); unknown enum values collapse to documented defaults
|
|
73
|
+
* and the renderer never throws.
|
|
62
74
|
*
|
|
63
75
|
* @type {object}
|
|
64
76
|
*/
|
|
@@ -78,6 +90,17 @@ var script = {
|
|
|
78
90
|
default: null,
|
|
79
91
|
},
|
|
80
92
|
/* eslint-enable vue/no-unused-properties */
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The moment the greeting and the date line are computed for. Leave
|
|
96
|
+
* empty for the current time.
|
|
97
|
+
*
|
|
98
|
+
* @type {Date|null}
|
|
99
|
+
*/
|
|
100
|
+
now: {
|
|
101
|
+
type: Date,
|
|
102
|
+
default: null,
|
|
103
|
+
},
|
|
81
104
|
},
|
|
82
105
|
|
|
83
106
|
data() {
|
|
@@ -113,9 +136,121 @@ var script = {
|
|
|
113
136
|
return typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''
|
|
114
137
|
},
|
|
115
138
|
|
|
116
|
-
/** Whether a non-empty title
|
|
139
|
+
/** Whether a heading renders: a non-empty title or a greeting. */
|
|
117
140
|
hasTitle() {
|
|
118
|
-
return this.
|
|
141
|
+
return this.headingText !== ''
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The moment the greeting and date line are computed for.
|
|
146
|
+
*
|
|
147
|
+
* @return {Date}
|
|
148
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
149
|
+
*/
|
|
150
|
+
reference() {
|
|
151
|
+
return this.now instanceof Date ? this.now : new Date()
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Whether the plain presentation is on (no coloured background).
|
|
156
|
+
*
|
|
157
|
+
* @return {boolean}
|
|
158
|
+
*/
|
|
159
|
+
isPlain() {
|
|
160
|
+
return Boolean(this.content && this.content.plain === true)
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The name the greeting uses: the first word of the display name, or
|
|
165
|
+
* the whole display name for `greeting: "full"`. '' when unknown.
|
|
166
|
+
*
|
|
167
|
+
* @return {string}
|
|
168
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
169
|
+
*/
|
|
170
|
+
greetingName() {
|
|
171
|
+
let user;
|
|
172
|
+
try {
|
|
173
|
+
user = getCurrentUser();
|
|
174
|
+
} catch {
|
|
175
|
+
user = null;
|
|
176
|
+
}
|
|
177
|
+
const displayName = (user && typeof user.displayName === 'string') ? user.displayName.trim() : '';
|
|
178
|
+
if (displayName === '') {
|
|
179
|
+
return ''
|
|
180
|
+
}
|
|
181
|
+
return this.content.greeting === 'full' ? displayName : displayName.split(/\s+/)[0]
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The greeting for the time of day, with the user's name when known,
|
|
186
|
+
* or '' when the greeting is off.
|
|
187
|
+
*
|
|
188
|
+
* @return {string}
|
|
189
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
190
|
+
*/
|
|
191
|
+
greetingText() {
|
|
192
|
+
const greeting = this.content && this.content.greeting;
|
|
193
|
+
if (greeting !== true && greeting !== 'full' && greeting !== 'first') {
|
|
194
|
+
return ''
|
|
195
|
+
}
|
|
196
|
+
const hour = this.reference.getHours();
|
|
197
|
+
const name = this.greetingName;
|
|
198
|
+
if (hour < 12) {
|
|
199
|
+
return name ? translate('nextcloud-vue', 'Good morning, {name}', { name }) : translate('nextcloud-vue', 'Good morning')
|
|
200
|
+
}
|
|
201
|
+
if (hour < 18) {
|
|
202
|
+
return name ? translate('nextcloud-vue', 'Good afternoon, {name}', { name }) : translate('nextcloud-vue', 'Good afternoon')
|
|
203
|
+
}
|
|
204
|
+
return name ? translate('nextcloud-vue', 'Good evening, {name}', { name }) : translate('nextcloud-vue', 'Good evening')
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The heading: the greeting when it is on, else the title.
|
|
209
|
+
*
|
|
210
|
+
* @return {string}
|
|
211
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
212
|
+
*/
|
|
213
|
+
headingText() {
|
|
214
|
+
return this.greetingText || this.title
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Today's date written out ("Monday 5 October 2026"), or '' when
|
|
219
|
+
* `showDate` is off.
|
|
220
|
+
*
|
|
221
|
+
* @return {string}
|
|
222
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
223
|
+
*/
|
|
224
|
+
dateLine() {
|
|
225
|
+
if (!this.content || this.content.showDate !== true) {
|
|
226
|
+
return ''
|
|
227
|
+
}
|
|
228
|
+
const options = { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' };
|
|
229
|
+
let locale;
|
|
230
|
+
try {
|
|
231
|
+
locale = getCanonicalLocale();
|
|
232
|
+
} catch {
|
|
233
|
+
locale = undefined;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
return new Intl.DateTimeFormat(locale || undefined, options).format(this.reference)
|
|
237
|
+
} catch {
|
|
238
|
+
return new Intl.DateTimeFormat(undefined, options).format(this.reference)
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Inline style of the date line: the heading's colour, or the muted
|
|
244
|
+
* text colour on a plain header.
|
|
245
|
+
*
|
|
246
|
+
* @return {object}
|
|
247
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
248
|
+
*/
|
|
249
|
+
dateStyle() {
|
|
250
|
+
return {
|
|
251
|
+
color: this.isPlain && !(this.content && this.content.textColor) ? 'var(--color-text-maxcontrast)' : this.textColor,
|
|
252
|
+
margin: 0,
|
|
253
|
+
}
|
|
119
254
|
},
|
|
120
255
|
|
|
121
256
|
/** Whether a non-empty subtitle is set. */
|
|
@@ -147,12 +282,19 @@ var script = {
|
|
|
147
282
|
return this.backgroundImageUrl !== '' && this.imageFailed === false
|
|
148
283
|
},
|
|
149
284
|
|
|
150
|
-
/**
|
|
285
|
+
/**
|
|
286
|
+
* Resolved background colour (default theme primary).
|
|
287
|
+
*
|
|
288
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
289
|
+
*/
|
|
151
290
|
backgroundColor() {
|
|
152
291
|
const value = this.content && this.content.backgroundColor;
|
|
153
292
|
if (typeof value === 'string' && value !== '') {
|
|
154
293
|
return value
|
|
155
294
|
}
|
|
295
|
+
if (this.isPlain) {
|
|
296
|
+
return 'transparent'
|
|
297
|
+
}
|
|
156
298
|
return 'var(--color-primary, #0070c0)'
|
|
157
299
|
},
|
|
158
300
|
|
|
@@ -210,13 +352,17 @@ var script = {
|
|
|
210
352
|
return HEIGHT_PIXELS[this.height]
|
|
211
353
|
},
|
|
212
354
|
|
|
213
|
-
/**
|
|
355
|
+
/**
|
|
356
|
+
* Resolved text alignment (default `center`).
|
|
357
|
+
*
|
|
358
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
359
|
+
*/
|
|
214
360
|
textAlign() {
|
|
215
361
|
const declared = this.content && this.content.textAlign;
|
|
216
362
|
if (typeof declared === 'string' && ALLOWED_TEXT_ALIGN.includes(declared)) {
|
|
217
363
|
return declared
|
|
218
364
|
}
|
|
219
|
-
return 'center'
|
|
365
|
+
return this.isPlain ? 'left' : 'center'
|
|
220
366
|
},
|
|
221
367
|
|
|
222
368
|
/** Resolved vertical alignment (default `middle`). */
|
|
@@ -228,7 +374,11 @@ var script = {
|
|
|
228
374
|
return 'middle'
|
|
229
375
|
},
|
|
230
376
|
|
|
231
|
-
/**
|
|
377
|
+
/**
|
|
378
|
+
* Resolved text colour with an auto-contrast fallback.
|
|
379
|
+
*
|
|
380
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
381
|
+
*/
|
|
232
382
|
textColor() {
|
|
233
383
|
const value = this.content && this.content.textColor;
|
|
234
384
|
if (typeof value === 'string' && value !== '') {
|
|
@@ -237,6 +387,9 @@ var script = {
|
|
|
237
387
|
if (this.hasBackgroundImage) {
|
|
238
388
|
return '#ffffff'
|
|
239
389
|
}
|
|
390
|
+
if (this.isPlain && !(this.content && this.content.backgroundColor)) {
|
|
391
|
+
return 'var(--color-main-text)'
|
|
392
|
+
}
|
|
240
393
|
return this.isLightColor(this.backgroundColor) ? '#000000' : '#ffffff'
|
|
241
394
|
},
|
|
242
395
|
|
|
@@ -281,7 +434,11 @@ var script = {
|
|
|
281
434
|
}
|
|
282
435
|
},
|
|
283
436
|
|
|
284
|
-
/**
|
|
437
|
+
/**
|
|
438
|
+
* Inline style for the content flex container.
|
|
439
|
+
*
|
|
440
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
441
|
+
*/
|
|
285
442
|
contentStyle() {
|
|
286
443
|
return {
|
|
287
444
|
position: 'relative',
|
|
@@ -292,7 +449,7 @@ var script = {
|
|
|
292
449
|
'flex-direction': 'column',
|
|
293
450
|
'align-items': this.flexAlignFromTextAlign,
|
|
294
451
|
'justify-content': VERTICAL_ALIGN_FLEX[this.verticalAlign],
|
|
295
|
-
padding: '16px 24px',
|
|
452
|
+
padding: this.isPlain ? '0' : '16px 24px',
|
|
296
453
|
'box-sizing': 'border-box',
|
|
297
454
|
gap: '8px',
|
|
298
455
|
'text-align': this.textAlign,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnHeaderWidget.vue2.js","sources":["../../../../src/components/CnHeaderWidget/CnHeaderWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-header-widget\" :style=\"wrapperStyle\">\n\t\t<div\n\t\t\tv-if=\"hasOverlay\"\n\t\t\tclass=\"cn-header-widget__overlay\"\n\t\t\t:style=\"overlayStyle\"\n\t\t\taria-hidden=\"true\" />\n\t\t<div class=\"cn-header-widget__content\" :style=\"contentStyle\">\n\t\t\t<h2 v-if=\"hasTitle\" class=\"cn-header-widget__title\" :style=\"textStyle\">\n\t\t\t\t{{ title }}\n\t\t\t</h2>\n\t\t\t<p v-if=\"hasSubtitle\" class=\"cn-header-widget__subtitle\" :style=\"textStyle\">\n\t\t\t\t{{ subtitle }}\n\t\t\t</p>\n\t\t\t<a\n\t\t\t\tv-if=\"hasCta\"\n\t\t\t\t:href=\"ctaUrl\"\n\t\t\t\t:class=\"ctaClasses\"\n\t\t\t\t:target=\"ctaTarget\"\n\t\t\t\t:rel=\"ctaRel\"\n\t\t\t\t:aria-label=\"ctaAriaLabel\">\n\t\t\t\t{{ ctaLabel }}\n\t\t\t</a>\n\t\t</div>\n\t\t<img\n\t\t\tv-if=\"hasBackgroundImage\"\n\t\t\tclass=\"cn-header-widget__probe\"\n\t\t\t:src=\"backgroundImageUrl\"\n\t\t\talt=\"\"\n\t\t\taria-hidden=\"true\"\n\t\t\t@error=\"onImageError\">\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { resolveImageUrl } from '../../utils/resolveImageUrl.js'\n\nconst ALLOWED_OVERLAY_MODES = ['none', 'tint', 'gradient-bottom']\nconst ALLOWED_HEIGHTS = ['small', 'medium', 'large', 'xlarge']\nconst ALLOWED_TEXT_ALIGN = ['left', 'center', 'right']\nconst ALLOWED_VERTICAL_ALIGN = ['top', 'middle', 'bottom']\nconst ALLOWED_CTA_STYLES = ['primary', 'secondary', 'ghost']\n\nconst HEIGHT_PIXELS = Object.freeze({\n\tsmall: 120,\n\tmedium: 200,\n\tlarge: 320,\n\txlarge: 480,\n})\n\nconst VERTICAL_ALIGN_FLEX = Object.freeze({\n\ttop: 'flex-start',\n\tmiddle: 'center',\n\tbottom: 'flex-end',\n})\n\n/**\n * CnHeaderWidget — full-width banner widget for dashboards. Renders a\n * configurable header with title, optional subtitle, optional background\n * image, optional colour overlay, and optional call-to-action button.\n *\n * Image source precedence: `backgroundImageFileId` takes priority over\n * `backgroundImageUrl` (resolved via the Nextcloud core preview route). When\n * the image fails to load the renderer silently falls back to the solid\n * `backgroundColor`; no error UI is shown.\n *\n * Height presets map to fixed pixels: small=120, medium=200, large=320,\n * xlarge=480. Title is `<h2>`, subtitle `<p>`, CTA `<a>` — all semantic HTML.\n * External CTA URLs (http/https) open in a new tab with\n * `rel=\"noopener noreferrer\"`; relative / anchor URLs stay in the current tab.\n *\n * Registered as the `header` dashboard widget type via the renderer's\n * `index.js`.\n */\nexport default {\n\tname: 'CnHeaderWidget',\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 `content.title` / `content.subtitle` / CTA label\n\t\t * are run through it. Defaults to an identity function so an\n\t\t * untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted widget content: `{title, subtitle, backgroundImageUrl,\n\t\t * backgroundImageFileId, backgroundColor, overlayMode, overlayColor,\n\t\t * overlayOpacity, textColor, textAlign, verticalAlign, height, cta}`.\n\t\t * All fields are optional except `title`; unknown enum values collapse\n\t\t * to documented defaults and the renderer never throws.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/* eslint-disable vue/no-unused-properties -- part of the widget contract: CnContainerChild and the dashboard pages bind :placement on every widget, so declaring it keeps it out of $attrs */\n\t\t/**\n\t\t * Placement entity — reserved to match the renderer contract.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tplacement: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t\t/* eslint-enable vue/no-unused-properties */\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Set true once the background image fires a DOM `error` so\n\t\t\t// subsequent renders fall back to `backgroundColor` only. The\n\t\t\t// probe `<img>` is invisible — its only job is load-failure\n\t\t\t// detection for the CSS background.\n\t\t\timageFailed: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the injected `cnTranslate` (the host\n\t\t * app's bound `t()`), identity by default.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t},\n\n\t\t/** The header title, translated, or '' when absent. */\n\t\ttitle() {\n\t\t\tconst value = this.content && this.content.title\n\t\t\treturn typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''\n\t\t},\n\n\t\t/** The header subtitle, translated, or '' when absent. */\n\t\tsubtitle() {\n\t\t\tconst value = this.content && this.content.subtitle\n\t\t\treturn typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''\n\t\t},\n\n\t\t/** Whether a non-empty title is set. */\n\t\thasTitle() {\n\t\t\treturn this.title !== ''\n\t\t},\n\n\t\t/** Whether a non-empty subtitle is set. */\n\t\thasSubtitle() {\n\t\t\treturn this.subtitle !== ''\n\t\t},\n\n\t\t/** Resolved background image URL (file id wins over URL). */\n\t\tbackgroundImageUrl() {\n\t\t\tconst fileId = this.content && this.content.backgroundImageFileId\n\t\t\tif (typeof fileId === 'number' && Number.isFinite(fileId) && fileId > 0) {\n\t\t\t\treturn `/index.php/core/preview?fileId=${fileId}&x=1024&y=512&a=1`\n\t\t\t}\n\t\t\tconst url = this.content && this.content.backgroundImageUrl\n\t\t\t// Accept http(s) plus uploaded/same-origin sources: data: and blob:\n\t\t\t// (from the form's upload) and root-relative paths (NC files/preview).\n\t\t\t// External http(s) images may still be blocked by the instance CSP —\n\t\t\t// uploading is the reliable, same-origin path. resolveImageUrl() adds\n\t\t\t// the webroot + /index.php to `/apps/...` resource paths so they load\n\t\t\t// on index.php-routed instances (other shapes pass through).\n\t\t\tif (typeof url === 'string' && /^(https?:\\/\\/|data:|blob:|\\/)/i.test(url)) {\n\t\t\t\treturn resolveImageUrl(url)\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** Whether a usable, non-errored background image is present. */\n\t\thasBackgroundImage() {\n\t\t\treturn this.backgroundImageUrl !== '' && this.imageFailed === false\n\t\t},\n\n\t\t/** Resolved background colour (default theme primary). */\n\t\tbackgroundColor() {\n\t\t\tconst value = this.content && this.content.backgroundColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\treturn 'var(--color-primary, #0070c0)'\n\t\t},\n\n\t\t/** Resolved overlay mode (`tint` when an image is present, else `none`). */\n\t\toverlayMode() {\n\t\t\tconst declared = this.content && this.content.overlayMode\n\t\t\tif (typeof declared === 'string' && ALLOWED_OVERLAY_MODES.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn this.hasBackgroundImage ? 'tint' : 'none'\n\t\t},\n\n\t\t/** Whether an overlay should render. */\n\t\thasOverlay() {\n\t\t\treturn this.overlayMode !== 'none'\n\t\t},\n\n\t\t/** Resolved overlay colour. */\n\t\toverlayColor() {\n\t\t\tconst value = this.content && this.content.overlayColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tconst bg = this.content && this.content.backgroundColor\n\t\t\treturn (typeof bg === 'string' && bg !== '') ? bg : '#000000'\n\t\t},\n\n\t\t/** Resolved overlay opacity, clamped to 0..1 (default 0.4). */\n\t\toverlayOpacity() {\n\t\t\tconst raw = this.content && this.content.overlayOpacity\n\t\t\tconst value = typeof raw === 'number' ? raw : Number.parseFloat(raw)\n\t\t\tif (Number.isFinite(value) === false) {\n\t\t\t\treturn 0.4\n\t\t\t}\n\t\t\tif (value < 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (value > 1) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/** Resolved height preset key (default `medium`). */\n\t\theight() {\n\t\t\tconst declared = this.content && this.content.height\n\t\t\tif (typeof declared === 'string' && ALLOWED_HEIGHTS.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'medium'\n\t\t},\n\n\t\t/** The pixel height for the active preset. */\n\t\theightPixels() {\n\t\t\treturn HEIGHT_PIXELS[this.height]\n\t\t},\n\n\t\t/** Resolved text alignment (default `center`). */\n\t\ttextAlign() {\n\t\t\tconst declared = this.content && this.content.textAlign\n\t\t\tif (typeof declared === 'string' && ALLOWED_TEXT_ALIGN.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'center'\n\t\t},\n\n\t\t/** Resolved vertical alignment (default `middle`). */\n\t\tverticalAlign() {\n\t\t\tconst declared = this.content && this.content.verticalAlign\n\t\t\tif (typeof declared === 'string' && ALLOWED_VERTICAL_ALIGN.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'middle'\n\t\t},\n\n\t\t/** Resolved text colour with an auto-contrast fallback. */\n\t\ttextColor() {\n\t\t\tconst value = this.content && this.content.textColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tif (this.hasBackgroundImage) {\n\t\t\t\treturn '#ffffff'\n\t\t\t}\n\t\t\treturn this.isLightColor(this.backgroundColor) ? '#000000' : '#ffffff'\n\t\t},\n\n\t\t/** Inline style for the banner wrapper. */\n\t\twrapperStyle() {\n\t\t\tconst style = {\n\t\t\t\tposition: 'relative',\n\t\t\t\twidth: '100%',\n\t\t\t\t// Fill the dashboard grid cell so the banner resizes with the\n\t\t\t\t// widget. A fixed pixel height ignored the cell, which made the\n\t\t\t\t// banner un-resizable and made it overflow/scroll inside a\n\t\t\t\t// smaller cell. The grid controls the size now.\n\t\t\t\theight: '100%',\n\t\t\t\toverflow: 'hidden',\n\t\t\t\t'background-color': this.backgroundColor,\n\t\t\t}\n\t\t\tif (this.hasBackgroundImage) {\n\t\t\t\tstyle['background-image'] = `url(\"${this.backgroundImageUrl}\")`\n\t\t\t\tstyle['background-size'] = 'cover'\n\t\t\t\tstyle['background-position'] = 'center center'\n\t\t\t\tstyle['background-repeat'] = 'no-repeat'\n\t\t\t}\n\t\t\treturn style\n\t\t},\n\n\t\t/** Inline style for the overlay layer. */\n\t\toverlayStyle() {\n\t\t\tif (this.overlayMode === 'gradient-bottom') {\n\t\t\t\treturn {\n\t\t\t\t\tposition: 'absolute',\n\t\t\t\t\tinset: 0,\n\t\t\t\t\t'background-image': `linear-gradient(to bottom, transparent 50%, ${this.overlayColor} 100%)`,\n\t\t\t\t\t'pointer-events': 'none',\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tposition: 'absolute',\n\t\t\t\tinset: 0,\n\t\t\t\t'background-color': this.overlayColor,\n\t\t\t\topacity: String(this.overlayOpacity),\n\t\t\t\t'pointer-events': 'none',\n\t\t\t}\n\t\t},\n\n\t\t/** Inline style for the content flex container. */\n\t\tcontentStyle() {\n\t\t\treturn {\n\t\t\t\tposition: 'relative',\n\t\t\t\t'z-index': 1,\n\t\t\t\twidth: '100%',\n\t\t\t\theight: '100%',\n\t\t\t\tdisplay: 'flex',\n\t\t\t\t'flex-direction': 'column',\n\t\t\t\t'align-items': this.flexAlignFromTextAlign,\n\t\t\t\t'justify-content': VERTICAL_ALIGN_FLEX[this.verticalAlign],\n\t\t\t\tpadding: '16px 24px',\n\t\t\t\t'box-sizing': 'border-box',\n\t\t\t\tgap: '8px',\n\t\t\t\t'text-align': this.textAlign,\n\t\t\t}\n\t\t},\n\n\t\t/** Flex cross-axis alignment derived from the text alignment. */\n\t\tflexAlignFromTextAlign() {\n\t\t\tif (this.textAlign === 'left') {\n\t\t\t\treturn 'flex-start'\n\t\t\t}\n\t\t\tif (this.textAlign === 'right') {\n\t\t\t\treturn 'flex-end'\n\t\t\t}\n\t\t\treturn 'center'\n\t\t},\n\n\t\t/** Inline style applying the resolved text colour. */\n\t\ttextStyle() {\n\t\t\treturn {\n\t\t\t\tcolor: this.textColor,\n\t\t\t\tmargin: 0,\n\t\t\t}\n\t\t},\n\n\t\t/** The validated CTA object, or null when incomplete. */\n\t\tcta() {\n\t\t\tconst raw = this.content && this.content.cta\n\t\t\tif (raw === null || typeof raw !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = typeof raw.label === 'string' ? raw.label.trim() : ''\n\t\t\tconst url = typeof raw.url === 'string' ? raw.url.trim() : ''\n\t\t\tif (label === '' || url === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tlet style = raw.style\n\t\t\tif (typeof style !== 'string' || ALLOWED_CTA_STYLES.includes(style) === false) {\n\t\t\t\tstyle = 'primary'\n\t\t\t}\n\t\t\treturn { label, url, style }\n\t\t},\n\n\t\t/** Whether a complete CTA is present. */\n\t\thasCta() {\n\t\t\treturn this.cta !== null\n\t\t},\n\n\t\t/** The CTA label, translated. */\n\t\tctaLabel() {\n\t\t\treturn this.hasCta && this.cta.label ? this.effectiveTranslate(this.cta.label) : ''\n\t\t},\n\n\t\t/** The CTA URL. */\n\t\tctaUrl() {\n\t\t\treturn this.hasCta ? this.cta.url : ''\n\t\t},\n\n\t\t/** Whether the CTA URL is an external http(s) link. */\n\t\tctaIsExternal() {\n\t\t\treturn this.hasCta && /^https?:\\/\\//i.test(this.ctaUrl)\n\t\t},\n\n\t\t/** The CTA anchor target (`_blank` for external links). */\n\t\tctaTarget() {\n\t\t\treturn this.ctaIsExternal ? '_blank' : null\n\t\t},\n\n\t\t/** The CTA anchor rel for external links. */\n\t\tctaRel() {\n\t\t\treturn this.ctaIsExternal ? 'noopener noreferrer' : null\n\t\t},\n\n\t\t/** The accessible CTA label, annotating external links. */\n\t\tctaAriaLabel() {\n\t\t\tif (this.hasCta === false) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (this.ctaIsExternal) {\n\t\t\t\treturn `${this.ctaLabel} (${t('nextcloud-vue', 'opens in new tab')})`\n\t\t\t}\n\t\t\treturn this.ctaLabel\n\t\t},\n\n\t\t/** The CTA class list (base + style modifier). */\n\t\tctaClasses() {\n\t\t\tif (this.hasCta === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t'cn-header-widget__cta',\n\t\t\t\t`cn-header-widget__cta--${this.cta.style}`,\n\t\t\t]\n\t\t},\n\t},\n\n\twatch: {\n\t\tbackgroundImageUrl() {\n\t\t\t// Re-arm the probe so a previously failing URL can recover when\n\t\t\t// the user edits the placement.\n\t\t\tthis.imageFailed = false\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Mark the background image as broken so the renderer falls back to\n\t\t * the solid colour.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonImageError() {\n\t\t\tthis.imageFailed = true\n\t\t},\n\n\t\t/**\n\t\t * Quick perceived-luminance check on a CSS hex colour. Returns true\n\t\t * for \"light\" (white text would be illegible). Non-hex colours\n\t\t * collapse to false (assume dark) — the safer default.\n\t\t *\n\t\t * @param {string} color the CSS colour value.\n\t\t * @return {boolean} true when the colour is perceived as light.\n\t\t */\n\t\tisLightColor(color) {\n\t\t\tif (typeof color !== 'string') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst match = color.trim().match(/^#([0-9a-f]{6})$/i)\n\t\t\tif (!match) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst hex = match[1]\n\t\t\tconst r = Number.parseInt(hex.slice(0, 2), 16)\n\t\t\tconst g = Number.parseInt(hex.slice(2, 4), 16)\n\t\t\tconst b = Number.parseInt(hex.slice(4, 6), 16)\n\t\t\tconst luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n\t\t\treturn luma > 0.6\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-header-widget {\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 120px;\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n.cn-header-widget__overlay {\n\tposition: absolute;\n\tinset: 0;\n}\n\n.cn-header-widget__content {\n\tposition: relative;\n\tz-index: 1;\n}\n\n.cn-header-widget__title {\n\tfont-size: 28px;\n\tfont-weight: 700;\n\tline-height: 1.2;\n\tmargin: 0;\n}\n\n.cn-header-widget__subtitle {\n\tfont-size: 16px;\n\tline-height: 1.4;\n\tmargin: 0;\n\topacity: 0.95;\n}\n\n.cn-header-widget__cta {\n\tdisplay: inline-block;\n\tmargin-top: 8px;\n\tpadding: 10px 18px;\n\tborder-radius: var(--border-radius, 4px);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttext-decoration: none;\n\tcursor: pointer;\n\ttransition: transform 0.15s ease, box-shadow 0.15s ease;\n}\n\n.cn-header-widget__cta:hover {\n\ttransform: translateY(-1px);\n\tbox-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n}\n\n.cn-header-widget__cta--primary {\n\tbackground-color: var(--color-primary, #0070c0);\n\tcolor: var(--color-primary-text, #ffffff);\n\tborder: 1px solid var(--color-primary, #0070c0);\n}\n\n.cn-header-widget__cta--secondary {\n\tbackground-color: var(--color-background-hover, rgba(255, 255, 255, 0.85));\n\tcolor: var(--color-main-text, #000000);\n\tborder: 1px solid var(--color-border, #cccccc);\n}\n\n.cn-header-widget__cta--ghost {\n\tbackground-color: transparent;\n\tcolor: inherit;\n\tborder: 1px solid currentColor;\n}\n\n.cn-header-widget__probe {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\topacity: 0;\n\tpointer-events: none;\n\tleft: -9999px;\n\ttop: -9999px;\n}\n\n@media print {\n\t.cn-header-widget {\n\t\tprint-color-adjust: exact;\n\t\t-webkit-print-color-adjust: exact;\n\t}\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnHeaderWidget.vue2.js","sources":["../../../../src/components/CnHeaderWidget/CnHeaderWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-header-widget\" :class=\"{ 'cn-header-widget--plain': isPlain }\" :style=\"wrapperStyle\">\n\t\t<div\n\t\t\tv-if=\"hasOverlay\"\n\t\t\tclass=\"cn-header-widget__overlay\"\n\t\t\t:style=\"overlayStyle\"\n\t\t\taria-hidden=\"true\" />\n\t\t<div class=\"cn-header-widget__content\" :style=\"contentStyle\">\n\t\t\t<p\n\t\t\t\tv-if=\"dateLine\"\n\t\t\t\tclass=\"cn-header-widget__date\"\n\t\t\t\t:style=\"dateStyle\"\n\t\t\t\tdata-testid=\"cn-header-widget-date\">\n\t\t\t\t{{ dateLine }}\n\t\t\t</p>\n\t\t\t<h2 v-if=\"hasTitle\" class=\"cn-header-widget__title\" :style=\"textStyle\">\n\t\t\t\t{{ headingText }}\n\t\t\t</h2>\n\t\t\t<p v-if=\"hasSubtitle\" class=\"cn-header-widget__subtitle\" :style=\"textStyle\">\n\t\t\t\t{{ subtitle }}\n\t\t\t</p>\n\t\t\t<a\n\t\t\t\tv-if=\"hasCta\"\n\t\t\t\t:href=\"ctaUrl\"\n\t\t\t\t:class=\"ctaClasses\"\n\t\t\t\t:target=\"ctaTarget\"\n\t\t\t\t:rel=\"ctaRel\"\n\t\t\t\t:aria-label=\"ctaAriaLabel\">\n\t\t\t\t{{ ctaLabel }}\n\t\t\t</a>\n\t\t</div>\n\t\t<img\n\t\t\tv-if=\"hasBackgroundImage\"\n\t\t\tclass=\"cn-header-widget__probe\"\n\t\t\t:src=\"backgroundImageUrl\"\n\t\t\talt=\"\"\n\t\t\taria-hidden=\"true\"\n\t\t\t@error=\"onImageError\">\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport { getCanonicalLocale, translate as t } from '@nextcloud/l10n'\nimport { resolveImageUrl } from '../../utils/resolveImageUrl.js'\n\nconst ALLOWED_OVERLAY_MODES = ['none', 'tint', 'gradient-bottom']\nconst ALLOWED_HEIGHTS = ['small', 'medium', 'large', 'xlarge']\nconst ALLOWED_TEXT_ALIGN = ['left', 'center', 'right']\nconst ALLOWED_VERTICAL_ALIGN = ['top', 'middle', 'bottom']\nconst ALLOWED_CTA_STYLES = ['primary', 'secondary', 'ghost']\n\nconst HEIGHT_PIXELS = Object.freeze({\n\tsmall: 120,\n\tmedium: 200,\n\tlarge: 320,\n\txlarge: 480,\n})\n\nconst VERTICAL_ALIGN_FLEX = Object.freeze({\n\ttop: 'flex-start',\n\tmiddle: 'center',\n\tbottom: 'flex-end',\n})\n\n/**\n * CnHeaderWidget — full-width banner widget for dashboards. Renders a\n * configurable header with title, optional subtitle, optional background\n * image, optional colour overlay, and optional call-to-action button.\n *\n * Image source precedence: `backgroundImageFileId` takes priority over\n * `backgroundImageUrl` (resolved via the Nextcloud core preview route). When\n * the image fails to load the renderer silently falls back to the solid\n * `backgroundColor`; no error UI is shown.\n *\n * Height presets map to fixed pixels: small=120, medium=200, large=320,\n * xlarge=480. Title is `<h2>`, subtitle `<p>`, CTA `<a>` — all semantic HTML.\n * External CTA URLs (http/https) open in a new tab with\n * `rel=\"noopener noreferrer\"`; relative / anchor URLs stay in the current tab.\n *\n * Optional greeting: `greeting: true` makes the heading greet the signed-in\n * user by time of day and first name (\"Good afternoon, Pieter\"; `\"full\"` uses\n * the whole display name), and `showDate: true` adds a line with today's\n * date above it. `plain: true` drops the coloured background and aligns the\n * text to the start, which is how a greeting usually sits on a dashboard.\n *\n * ```json\n * { \"widgetKey\": \"header\", \"props\": { \"content\": { \"greeting\": true, \"showDate\": true, \"plain\": true } } }\n * ```\n *\n * Registered as the `header` dashboard widget type via the renderer's\n * `index.js`.\n */\nexport default {\n\tname: 'CnHeaderWidget',\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 `content.title` / `content.subtitle` / CTA label\n\t\t * are run through it. Defaults to an identity function so an\n\t\t * untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted widget content: `{title, subtitle, backgroundImageUrl,\n\t\t * backgroundImageFileId, backgroundColor, overlayMode, overlayColor,\n\t\t * overlayOpacity, textColor, textAlign, verticalAlign, height, cta,\n\t\t * greeting, showDate, plain}`. All fields are optional except `title`\n\t\t * (or `greeting`); unknown enum values collapse to documented defaults\n\t\t * and the renderer never throws.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/* eslint-disable vue/no-unused-properties -- part of the widget contract: CnContainerChild and the dashboard pages bind :placement on every widget, so declaring it keeps it out of $attrs */\n\t\t/**\n\t\t * Placement entity — reserved to match the renderer contract.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tplacement: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t\t/* eslint-enable vue/no-unused-properties */\n\n\t\t/**\n\t\t * The moment the greeting and the date line are computed for. Leave\n\t\t * empty for the current time.\n\t\t *\n\t\t * @type {Date|null}\n\t\t */\n\t\tnow: {\n\t\t\ttype: Date,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Set true once the background image fires a DOM `error` so\n\t\t\t// subsequent renders fall back to `backgroundColor` only. The\n\t\t\t// probe `<img>` is invisible — its only job is load-failure\n\t\t\t// detection for the CSS background.\n\t\t\timageFailed: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the injected `cnTranslate` (the host\n\t\t * app's bound `t()`), identity by default.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t},\n\n\t\t/** The header title, translated, or '' when absent. */\n\t\ttitle() {\n\t\t\tconst value = this.content && this.content.title\n\t\t\treturn typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''\n\t\t},\n\n\t\t/** The header subtitle, translated, or '' when absent. */\n\t\tsubtitle() {\n\t\t\tconst value = this.content && this.content.subtitle\n\t\t\treturn typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''\n\t\t},\n\n\t\t/** Whether a heading renders: a non-empty title or a greeting. */\n\t\thasTitle() {\n\t\t\treturn this.headingText !== ''\n\t\t},\n\n\t\t/**\n\t\t * The moment the greeting and date line are computed for.\n\t\t *\n\t\t * @return {Date}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\treference() {\n\t\t\treturn this.now instanceof Date ? this.now : new Date()\n\t\t},\n\n\t\t/**\n\t\t * Whether the plain presentation is on (no coloured background).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisPlain() {\n\t\t\treturn Boolean(this.content && this.content.plain === true)\n\t\t},\n\n\t\t/**\n\t\t * The name the greeting uses: the first word of the display name, or\n\t\t * the whole display name for `greeting: \"full\"`. '' when unknown.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tgreetingName() {\n\t\t\tlet user\n\t\t\ttry {\n\t\t\t\tuser = getCurrentUser()\n\t\t\t} catch {\n\t\t\t\tuser = null\n\t\t\t}\n\t\t\tconst displayName = (user && typeof user.displayName === 'string') ? user.displayName.trim() : ''\n\t\t\tif (displayName === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.content.greeting === 'full' ? displayName : displayName.split(/\\s+/)[0]\n\t\t},\n\n\t\t/**\n\t\t * The greeting for the time of day, with the user's name when known,\n\t\t * or '' when the greeting is off.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tgreetingText() {\n\t\t\tconst greeting = this.content && this.content.greeting\n\t\t\tif (greeting !== true && greeting !== 'full' && greeting !== 'first') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst hour = this.reference.getHours()\n\t\t\tconst name = this.greetingName\n\t\t\tif (hour < 12) {\n\t\t\t\treturn name ? t('nextcloud-vue', 'Good morning, {name}', { name }) : t('nextcloud-vue', 'Good morning')\n\t\t\t}\n\t\t\tif (hour < 18) {\n\t\t\t\treturn name ? t('nextcloud-vue', 'Good afternoon, {name}', { name }) : t('nextcloud-vue', 'Good afternoon')\n\t\t\t}\n\t\t\treturn name ? t('nextcloud-vue', 'Good evening, {name}', { name }) : t('nextcloud-vue', 'Good evening')\n\t\t},\n\n\t\t/**\n\t\t * The heading: the greeting when it is on, else the title.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\theadingText() {\n\t\t\treturn this.greetingText || this.title\n\t\t},\n\n\t\t/**\n\t\t * Today's date written out (\"Monday 5 October 2026\"), or '' when\n\t\t * `showDate` is off.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tdateLine() {\n\t\t\tif (!this.content || this.content.showDate !== true) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst options = { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }\n\t\t\tlet locale\n\t\t\ttry {\n\t\t\t\tlocale = getCanonicalLocale()\n\t\t\t} catch {\n\t\t\t\tlocale = undefined\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(locale || undefined, options).format(this.reference)\n\t\t\t} catch {\n\t\t\t\treturn new Intl.DateTimeFormat(undefined, options).format(this.reference)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Inline style of the date line: the heading's colour, or the muted\n\t\t * text colour on a plain header.\n\t\t *\n\t\t * @return {object}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tdateStyle() {\n\t\t\treturn {\n\t\t\t\tcolor: this.isPlain && !(this.content && this.content.textColor) ? 'var(--color-text-maxcontrast)' : this.textColor,\n\t\t\t\tmargin: 0,\n\t\t\t}\n\t\t},\n\n\t\t/** Whether a non-empty subtitle is set. */\n\t\thasSubtitle() {\n\t\t\treturn this.subtitle !== ''\n\t\t},\n\n\t\t/** Resolved background image URL (file id wins over URL). */\n\t\tbackgroundImageUrl() {\n\t\t\tconst fileId = this.content && this.content.backgroundImageFileId\n\t\t\tif (typeof fileId === 'number' && Number.isFinite(fileId) && fileId > 0) {\n\t\t\t\treturn `/index.php/core/preview?fileId=${fileId}&x=1024&y=512&a=1`\n\t\t\t}\n\t\t\tconst url = this.content && this.content.backgroundImageUrl\n\t\t\t// Accept http(s) plus uploaded/same-origin sources: data: and blob:\n\t\t\t// (from the form's upload) and root-relative paths (NC files/preview).\n\t\t\t// External http(s) images may still be blocked by the instance CSP —\n\t\t\t// uploading is the reliable, same-origin path. resolveImageUrl() adds\n\t\t\t// the webroot + /index.php to `/apps/...` resource paths so they load\n\t\t\t// on index.php-routed instances (other shapes pass through).\n\t\t\tif (typeof url === 'string' && /^(https?:\\/\\/|data:|blob:|\\/)/i.test(url)) {\n\t\t\t\treturn resolveImageUrl(url)\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** Whether a usable, non-errored background image is present. */\n\t\thasBackgroundImage() {\n\t\t\treturn this.backgroundImageUrl !== '' && this.imageFailed === false\n\t\t},\n\n\t\t/**\n\t\t * Resolved background colour (default theme primary).\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tbackgroundColor() {\n\t\t\tconst value = this.content && this.content.backgroundColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tif (this.isPlain) {\n\t\t\t\treturn 'transparent'\n\t\t\t}\n\t\t\treturn 'var(--color-primary, #0070c0)'\n\t\t},\n\n\t\t/** Resolved overlay mode (`tint` when an image is present, else `none`). */\n\t\toverlayMode() {\n\t\t\tconst declared = this.content && this.content.overlayMode\n\t\t\tif (typeof declared === 'string' && ALLOWED_OVERLAY_MODES.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn this.hasBackgroundImage ? 'tint' : 'none'\n\t\t},\n\n\t\t/** Whether an overlay should render. */\n\t\thasOverlay() {\n\t\t\treturn this.overlayMode !== 'none'\n\t\t},\n\n\t\t/** Resolved overlay colour. */\n\t\toverlayColor() {\n\t\t\tconst value = this.content && this.content.overlayColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tconst bg = this.content && this.content.backgroundColor\n\t\t\treturn (typeof bg === 'string' && bg !== '') ? bg : '#000000'\n\t\t},\n\n\t\t/** Resolved overlay opacity, clamped to 0..1 (default 0.4). */\n\t\toverlayOpacity() {\n\t\t\tconst raw = this.content && this.content.overlayOpacity\n\t\t\tconst value = typeof raw === 'number' ? raw : Number.parseFloat(raw)\n\t\t\tif (Number.isFinite(value) === false) {\n\t\t\t\treturn 0.4\n\t\t\t}\n\t\t\tif (value < 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (value > 1) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/** Resolved height preset key (default `medium`). */\n\t\theight() {\n\t\t\tconst declared = this.content && this.content.height\n\t\t\tif (typeof declared === 'string' && ALLOWED_HEIGHTS.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'medium'\n\t\t},\n\n\t\t/** The pixel height for the active preset. */\n\t\theightPixels() {\n\t\t\treturn HEIGHT_PIXELS[this.height]\n\t\t},\n\n\t\t/**\n\t\t * Resolved text alignment (default `center`).\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\ttextAlign() {\n\t\t\tconst declared = this.content && this.content.textAlign\n\t\t\tif (typeof declared === 'string' && ALLOWED_TEXT_ALIGN.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn this.isPlain ? 'left' : 'center'\n\t\t},\n\n\t\t/** Resolved vertical alignment (default `middle`). */\n\t\tverticalAlign() {\n\t\t\tconst declared = this.content && this.content.verticalAlign\n\t\t\tif (typeof declared === 'string' && ALLOWED_VERTICAL_ALIGN.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'middle'\n\t\t},\n\n\t\t/**\n\t\t * Resolved text colour with an auto-contrast fallback.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\ttextColor() {\n\t\t\tconst value = this.content && this.content.textColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tif (this.hasBackgroundImage) {\n\t\t\t\treturn '#ffffff'\n\t\t\t}\n\t\t\tif (this.isPlain && !(this.content && this.content.backgroundColor)) {\n\t\t\t\treturn 'var(--color-main-text)'\n\t\t\t}\n\t\t\treturn this.isLightColor(this.backgroundColor) ? '#000000' : '#ffffff'\n\t\t},\n\n\t\t/** Inline style for the banner wrapper. */\n\t\twrapperStyle() {\n\t\t\tconst style = {\n\t\t\t\tposition: 'relative',\n\t\t\t\twidth: '100%',\n\t\t\t\t// Fill the dashboard grid cell so the banner resizes with the\n\t\t\t\t// widget. A fixed pixel height ignored the cell, which made the\n\t\t\t\t// banner un-resizable and made it overflow/scroll inside a\n\t\t\t\t// smaller cell. The grid controls the size now.\n\t\t\t\theight: '100%',\n\t\t\t\toverflow: 'hidden',\n\t\t\t\t'background-color': this.backgroundColor,\n\t\t\t}\n\t\t\tif (this.hasBackgroundImage) {\n\t\t\t\tstyle['background-image'] = `url(\"${this.backgroundImageUrl}\")`\n\t\t\t\tstyle['background-size'] = 'cover'\n\t\t\t\tstyle['background-position'] = 'center center'\n\t\t\t\tstyle['background-repeat'] = 'no-repeat'\n\t\t\t}\n\t\t\treturn style\n\t\t},\n\n\t\t/** Inline style for the overlay layer. */\n\t\toverlayStyle() {\n\t\t\tif (this.overlayMode === 'gradient-bottom') {\n\t\t\t\treturn {\n\t\t\t\t\tposition: 'absolute',\n\t\t\t\t\tinset: 0,\n\t\t\t\t\t'background-image': `linear-gradient(to bottom, transparent 50%, ${this.overlayColor} 100%)`,\n\t\t\t\t\t'pointer-events': 'none',\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tposition: 'absolute',\n\t\t\t\tinset: 0,\n\t\t\t\t'background-color': this.overlayColor,\n\t\t\t\topacity: String(this.overlayOpacity),\n\t\t\t\t'pointer-events': 'none',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Inline style for the content flex container.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tcontentStyle() {\n\t\t\treturn {\n\t\t\t\tposition: 'relative',\n\t\t\t\t'z-index': 1,\n\t\t\t\twidth: '100%',\n\t\t\t\theight: '100%',\n\t\t\t\tdisplay: 'flex',\n\t\t\t\t'flex-direction': 'column',\n\t\t\t\t'align-items': this.flexAlignFromTextAlign,\n\t\t\t\t'justify-content': VERTICAL_ALIGN_FLEX[this.verticalAlign],\n\t\t\t\tpadding: this.isPlain ? '0' : '16px 24px',\n\t\t\t\t'box-sizing': 'border-box',\n\t\t\t\tgap: '8px',\n\t\t\t\t'text-align': this.textAlign,\n\t\t\t}\n\t\t},\n\n\t\t/** Flex cross-axis alignment derived from the text alignment. */\n\t\tflexAlignFromTextAlign() {\n\t\t\tif (this.textAlign === 'left') {\n\t\t\t\treturn 'flex-start'\n\t\t\t}\n\t\t\tif (this.textAlign === 'right') {\n\t\t\t\treturn 'flex-end'\n\t\t\t}\n\t\t\treturn 'center'\n\t\t},\n\n\t\t/** Inline style applying the resolved text colour. */\n\t\ttextStyle() {\n\t\t\treturn {\n\t\t\t\tcolor: this.textColor,\n\t\t\t\tmargin: 0,\n\t\t\t}\n\t\t},\n\n\t\t/** The validated CTA object, or null when incomplete. */\n\t\tcta() {\n\t\t\tconst raw = this.content && this.content.cta\n\t\t\tif (raw === null || typeof raw !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = typeof raw.label === 'string' ? raw.label.trim() : ''\n\t\t\tconst url = typeof raw.url === 'string' ? raw.url.trim() : ''\n\t\t\tif (label === '' || url === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tlet style = raw.style\n\t\t\tif (typeof style !== 'string' || ALLOWED_CTA_STYLES.includes(style) === false) {\n\t\t\t\tstyle = 'primary'\n\t\t\t}\n\t\t\treturn { label, url, style }\n\t\t},\n\n\t\t/** Whether a complete CTA is present. */\n\t\thasCta() {\n\t\t\treturn this.cta !== null\n\t\t},\n\n\t\t/** The CTA label, translated. */\n\t\tctaLabel() {\n\t\t\treturn this.hasCta && this.cta.label ? this.effectiveTranslate(this.cta.label) : ''\n\t\t},\n\n\t\t/** The CTA URL. */\n\t\tctaUrl() {\n\t\t\treturn this.hasCta ? this.cta.url : ''\n\t\t},\n\n\t\t/** Whether the CTA URL is an external http(s) link. */\n\t\tctaIsExternal() {\n\t\t\treturn this.hasCta && /^https?:\\/\\//i.test(this.ctaUrl)\n\t\t},\n\n\t\t/** The CTA anchor target (`_blank` for external links). */\n\t\tctaTarget() {\n\t\t\treturn this.ctaIsExternal ? '_blank' : null\n\t\t},\n\n\t\t/** The CTA anchor rel for external links. */\n\t\tctaRel() {\n\t\t\treturn this.ctaIsExternal ? 'noopener noreferrer' : null\n\t\t},\n\n\t\t/** The accessible CTA label, annotating external links. */\n\t\tctaAriaLabel() {\n\t\t\tif (this.hasCta === false) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (this.ctaIsExternal) {\n\t\t\t\treturn `${this.ctaLabel} (${t('nextcloud-vue', 'opens in new tab')})`\n\t\t\t}\n\t\t\treturn this.ctaLabel\n\t\t},\n\n\t\t/** The CTA class list (base + style modifier). */\n\t\tctaClasses() {\n\t\t\tif (this.hasCta === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t'cn-header-widget__cta',\n\t\t\t\t`cn-header-widget__cta--${this.cta.style}`,\n\t\t\t]\n\t\t},\n\t},\n\n\twatch: {\n\t\tbackgroundImageUrl() {\n\t\t\t// Re-arm the probe so a previously failing URL can recover when\n\t\t\t// the user edits the placement.\n\t\t\tthis.imageFailed = false\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Mark the background image as broken so the renderer falls back to\n\t\t * the solid colour.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonImageError() {\n\t\t\tthis.imageFailed = true\n\t\t},\n\n\t\t/**\n\t\t * Quick perceived-luminance check on a CSS hex colour. Returns true\n\t\t * for \"light\" (white text would be illegible). Non-hex colours\n\t\t * collapse to false (assume dark) — the safer default.\n\t\t *\n\t\t * @param {string} color the CSS colour value.\n\t\t * @return {boolean} true when the colour is perceived as light.\n\t\t */\n\t\tisLightColor(color) {\n\t\t\tif (typeof color !== 'string') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst match = color.trim().match(/^#([0-9a-f]{6})$/i)\n\t\t\tif (!match) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst hex = match[1]\n\t\t\tconst r = Number.parseInt(hex.slice(0, 2), 16)\n\t\t\tconst g = Number.parseInt(hex.slice(2, 4), 16)\n\t\t\tconst b = Number.parseInt(hex.slice(4, 6), 16)\n\t\t\tconst luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n\t\t\treturn luma > 0.6\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-header-widget {\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 120px;\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n.cn-header-widget--plain {\n\tmin-height: 0;\n\tborder-radius: 0;\n}\n\n.cn-header-widget__date {\n\tfont-size: 14px;\n\tline-height: 1.4;\n\tmargin: 0;\n}\n\n.cn-header-widget__overlay {\n\tposition: absolute;\n\tinset: 0;\n}\n\n.cn-header-widget__content {\n\tposition: relative;\n\tz-index: 1;\n}\n\n.cn-header-widget__title {\n\tfont-size: 28px;\n\tfont-weight: 700;\n\tline-height: 1.2;\n\tmargin: 0;\n}\n\n.cn-header-widget__subtitle {\n\tfont-size: 16px;\n\tline-height: 1.4;\n\tmargin: 0;\n\topacity: 0.95;\n}\n\n.cn-header-widget__cta {\n\tdisplay: inline-block;\n\tmargin-top: 8px;\n\tpadding: 10px 18px;\n\tborder-radius: var(--border-radius, 4px);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttext-decoration: none;\n\tcursor: pointer;\n\ttransition: transform 0.15s ease, box-shadow 0.15s ease;\n}\n\n.cn-header-widget__cta:hover {\n\ttransform: translateY(-1px);\n\tbox-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n}\n\n.cn-header-widget__cta--primary {\n\tbackground-color: var(--color-primary, #0070c0);\n\tcolor: var(--color-primary-text, #ffffff);\n\tborder: 1px solid var(--color-primary, #0070c0);\n}\n\n.cn-header-widget__cta--secondary {\n\tbackground-color: var(--color-background-hover, rgba(255, 255, 255, 0.85));\n\tcolor: var(--color-main-text, #000000);\n\tborder: 1px solid var(--color-border, #cccccc);\n}\n\n.cn-header-widget__cta--ghost {\n\tbackground-color: transparent;\n\tcolor: inherit;\n\tborder: 1px solid currentColor;\n}\n\n.cn-header-widget__probe {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\topacity: 0;\n\tpointer-events: none;\n\tleft: -9999px;\n\ttop: -9999px;\n}\n\n@media print {\n\t.cn-header-widget {\n\t\tprint-color-adjust: exact;\n\t\t-webkit-print-color-adjust: exact;\n\t}\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { openBlock, createElementBlock, normalizeStyle, createCommentVNode, createElementVNode, toDisplayString
|
|
1
|
+
import { openBlock, createElementBlock, normalizeClass, normalizeStyle, createCommentVNode, createElementVNode, toDisplayString } from 'vue';
|
|
2
2
|
|
|
3
3
|
const _hoisted_1 = ["href", "target", "rel", "aria-label"];
|
|
4
4
|
const _hoisted_2 = ["src"];
|
|
5
5
|
|
|
6
6
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
7
7
|
return (openBlock(), createElementBlock("div", {
|
|
8
|
-
class: "cn-header-widget",
|
|
8
|
+
class: normalizeClass(["cn-header-widget", { 'cn-header-widget--plain': $options.isPlain }]),
|
|
9
9
|
style: normalizeStyle($options.wrapperStyle)
|
|
10
10
|
}, [
|
|
11
11
|
($options.hasOverlay)
|
|
@@ -20,23 +20,31 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
20
20
|
class: "cn-header-widget__content",
|
|
21
21
|
style: normalizeStyle($options.contentStyle)
|
|
22
22
|
}, [
|
|
23
|
+
($options.dateLine)
|
|
24
|
+
? (openBlock(), createElementBlock("p", {
|
|
25
|
+
key: 0,
|
|
26
|
+
class: "cn-header-widget__date",
|
|
27
|
+
style: normalizeStyle($options.dateStyle),
|
|
28
|
+
"data-testid": "cn-header-widget-date"
|
|
29
|
+
}, toDisplayString($options.dateLine), 5 /* TEXT, STYLE */))
|
|
30
|
+
: createCommentVNode("v-if", true),
|
|
23
31
|
($options.hasTitle)
|
|
24
32
|
? (openBlock(), createElementBlock("h2", {
|
|
25
|
-
key:
|
|
33
|
+
key: 1,
|
|
26
34
|
class: "cn-header-widget__title",
|
|
27
35
|
style: normalizeStyle($options.textStyle)
|
|
28
|
-
}, toDisplayString($options.
|
|
36
|
+
}, toDisplayString($options.headingText), 5 /* TEXT, STYLE */))
|
|
29
37
|
: createCommentVNode("v-if", true),
|
|
30
38
|
($options.hasSubtitle)
|
|
31
39
|
? (openBlock(), createElementBlock("p", {
|
|
32
|
-
key:
|
|
40
|
+
key: 2,
|
|
33
41
|
class: "cn-header-widget__subtitle",
|
|
34
42
|
style: normalizeStyle($options.textStyle)
|
|
35
43
|
}, toDisplayString($options.subtitle), 5 /* TEXT, STYLE */))
|
|
36
44
|
: createCommentVNode("v-if", true),
|
|
37
45
|
($options.hasCta)
|
|
38
46
|
? (openBlock(), createElementBlock("a", {
|
|
39
|
-
key:
|
|
47
|
+
key: 3,
|
|
40
48
|
href: $options.ctaUrl,
|
|
41
49
|
class: normalizeClass($options.ctaClasses),
|
|
42
50
|
target: $options.ctaTarget,
|
|
@@ -55,7 +63,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
55
63
|
onError: _cache[0] || (_cache[0] = (...args) => ($options.onImageError && $options.onImageError(...args)))
|
|
56
64
|
}, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_2))
|
|
57
65
|
: createCommentVNode("v-if", true)
|
|
58
|
-
],
|
|
66
|
+
], 6 /* CLASS, STYLE */))
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
export { render };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnHeaderWidget.vue3.js","sources":["../../../../src/components/CnHeaderWidget/CnHeaderWidget.vue?vue&type=template&id=1b97430f&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-header-widget\" :style=\"wrapperStyle\">\n\t\t<div\n\t\t\tv-if=\"hasOverlay\"\n\t\t\tclass=\"cn-header-widget__overlay\"\n\t\t\t:style=\"overlayStyle\"\n\t\t\taria-hidden=\"true\" />\n\t\t<div class=\"cn-header-widget__content\" :style=\"contentStyle\">\n\t\t\t<h2 v-if=\"hasTitle\" class=\"cn-header-widget__title\" :style=\"textStyle\">\n\t\t\t\t{{ title }}\n\t\t\t</h2>\n\t\t\t<p v-if=\"hasSubtitle\" class=\"cn-header-widget__subtitle\" :style=\"textStyle\">\n\t\t\t\t{{ subtitle }}\n\t\t\t</p>\n\t\t\t<a\n\t\t\t\tv-if=\"hasCta\"\n\t\t\t\t:href=\"ctaUrl\"\n\t\t\t\t:class=\"ctaClasses\"\n\t\t\t\t:target=\"ctaTarget\"\n\t\t\t\t:rel=\"ctaRel\"\n\t\t\t\t:aria-label=\"ctaAriaLabel\">\n\t\t\t\t{{ ctaLabel }}\n\t\t\t</a>\n\t\t</div>\n\t\t<img\n\t\t\tv-if=\"hasBackgroundImage\"\n\t\t\tclass=\"cn-header-widget__probe\"\n\t\t\t:src=\"backgroundImageUrl\"\n\t\t\talt=\"\"\n\t\t\taria-hidden=\"true\"\n\t\t\t@error=\"onImageError\">\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { resolveImageUrl } from '../../utils/resolveImageUrl.js'\n\nconst ALLOWED_OVERLAY_MODES = ['none', 'tint', 'gradient-bottom']\nconst ALLOWED_HEIGHTS = ['small', 'medium', 'large', 'xlarge']\nconst ALLOWED_TEXT_ALIGN = ['left', 'center', 'right']\nconst ALLOWED_VERTICAL_ALIGN = ['top', 'middle', 'bottom']\nconst ALLOWED_CTA_STYLES = ['primary', 'secondary', 'ghost']\n\nconst HEIGHT_PIXELS = Object.freeze({\n\tsmall: 120,\n\tmedium: 200,\n\tlarge: 320,\n\txlarge: 480,\n})\n\nconst VERTICAL_ALIGN_FLEX = Object.freeze({\n\ttop: 'flex-start',\n\tmiddle: 'center',\n\tbottom: 'flex-end',\n})\n\n/**\n * CnHeaderWidget — full-width banner widget for dashboards. Renders a\n * configurable header with title, optional subtitle, optional background\n * image, optional colour overlay, and optional call-to-action button.\n *\n * Image source precedence: `backgroundImageFileId` takes priority over\n * `backgroundImageUrl` (resolved via the Nextcloud core preview route). When\n * the image fails to load the renderer silently falls back to the solid\n * `backgroundColor`; no error UI is shown.\n *\n * Height presets map to fixed pixels: small=120, medium=200, large=320,\n * xlarge=480. Title is `<h2>`, subtitle `<p>`, CTA `<a>` — all semantic HTML.\n * External CTA URLs (http/https) open in a new tab with\n * `rel=\"noopener noreferrer\"`; relative / anchor URLs stay in the current tab.\n *\n * Registered as the `header` dashboard widget type via the renderer's\n * `index.js`.\n */\nexport default {\n\tname: 'CnHeaderWidget',\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 `content.title` / `content.subtitle` / CTA label\n\t\t * are run through it. Defaults to an identity function so an\n\t\t * untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted widget content: `{title, subtitle, backgroundImageUrl,\n\t\t * backgroundImageFileId, backgroundColor, overlayMode, overlayColor,\n\t\t * overlayOpacity, textColor, textAlign, verticalAlign, height, cta}`.\n\t\t * All fields are optional except `title`; unknown enum values collapse\n\t\t * to documented defaults and the renderer never throws.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/* eslint-disable vue/no-unused-properties -- part of the widget contract: CnContainerChild and the dashboard pages bind :placement on every widget, so declaring it keeps it out of $attrs */\n\t\t/**\n\t\t * Placement entity — reserved to match the renderer contract.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tplacement: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t\t/* eslint-enable vue/no-unused-properties */\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Set true once the background image fires a DOM `error` so\n\t\t\t// subsequent renders fall back to `backgroundColor` only. The\n\t\t\t// probe `<img>` is invisible — its only job is load-failure\n\t\t\t// detection for the CSS background.\n\t\t\timageFailed: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the injected `cnTranslate` (the host\n\t\t * app's bound `t()`), identity by default.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t},\n\n\t\t/** The header title, translated, or '' when absent. */\n\t\ttitle() {\n\t\t\tconst value = this.content && this.content.title\n\t\t\treturn typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''\n\t\t},\n\n\t\t/** The header subtitle, translated, or '' when absent. */\n\t\tsubtitle() {\n\t\t\tconst value = this.content && this.content.subtitle\n\t\t\treturn typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''\n\t\t},\n\n\t\t/** Whether a non-empty title is set. */\n\t\thasTitle() {\n\t\t\treturn this.title !== ''\n\t\t},\n\n\t\t/** Whether a non-empty subtitle is set. */\n\t\thasSubtitle() {\n\t\t\treturn this.subtitle !== ''\n\t\t},\n\n\t\t/** Resolved background image URL (file id wins over URL). */\n\t\tbackgroundImageUrl() {\n\t\t\tconst fileId = this.content && this.content.backgroundImageFileId\n\t\t\tif (typeof fileId === 'number' && Number.isFinite(fileId) && fileId > 0) {\n\t\t\t\treturn `/index.php/core/preview?fileId=${fileId}&x=1024&y=512&a=1`\n\t\t\t}\n\t\t\tconst url = this.content && this.content.backgroundImageUrl\n\t\t\t// Accept http(s) plus uploaded/same-origin sources: data: and blob:\n\t\t\t// (from the form's upload) and root-relative paths (NC files/preview).\n\t\t\t// External http(s) images may still be blocked by the instance CSP —\n\t\t\t// uploading is the reliable, same-origin path. resolveImageUrl() adds\n\t\t\t// the webroot + /index.php to `/apps/...` resource paths so they load\n\t\t\t// on index.php-routed instances (other shapes pass through).\n\t\t\tif (typeof url === 'string' && /^(https?:\\/\\/|data:|blob:|\\/)/i.test(url)) {\n\t\t\t\treturn resolveImageUrl(url)\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** Whether a usable, non-errored background image is present. */\n\t\thasBackgroundImage() {\n\t\t\treturn this.backgroundImageUrl !== '' && this.imageFailed === false\n\t\t},\n\n\t\t/** Resolved background colour (default theme primary). */\n\t\tbackgroundColor() {\n\t\t\tconst value = this.content && this.content.backgroundColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\treturn 'var(--color-primary, #0070c0)'\n\t\t},\n\n\t\t/** Resolved overlay mode (`tint` when an image is present, else `none`). */\n\t\toverlayMode() {\n\t\t\tconst declared = this.content && this.content.overlayMode\n\t\t\tif (typeof declared === 'string' && ALLOWED_OVERLAY_MODES.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn this.hasBackgroundImage ? 'tint' : 'none'\n\t\t},\n\n\t\t/** Whether an overlay should render. */\n\t\thasOverlay() {\n\t\t\treturn this.overlayMode !== 'none'\n\t\t},\n\n\t\t/** Resolved overlay colour. */\n\t\toverlayColor() {\n\t\t\tconst value = this.content && this.content.overlayColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tconst bg = this.content && this.content.backgroundColor\n\t\t\treturn (typeof bg === 'string' && bg !== '') ? bg : '#000000'\n\t\t},\n\n\t\t/** Resolved overlay opacity, clamped to 0..1 (default 0.4). */\n\t\toverlayOpacity() {\n\t\t\tconst raw = this.content && this.content.overlayOpacity\n\t\t\tconst value = typeof raw === 'number' ? raw : Number.parseFloat(raw)\n\t\t\tif (Number.isFinite(value) === false) {\n\t\t\t\treturn 0.4\n\t\t\t}\n\t\t\tif (value < 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (value > 1) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/** Resolved height preset key (default `medium`). */\n\t\theight() {\n\t\t\tconst declared = this.content && this.content.height\n\t\t\tif (typeof declared === 'string' && ALLOWED_HEIGHTS.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'medium'\n\t\t},\n\n\t\t/** The pixel height for the active preset. */\n\t\theightPixels() {\n\t\t\treturn HEIGHT_PIXELS[this.height]\n\t\t},\n\n\t\t/** Resolved text alignment (default `center`). */\n\t\ttextAlign() {\n\t\t\tconst declared = this.content && this.content.textAlign\n\t\t\tif (typeof declared === 'string' && ALLOWED_TEXT_ALIGN.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'center'\n\t\t},\n\n\t\t/** Resolved vertical alignment (default `middle`). */\n\t\tverticalAlign() {\n\t\t\tconst declared = this.content && this.content.verticalAlign\n\t\t\tif (typeof declared === 'string' && ALLOWED_VERTICAL_ALIGN.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'middle'\n\t\t},\n\n\t\t/** Resolved text colour with an auto-contrast fallback. */\n\t\ttextColor() {\n\t\t\tconst value = this.content && this.content.textColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tif (this.hasBackgroundImage) {\n\t\t\t\treturn '#ffffff'\n\t\t\t}\n\t\t\treturn this.isLightColor(this.backgroundColor) ? '#000000' : '#ffffff'\n\t\t},\n\n\t\t/** Inline style for the banner wrapper. */\n\t\twrapperStyle() {\n\t\t\tconst style = {\n\t\t\t\tposition: 'relative',\n\t\t\t\twidth: '100%',\n\t\t\t\t// Fill the dashboard grid cell so the banner resizes with the\n\t\t\t\t// widget. A fixed pixel height ignored the cell, which made the\n\t\t\t\t// banner un-resizable and made it overflow/scroll inside a\n\t\t\t\t// smaller cell. The grid controls the size now.\n\t\t\t\theight: '100%',\n\t\t\t\toverflow: 'hidden',\n\t\t\t\t'background-color': this.backgroundColor,\n\t\t\t}\n\t\t\tif (this.hasBackgroundImage) {\n\t\t\t\tstyle['background-image'] = `url(\"${this.backgroundImageUrl}\")`\n\t\t\t\tstyle['background-size'] = 'cover'\n\t\t\t\tstyle['background-position'] = 'center center'\n\t\t\t\tstyle['background-repeat'] = 'no-repeat'\n\t\t\t}\n\t\t\treturn style\n\t\t},\n\n\t\t/** Inline style for the overlay layer. */\n\t\toverlayStyle() {\n\t\t\tif (this.overlayMode === 'gradient-bottom') {\n\t\t\t\treturn {\n\t\t\t\t\tposition: 'absolute',\n\t\t\t\t\tinset: 0,\n\t\t\t\t\t'background-image': `linear-gradient(to bottom, transparent 50%, ${this.overlayColor} 100%)`,\n\t\t\t\t\t'pointer-events': 'none',\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tposition: 'absolute',\n\t\t\t\tinset: 0,\n\t\t\t\t'background-color': this.overlayColor,\n\t\t\t\topacity: String(this.overlayOpacity),\n\t\t\t\t'pointer-events': 'none',\n\t\t\t}\n\t\t},\n\n\t\t/** Inline style for the content flex container. */\n\t\tcontentStyle() {\n\t\t\treturn {\n\t\t\t\tposition: 'relative',\n\t\t\t\t'z-index': 1,\n\t\t\t\twidth: '100%',\n\t\t\t\theight: '100%',\n\t\t\t\tdisplay: 'flex',\n\t\t\t\t'flex-direction': 'column',\n\t\t\t\t'align-items': this.flexAlignFromTextAlign,\n\t\t\t\t'justify-content': VERTICAL_ALIGN_FLEX[this.verticalAlign],\n\t\t\t\tpadding: '16px 24px',\n\t\t\t\t'box-sizing': 'border-box',\n\t\t\t\tgap: '8px',\n\t\t\t\t'text-align': this.textAlign,\n\t\t\t}\n\t\t},\n\n\t\t/** Flex cross-axis alignment derived from the text alignment. */\n\t\tflexAlignFromTextAlign() {\n\t\t\tif (this.textAlign === 'left') {\n\t\t\t\treturn 'flex-start'\n\t\t\t}\n\t\t\tif (this.textAlign === 'right') {\n\t\t\t\treturn 'flex-end'\n\t\t\t}\n\t\t\treturn 'center'\n\t\t},\n\n\t\t/** Inline style applying the resolved text colour. */\n\t\ttextStyle() {\n\t\t\treturn {\n\t\t\t\tcolor: this.textColor,\n\t\t\t\tmargin: 0,\n\t\t\t}\n\t\t},\n\n\t\t/** The validated CTA object, or null when incomplete. */\n\t\tcta() {\n\t\t\tconst raw = this.content && this.content.cta\n\t\t\tif (raw === null || typeof raw !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = typeof raw.label === 'string' ? raw.label.trim() : ''\n\t\t\tconst url = typeof raw.url === 'string' ? raw.url.trim() : ''\n\t\t\tif (label === '' || url === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tlet style = raw.style\n\t\t\tif (typeof style !== 'string' || ALLOWED_CTA_STYLES.includes(style) === false) {\n\t\t\t\tstyle = 'primary'\n\t\t\t}\n\t\t\treturn { label, url, style }\n\t\t},\n\n\t\t/** Whether a complete CTA is present. */\n\t\thasCta() {\n\t\t\treturn this.cta !== null\n\t\t},\n\n\t\t/** The CTA label, translated. */\n\t\tctaLabel() {\n\t\t\treturn this.hasCta && this.cta.label ? this.effectiveTranslate(this.cta.label) : ''\n\t\t},\n\n\t\t/** The CTA URL. */\n\t\tctaUrl() {\n\t\t\treturn this.hasCta ? this.cta.url : ''\n\t\t},\n\n\t\t/** Whether the CTA URL is an external http(s) link. */\n\t\tctaIsExternal() {\n\t\t\treturn this.hasCta && /^https?:\\/\\//i.test(this.ctaUrl)\n\t\t},\n\n\t\t/** The CTA anchor target (`_blank` for external links). */\n\t\tctaTarget() {\n\t\t\treturn this.ctaIsExternal ? '_blank' : null\n\t\t},\n\n\t\t/** The CTA anchor rel for external links. */\n\t\tctaRel() {\n\t\t\treturn this.ctaIsExternal ? 'noopener noreferrer' : null\n\t\t},\n\n\t\t/** The accessible CTA label, annotating external links. */\n\t\tctaAriaLabel() {\n\t\t\tif (this.hasCta === false) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (this.ctaIsExternal) {\n\t\t\t\treturn `${this.ctaLabel} (${t('nextcloud-vue', 'opens in new tab')})`\n\t\t\t}\n\t\t\treturn this.ctaLabel\n\t\t},\n\n\t\t/** The CTA class list (base + style modifier). */\n\t\tctaClasses() {\n\t\t\tif (this.hasCta === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t'cn-header-widget__cta',\n\t\t\t\t`cn-header-widget__cta--${this.cta.style}`,\n\t\t\t]\n\t\t},\n\t},\n\n\twatch: {\n\t\tbackgroundImageUrl() {\n\t\t\t// Re-arm the probe so a previously failing URL can recover when\n\t\t\t// the user edits the placement.\n\t\t\tthis.imageFailed = false\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Mark the background image as broken so the renderer falls back to\n\t\t * the solid colour.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonImageError() {\n\t\t\tthis.imageFailed = true\n\t\t},\n\n\t\t/**\n\t\t * Quick perceived-luminance check on a CSS hex colour. Returns true\n\t\t * for \"light\" (white text would be illegible). Non-hex colours\n\t\t * collapse to false (assume dark) — the safer default.\n\t\t *\n\t\t * @param {string} color the CSS colour value.\n\t\t * @return {boolean} true when the colour is perceived as light.\n\t\t */\n\t\tisLightColor(color) {\n\t\t\tif (typeof color !== 'string') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst match = color.trim().match(/^#([0-9a-f]{6})$/i)\n\t\t\tif (!match) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst hex = match[1]\n\t\t\tconst r = Number.parseInt(hex.slice(0, 2), 16)\n\t\t\tconst g = Number.parseInt(hex.slice(2, 4), 16)\n\t\t\tconst b = Number.parseInt(hex.slice(4, 6), 16)\n\t\t\tconst luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n\t\t\treturn luma > 0.6\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-header-widget {\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 120px;\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n.cn-header-widget__overlay {\n\tposition: absolute;\n\tinset: 0;\n}\n\n.cn-header-widget__content {\n\tposition: relative;\n\tz-index: 1;\n}\n\n.cn-header-widget__title {\n\tfont-size: 28px;\n\tfont-weight: 700;\n\tline-height: 1.2;\n\tmargin: 0;\n}\n\n.cn-header-widget__subtitle {\n\tfont-size: 16px;\n\tline-height: 1.4;\n\tmargin: 0;\n\topacity: 0.95;\n}\n\n.cn-header-widget__cta {\n\tdisplay: inline-block;\n\tmargin-top: 8px;\n\tpadding: 10px 18px;\n\tborder-radius: var(--border-radius, 4px);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttext-decoration: none;\n\tcursor: pointer;\n\ttransition: transform 0.15s ease, box-shadow 0.15s ease;\n}\n\n.cn-header-widget__cta:hover {\n\ttransform: translateY(-1px);\n\tbox-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n}\n\n.cn-header-widget__cta--primary {\n\tbackground-color: var(--color-primary, #0070c0);\n\tcolor: var(--color-primary-text, #ffffff);\n\tborder: 1px solid var(--color-primary, #0070c0);\n}\n\n.cn-header-widget__cta--secondary {\n\tbackground-color: var(--color-background-hover, rgba(255, 255, 255, 0.85));\n\tcolor: var(--color-main-text, #000000);\n\tborder: 1px solid var(--color-border, #cccccc);\n}\n\n.cn-header-widget__cta--ghost {\n\tbackground-color: transparent;\n\tcolor: inherit;\n\tborder: 1px solid currentColor;\n}\n\n.cn-header-widget__probe {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\topacity: 0;\n\tpointer-events: none;\n\tleft: -9999px;\n\ttop: -9999px;\n}\n\n@media print {\n\t.cn-header-widget {\n\t\tprint-color-adjust: exact;\n\t\t-webkit-print-color-adjust: exact;\n\t}\n}\n</style>\n"],"names":["_createElementBlock"],"mappings":";;;;;;uBAWGA;AAAG,IAAA,KAAK,EAAC,kBAAW;AAAA,IAAA,KAAA,iBAAA,QAAA,CAAA,YAAA,CAAA;;KAEd,QAAU,CAAA,UAAA;sBADjBA,kBAIuB,CAAA,KAAA,EAAA;;AAFrB,UAAA,KAAI,EAAC,2BAAK;AACV,UAAA,KAAG,iBAAA,QAAA,CAAA,YAAA,CAAA;AACJ,UAAA,aAAW,EAAC,MAAM;;;sBAiBb,CAAA,KAAA,EAAA;AAhBD,MAAA,KAAK,EAAC,2BAAI;AAAA,MAAA,KAAA,iBAAA,QAAA,CAAA,YAAA,CAAA;;OACX,QAAA,CAAA,QAAA;wBAAHA,kBAEK,CAAA,IAAA,EAAA;;AAFF,YAAA,KAAA,EAAA,yBAAA;AAAA,YAAA,KAAA,iBAAA,QAAA,CAAA,SAAA,CAAA;6BACF,QAAA,CAAA,KAAA,CAAA,EAAA,CAAA,mBAAA;;OAEQ,QAAK,CAAA,WAAA;0CAEV,CAAA,GAAA,EAAA;;AAFU,YAAA,KAAA,EAAA,4BAAA;AAAA,YAAA,KAAA,iBAAA,QAAA,CAAA,SAAA,CAAA;6BACV,QAAQ,CAAA,QAAA,CAAA,EAAA,CAAA,mBAAA;;OAGL,QAAM,CAAA,MAAA;0CAOT,CAAA,GAAA,EAAA;;AANF,YAAA,IAAI,EAAE,QAAM,CAAA,MAAA;AACZ,YAAA,KAAC,iBAAA,QAAA,CAAA,UAAA,CAAA;AACD,YAAA,MAAE,EAAA,QAAA,CAAA,SAAA;AACF,YAAA,GAAA,EAAA,QAAA,CAAA,MAAA;AACA,YAAA,YAAU,EAAE,QAAU,CAAA,YAAA;6BACpB,QAAQ,CAAA,QAAA,CAAA,EAAA,EAAA,2BAAA,UAAA,CAAA;;;KAIN,QAAe,CAAA,kBAAA;sBADrBA;;AAEA,UAAA,KAAG,EAAA,yBAAA;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnHeaderWidget.vue3.js","sources":["../../../../src/components/CnHeaderWidget/CnHeaderWidget.vue?vue&type=template&id=1b97430f&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-header-widget\" :class=\"{ 'cn-header-widget--plain': isPlain }\" :style=\"wrapperStyle\">\n\t\t<div\n\t\t\tv-if=\"hasOverlay\"\n\t\t\tclass=\"cn-header-widget__overlay\"\n\t\t\t:style=\"overlayStyle\"\n\t\t\taria-hidden=\"true\" />\n\t\t<div class=\"cn-header-widget__content\" :style=\"contentStyle\">\n\t\t\t<p\n\t\t\t\tv-if=\"dateLine\"\n\t\t\t\tclass=\"cn-header-widget__date\"\n\t\t\t\t:style=\"dateStyle\"\n\t\t\t\tdata-testid=\"cn-header-widget-date\">\n\t\t\t\t{{ dateLine }}\n\t\t\t</p>\n\t\t\t<h2 v-if=\"hasTitle\" class=\"cn-header-widget__title\" :style=\"textStyle\">\n\t\t\t\t{{ headingText }}\n\t\t\t</h2>\n\t\t\t<p v-if=\"hasSubtitle\" class=\"cn-header-widget__subtitle\" :style=\"textStyle\">\n\t\t\t\t{{ subtitle }}\n\t\t\t</p>\n\t\t\t<a\n\t\t\t\tv-if=\"hasCta\"\n\t\t\t\t:href=\"ctaUrl\"\n\t\t\t\t:class=\"ctaClasses\"\n\t\t\t\t:target=\"ctaTarget\"\n\t\t\t\t:rel=\"ctaRel\"\n\t\t\t\t:aria-label=\"ctaAriaLabel\">\n\t\t\t\t{{ ctaLabel }}\n\t\t\t</a>\n\t\t</div>\n\t\t<img\n\t\t\tv-if=\"hasBackgroundImage\"\n\t\t\tclass=\"cn-header-widget__probe\"\n\t\t\t:src=\"backgroundImageUrl\"\n\t\t\talt=\"\"\n\t\t\taria-hidden=\"true\"\n\t\t\t@error=\"onImageError\">\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport { getCanonicalLocale, translate as t } from '@nextcloud/l10n'\nimport { resolveImageUrl } from '../../utils/resolveImageUrl.js'\n\nconst ALLOWED_OVERLAY_MODES = ['none', 'tint', 'gradient-bottom']\nconst ALLOWED_HEIGHTS = ['small', 'medium', 'large', 'xlarge']\nconst ALLOWED_TEXT_ALIGN = ['left', 'center', 'right']\nconst ALLOWED_VERTICAL_ALIGN = ['top', 'middle', 'bottom']\nconst ALLOWED_CTA_STYLES = ['primary', 'secondary', 'ghost']\n\nconst HEIGHT_PIXELS = Object.freeze({\n\tsmall: 120,\n\tmedium: 200,\n\tlarge: 320,\n\txlarge: 480,\n})\n\nconst VERTICAL_ALIGN_FLEX = Object.freeze({\n\ttop: 'flex-start',\n\tmiddle: 'center',\n\tbottom: 'flex-end',\n})\n\n/**\n * CnHeaderWidget — full-width banner widget for dashboards. Renders a\n * configurable header with title, optional subtitle, optional background\n * image, optional colour overlay, and optional call-to-action button.\n *\n * Image source precedence: `backgroundImageFileId` takes priority over\n * `backgroundImageUrl` (resolved via the Nextcloud core preview route). When\n * the image fails to load the renderer silently falls back to the solid\n * `backgroundColor`; no error UI is shown.\n *\n * Height presets map to fixed pixels: small=120, medium=200, large=320,\n * xlarge=480. Title is `<h2>`, subtitle `<p>`, CTA `<a>` — all semantic HTML.\n * External CTA URLs (http/https) open in a new tab with\n * `rel=\"noopener noreferrer\"`; relative / anchor URLs stay in the current tab.\n *\n * Optional greeting: `greeting: true` makes the heading greet the signed-in\n * user by time of day and first name (\"Good afternoon, Pieter\"; `\"full\"` uses\n * the whole display name), and `showDate: true` adds a line with today's\n * date above it. `plain: true` drops the coloured background and aligns the\n * text to the start, which is how a greeting usually sits on a dashboard.\n *\n * ```json\n * { \"widgetKey\": \"header\", \"props\": { \"content\": { \"greeting\": true, \"showDate\": true, \"plain\": true } } }\n * ```\n *\n * Registered as the `header` dashboard widget type via the renderer's\n * `index.js`.\n */\nexport default {\n\tname: 'CnHeaderWidget',\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 `content.title` / `content.subtitle` / CTA label\n\t\t * are run through it. Defaults to an identity function so an\n\t\t * untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted widget content: `{title, subtitle, backgroundImageUrl,\n\t\t * backgroundImageFileId, backgroundColor, overlayMode, overlayColor,\n\t\t * overlayOpacity, textColor, textAlign, verticalAlign, height, cta,\n\t\t * greeting, showDate, plain}`. All fields are optional except `title`\n\t\t * (or `greeting`); unknown enum values collapse to documented defaults\n\t\t * and the renderer never throws.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/* eslint-disable vue/no-unused-properties -- part of the widget contract: CnContainerChild and the dashboard pages bind :placement on every widget, so declaring it keeps it out of $attrs */\n\t\t/**\n\t\t * Placement entity — reserved to match the renderer contract.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tplacement: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t\t/* eslint-enable vue/no-unused-properties */\n\n\t\t/**\n\t\t * The moment the greeting and the date line are computed for. Leave\n\t\t * empty for the current time.\n\t\t *\n\t\t * @type {Date|null}\n\t\t */\n\t\tnow: {\n\t\t\ttype: Date,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Set true once the background image fires a DOM `error` so\n\t\t\t// subsequent renders fall back to `backgroundColor` only. The\n\t\t\t// probe `<img>` is invisible — its only job is load-failure\n\t\t\t// detection for the CSS background.\n\t\t\timageFailed: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the injected `cnTranslate` (the host\n\t\t * app's bound `t()`), identity by default.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t},\n\n\t\t/** The header title, translated, or '' when absent. */\n\t\ttitle() {\n\t\t\tconst value = this.content && this.content.title\n\t\t\treturn typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''\n\t\t},\n\n\t\t/** The header subtitle, translated, or '' when absent. */\n\t\tsubtitle() {\n\t\t\tconst value = this.content && this.content.subtitle\n\t\t\treturn typeof value === 'string' && value !== '' ? this.effectiveTranslate(value) : ''\n\t\t},\n\n\t\t/** Whether a heading renders: a non-empty title or a greeting. */\n\t\thasTitle() {\n\t\t\treturn this.headingText !== ''\n\t\t},\n\n\t\t/**\n\t\t * The moment the greeting and date line are computed for.\n\t\t *\n\t\t * @return {Date}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\treference() {\n\t\t\treturn this.now instanceof Date ? this.now : new Date()\n\t\t},\n\n\t\t/**\n\t\t * Whether the plain presentation is on (no coloured background).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisPlain() {\n\t\t\treturn Boolean(this.content && this.content.plain === true)\n\t\t},\n\n\t\t/**\n\t\t * The name the greeting uses: the first word of the display name, or\n\t\t * the whole display name for `greeting: \"full\"`. '' when unknown.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tgreetingName() {\n\t\t\tlet user\n\t\t\ttry {\n\t\t\t\tuser = getCurrentUser()\n\t\t\t} catch {\n\t\t\t\tuser = null\n\t\t\t}\n\t\t\tconst displayName = (user && typeof user.displayName === 'string') ? user.displayName.trim() : ''\n\t\t\tif (displayName === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.content.greeting === 'full' ? displayName : displayName.split(/\\s+/)[0]\n\t\t},\n\n\t\t/**\n\t\t * The greeting for the time of day, with the user's name when known,\n\t\t * or '' when the greeting is off.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tgreetingText() {\n\t\t\tconst greeting = this.content && this.content.greeting\n\t\t\tif (greeting !== true && greeting !== 'full' && greeting !== 'first') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst hour = this.reference.getHours()\n\t\t\tconst name = this.greetingName\n\t\t\tif (hour < 12) {\n\t\t\t\treturn name ? t('nextcloud-vue', 'Good morning, {name}', { name }) : t('nextcloud-vue', 'Good morning')\n\t\t\t}\n\t\t\tif (hour < 18) {\n\t\t\t\treturn name ? t('nextcloud-vue', 'Good afternoon, {name}', { name }) : t('nextcloud-vue', 'Good afternoon')\n\t\t\t}\n\t\t\treturn name ? t('nextcloud-vue', 'Good evening, {name}', { name }) : t('nextcloud-vue', 'Good evening')\n\t\t},\n\n\t\t/**\n\t\t * The heading: the greeting when it is on, else the title.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\theadingText() {\n\t\t\treturn this.greetingText || this.title\n\t\t},\n\n\t\t/**\n\t\t * Today's date written out (\"Monday 5 October 2026\"), or '' when\n\t\t * `showDate` is off.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tdateLine() {\n\t\t\tif (!this.content || this.content.showDate !== true) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst options = { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }\n\t\t\tlet locale\n\t\t\ttry {\n\t\t\t\tlocale = getCanonicalLocale()\n\t\t\t} catch {\n\t\t\t\tlocale = undefined\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(locale || undefined, options).format(this.reference)\n\t\t\t} catch {\n\t\t\t\treturn new Intl.DateTimeFormat(undefined, options).format(this.reference)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Inline style of the date line: the heading's colour, or the muted\n\t\t * text colour on a plain header.\n\t\t *\n\t\t * @return {object}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tdateStyle() {\n\t\t\treturn {\n\t\t\t\tcolor: this.isPlain && !(this.content && this.content.textColor) ? 'var(--color-text-maxcontrast)' : this.textColor,\n\t\t\t\tmargin: 0,\n\t\t\t}\n\t\t},\n\n\t\t/** Whether a non-empty subtitle is set. */\n\t\thasSubtitle() {\n\t\t\treturn this.subtitle !== ''\n\t\t},\n\n\t\t/** Resolved background image URL (file id wins over URL). */\n\t\tbackgroundImageUrl() {\n\t\t\tconst fileId = this.content && this.content.backgroundImageFileId\n\t\t\tif (typeof fileId === 'number' && Number.isFinite(fileId) && fileId > 0) {\n\t\t\t\treturn `/index.php/core/preview?fileId=${fileId}&x=1024&y=512&a=1`\n\t\t\t}\n\t\t\tconst url = this.content && this.content.backgroundImageUrl\n\t\t\t// Accept http(s) plus uploaded/same-origin sources: data: and blob:\n\t\t\t// (from the form's upload) and root-relative paths (NC files/preview).\n\t\t\t// External http(s) images may still be blocked by the instance CSP —\n\t\t\t// uploading is the reliable, same-origin path. resolveImageUrl() adds\n\t\t\t// the webroot + /index.php to `/apps/...` resource paths so they load\n\t\t\t// on index.php-routed instances (other shapes pass through).\n\t\t\tif (typeof url === 'string' && /^(https?:\\/\\/|data:|blob:|\\/)/i.test(url)) {\n\t\t\t\treturn resolveImageUrl(url)\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** Whether a usable, non-errored background image is present. */\n\t\thasBackgroundImage() {\n\t\t\treturn this.backgroundImageUrl !== '' && this.imageFailed === false\n\t\t},\n\n\t\t/**\n\t\t * Resolved background colour (default theme primary).\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tbackgroundColor() {\n\t\t\tconst value = this.content && this.content.backgroundColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tif (this.isPlain) {\n\t\t\t\treturn 'transparent'\n\t\t\t}\n\t\t\treturn 'var(--color-primary, #0070c0)'\n\t\t},\n\n\t\t/** Resolved overlay mode (`tint` when an image is present, else `none`). */\n\t\toverlayMode() {\n\t\t\tconst declared = this.content && this.content.overlayMode\n\t\t\tif (typeof declared === 'string' && ALLOWED_OVERLAY_MODES.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn this.hasBackgroundImage ? 'tint' : 'none'\n\t\t},\n\n\t\t/** Whether an overlay should render. */\n\t\thasOverlay() {\n\t\t\treturn this.overlayMode !== 'none'\n\t\t},\n\n\t\t/** Resolved overlay colour. */\n\t\toverlayColor() {\n\t\t\tconst value = this.content && this.content.overlayColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tconst bg = this.content && this.content.backgroundColor\n\t\t\treturn (typeof bg === 'string' && bg !== '') ? bg : '#000000'\n\t\t},\n\n\t\t/** Resolved overlay opacity, clamped to 0..1 (default 0.4). */\n\t\toverlayOpacity() {\n\t\t\tconst raw = this.content && this.content.overlayOpacity\n\t\t\tconst value = typeof raw === 'number' ? raw : Number.parseFloat(raw)\n\t\t\tif (Number.isFinite(value) === false) {\n\t\t\t\treturn 0.4\n\t\t\t}\n\t\t\tif (value < 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (value > 1) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/** Resolved height preset key (default `medium`). */\n\t\theight() {\n\t\t\tconst declared = this.content && this.content.height\n\t\t\tif (typeof declared === 'string' && ALLOWED_HEIGHTS.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'medium'\n\t\t},\n\n\t\t/** The pixel height for the active preset. */\n\t\theightPixels() {\n\t\t\treturn HEIGHT_PIXELS[this.height]\n\t\t},\n\n\t\t/**\n\t\t * Resolved text alignment (default `center`).\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\ttextAlign() {\n\t\t\tconst declared = this.content && this.content.textAlign\n\t\t\tif (typeof declared === 'string' && ALLOWED_TEXT_ALIGN.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn this.isPlain ? 'left' : 'center'\n\t\t},\n\n\t\t/** Resolved vertical alignment (default `middle`). */\n\t\tverticalAlign() {\n\t\t\tconst declared = this.content && this.content.verticalAlign\n\t\t\tif (typeof declared === 'string' && ALLOWED_VERTICAL_ALIGN.includes(declared)) {\n\t\t\t\treturn declared\n\t\t\t}\n\t\t\treturn 'middle'\n\t\t},\n\n\t\t/**\n\t\t * Resolved text colour with an auto-contrast fallback.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\ttextColor() {\n\t\t\tconst value = this.content && this.content.textColor\n\t\t\tif (typeof value === 'string' && value !== '') {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\tif (this.hasBackgroundImage) {\n\t\t\t\treturn '#ffffff'\n\t\t\t}\n\t\t\tif (this.isPlain && !(this.content && this.content.backgroundColor)) {\n\t\t\t\treturn 'var(--color-main-text)'\n\t\t\t}\n\t\t\treturn this.isLightColor(this.backgroundColor) ? '#000000' : '#ffffff'\n\t\t},\n\n\t\t/** Inline style for the banner wrapper. */\n\t\twrapperStyle() {\n\t\t\tconst style = {\n\t\t\t\tposition: 'relative',\n\t\t\t\twidth: '100%',\n\t\t\t\t// Fill the dashboard grid cell so the banner resizes with the\n\t\t\t\t// widget. A fixed pixel height ignored the cell, which made the\n\t\t\t\t// banner un-resizable and made it overflow/scroll inside a\n\t\t\t\t// smaller cell. The grid controls the size now.\n\t\t\t\theight: '100%',\n\t\t\t\toverflow: 'hidden',\n\t\t\t\t'background-color': this.backgroundColor,\n\t\t\t}\n\t\t\tif (this.hasBackgroundImage) {\n\t\t\t\tstyle['background-image'] = `url(\"${this.backgroundImageUrl}\")`\n\t\t\t\tstyle['background-size'] = 'cover'\n\t\t\t\tstyle['background-position'] = 'center center'\n\t\t\t\tstyle['background-repeat'] = 'no-repeat'\n\t\t\t}\n\t\t\treturn style\n\t\t},\n\n\t\t/** Inline style for the overlay layer. */\n\t\toverlayStyle() {\n\t\t\tif (this.overlayMode === 'gradient-bottom') {\n\t\t\t\treturn {\n\t\t\t\t\tposition: 'absolute',\n\t\t\t\t\tinset: 0,\n\t\t\t\t\t'background-image': `linear-gradient(to bottom, transparent 50%, ${this.overlayColor} 100%)`,\n\t\t\t\t\t'pointer-events': 'none',\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tposition: 'absolute',\n\t\t\t\tinset: 0,\n\t\t\t\t'background-color': this.overlayColor,\n\t\t\t\topacity: String(this.overlayOpacity),\n\t\t\t\t'pointer-events': 'none',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Inline style for the content flex container.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tcontentStyle() {\n\t\t\treturn {\n\t\t\t\tposition: 'relative',\n\t\t\t\t'z-index': 1,\n\t\t\t\twidth: '100%',\n\t\t\t\theight: '100%',\n\t\t\t\tdisplay: 'flex',\n\t\t\t\t'flex-direction': 'column',\n\t\t\t\t'align-items': this.flexAlignFromTextAlign,\n\t\t\t\t'justify-content': VERTICAL_ALIGN_FLEX[this.verticalAlign],\n\t\t\t\tpadding: this.isPlain ? '0' : '16px 24px',\n\t\t\t\t'box-sizing': 'border-box',\n\t\t\t\tgap: '8px',\n\t\t\t\t'text-align': this.textAlign,\n\t\t\t}\n\t\t},\n\n\t\t/** Flex cross-axis alignment derived from the text alignment. */\n\t\tflexAlignFromTextAlign() {\n\t\t\tif (this.textAlign === 'left') {\n\t\t\t\treturn 'flex-start'\n\t\t\t}\n\t\t\tif (this.textAlign === 'right') {\n\t\t\t\treturn 'flex-end'\n\t\t\t}\n\t\t\treturn 'center'\n\t\t},\n\n\t\t/** Inline style applying the resolved text colour. */\n\t\ttextStyle() {\n\t\t\treturn {\n\t\t\t\tcolor: this.textColor,\n\t\t\t\tmargin: 0,\n\t\t\t}\n\t\t},\n\n\t\t/** The validated CTA object, or null when incomplete. */\n\t\tcta() {\n\t\t\tconst raw = this.content && this.content.cta\n\t\t\tif (raw === null || typeof raw !== 'object') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst label = typeof raw.label === 'string' ? raw.label.trim() : ''\n\t\t\tconst url = typeof raw.url === 'string' ? raw.url.trim() : ''\n\t\t\tif (label === '' || url === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tlet style = raw.style\n\t\t\tif (typeof style !== 'string' || ALLOWED_CTA_STYLES.includes(style) === false) {\n\t\t\t\tstyle = 'primary'\n\t\t\t}\n\t\t\treturn { label, url, style }\n\t\t},\n\n\t\t/** Whether a complete CTA is present. */\n\t\thasCta() {\n\t\t\treturn this.cta !== null\n\t\t},\n\n\t\t/** The CTA label, translated. */\n\t\tctaLabel() {\n\t\t\treturn this.hasCta && this.cta.label ? this.effectiveTranslate(this.cta.label) : ''\n\t\t},\n\n\t\t/** The CTA URL. */\n\t\tctaUrl() {\n\t\t\treturn this.hasCta ? this.cta.url : ''\n\t\t},\n\n\t\t/** Whether the CTA URL is an external http(s) link. */\n\t\tctaIsExternal() {\n\t\t\treturn this.hasCta && /^https?:\\/\\//i.test(this.ctaUrl)\n\t\t},\n\n\t\t/** The CTA anchor target (`_blank` for external links). */\n\t\tctaTarget() {\n\t\t\treturn this.ctaIsExternal ? '_blank' : null\n\t\t},\n\n\t\t/** The CTA anchor rel for external links. */\n\t\tctaRel() {\n\t\t\treturn this.ctaIsExternal ? 'noopener noreferrer' : null\n\t\t},\n\n\t\t/** The accessible CTA label, annotating external links. */\n\t\tctaAriaLabel() {\n\t\t\tif (this.hasCta === false) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (this.ctaIsExternal) {\n\t\t\t\treturn `${this.ctaLabel} (${t('nextcloud-vue', 'opens in new tab')})`\n\t\t\t}\n\t\t\treturn this.ctaLabel\n\t\t},\n\n\t\t/** The CTA class list (base + style modifier). */\n\t\tctaClasses() {\n\t\t\tif (this.hasCta === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t'cn-header-widget__cta',\n\t\t\t\t`cn-header-widget__cta--${this.cta.style}`,\n\t\t\t]\n\t\t},\n\t},\n\n\twatch: {\n\t\tbackgroundImageUrl() {\n\t\t\t// Re-arm the probe so a previously failing URL can recover when\n\t\t\t// the user edits the placement.\n\t\t\tthis.imageFailed = false\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Mark the background image as broken so the renderer falls back to\n\t\t * the solid colour.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonImageError() {\n\t\t\tthis.imageFailed = true\n\t\t},\n\n\t\t/**\n\t\t * Quick perceived-luminance check on a CSS hex colour. Returns true\n\t\t * for \"light\" (white text would be illegible). Non-hex colours\n\t\t * collapse to false (assume dark) — the safer default.\n\t\t *\n\t\t * @param {string} color the CSS colour value.\n\t\t * @return {boolean} true when the colour is perceived as light.\n\t\t */\n\t\tisLightColor(color) {\n\t\t\tif (typeof color !== 'string') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst match = color.trim().match(/^#([0-9a-f]{6})$/i)\n\t\t\tif (!match) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst hex = match[1]\n\t\t\tconst r = Number.parseInt(hex.slice(0, 2), 16)\n\t\t\tconst g = Number.parseInt(hex.slice(2, 4), 16)\n\t\t\tconst b = Number.parseInt(hex.slice(4, 6), 16)\n\t\t\tconst luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n\t\t\treturn luma > 0.6\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-header-widget {\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 120px;\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n.cn-header-widget--plain {\n\tmin-height: 0;\n\tborder-radius: 0;\n}\n\n.cn-header-widget__date {\n\tfont-size: 14px;\n\tline-height: 1.4;\n\tmargin: 0;\n}\n\n.cn-header-widget__overlay {\n\tposition: absolute;\n\tinset: 0;\n}\n\n.cn-header-widget__content {\n\tposition: relative;\n\tz-index: 1;\n}\n\n.cn-header-widget__title {\n\tfont-size: 28px;\n\tfont-weight: 700;\n\tline-height: 1.2;\n\tmargin: 0;\n}\n\n.cn-header-widget__subtitle {\n\tfont-size: 16px;\n\tline-height: 1.4;\n\tmargin: 0;\n\topacity: 0.95;\n}\n\n.cn-header-widget__cta {\n\tdisplay: inline-block;\n\tmargin-top: 8px;\n\tpadding: 10px 18px;\n\tborder-radius: var(--border-radius, 4px);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttext-decoration: none;\n\tcursor: pointer;\n\ttransition: transform 0.15s ease, box-shadow 0.15s ease;\n}\n\n.cn-header-widget__cta:hover {\n\ttransform: translateY(-1px);\n\tbox-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n}\n\n.cn-header-widget__cta--primary {\n\tbackground-color: var(--color-primary, #0070c0);\n\tcolor: var(--color-primary-text, #ffffff);\n\tborder: 1px solid var(--color-primary, #0070c0);\n}\n\n.cn-header-widget__cta--secondary {\n\tbackground-color: var(--color-background-hover, rgba(255, 255, 255, 0.85));\n\tcolor: var(--color-main-text, #000000);\n\tborder: 1px solid var(--color-border, #cccccc);\n}\n\n.cn-header-widget__cta--ghost {\n\tbackground-color: transparent;\n\tcolor: inherit;\n\tborder: 1px solid currentColor;\n}\n\n.cn-header-widget__probe {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\topacity: 0;\n\tpointer-events: none;\n\tleft: -9999px;\n\ttop: -9999px;\n}\n\n@media print {\n\t.cn-header-widget {\n\t\tprint-color-adjust: exact;\n\t\t-webkit-print-color-adjust: exact;\n\t}\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode"],"mappings":";;;;;;uBAWGA;IAAG,KAAK,EAAAC,cAAA,CAAA,CAAC,kBAAW,EAAA,EAAA,yBAAA,EAAA,QAAA,CAAA,OAAA,EAAA,CAAA,CAAA;AAAA,IAAA,KAAA,iBAAA,QAAA,CAAA,YAAA,CAAA;;KAEnB,QAAA,CAAA,UAAA;sBADFD,kBAImB,CAAA,KAAA,EAAA;;AAFjB,UAAA,KAAI,EAAC,2BAAS;AACd,UAAA,KAAK,iBAAE,QAAY,CAAA,YAAA,CAAA;AACnB,UAAA,aAAU,EAAC,MAAM;;;IACjBE,kBAuBI,CAAA,KAAA,EAAA;AAvBD,MAAA,KAAK,EAAC,2BAA0B;AAAA,MAAA,KAAA,iBAAA,QAAA,CAAA,YAAA,CAAA;;OAEjC,QAAA,CAAA,QAAA;0CAKC,CAAA,GAAA,EAAA;;AAJH,YAAA,KAAK,EAAC,wBAAwB;AAC7B,YAAA,KAAK,iBAAE,QAAQ,CAAA,SAAA,CAAA;AAChB,YAAA,aAAG,EAAA,uBAAA;6BACA,QAAQ,CAAA,QAAA,CAAA,EAAA,CAAA,mBAAA;;OAET,QAAA,CAAA,QAAA;wBAAHF,kBAEK,CAAA,IAAA,EAAA;;AAFF,YAAA,KAAA,EAAA,yBAAA;AAAA,YAAA,KAAA,iBAAA,QAAA,CAAA,SAAA,CAAA;6BACF,QAAA,CAAA,WAAA,CAAA,EAAA,CAAA,mBAAA;;OAEQ,QAAK,CAAA,WAAA;0CAEV,CAAA,GAAA,EAAA;;AAFU,YAAA,KAAA,EAAA,4BAAA;AAAA,YAAA,KAAA,iBAAA,QAAA,CAAA,SAAA,CAAA;6BACV,QAAQ,CAAA,QAAA,CAAA,EAAA,CAAA,mBAAA;;OAGL,QAAM,CAAA,MAAA;0CAOT,CAAA,GAAA,EAAA;;AANF,YAAA,IAAI,EAAE,QAAM,CAAA,MAAA;AACZ,YAAA,KAAC,iBAAA,QAAA,CAAA,UAAA,CAAA;AACD,YAAA,MAAE,EAAA,QAAA,CAAA,SAAA;AACF,YAAA,GAAA,EAAA,QAAA,CAAA,MAAA;AACA,YAAA,YAAU,EAAE,QAAU,CAAA,YAAA;6BACpB,QAAQ,CAAA,QAAA,CAAA,EAAA,EAAA,2BAAA,UAAA,CAAA;;;KAIN,QAAe,CAAA,kBAAA;sBADrBA;;AAEA,UAAA,KAAG,EAAA,yBAAA;;;;;;;;;;;;"}
|