@conduction/nextcloud-vue 2.59.0 → 2.61.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/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.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +80 -1
- 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 +232 -10
- 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 +46 -0
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
- 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/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/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/CnDashboardPage/CnDashboardPage.vue2.js +10 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +303 -144
- 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/CnHeaderWidget/CnHeaderWidget.vue2.js +171 -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 +132 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
- 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/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/CnPageRenderer/pageTypes.js +1 -0
- package/dist/esm/components/CnPageRenderer/pageTypes.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/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/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/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 +16 -0
- package/dist/esm/index.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 +1036 -19
- 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/headers.js +28 -2
- package/dist/esm/utils/headers.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +5 -0
- package/dist/esm/utils/libraryWidgetKeys.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/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +16019 -9528
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1036 -19
- package/l10n/en.json +55 -1
- package/l10n/nl.json +55 -1
- package/package.json +1 -1
- package/scripts/.jsdoc-baselines.json +4 -0
- package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
- package/src/components/CnAppNav/CnAppNav.md +26 -0
- package/src/components/CnAppNav/CnAppNav.vue +145 -2
- package/src/components/CnBannerWidget/CnBannerWidget.vue +373 -11
- package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
- package/src/components/CnBoardView/CnBoardView.vue +77 -0
- 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/CnCellRenderer/CnCellRenderer.vue +207 -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/CnDashboardPage/CnDashboardPage.vue +10 -2
- package/src/components/CnDetailPage/CnDetailPage.md +16 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +687 -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/CnHeaderWidget/CnHeaderWidget.vue +191 -14
- package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
- package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +484 -0
- package/src/components/CnLinkCardsPage/index.js +4 -0
- 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/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
- package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
- package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
- package/src/components/CnPageRenderer/pageTypes.js +1 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
- 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/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 +244 -10
- package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
- 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 +12 -0
- package/src/css/detail-page.css +57 -2
- package/src/index.js +16 -0
- package/src/schemas/app-manifest-v2.schema.json +433 -3
- package/src/schemas/app-manifest.schema.json +2 -1
- package/src/types/manifest.d.ts +1 -1
- 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/headers.js +28 -2
- package/src/utils/index.js +1 -0
- package/src/utils/libraryWidgetKeys.js +5 -0
- package/src/utils/readPath.js +27 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { translate } from '@nextcloud/l10n';
|
|
2
|
-
import { NcTextField, NcSelect, NcButton } from '@nextcloud/vue';
|
|
2
|
+
import { NcTextField, NcSelect, NcButton, NcCheckboxRadioSwitch } from '@nextcloud/vue';
|
|
3
3
|
import __sfc_main from '../CnColorPicker/CnColorPicker.vue.js';
|
|
4
4
|
import { extractTransportUrl, warnUploadFnDeprecated, readFileAsDataUrl, embedAsDataUrl } from '../../utils/widgetUpload.js';
|
|
5
5
|
|
|
@@ -43,6 +43,7 @@ var script = {
|
|
|
43
43
|
NcTextField,
|
|
44
44
|
NcSelect,
|
|
45
45
|
NcButton,
|
|
46
|
+
NcCheckboxRadioSwitch,
|
|
46
47
|
CnColorPicker: __sfc_main,
|
|
47
48
|
},
|
|
48
49
|
|
|
@@ -119,6 +120,11 @@ var script = {
|
|
|
119
120
|
return {
|
|
120
121
|
title: typeof initial.title === 'string' ? initial.title : DEFAULT_CONTENT.title,
|
|
121
122
|
subtitle: typeof initial.subtitle === 'string' ? initial.subtitle : DEFAULT_CONTENT.subtitle,
|
|
123
|
+
greeting: initial.greeting === true || initial.greeting === 'full' || initial.greeting === 'first',
|
|
124
|
+
// `"full"` (whole display name) is a manifest choice; kept as written.
|
|
125
|
+
greetingMode: initial.greeting === 'full' ? 'full' : true,
|
|
126
|
+
showDate: initial.showDate === true,
|
|
127
|
+
plain: initial.plain === true,
|
|
122
128
|
backgroundImageUrl: typeof initial.backgroundImageUrl === 'string'
|
|
123
129
|
? initial.backgroundImageUrl
|
|
124
130
|
: DEFAULT_CONTENT.backgroundImageUrl,
|
|
@@ -231,9 +237,35 @@ var script = {
|
|
|
231
237
|
}
|
|
232
238
|
},
|
|
233
239
|
|
|
234
|
-
/**
|
|
240
|
+
/**
|
|
241
|
+
* The greeting keys, written only when they are on, so a header that
|
|
242
|
+
* never used them keeps the content blob it always had.
|
|
243
|
+
*
|
|
244
|
+
* @return {object}
|
|
245
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
246
|
+
*/
|
|
247
|
+
greetingContent() {
|
|
248
|
+
const content = {};
|
|
249
|
+
if (this.greeting) {
|
|
250
|
+
content.greeting = this.greetingMode;
|
|
251
|
+
}
|
|
252
|
+
if (this.showDate) {
|
|
253
|
+
content.showDate = true;
|
|
254
|
+
}
|
|
255
|
+
if (this.plain) {
|
|
256
|
+
content.plain = true;
|
|
257
|
+
}
|
|
258
|
+
return content
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The full content blob assembled from the current field values.
|
|
263
|
+
*
|
|
264
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
265
|
+
*/
|
|
235
266
|
assembledContent() {
|
|
236
267
|
return {
|
|
268
|
+
...this.greetingContent,
|
|
237
269
|
title: this.title,
|
|
238
270
|
subtitle: this.subtitle,
|
|
239
271
|
backgroundImageUrl: this.backgroundImageUrl,
|
|
@@ -375,10 +407,12 @@ var script = {
|
|
|
375
407
|
* when either CTA field is filled.
|
|
376
408
|
*
|
|
377
409
|
* @return {string[]} the validation errors.
|
|
410
|
+
* @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
|
|
378
411
|
*/
|
|
379
412
|
validate() {
|
|
380
413
|
const errors = [];
|
|
381
|
-
|
|
414
|
+
// A greeting is the heading, so a greeting header needs no title.
|
|
415
|
+
if (!this.greeting && (typeof this.title !== 'string' || this.title.trim() === '')) {
|
|
382
416
|
errors.push(translate('nextcloud-vue', 'Title is required'));
|
|
383
417
|
}
|
|
384
418
|
if (typeof this.backgroundImageUrl === 'string'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnHeaderWidgetForm.vue2.js","sources":["../../../../src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.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-form\">\n\t\t<NcTextField\n\t\t\t:modelValue=\"title\"\n\t\t\t:label=\"t('nextcloud-vue', 'Title')\"\n\t\t\t:placeholder=\"t('nextcloud-vue', 'Header title')\"\n\t\t\trequired\n\t\t\t@update:modelValue=\"updateField('title', $event)\" />\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"subtitle\"\n\t\t\t:label=\"t('nextcloud-vue', 'Subtitle (optional)')\"\n\t\t\t:placeholder=\"t('nextcloud-vue', 'Optional subtitle')\"\n\t\t\t@update:modelValue=\"updateField('subtitle', $event)\" />\n\n\t\t<!-- Pick a background image. Selection does NOT upload — the file is held\n\t\t and only uploaded when the host modal calls commit() on submit, so\n\t\t re-picking or cancelling writes nothing. Uploads are the reliable\n\t\t path: external image URLs are often blocked by the Nextcloud\n\t\t Content-Security-Policy, so only the background colour would show. -->\n\t\t<div class=\"cn-header-widget-form__upload-row\">\n\t\t\t<label class=\"cn-header-widget-form__upload-label\">\n\t\t\t\t<input\n\t\t\t\t\tref=\"fileInput\"\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\taccept=\"image/*\"\n\t\t\t\t\tclass=\"cn-header-widget-form__file-input\"\n\t\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t\t@change=\"handleFileSelect\">\n\t\t\t\t<span class=\"cn-header-widget-form__upload-button\">\n\t\t\t\t\t{{ pendingFile ? t('nextcloud-vue', 'Change background image') : t('nextcloud-vue', 'Choose background image') }}\n\t\t\t\t</span>\n\t\t\t</label>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"pendingFile\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t@click=\"clearPendingFile\">\n\t\t\t\t{{ t('nextcloud-vue', 'Remove') }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<p v-if=\"pendingFile\" class=\"cn-header-widget-form__pending\">\n\t\t\t{{ t('nextcloud-vue', 'Ready to upload on save: {name}', { name: pendingFile.name }) }}\n\t\t</p>\n\t\t<p v-if=\"uploadError\" class=\"cn-header-widget-form__error\" role=\"alert\">\n\t\t\t{{ uploadError }}\n\t\t</p>\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"backgroundImageUrl\"\n\t\t\t:label=\"t('nextcloud-vue', 'Background image URL')\"\n\t\t\tplaceholder=\"https://example.com/banner.jpg\"\n\t\t\t:disabled=\"!!pendingFile\"\n\t\t\t@update:modelValue=\"updateField('backgroundImageUrl', $event)\" />\n\n\t\t<label class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Background color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"backgroundColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('backgroundColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('backgroundColor', '')\" />\n\t\t</label>\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"overlayMode\"\n\t\t\t:options=\"overlayModeOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Overlay mode')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('overlayMode', $event)\" />\n\n\t\t<label v-if=\"overlayMode !== 'none'\" class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Overlay color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"overlayColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('overlayColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('overlayColor', '')\" />\n\t\t</label>\n\n\t\t<label v-if=\"overlayMode === 'tint'\" class=\"cn-header-widget-form__field\">\n\t\t\t<span class=\"cn-header-widget-form__label\">\n\t\t\t\t{{ t('nextcloud-vue', 'Overlay opacity') }} ({{ overlayOpacity.toFixed(2) }})\n\t\t\t</span>\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\tmin=\"0\"\n\t\t\t\tmax=\"1\"\n\t\t\t\tstep=\"0.05\"\n\t\t\t\t:value=\"overlayOpacity\"\n\t\t\t\tclass=\"cn-header-widget-form__range\"\n\t\t\t\t@input=\"updateField('overlayOpacity', parseFloat($event.target.value))\">\n\t\t</label>\n\n\t\t<label class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Text color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"textColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('textColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('textColor', '')\" />\n\t\t</label>\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"textAlign\"\n\t\t\t:options=\"textAlignOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Text alignment')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('textAlign', $event)\" />\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"verticalAlign\"\n\t\t\t:options=\"verticalAlignOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Vertical alignment')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('verticalAlign', $event)\" />\n\n\t\t<!-- The banner fills its dashboard grid cell, so a fixed Height preset\n\t\t no longer changes anything — resize the widget on the grid instead.\n\t\t (The `height` field is kept in the saved content for back-compat\n\t\t but is no longer surfaced as a control.) -->\n\n\t\t<fieldset class=\"cn-header-widget-form__fieldset\">\n\t\t\t<legend class=\"cn-header-widget-form__legend\">\n\t\t\t\t{{ t('nextcloud-vue', 'Call-to-action button (optional)') }}\n\t\t\t</legend>\n\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"ctaLabel\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Button text')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Sign up')\"\n\t\t\t\t@update:modelValue=\"updateCta('label', $event)\" />\n\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"ctaUrl\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Target URL')\"\n\t\t\t\tplaceholder=\"https://...\"\n\t\t\t\t@update:modelValue=\"updateCta('url', $event)\" />\n\n\t\t\t<NcSelect\n\t\t\t\t:modelValue=\"ctaStyle\"\n\t\t\t\t:options=\"ctaStyleOptions\"\n\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Button style')\"\n\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\tlabel=\"label\"\n\t\t\t\t:clearable=\"false\"\n\t\t\t\t@update:modelValue=\"updateCta('style', $event)\" />\n\t\t</fieldset>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnColorPicker from '../CnColorPicker/CnColorPicker.vue'\nimport { embedAsDataUrl, extractTransportUrl, readFileAsDataUrl, warnUploadFnDeprecated } from '../../utils/widgetUpload.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 DEFAULT_CONTENT = Object.freeze({\n\ttitle: '',\n\tsubtitle: '',\n\tbackgroundImageUrl: '',\n\tbackgroundImageFileId: null,\n\tbackgroundColor: '',\n\toverlayMode: 'none',\n\toverlayColor: '',\n\toverlayOpacity: 0.4,\n\ttextColor: '',\n\ttextAlign: 'center',\n\tverticalAlign: 'middle',\n\theight: 'medium',\n\tcta: null,\n})\n\n/**\n * CnHeaderWidgetForm — the `CnAddWidgetModal` sub-form for creating or editing\n * a `header` widget placement (renderer: `CnHeaderWidget`).\n *\n * Controls cover every persisted field: title, subtitle, background image URL,\n * background colour, overlay mode/colour/opacity, text colour/alignment/\n * vertical alignment, height preset, and an optional call-to-action with\n * label, URL, and style. `validate()` requires `title`, requires any provided\n * `backgroundImageUrl` to be http(s), and flags a half-filled CTA (label XOR\n * url). Emits `update:content` with the assembled blob on change.\n */\nexport default {\n\tname: 'CnHeaderWidgetForm',\n\n\tcomponents: {\n\t\tNcTextField,\n\t\tNcSelect,\n\t\tNcButton,\n\t\tCnColorPicker,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited, or `null` in create mode.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Initial content values — used when not editing and the parent\n\t\t * supplies registry defaults.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_CONTENT }),\n\t\t},\n\n\t\t/**\n\t\t * Optional raw-file upload transport: `async (file: File) => ({ url })`.\n\t\t * Named `fileUploadFn` (not `uploadFn`) to match `CnAddWidgetModal`'s\n\t\t * File-typed sub-form transport. Called by `commit()` on submit (never on\n\t\t * file selection) with the raw picked `File`; the returned hosted URL is\n\t\t * stored as `backgroundImageUrl`. When omitted, the file is embedded as a\n\t\t * data URL on commit instead (same-origin, so it isn't blocked by the CSP\n\t\t * the way external http(s) URLs are) — but only when the raw file is ≤ 1 MB\n\t\t * (~1.37 MB once base64-encoded and stored) so a huge inline blob can't\n\t\t * freeze the tab. Wire a transport for anything larger.\n\t\t *\n\t\t * @type {((file: File) => Promise<{ url: string }>)|null}\n\t\t */\n\t\tfileUploadFn: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Legacy base64 upload transport, superseded by `fileUploadFn`.\n\t\t *\n\t\t * @deprecated Use `fileUploadFn` instead. Legacy base64 transport\n\t\t * `async (dataUrl: string) => ({ url })`, kept for backward compatibility.\n\t\t * When `fileUploadFn` is not set but this is, `commit()` reads the file to\n\t\t * a data URL and hands that to this function (emitting a one-time\n\t\t * console.warn). `fileUploadFn` takes precedence when both are provided.\n\t\t *\n\t\t * @type {((dataUrl: string) => Promise<{ url: string }>)|null}\n\t\t */\n\t\tuploadFn: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the assembled content blob on every field change.\n\t\t *\n\t\t * @event update:content\n\t\t * @type {object}\n\t\t */\n\t\t'update:content',\n\t],\n\n\tdata() {\n\t\tconst initial = (this.editingWidget && this.editingWidget.content) || this.value || {}\n\t\tconst cta = (initial.cta && typeof initial.cta === 'object') ? initial.cta : null\n\t\treturn {\n\t\t\ttitle: typeof initial.title === 'string' ? initial.title : DEFAULT_CONTENT.title,\n\t\t\tsubtitle: typeof initial.subtitle === 'string' ? initial.subtitle : DEFAULT_CONTENT.subtitle,\n\t\t\tbackgroundImageUrl: typeof initial.backgroundImageUrl === 'string'\n\t\t\t\t? initial.backgroundImageUrl\n\t\t\t\t: DEFAULT_CONTENT.backgroundImageUrl,\n\n\t\t\tbackgroundImageFileId: (typeof initial.backgroundImageFileId === 'number')\n\t\t\t\t? initial.backgroundImageFileId\n\t\t\t\t: DEFAULT_CONTENT.backgroundImageFileId,\n\n\t\t\tbackgroundColor: typeof initial.backgroundColor === 'string'\n\t\t\t\t? initial.backgroundColor\n\t\t\t\t: DEFAULT_CONTENT.backgroundColor,\n\n\t\t\toverlayMode: ALLOWED_OVERLAY_MODES.includes(initial.overlayMode)\n\t\t\t\t? initial.overlayMode\n\t\t\t\t: DEFAULT_CONTENT.overlayMode,\n\n\t\t\toverlayColor: typeof initial.overlayColor === 'string'\n\t\t\t\t? initial.overlayColor\n\t\t\t\t: DEFAULT_CONTENT.overlayColor,\n\n\t\t\toverlayOpacity: typeof initial.overlayOpacity === 'number'\n\t\t\t\t? initial.overlayOpacity\n\t\t\t\t: DEFAULT_CONTENT.overlayOpacity,\n\n\t\t\ttextColor: typeof initial.textColor === 'string'\n\t\t\t\t? initial.textColor\n\t\t\t\t: DEFAULT_CONTENT.textColor,\n\n\t\t\ttextAlign: ALLOWED_TEXT_ALIGN.includes(initial.textAlign)\n\t\t\t\t? initial.textAlign\n\t\t\t\t: DEFAULT_CONTENT.textAlign,\n\n\t\t\tverticalAlign: ALLOWED_VERTICAL_ALIGN.includes(initial.verticalAlign)\n\t\t\t\t? initial.verticalAlign\n\t\t\t\t: DEFAULT_CONTENT.verticalAlign,\n\n\t\t\theight: ALLOWED_HEIGHTS.includes(initial.height)\n\t\t\t\t? initial.height\n\t\t\t\t: DEFAULT_CONTENT.height,\n\n\t\t\tctaLabel: cta && typeof cta.label === 'string' ? cta.label : '',\n\t\t\tctaUrl: cta && typeof cta.url === 'string' ? cta.url : '',\n\t\t\tctaStyle: (cta && ALLOWED_CTA_STYLES.includes(cta.style)) ? cta.style : 'primary',\n\t\t\tuploading: false,\n\t\t\tuploadError: '',\n\t\t\t// The picked-but-not-yet-uploaded file. Upload is deferred to commit()\n\t\t\t// so nothing is written on selection.\n\t\t\tpendingFile: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Overlay-mode select options. */\n\t\toverlayModeOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'none', label: t('nextcloud-vue', 'None') },\n\t\t\t\t{ value: 'tint', label: t('nextcloud-vue', 'Tinted overlay') },\n\t\t\t\t{ value: 'gradient-bottom', label: t('nextcloud-vue', 'Gradient bottom') },\n\t\t\t]\n\t\t},\n\n\t\t/** Text-alignment select options. */\n\t\ttextAlignOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'left', label: t('nextcloud-vue', 'Left') },\n\t\t\t\t{ value: 'center', label: t('nextcloud-vue', 'Center') },\n\t\t\t\t{ value: 'right', label: t('nextcloud-vue', 'Right') },\n\t\t\t]\n\t\t},\n\n\t\t/** Vertical-alignment select options. */\n\t\tverticalAlignOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'top', label: t('nextcloud-vue', 'Top') },\n\t\t\t\t{ value: 'middle', label: t('nextcloud-vue', 'Middle') },\n\t\t\t\t{ value: 'bottom', label: t('nextcloud-vue', 'Bottom') },\n\t\t\t]\n\t\t},\n\n\t\t/** Height-preset select options. */\n\t\theightOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'small', label: t('nextcloud-vue', 'Small (120px)') },\n\t\t\t\t{ value: 'medium', label: t('nextcloud-vue', 'Medium (200px)') },\n\t\t\t\t{ value: 'large', label: t('nextcloud-vue', 'Large (320px)') },\n\t\t\t\t{ value: 'xlarge', label: t('nextcloud-vue', 'Extra large (480px)') },\n\t\t\t]\n\t\t},\n\n\t\t/** CTA-style select options. */\n\t\tctaStyleOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'primary', label: t('nextcloud-vue', 'Primary') },\n\t\t\t\t{ value: 'secondary', label: t('nextcloud-vue', 'Secondary') },\n\t\t\t\t{ value: 'ghost', label: t('nextcloud-vue', 'Ghost') },\n\t\t\t]\n\t\t},\n\n\t\t/** The assembled CTA object, or null when both fields are empty. */\n\t\tctaPayload() {\n\t\t\tconst label = typeof this.ctaLabel === 'string' ? this.ctaLabel.trim() : ''\n\t\t\tconst url = typeof this.ctaUrl === 'string' ? this.ctaUrl.trim() : ''\n\t\t\tif (label === '' && url === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tlabel,\n\t\t\t\turl,\n\t\t\t\tstyle: this.ctaStyle,\n\t\t\t}\n\t\t},\n\n\t\t/** The full content blob assembled from the current field values. */\n\t\tassembledContent() {\n\t\t\treturn {\n\t\t\t\ttitle: this.title,\n\t\t\t\tsubtitle: this.subtitle,\n\t\t\t\tbackgroundImageUrl: this.backgroundImageUrl,\n\t\t\t\tbackgroundImageFileId: this.backgroundImageFileId,\n\t\t\t\tbackgroundColor: this.backgroundColor,\n\t\t\t\toverlayMode: this.overlayMode,\n\t\t\t\toverlayColor: this.overlayColor,\n\t\t\t\toverlayOpacity: this.overlayOpacity,\n\t\t\t\ttextColor: this.textColor,\n\t\t\t\ttextAlign: this.textAlign,\n\t\t\t\tverticalAlign: this.verticalAlign,\n\t\t\t\theight: this.height,\n\t\t\t\tcta: this.ctaPayload,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Set a top-level field and re-emit the assembled payload.\n\t\t *\n\t\t * @param {string} field one of the top-level content keys.\n\t\t * @param {unknown} value the new value.\n\t\t * @return {void}\n\t\t */\n\t\tupdateField(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Hold the picked file for a deferred upload. Does NOT upload — that\n\t\t * happens in commit() when the host modal submits, so re-picking or\n\t\t * cancelling writes nothing.\n\t\t *\n\t\t * @param {Event} event the file-input change event.\n\t\t * @return {void}\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\thandleFileSelect(event) {\n\t\t\tconst file = event.target.files && event.target.files[0]\n\t\t\tif (!file) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tthis.pendingFile = file\n\t\t\tthis.resetFileInput()\n\t\t},\n\n\t\t/**\n\t\t * Upload the pending file (if any) and store it as backgroundImageUrl.\n\t\t * Called by the host modal on submit. A no-op when no file is pending, so\n\t\t * editing without changing the image keeps the existing value.\n\t\t *\n\t\t * @return {Promise<void>} resolves once the URL is set.\n\t\t * @throws {Error} when the upload fails, so the modal can block submit.\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\tasync commit() {\n\t\t\tif (this.pendingFile === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tthis.uploading = true\n\t\t\ttry {\n\t\t\t\tlet resolvedUrl\n\t\t\t\tif (typeof this.fileUploadFn === 'function') {\n\t\t\t\t\tresolvedUrl = extractTransportUrl(await this.fileUploadFn(this.pendingFile))\n\t\t\t\t} else if (typeof this.uploadFn === 'function') {\n\t\t\t\t\t// Deprecated path: the legacy uploadFn expects a base64 data URL.\n\t\t\t\t\twarnUploadFnDeprecated('CnHeaderWidgetForm')\n\t\t\t\t\tconst dataUrl = await readFileAsDataUrl(this.pendingFile)\n\t\t\t\t\tresolvedUrl = extractTransportUrl(await this.uploadFn(dataUrl))\n\t\t\t\t} else {\n\t\t\t\t\tresolvedUrl = await embedAsDataUrl(this.pendingFile)\n\t\t\t\t}\n\t\t\t\tthis.pendingFile = null\n\t\t\t\tthis.updateField('backgroundImageUrl', resolvedUrl)\n\t\t\t} catch (err) {\n\t\t\t\tthis.uploadError = (err && err.message) || t('nextcloud-vue', 'Failed to upload image')\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('Header image upload failed:', err)\n\t\t\t\tthrow err\n\t\t\t} finally {\n\t\t\t\tthis.uploading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Discard the pending file, re-enabling the URL field. Called by the\n\t\t * Remove button.\n\t\t *\n\t\t * @return {void}\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\tclearPendingFile() {\n\t\t\tthis.pendingFile = null\n\t\t\tthis.uploadError = ''\n\t\t\tthis.resetFileInput()\n\t\t},\n\n\t\t/**\n\t\t * Clear the native file input so re-selecting the same file re-fires.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetFileInput() {\n\t\t\tif (this.$refs.fileInput) {\n\t\t\t\tthis.$refs.fileInput.value = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Set a CTA sub-field (label, url, style) and re-emit.\n\t\t *\n\t\t * @param {string} field one of: label, url, style.\n\t\t * @param {string} value the new value.\n\t\t * @return {void}\n\t\t */\n\t\tupdateCta(field, value) {\n\t\t\tif (field === 'label') {\n\t\t\t\tthis.ctaLabel = value\n\t\t\t} else if (field === 'url') {\n\t\t\t\tthis.ctaUrl = value\n\t\t\t} else if (field === 'style') {\n\t\t\t\tthis.ctaStyle = value\n\t\t\t}\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Validate the form; an empty array means valid. Requires a title,\n\t\t * an http(s) background image URL when provided, and a complete CTA\n\t\t * when either CTA field is filled.\n\t\t *\n\t\t * @return {string[]} the validation errors.\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = []\n\t\t\tif (typeof this.title !== 'string' || this.title.trim() === '') {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Title is required'))\n\t\t\t}\n\t\t\tif (typeof this.backgroundImageUrl === 'string'\n\t\t\t\t&& this.backgroundImageUrl.trim() !== ''\n\t\t\t\t&& /^(https?:\\/\\/|data:|blob:|\\/)/i.test(this.backgroundImageUrl.trim()) === false) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Background image must be a URL or an uploaded image'))\n\t\t\t}\n\t\t\tconst labelEmpty = typeof this.ctaLabel !== 'string' || this.ctaLabel.trim() === ''\n\t\t\tconst urlEmpty = typeof this.ctaUrl !== 'string' || this.ctaUrl.trim() === ''\n\t\t\tif (labelEmpty !== urlEmpty) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Call-to-action requires both label and URL'))\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-header-widget-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-header-widget-form__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__label {\n\tfont-weight: 500;\n}\n\n.cn-header-widget-form__color-label {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__upload-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-header-widget-form__upload-label {\n\tdisplay: inline-flex;\n\tcursor: pointer;\n}\n\n.cn-header-widget-form__pending {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-header-widget-form__file-input {\n\tdisplay: none;\n}\n\n.cn-header-widget-form__upload-button {\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-hover);\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__upload-label:hover .cn-header-widget-form__upload-button {\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-header-widget-form__error {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-error);\n}\n\n.cn-header-widget-form__color {\n\twidth: 48px;\n\theight: 32px;\n\tpadding: 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n\tbackground: transparent;\n}\n\n.cn-header-widget-form__range {\n\twidth: 100%;\n}\n\n.cn-header-widget-form__fieldset {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 12px;\n}\n\n.cn-header-widget-form__legend {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tpadding: 0 4px;\n}\n</style>\n"],"names":["CnColorPicker","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnHeaderWidgetForm.vue2.js","sources":["../../../../src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.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-form\">\n\t\t<NcTextField\n\t\t\t:modelValue=\"title\"\n\t\t\t:label=\"t('nextcloud-vue', 'Title')\"\n\t\t\t:placeholder=\"t('nextcloud-vue', 'Header title')\"\n\t\t\trequired\n\t\t\t@update:modelValue=\"updateField('title', $event)\" />\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"subtitle\"\n\t\t\t:label=\"t('nextcloud-vue', 'Subtitle (optional)')\"\n\t\t\t:placeholder=\"t('nextcloud-vue', 'Optional subtitle')\"\n\t\t\t@update:modelValue=\"updateField('subtitle', $event)\" />\n\n\t\t<NcCheckboxRadioSwitch\n\t\t\t:modelValue=\"greeting\"\n\t\t\t@update:modelValue=\"updateField('greeting', $event)\">\n\t\t\t{{ t('nextcloud-vue', 'Greet the user by name instead of showing the title') }}\n\t\t</NcCheckboxRadioSwitch>\n\t\t<NcCheckboxRadioSwitch\n\t\t\t:modelValue=\"showDate\"\n\t\t\t@update:modelValue=\"updateField('showDate', $event)\">\n\t\t\t{{ t('nextcloud-vue', 'Show the date of today above the heading') }}\n\t\t</NcCheckboxRadioSwitch>\n\t\t<NcCheckboxRadioSwitch\n\t\t\t:modelValue=\"plain\"\n\t\t\t@update:modelValue=\"updateField('plain', $event)\">\n\t\t\t{{ t('nextcloud-vue', 'Plain header without a coloured background') }}\n\t\t</NcCheckboxRadioSwitch>\n\n\t\t<!-- Pick a background image. Selection does NOT upload — the file is held\n\t\t and only uploaded when the host modal calls commit() on submit, so\n\t\t re-picking or cancelling writes nothing. Uploads are the reliable\n\t\t path: external image URLs are often blocked by the Nextcloud\n\t\t Content-Security-Policy, so only the background colour would show. -->\n\t\t<div class=\"cn-header-widget-form__upload-row\">\n\t\t\t<label class=\"cn-header-widget-form__upload-label\">\n\t\t\t\t<input\n\t\t\t\t\tref=\"fileInput\"\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\taccept=\"image/*\"\n\t\t\t\t\tclass=\"cn-header-widget-form__file-input\"\n\t\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t\t@change=\"handleFileSelect\">\n\t\t\t\t<span class=\"cn-header-widget-form__upload-button\">\n\t\t\t\t\t{{ pendingFile ? t('nextcloud-vue', 'Change background image') : t('nextcloud-vue', 'Choose background image') }}\n\t\t\t\t</span>\n\t\t\t</label>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"pendingFile\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t@click=\"clearPendingFile\">\n\t\t\t\t{{ t('nextcloud-vue', 'Remove') }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<p v-if=\"pendingFile\" class=\"cn-header-widget-form__pending\">\n\t\t\t{{ t('nextcloud-vue', 'Ready to upload on save: {name}', { name: pendingFile.name }) }}\n\t\t</p>\n\t\t<p v-if=\"uploadError\" class=\"cn-header-widget-form__error\" role=\"alert\">\n\t\t\t{{ uploadError }}\n\t\t</p>\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"backgroundImageUrl\"\n\t\t\t:label=\"t('nextcloud-vue', 'Background image URL')\"\n\t\t\tplaceholder=\"https://example.com/banner.jpg\"\n\t\t\t:disabled=\"!!pendingFile\"\n\t\t\t@update:modelValue=\"updateField('backgroundImageUrl', $event)\" />\n\n\t\t<label class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Background color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"backgroundColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('backgroundColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('backgroundColor', '')\" />\n\t\t</label>\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"overlayMode\"\n\t\t\t:options=\"overlayModeOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Overlay mode')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('overlayMode', $event)\" />\n\n\t\t<label v-if=\"overlayMode !== 'none'\" class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Overlay color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"overlayColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('overlayColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('overlayColor', '')\" />\n\t\t</label>\n\n\t\t<label v-if=\"overlayMode === 'tint'\" class=\"cn-header-widget-form__field\">\n\t\t\t<span class=\"cn-header-widget-form__label\">\n\t\t\t\t{{ t('nextcloud-vue', 'Overlay opacity') }} ({{ overlayOpacity.toFixed(2) }})\n\t\t\t</span>\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\tmin=\"0\"\n\t\t\t\tmax=\"1\"\n\t\t\t\tstep=\"0.05\"\n\t\t\t\t:value=\"overlayOpacity\"\n\t\t\t\tclass=\"cn-header-widget-form__range\"\n\t\t\t\t@input=\"updateField('overlayOpacity', parseFloat($event.target.value))\">\n\t\t</label>\n\n\t\t<label class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Text color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"textColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('textColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('textColor', '')\" />\n\t\t</label>\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"textAlign\"\n\t\t\t:options=\"textAlignOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Text alignment')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('textAlign', $event)\" />\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"verticalAlign\"\n\t\t\t:options=\"verticalAlignOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Vertical alignment')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('verticalAlign', $event)\" />\n\n\t\t<!-- The banner fills its dashboard grid cell, so a fixed Height preset\n\t\t no longer changes anything — resize the widget on the grid instead.\n\t\t (The `height` field is kept in the saved content for back-compat\n\t\t but is no longer surfaced as a control.) -->\n\n\t\t<fieldset class=\"cn-header-widget-form__fieldset\">\n\t\t\t<legend class=\"cn-header-widget-form__legend\">\n\t\t\t\t{{ t('nextcloud-vue', 'Call-to-action button (optional)') }}\n\t\t\t</legend>\n\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"ctaLabel\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Button text')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Sign up')\"\n\t\t\t\t@update:modelValue=\"updateCta('label', $event)\" />\n\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"ctaUrl\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Target URL')\"\n\t\t\t\tplaceholder=\"https://...\"\n\t\t\t\t@update:modelValue=\"updateCta('url', $event)\" />\n\n\t\t\t<NcSelect\n\t\t\t\t:modelValue=\"ctaStyle\"\n\t\t\t\t:options=\"ctaStyleOptions\"\n\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Button style')\"\n\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\tlabel=\"label\"\n\t\t\t\t:clearable=\"false\"\n\t\t\t\t@update:modelValue=\"updateCta('style', $event)\" />\n\t\t</fieldset>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnColorPicker from '../CnColorPicker/CnColorPicker.vue'\nimport { embedAsDataUrl, extractTransportUrl, readFileAsDataUrl, warnUploadFnDeprecated } from '../../utils/widgetUpload.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 DEFAULT_CONTENT = Object.freeze({\n\ttitle: '',\n\tsubtitle: '',\n\tbackgroundImageUrl: '',\n\tbackgroundImageFileId: null,\n\tbackgroundColor: '',\n\toverlayMode: 'none',\n\toverlayColor: '',\n\toverlayOpacity: 0.4,\n\ttextColor: '',\n\ttextAlign: 'center',\n\tverticalAlign: 'middle',\n\theight: 'medium',\n\tcta: null,\n})\n\n/**\n * CnHeaderWidgetForm — the `CnAddWidgetModal` sub-form for creating or editing\n * a `header` widget placement (renderer: `CnHeaderWidget`).\n *\n * Controls cover every persisted field: title, subtitle, background image URL,\n * background colour, overlay mode/colour/opacity, text colour/alignment/\n * vertical alignment, height preset, and an optional call-to-action with\n * label, URL, and style. `validate()` requires `title`, requires any provided\n * `backgroundImageUrl` to be http(s), and flags a half-filled CTA (label XOR\n * url). Emits `update:content` with the assembled blob on change.\n */\nexport default {\n\tname: 'CnHeaderWidgetForm',\n\n\tcomponents: {\n\t\tNcTextField,\n\t\tNcSelect,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tCnColorPicker,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited, or `null` in create mode.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Initial content values — used when not editing and the parent\n\t\t * supplies registry defaults.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_CONTENT }),\n\t\t},\n\n\t\t/**\n\t\t * Optional raw-file upload transport: `async (file: File) => ({ url })`.\n\t\t * Named `fileUploadFn` (not `uploadFn`) to match `CnAddWidgetModal`'s\n\t\t * File-typed sub-form transport. Called by `commit()` on submit (never on\n\t\t * file selection) with the raw picked `File`; the returned hosted URL is\n\t\t * stored as `backgroundImageUrl`. When omitted, the file is embedded as a\n\t\t * data URL on commit instead (same-origin, so it isn't blocked by the CSP\n\t\t * the way external http(s) URLs are) — but only when the raw file is ≤ 1 MB\n\t\t * (~1.37 MB once base64-encoded and stored) so a huge inline blob can't\n\t\t * freeze the tab. Wire a transport for anything larger.\n\t\t *\n\t\t * @type {((file: File) => Promise<{ url: string }>)|null}\n\t\t */\n\t\tfileUploadFn: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Legacy base64 upload transport, superseded by `fileUploadFn`.\n\t\t *\n\t\t * @deprecated Use `fileUploadFn` instead. Legacy base64 transport\n\t\t * `async (dataUrl: string) => ({ url })`, kept for backward compatibility.\n\t\t * When `fileUploadFn` is not set but this is, `commit()` reads the file to\n\t\t * a data URL and hands that to this function (emitting a one-time\n\t\t * console.warn). `fileUploadFn` takes precedence when both are provided.\n\t\t *\n\t\t * @type {((dataUrl: string) => Promise<{ url: string }>)|null}\n\t\t */\n\t\tuploadFn: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the assembled content blob on every field change.\n\t\t *\n\t\t * @event update:content\n\t\t * @type {object}\n\t\t */\n\t\t'update:content',\n\t],\n\n\tdata() {\n\t\tconst initial = (this.editingWidget && this.editingWidget.content) || this.value || {}\n\t\tconst cta = (initial.cta && typeof initial.cta === 'object') ? initial.cta : null\n\t\treturn {\n\t\t\ttitle: typeof initial.title === 'string' ? initial.title : DEFAULT_CONTENT.title,\n\t\t\tsubtitle: typeof initial.subtitle === 'string' ? initial.subtitle : DEFAULT_CONTENT.subtitle,\n\t\t\tgreeting: initial.greeting === true || initial.greeting === 'full' || initial.greeting === 'first',\n\t\t\t// `\"full\"` (whole display name) is a manifest choice; kept as written.\n\t\t\tgreetingMode: initial.greeting === 'full' ? 'full' : true,\n\t\t\tshowDate: initial.showDate === true,\n\t\t\tplain: initial.plain === true,\n\t\t\tbackgroundImageUrl: typeof initial.backgroundImageUrl === 'string'\n\t\t\t\t? initial.backgroundImageUrl\n\t\t\t\t: DEFAULT_CONTENT.backgroundImageUrl,\n\n\t\t\tbackgroundImageFileId: (typeof initial.backgroundImageFileId === 'number')\n\t\t\t\t? initial.backgroundImageFileId\n\t\t\t\t: DEFAULT_CONTENT.backgroundImageFileId,\n\n\t\t\tbackgroundColor: typeof initial.backgroundColor === 'string'\n\t\t\t\t? initial.backgroundColor\n\t\t\t\t: DEFAULT_CONTENT.backgroundColor,\n\n\t\t\toverlayMode: ALLOWED_OVERLAY_MODES.includes(initial.overlayMode)\n\t\t\t\t? initial.overlayMode\n\t\t\t\t: DEFAULT_CONTENT.overlayMode,\n\n\t\t\toverlayColor: typeof initial.overlayColor === 'string'\n\t\t\t\t? initial.overlayColor\n\t\t\t\t: DEFAULT_CONTENT.overlayColor,\n\n\t\t\toverlayOpacity: typeof initial.overlayOpacity === 'number'\n\t\t\t\t? initial.overlayOpacity\n\t\t\t\t: DEFAULT_CONTENT.overlayOpacity,\n\n\t\t\ttextColor: typeof initial.textColor === 'string'\n\t\t\t\t? initial.textColor\n\t\t\t\t: DEFAULT_CONTENT.textColor,\n\n\t\t\ttextAlign: ALLOWED_TEXT_ALIGN.includes(initial.textAlign)\n\t\t\t\t? initial.textAlign\n\t\t\t\t: DEFAULT_CONTENT.textAlign,\n\n\t\t\tverticalAlign: ALLOWED_VERTICAL_ALIGN.includes(initial.verticalAlign)\n\t\t\t\t? initial.verticalAlign\n\t\t\t\t: DEFAULT_CONTENT.verticalAlign,\n\n\t\t\theight: ALLOWED_HEIGHTS.includes(initial.height)\n\t\t\t\t? initial.height\n\t\t\t\t: DEFAULT_CONTENT.height,\n\n\t\t\tctaLabel: cta && typeof cta.label === 'string' ? cta.label : '',\n\t\t\tctaUrl: cta && typeof cta.url === 'string' ? cta.url : '',\n\t\t\tctaStyle: (cta && ALLOWED_CTA_STYLES.includes(cta.style)) ? cta.style : 'primary',\n\t\t\tuploading: false,\n\t\t\tuploadError: '',\n\t\t\t// The picked-but-not-yet-uploaded file. Upload is deferred to commit()\n\t\t\t// so nothing is written on selection.\n\t\t\tpendingFile: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Overlay-mode select options. */\n\t\toverlayModeOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'none', label: t('nextcloud-vue', 'None') },\n\t\t\t\t{ value: 'tint', label: t('nextcloud-vue', 'Tinted overlay') },\n\t\t\t\t{ value: 'gradient-bottom', label: t('nextcloud-vue', 'Gradient bottom') },\n\t\t\t]\n\t\t},\n\n\t\t/** Text-alignment select options. */\n\t\ttextAlignOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'left', label: t('nextcloud-vue', 'Left') },\n\t\t\t\t{ value: 'center', label: t('nextcloud-vue', 'Center') },\n\t\t\t\t{ value: 'right', label: t('nextcloud-vue', 'Right') },\n\t\t\t]\n\t\t},\n\n\t\t/** Vertical-alignment select options. */\n\t\tverticalAlignOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'top', label: t('nextcloud-vue', 'Top') },\n\t\t\t\t{ value: 'middle', label: t('nextcloud-vue', 'Middle') },\n\t\t\t\t{ value: 'bottom', label: t('nextcloud-vue', 'Bottom') },\n\t\t\t]\n\t\t},\n\n\t\t/** Height-preset select options. */\n\t\theightOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'small', label: t('nextcloud-vue', 'Small (120px)') },\n\t\t\t\t{ value: 'medium', label: t('nextcloud-vue', 'Medium (200px)') },\n\t\t\t\t{ value: 'large', label: t('nextcloud-vue', 'Large (320px)') },\n\t\t\t\t{ value: 'xlarge', label: t('nextcloud-vue', 'Extra large (480px)') },\n\t\t\t]\n\t\t},\n\n\t\t/** CTA-style select options. */\n\t\tctaStyleOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'primary', label: t('nextcloud-vue', 'Primary') },\n\t\t\t\t{ value: 'secondary', label: t('nextcloud-vue', 'Secondary') },\n\t\t\t\t{ value: 'ghost', label: t('nextcloud-vue', 'Ghost') },\n\t\t\t]\n\t\t},\n\n\t\t/** The assembled CTA object, or null when both fields are empty. */\n\t\tctaPayload() {\n\t\t\tconst label = typeof this.ctaLabel === 'string' ? this.ctaLabel.trim() : ''\n\t\t\tconst url = typeof this.ctaUrl === 'string' ? this.ctaUrl.trim() : ''\n\t\t\tif (label === '' && url === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tlabel,\n\t\t\t\turl,\n\t\t\t\tstyle: this.ctaStyle,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The greeting keys, written only when they are on, so a header that\n\t\t * never used them keeps the content blob it always had.\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\tgreetingContent() {\n\t\t\tconst content = {}\n\t\t\tif (this.greeting) {\n\t\t\t\tcontent.greeting = this.greetingMode\n\t\t\t}\n\t\t\tif (this.showDate) {\n\t\t\t\tcontent.showDate = true\n\t\t\t}\n\t\t\tif (this.plain) {\n\t\t\t\tcontent.plain = true\n\t\t\t}\n\t\t\treturn content\n\t\t},\n\n\t\t/**\n\t\t * The full content blob assembled from the current field values.\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\tassembledContent() {\n\t\t\treturn {\n\t\t\t\t...this.greetingContent,\n\t\t\t\ttitle: this.title,\n\t\t\t\tsubtitle: this.subtitle,\n\t\t\t\tbackgroundImageUrl: this.backgroundImageUrl,\n\t\t\t\tbackgroundImageFileId: this.backgroundImageFileId,\n\t\t\t\tbackgroundColor: this.backgroundColor,\n\t\t\t\toverlayMode: this.overlayMode,\n\t\t\t\toverlayColor: this.overlayColor,\n\t\t\t\toverlayOpacity: this.overlayOpacity,\n\t\t\t\ttextColor: this.textColor,\n\t\t\t\ttextAlign: this.textAlign,\n\t\t\t\tverticalAlign: this.verticalAlign,\n\t\t\t\theight: this.height,\n\t\t\t\tcta: this.ctaPayload,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Set a top-level field and re-emit the assembled payload.\n\t\t *\n\t\t * @param {string} field one of the top-level content keys.\n\t\t * @param {unknown} value the new value.\n\t\t * @return {void}\n\t\t */\n\t\tupdateField(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Hold the picked file for a deferred upload. Does NOT upload — that\n\t\t * happens in commit() when the host modal submits, so re-picking or\n\t\t * cancelling writes nothing.\n\t\t *\n\t\t * @param {Event} event the file-input change event.\n\t\t * @return {void}\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\thandleFileSelect(event) {\n\t\t\tconst file = event.target.files && event.target.files[0]\n\t\t\tif (!file) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tthis.pendingFile = file\n\t\t\tthis.resetFileInput()\n\t\t},\n\n\t\t/**\n\t\t * Upload the pending file (if any) and store it as backgroundImageUrl.\n\t\t * Called by the host modal on submit. A no-op when no file is pending, so\n\t\t * editing without changing the image keeps the existing value.\n\t\t *\n\t\t * @return {Promise<void>} resolves once the URL is set.\n\t\t * @throws {Error} when the upload fails, so the modal can block submit.\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\tasync commit() {\n\t\t\tif (this.pendingFile === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tthis.uploading = true\n\t\t\ttry {\n\t\t\t\tlet resolvedUrl\n\t\t\t\tif (typeof this.fileUploadFn === 'function') {\n\t\t\t\t\tresolvedUrl = extractTransportUrl(await this.fileUploadFn(this.pendingFile))\n\t\t\t\t} else if (typeof this.uploadFn === 'function') {\n\t\t\t\t\t// Deprecated path: the legacy uploadFn expects a base64 data URL.\n\t\t\t\t\twarnUploadFnDeprecated('CnHeaderWidgetForm')\n\t\t\t\t\tconst dataUrl = await readFileAsDataUrl(this.pendingFile)\n\t\t\t\t\tresolvedUrl = extractTransportUrl(await this.uploadFn(dataUrl))\n\t\t\t\t} else {\n\t\t\t\t\tresolvedUrl = await embedAsDataUrl(this.pendingFile)\n\t\t\t\t}\n\t\t\t\tthis.pendingFile = null\n\t\t\t\tthis.updateField('backgroundImageUrl', resolvedUrl)\n\t\t\t} catch (err) {\n\t\t\t\tthis.uploadError = (err && err.message) || t('nextcloud-vue', 'Failed to upload image')\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('Header image upload failed:', err)\n\t\t\t\tthrow err\n\t\t\t} finally {\n\t\t\t\tthis.uploading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Discard the pending file, re-enabling the URL field. Called by the\n\t\t * Remove button.\n\t\t *\n\t\t * @return {void}\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\tclearPendingFile() {\n\t\t\tthis.pendingFile = null\n\t\t\tthis.uploadError = ''\n\t\t\tthis.resetFileInput()\n\t\t},\n\n\t\t/**\n\t\t * Clear the native file input so re-selecting the same file re-fires.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetFileInput() {\n\t\t\tif (this.$refs.fileInput) {\n\t\t\t\tthis.$refs.fileInput.value = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Set a CTA sub-field (label, url, style) and re-emit.\n\t\t *\n\t\t * @param {string} field one of: label, url, style.\n\t\t * @param {string} value the new value.\n\t\t * @return {void}\n\t\t */\n\t\tupdateCta(field, value) {\n\t\t\tif (field === 'label') {\n\t\t\t\tthis.ctaLabel = value\n\t\t\t} else if (field === 'url') {\n\t\t\t\tthis.ctaUrl = value\n\t\t\t} else if (field === 'style') {\n\t\t\t\tthis.ctaStyle = value\n\t\t\t}\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Validate the form; an empty array means valid. Requires a title,\n\t\t * an http(s) background image URL when provided, and a complete CTA\n\t\t * when either CTA field is filled.\n\t\t *\n\t\t * @return {string[]} the validation errors.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = []\n\t\t\t// A greeting is the heading, so a greeting header needs no title.\n\t\t\tif (!this.greeting && (typeof this.title !== 'string' || this.title.trim() === '')) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Title is required'))\n\t\t\t}\n\t\t\tif (typeof this.backgroundImageUrl === 'string'\n\t\t\t\t&& this.backgroundImageUrl.trim() !== ''\n\t\t\t\t&& /^(https?:\\/\\/|data:|blob:|\\/)/i.test(this.backgroundImageUrl.trim()) === false) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Background image must be a URL or an uploaded image'))\n\t\t\t}\n\t\t\tconst labelEmpty = typeof this.ctaLabel !== 'string' || this.ctaLabel.trim() === ''\n\t\t\tconst urlEmpty = typeof this.ctaUrl !== 'string' || this.ctaUrl.trim() === ''\n\t\t\tif (labelEmpty !== urlEmpty) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Call-to-action requires both label and URL'))\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-header-widget-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-header-widget-form__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__label {\n\tfont-weight: 500;\n}\n\n.cn-header-widget-form__color-label {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__upload-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-header-widget-form__upload-label {\n\tdisplay: inline-flex;\n\tcursor: pointer;\n}\n\n.cn-header-widget-form__pending {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-header-widget-form__file-input {\n\tdisplay: none;\n}\n\n.cn-header-widget-form__upload-button {\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-hover);\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__upload-label:hover .cn-header-widget-form__upload-button {\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-header-widget-form__error {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-error);\n}\n\n.cn-header-widget-form__color {\n\twidth: 48px;\n\theight: 32px;\n\tpadding: 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n\tbackground: transparent;\n}\n\n.cn-header-widget-form__range {\n\twidth: 100%;\n}\n\n.cn-header-widget-form__fieldset {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 12px;\n}\n\n.cn-header-widget-form__legend {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tpadding: 0 4px;\n}\n</style>\n"],"names":["CnColorPicker","t"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveComponent, openBlock, createElementBlock, createVNode,
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, createVNode, withCtx, createTextVNode, toDisplayString, createElementVNode, createBlock, createCommentVNode } from 'vue';
|
|
2
2
|
|
|
3
3
|
const _hoisted_1 = { class: "cn-header-widget-form" };
|
|
4
4
|
const _hoisted_2 = { class: "cn-header-widget-form__upload-row" };
|
|
@@ -31,6 +31,7 @@ const _hoisted_15 = { class: "cn-header-widget-form__legend" };
|
|
|
31
31
|
|
|
32
32
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
33
33
|
const _component_NcTextField = resolveComponent("NcTextField");
|
|
34
|
+
const _component_NcCheckboxRadioSwitch = resolveComponent("NcCheckboxRadioSwitch");
|
|
34
35
|
const _component_NcButton = resolveComponent("NcButton");
|
|
35
36
|
const _component_CnColorPicker = resolveComponent("CnColorPicker");
|
|
36
37
|
const _component_NcSelect = resolveComponent("NcSelect");
|
|
@@ -49,6 +50,33 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
49
50
|
placeholder: $options.t('nextcloud-vue', 'Optional subtitle'),
|
|
50
51
|
"onUpdate:modelValue": _cache[1] || (_cache[1] = $event => ($options.updateField('subtitle', $event)))
|
|
51
52
|
}, null, 8 /* PROPS */, ["modelValue", "label", "placeholder"]),
|
|
53
|
+
createVNode(_component_NcCheckboxRadioSwitch, {
|
|
54
|
+
modelValue: $data.greeting,
|
|
55
|
+
"onUpdate:modelValue": _cache[2] || (_cache[2] = $event => ($options.updateField('greeting', $event)))
|
|
56
|
+
}, {
|
|
57
|
+
default: withCtx(() => [
|
|
58
|
+
createTextVNode(toDisplayString($options.t('nextcloud-vue', 'Greet the user by name instead of showing the title')), 1 /* TEXT */)
|
|
59
|
+
]),
|
|
60
|
+
_: 1 /* STABLE */
|
|
61
|
+
}, 8 /* PROPS */, ["modelValue"]),
|
|
62
|
+
createVNode(_component_NcCheckboxRadioSwitch, {
|
|
63
|
+
modelValue: $data.showDate,
|
|
64
|
+
"onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ($options.updateField('showDate', $event)))
|
|
65
|
+
}, {
|
|
66
|
+
default: withCtx(() => [
|
|
67
|
+
createTextVNode(toDisplayString($options.t('nextcloud-vue', 'Show the date of today above the heading')), 1 /* TEXT */)
|
|
68
|
+
]),
|
|
69
|
+
_: 1 /* STABLE */
|
|
70
|
+
}, 8 /* PROPS */, ["modelValue"]),
|
|
71
|
+
createVNode(_component_NcCheckboxRadioSwitch, {
|
|
72
|
+
modelValue: $data.plain,
|
|
73
|
+
"onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ($options.updateField('plain', $event)))
|
|
74
|
+
}, {
|
|
75
|
+
default: withCtx(() => [
|
|
76
|
+
createTextVNode(toDisplayString($options.t('nextcloud-vue', 'Plain header without a coloured background')), 1 /* TEXT */)
|
|
77
|
+
]),
|
|
78
|
+
_: 1 /* STABLE */
|
|
79
|
+
}, 8 /* PROPS */, ["modelValue"]),
|
|
52
80
|
createElementVNode("div", _hoisted_2, [
|
|
53
81
|
createElementVNode("label", _hoisted_3, [
|
|
54
82
|
createElementVNode("input", {
|
|
@@ -57,7 +85,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
57
85
|
accept: "image/*",
|
|
58
86
|
class: "cn-header-widget-form__file-input",
|
|
59
87
|
disabled: $data.uploading,
|
|
60
|
-
onChange: _cache[
|
|
88
|
+
onChange: _cache[5] || (_cache[5] = (...args) => ($options.handleFileSelect && $options.handleFileSelect(...args)))
|
|
61
89
|
}, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_4),
|
|
62
90
|
createElementVNode("span", _hoisted_5, toDisplayString($data.pendingFile ? $options.t('nextcloud-vue', 'Change background image') : $options.t('nextcloud-vue', 'Choose background image')), 1 /* TEXT */)
|
|
63
91
|
]),
|
|
@@ -86,15 +114,15 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
86
114
|
label: $options.t('nextcloud-vue', 'Background image URL'),
|
|
87
115
|
placeholder: "https://example.com/banner.jpg",
|
|
88
116
|
disabled: !!$data.pendingFile,
|
|
89
|
-
"onUpdate:modelValue": _cache[
|
|
117
|
+
"onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ($options.updateField('backgroundImageUrl', $event)))
|
|
90
118
|
}, null, 8 /* PROPS */, ["modelValue", "label", "disabled"]),
|
|
91
119
|
createElementVNode("label", _hoisted_8, [
|
|
92
120
|
createTextVNode(toDisplayString($options.t('nextcloud-vue', 'Background color')) + " ", 1 /* TEXT */),
|
|
93
121
|
createVNode(_component_CnColorPicker, {
|
|
94
122
|
value: $data.backgroundColor,
|
|
95
123
|
clearable: "",
|
|
96
|
-
onInput: _cache[
|
|
97
|
-
onClear: _cache[
|
|
124
|
+
onInput: _cache[7] || (_cache[7] = $event => ($options.updateField('backgroundColor', $event.hex))),
|
|
125
|
+
onClear: _cache[8] || (_cache[8] = $event => ($options.updateField('backgroundColor', '')))
|
|
98
126
|
}, null, 8 /* PROPS */, ["value"])
|
|
99
127
|
]),
|
|
100
128
|
createVNode(_component_NcSelect, {
|
|
@@ -104,7 +132,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
104
132
|
reduce: (option) => option.value,
|
|
105
133
|
label: "label",
|
|
106
134
|
clearable: false,
|
|
107
|
-
"onUpdate:modelValue": _cache[
|
|
135
|
+
"onUpdate:modelValue": _cache[9] || (_cache[9] = $event => ($options.updateField('overlayMode', $event)))
|
|
108
136
|
}, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel", "reduce"]),
|
|
109
137
|
($data.overlayMode !== 'none')
|
|
110
138
|
? (openBlock(), createElementBlock("label", _hoisted_9, [
|
|
@@ -112,8 +140,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
112
140
|
createVNode(_component_CnColorPicker, {
|
|
113
141
|
value: $data.overlayColor,
|
|
114
142
|
clearable: "",
|
|
115
|
-
onInput: _cache[
|
|
116
|
-
onClear: _cache[
|
|
143
|
+
onInput: _cache[10] || (_cache[10] = $event => ($options.updateField('overlayColor', $event.hex))),
|
|
144
|
+
onClear: _cache[11] || (_cache[11] = $event => ($options.updateField('overlayColor', '')))
|
|
117
145
|
}, null, 8 /* PROPS */, ["value"])
|
|
118
146
|
]))
|
|
119
147
|
: createCommentVNode("v-if", true),
|
|
@@ -127,7 +155,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
127
155
|
step: "0.05",
|
|
128
156
|
value: $data.overlayOpacity,
|
|
129
157
|
class: "cn-header-widget-form__range",
|
|
130
|
-
onInput: _cache[
|
|
158
|
+
onInput: _cache[12] || (_cache[12] = $event => ($options.updateField('overlayOpacity', parseFloat($event.target.value))))
|
|
131
159
|
}, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_12)
|
|
132
160
|
]))
|
|
133
161
|
: createCommentVNode("v-if", true),
|
|
@@ -136,8 +164,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
136
164
|
createVNode(_component_CnColorPicker, {
|
|
137
165
|
value: $data.textColor,
|
|
138
166
|
clearable: "",
|
|
139
|
-
onInput: _cache[
|
|
140
|
-
onClear: _cache[
|
|
167
|
+
onInput: _cache[13] || (_cache[13] = $event => ($options.updateField('textColor', $event.hex))),
|
|
168
|
+
onClear: _cache[14] || (_cache[14] = $event => ($options.updateField('textColor', '')))
|
|
141
169
|
}, null, 8 /* PROPS */, ["value"])
|
|
142
170
|
]),
|
|
143
171
|
createVNode(_component_NcSelect, {
|
|
@@ -147,7 +175,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
147
175
|
reduce: (option) => option.value,
|
|
148
176
|
label: "label",
|
|
149
177
|
clearable: false,
|
|
150
|
-
"onUpdate:modelValue": _cache[
|
|
178
|
+
"onUpdate:modelValue": _cache[15] || (_cache[15] = $event => ($options.updateField('textAlign', $event)))
|
|
151
179
|
}, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel", "reduce"]),
|
|
152
180
|
createVNode(_component_NcSelect, {
|
|
153
181
|
modelValue: $data.verticalAlign,
|
|
@@ -156,7 +184,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
156
184
|
reduce: (option) => option.value,
|
|
157
185
|
label: "label",
|
|
158
186
|
clearable: false,
|
|
159
|
-
"onUpdate:modelValue": _cache[
|
|
187
|
+
"onUpdate:modelValue": _cache[16] || (_cache[16] = $event => ($options.updateField('verticalAlign', $event)))
|
|
160
188
|
}, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel", "reduce"]),
|
|
161
189
|
createElementVNode("fieldset", _hoisted_14, [
|
|
162
190
|
createElementVNode("legend", _hoisted_15, toDisplayString($options.t('nextcloud-vue', 'Call-to-action button (optional)')), 1 /* TEXT */),
|
|
@@ -164,13 +192,13 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
164
192
|
modelValue: $data.ctaLabel,
|
|
165
193
|
label: $options.t('nextcloud-vue', 'Button text'),
|
|
166
194
|
placeholder: $options.t('nextcloud-vue', 'Sign up'),
|
|
167
|
-
"onUpdate:modelValue": _cache[
|
|
195
|
+
"onUpdate:modelValue": _cache[17] || (_cache[17] = $event => ($options.updateCta('label', $event)))
|
|
168
196
|
}, null, 8 /* PROPS */, ["modelValue", "label", "placeholder"]),
|
|
169
197
|
createVNode(_component_NcTextField, {
|
|
170
198
|
modelValue: $data.ctaUrl,
|
|
171
199
|
label: $options.t('nextcloud-vue', 'Target URL'),
|
|
172
200
|
placeholder: "https://...",
|
|
173
|
-
"onUpdate:modelValue": _cache[
|
|
201
|
+
"onUpdate:modelValue": _cache[18] || (_cache[18] = $event => ($options.updateCta('url', $event)))
|
|
174
202
|
}, null, 8 /* PROPS */, ["modelValue", "label"]),
|
|
175
203
|
createVNode(_component_NcSelect, {
|
|
176
204
|
modelValue: $data.ctaStyle,
|
|
@@ -179,7 +207,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
179
207
|
reduce: (option) => option.value,
|
|
180
208
|
label: "label",
|
|
181
209
|
clearable: false,
|
|
182
|
-
"onUpdate:modelValue": _cache[
|
|
210
|
+
"onUpdate:modelValue": _cache[19] || (_cache[19] = $event => ($options.updateCta('style', $event)))
|
|
183
211
|
}, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel", "reduce"])
|
|
184
212
|
])
|
|
185
213
|
]))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnHeaderWidgetForm.vue3.js","sources":["../../../../src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue?vue&type=template&id=3741270f&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-form\">\n\t\t<NcTextField\n\t\t\t:modelValue=\"title\"\n\t\t\t:label=\"t('nextcloud-vue', 'Title')\"\n\t\t\t:placeholder=\"t('nextcloud-vue', 'Header title')\"\n\t\t\trequired\n\t\t\t@update:modelValue=\"updateField('title', $event)\" />\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"subtitle\"\n\t\t\t:label=\"t('nextcloud-vue', 'Subtitle (optional)')\"\n\t\t\t:placeholder=\"t('nextcloud-vue', 'Optional subtitle')\"\n\t\t\t@update:modelValue=\"updateField('subtitle', $event)\" />\n\n\t\t<!-- Pick a background image. Selection does NOT upload — the file is held\n\t\t and only uploaded when the host modal calls commit() on submit, so\n\t\t re-picking or cancelling writes nothing. Uploads are the reliable\n\t\t path: external image URLs are often blocked by the Nextcloud\n\t\t Content-Security-Policy, so only the background colour would show. -->\n\t\t<div class=\"cn-header-widget-form__upload-row\">\n\t\t\t<label class=\"cn-header-widget-form__upload-label\">\n\t\t\t\t<input\n\t\t\t\t\tref=\"fileInput\"\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\taccept=\"image/*\"\n\t\t\t\t\tclass=\"cn-header-widget-form__file-input\"\n\t\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t\t@change=\"handleFileSelect\">\n\t\t\t\t<span class=\"cn-header-widget-form__upload-button\">\n\t\t\t\t\t{{ pendingFile ? t('nextcloud-vue', 'Change background image') : t('nextcloud-vue', 'Choose background image') }}\n\t\t\t\t</span>\n\t\t\t</label>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"pendingFile\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t@click=\"clearPendingFile\">\n\t\t\t\t{{ t('nextcloud-vue', 'Remove') }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<p v-if=\"pendingFile\" class=\"cn-header-widget-form__pending\">\n\t\t\t{{ t('nextcloud-vue', 'Ready to upload on save: {name}', { name: pendingFile.name }) }}\n\t\t</p>\n\t\t<p v-if=\"uploadError\" class=\"cn-header-widget-form__error\" role=\"alert\">\n\t\t\t{{ uploadError }}\n\t\t</p>\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"backgroundImageUrl\"\n\t\t\t:label=\"t('nextcloud-vue', 'Background image URL')\"\n\t\t\tplaceholder=\"https://example.com/banner.jpg\"\n\t\t\t:disabled=\"!!pendingFile\"\n\t\t\t@update:modelValue=\"updateField('backgroundImageUrl', $event)\" />\n\n\t\t<label class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Background color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"backgroundColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('backgroundColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('backgroundColor', '')\" />\n\t\t</label>\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"overlayMode\"\n\t\t\t:options=\"overlayModeOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Overlay mode')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('overlayMode', $event)\" />\n\n\t\t<label v-if=\"overlayMode !== 'none'\" class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Overlay color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"overlayColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('overlayColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('overlayColor', '')\" />\n\t\t</label>\n\n\t\t<label v-if=\"overlayMode === 'tint'\" class=\"cn-header-widget-form__field\">\n\t\t\t<span class=\"cn-header-widget-form__label\">\n\t\t\t\t{{ t('nextcloud-vue', 'Overlay opacity') }} ({{ overlayOpacity.toFixed(2) }})\n\t\t\t</span>\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\tmin=\"0\"\n\t\t\t\tmax=\"1\"\n\t\t\t\tstep=\"0.05\"\n\t\t\t\t:value=\"overlayOpacity\"\n\t\t\t\tclass=\"cn-header-widget-form__range\"\n\t\t\t\t@input=\"updateField('overlayOpacity', parseFloat($event.target.value))\">\n\t\t</label>\n\n\t\t<label class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Text color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"textColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('textColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('textColor', '')\" />\n\t\t</label>\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"textAlign\"\n\t\t\t:options=\"textAlignOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Text alignment')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('textAlign', $event)\" />\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"verticalAlign\"\n\t\t\t:options=\"verticalAlignOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Vertical alignment')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('verticalAlign', $event)\" />\n\n\t\t<!-- The banner fills its dashboard grid cell, so a fixed Height preset\n\t\t no longer changes anything — resize the widget on the grid instead.\n\t\t (The `height` field is kept in the saved content for back-compat\n\t\t but is no longer surfaced as a control.) -->\n\n\t\t<fieldset class=\"cn-header-widget-form__fieldset\">\n\t\t\t<legend class=\"cn-header-widget-form__legend\">\n\t\t\t\t{{ t('nextcloud-vue', 'Call-to-action button (optional)') }}\n\t\t\t</legend>\n\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"ctaLabel\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Button text')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Sign up')\"\n\t\t\t\t@update:modelValue=\"updateCta('label', $event)\" />\n\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"ctaUrl\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Target URL')\"\n\t\t\t\tplaceholder=\"https://...\"\n\t\t\t\t@update:modelValue=\"updateCta('url', $event)\" />\n\n\t\t\t<NcSelect\n\t\t\t\t:modelValue=\"ctaStyle\"\n\t\t\t\t:options=\"ctaStyleOptions\"\n\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Button style')\"\n\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\tlabel=\"label\"\n\t\t\t\t:clearable=\"false\"\n\t\t\t\t@update:modelValue=\"updateCta('style', $event)\" />\n\t\t</fieldset>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnColorPicker from '../CnColorPicker/CnColorPicker.vue'\nimport { embedAsDataUrl, extractTransportUrl, readFileAsDataUrl, warnUploadFnDeprecated } from '../../utils/widgetUpload.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 DEFAULT_CONTENT = Object.freeze({\n\ttitle: '',\n\tsubtitle: '',\n\tbackgroundImageUrl: '',\n\tbackgroundImageFileId: null,\n\tbackgroundColor: '',\n\toverlayMode: 'none',\n\toverlayColor: '',\n\toverlayOpacity: 0.4,\n\ttextColor: '',\n\ttextAlign: 'center',\n\tverticalAlign: 'middle',\n\theight: 'medium',\n\tcta: null,\n})\n\n/**\n * CnHeaderWidgetForm — the `CnAddWidgetModal` sub-form for creating or editing\n * a `header` widget placement (renderer: `CnHeaderWidget`).\n *\n * Controls cover every persisted field: title, subtitle, background image URL,\n * background colour, overlay mode/colour/opacity, text colour/alignment/\n * vertical alignment, height preset, and an optional call-to-action with\n * label, URL, and style. `validate()` requires `title`, requires any provided\n * `backgroundImageUrl` to be http(s), and flags a half-filled CTA (label XOR\n * url). Emits `update:content` with the assembled blob on change.\n */\nexport default {\n\tname: 'CnHeaderWidgetForm',\n\n\tcomponents: {\n\t\tNcTextField,\n\t\tNcSelect,\n\t\tNcButton,\n\t\tCnColorPicker,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited, or `null` in create mode.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Initial content values — used when not editing and the parent\n\t\t * supplies registry defaults.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_CONTENT }),\n\t\t},\n\n\t\t/**\n\t\t * Optional raw-file upload transport: `async (file: File) => ({ url })`.\n\t\t * Named `fileUploadFn` (not `uploadFn`) to match `CnAddWidgetModal`'s\n\t\t * File-typed sub-form transport. Called by `commit()` on submit (never on\n\t\t * file selection) with the raw picked `File`; the returned hosted URL is\n\t\t * stored as `backgroundImageUrl`. When omitted, the file is embedded as a\n\t\t * data URL on commit instead (same-origin, so it isn't blocked by the CSP\n\t\t * the way external http(s) URLs are) — but only when the raw file is ≤ 1 MB\n\t\t * (~1.37 MB once base64-encoded and stored) so a huge inline blob can't\n\t\t * freeze the tab. Wire a transport for anything larger.\n\t\t *\n\t\t * @type {((file: File) => Promise<{ url: string }>)|null}\n\t\t */\n\t\tfileUploadFn: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Legacy base64 upload transport, superseded by `fileUploadFn`.\n\t\t *\n\t\t * @deprecated Use `fileUploadFn` instead. Legacy base64 transport\n\t\t * `async (dataUrl: string) => ({ url })`, kept for backward compatibility.\n\t\t * When `fileUploadFn` is not set but this is, `commit()` reads the file to\n\t\t * a data URL and hands that to this function (emitting a one-time\n\t\t * console.warn). `fileUploadFn` takes precedence when both are provided.\n\t\t *\n\t\t * @type {((dataUrl: string) => Promise<{ url: string }>)|null}\n\t\t */\n\t\tuploadFn: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the assembled content blob on every field change.\n\t\t *\n\t\t * @event update:content\n\t\t * @type {object}\n\t\t */\n\t\t'update:content',\n\t],\n\n\tdata() {\n\t\tconst initial = (this.editingWidget && this.editingWidget.content) || this.value || {}\n\t\tconst cta = (initial.cta && typeof initial.cta === 'object') ? initial.cta : null\n\t\treturn {\n\t\t\ttitle: typeof initial.title === 'string' ? initial.title : DEFAULT_CONTENT.title,\n\t\t\tsubtitle: typeof initial.subtitle === 'string' ? initial.subtitle : DEFAULT_CONTENT.subtitle,\n\t\t\tbackgroundImageUrl: typeof initial.backgroundImageUrl === 'string'\n\t\t\t\t? initial.backgroundImageUrl\n\t\t\t\t: DEFAULT_CONTENT.backgroundImageUrl,\n\n\t\t\tbackgroundImageFileId: (typeof initial.backgroundImageFileId === 'number')\n\t\t\t\t? initial.backgroundImageFileId\n\t\t\t\t: DEFAULT_CONTENT.backgroundImageFileId,\n\n\t\t\tbackgroundColor: typeof initial.backgroundColor === 'string'\n\t\t\t\t? initial.backgroundColor\n\t\t\t\t: DEFAULT_CONTENT.backgroundColor,\n\n\t\t\toverlayMode: ALLOWED_OVERLAY_MODES.includes(initial.overlayMode)\n\t\t\t\t? initial.overlayMode\n\t\t\t\t: DEFAULT_CONTENT.overlayMode,\n\n\t\t\toverlayColor: typeof initial.overlayColor === 'string'\n\t\t\t\t? initial.overlayColor\n\t\t\t\t: DEFAULT_CONTENT.overlayColor,\n\n\t\t\toverlayOpacity: typeof initial.overlayOpacity === 'number'\n\t\t\t\t? initial.overlayOpacity\n\t\t\t\t: DEFAULT_CONTENT.overlayOpacity,\n\n\t\t\ttextColor: typeof initial.textColor === 'string'\n\t\t\t\t? initial.textColor\n\t\t\t\t: DEFAULT_CONTENT.textColor,\n\n\t\t\ttextAlign: ALLOWED_TEXT_ALIGN.includes(initial.textAlign)\n\t\t\t\t? initial.textAlign\n\t\t\t\t: DEFAULT_CONTENT.textAlign,\n\n\t\t\tverticalAlign: ALLOWED_VERTICAL_ALIGN.includes(initial.verticalAlign)\n\t\t\t\t? initial.verticalAlign\n\t\t\t\t: DEFAULT_CONTENT.verticalAlign,\n\n\t\t\theight: ALLOWED_HEIGHTS.includes(initial.height)\n\t\t\t\t? initial.height\n\t\t\t\t: DEFAULT_CONTENT.height,\n\n\t\t\tctaLabel: cta && typeof cta.label === 'string' ? cta.label : '',\n\t\t\tctaUrl: cta && typeof cta.url === 'string' ? cta.url : '',\n\t\t\tctaStyle: (cta && ALLOWED_CTA_STYLES.includes(cta.style)) ? cta.style : 'primary',\n\t\t\tuploading: false,\n\t\t\tuploadError: '',\n\t\t\t// The picked-but-not-yet-uploaded file. Upload is deferred to commit()\n\t\t\t// so nothing is written on selection.\n\t\t\tpendingFile: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Overlay-mode select options. */\n\t\toverlayModeOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'none', label: t('nextcloud-vue', 'None') },\n\t\t\t\t{ value: 'tint', label: t('nextcloud-vue', 'Tinted overlay') },\n\t\t\t\t{ value: 'gradient-bottom', label: t('nextcloud-vue', 'Gradient bottom') },\n\t\t\t]\n\t\t},\n\n\t\t/** Text-alignment select options. */\n\t\ttextAlignOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'left', label: t('nextcloud-vue', 'Left') },\n\t\t\t\t{ value: 'center', label: t('nextcloud-vue', 'Center') },\n\t\t\t\t{ value: 'right', label: t('nextcloud-vue', 'Right') },\n\t\t\t]\n\t\t},\n\n\t\t/** Vertical-alignment select options. */\n\t\tverticalAlignOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'top', label: t('nextcloud-vue', 'Top') },\n\t\t\t\t{ value: 'middle', label: t('nextcloud-vue', 'Middle') },\n\t\t\t\t{ value: 'bottom', label: t('nextcloud-vue', 'Bottom') },\n\t\t\t]\n\t\t},\n\n\t\t/** Height-preset select options. */\n\t\theightOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'small', label: t('nextcloud-vue', 'Small (120px)') },\n\t\t\t\t{ value: 'medium', label: t('nextcloud-vue', 'Medium (200px)') },\n\t\t\t\t{ value: 'large', label: t('nextcloud-vue', 'Large (320px)') },\n\t\t\t\t{ value: 'xlarge', label: t('nextcloud-vue', 'Extra large (480px)') },\n\t\t\t]\n\t\t},\n\n\t\t/** CTA-style select options. */\n\t\tctaStyleOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'primary', label: t('nextcloud-vue', 'Primary') },\n\t\t\t\t{ value: 'secondary', label: t('nextcloud-vue', 'Secondary') },\n\t\t\t\t{ value: 'ghost', label: t('nextcloud-vue', 'Ghost') },\n\t\t\t]\n\t\t},\n\n\t\t/** The assembled CTA object, or null when both fields are empty. */\n\t\tctaPayload() {\n\t\t\tconst label = typeof this.ctaLabel === 'string' ? this.ctaLabel.trim() : ''\n\t\t\tconst url = typeof this.ctaUrl === 'string' ? this.ctaUrl.trim() : ''\n\t\t\tif (label === '' && url === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tlabel,\n\t\t\t\turl,\n\t\t\t\tstyle: this.ctaStyle,\n\t\t\t}\n\t\t},\n\n\t\t/** The full content blob assembled from the current field values. */\n\t\tassembledContent() {\n\t\t\treturn {\n\t\t\t\ttitle: this.title,\n\t\t\t\tsubtitle: this.subtitle,\n\t\t\t\tbackgroundImageUrl: this.backgroundImageUrl,\n\t\t\t\tbackgroundImageFileId: this.backgroundImageFileId,\n\t\t\t\tbackgroundColor: this.backgroundColor,\n\t\t\t\toverlayMode: this.overlayMode,\n\t\t\t\toverlayColor: this.overlayColor,\n\t\t\t\toverlayOpacity: this.overlayOpacity,\n\t\t\t\ttextColor: this.textColor,\n\t\t\t\ttextAlign: this.textAlign,\n\t\t\t\tverticalAlign: this.verticalAlign,\n\t\t\t\theight: this.height,\n\t\t\t\tcta: this.ctaPayload,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Set a top-level field and re-emit the assembled payload.\n\t\t *\n\t\t * @param {string} field one of the top-level content keys.\n\t\t * @param {unknown} value the new value.\n\t\t * @return {void}\n\t\t */\n\t\tupdateField(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Hold the picked file for a deferred upload. Does NOT upload — that\n\t\t * happens in commit() when the host modal submits, so re-picking or\n\t\t * cancelling writes nothing.\n\t\t *\n\t\t * @param {Event} event the file-input change event.\n\t\t * @return {void}\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\thandleFileSelect(event) {\n\t\t\tconst file = event.target.files && event.target.files[0]\n\t\t\tif (!file) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tthis.pendingFile = file\n\t\t\tthis.resetFileInput()\n\t\t},\n\n\t\t/**\n\t\t * Upload the pending file (if any) and store it as backgroundImageUrl.\n\t\t * Called by the host modal on submit. A no-op when no file is pending, so\n\t\t * editing without changing the image keeps the existing value.\n\t\t *\n\t\t * @return {Promise<void>} resolves once the URL is set.\n\t\t * @throws {Error} when the upload fails, so the modal can block submit.\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\tasync commit() {\n\t\t\tif (this.pendingFile === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tthis.uploading = true\n\t\t\ttry {\n\t\t\t\tlet resolvedUrl\n\t\t\t\tif (typeof this.fileUploadFn === 'function') {\n\t\t\t\t\tresolvedUrl = extractTransportUrl(await this.fileUploadFn(this.pendingFile))\n\t\t\t\t} else if (typeof this.uploadFn === 'function') {\n\t\t\t\t\t// Deprecated path: the legacy uploadFn expects a base64 data URL.\n\t\t\t\t\twarnUploadFnDeprecated('CnHeaderWidgetForm')\n\t\t\t\t\tconst dataUrl = await readFileAsDataUrl(this.pendingFile)\n\t\t\t\t\tresolvedUrl = extractTransportUrl(await this.uploadFn(dataUrl))\n\t\t\t\t} else {\n\t\t\t\t\tresolvedUrl = await embedAsDataUrl(this.pendingFile)\n\t\t\t\t}\n\t\t\t\tthis.pendingFile = null\n\t\t\t\tthis.updateField('backgroundImageUrl', resolvedUrl)\n\t\t\t} catch (err) {\n\t\t\t\tthis.uploadError = (err && err.message) || t('nextcloud-vue', 'Failed to upload image')\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('Header image upload failed:', err)\n\t\t\t\tthrow err\n\t\t\t} finally {\n\t\t\t\tthis.uploading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Discard the pending file, re-enabling the URL field. Called by the\n\t\t * Remove button.\n\t\t *\n\t\t * @return {void}\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\tclearPendingFile() {\n\t\t\tthis.pendingFile = null\n\t\t\tthis.uploadError = ''\n\t\t\tthis.resetFileInput()\n\t\t},\n\n\t\t/**\n\t\t * Clear the native file input so re-selecting the same file re-fires.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetFileInput() {\n\t\t\tif (this.$refs.fileInput) {\n\t\t\t\tthis.$refs.fileInput.value = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Set a CTA sub-field (label, url, style) and re-emit.\n\t\t *\n\t\t * @param {string} field one of: label, url, style.\n\t\t * @param {string} value the new value.\n\t\t * @return {void}\n\t\t */\n\t\tupdateCta(field, value) {\n\t\t\tif (field === 'label') {\n\t\t\t\tthis.ctaLabel = value\n\t\t\t} else if (field === 'url') {\n\t\t\t\tthis.ctaUrl = value\n\t\t\t} else if (field === 'style') {\n\t\t\t\tthis.ctaStyle = value\n\t\t\t}\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Validate the form; an empty array means valid. Requires a title,\n\t\t * an http(s) background image URL when provided, and a complete CTA\n\t\t * when either CTA field is filled.\n\t\t *\n\t\t * @return {string[]} the validation errors.\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = []\n\t\t\tif (typeof this.title !== 'string' || this.title.trim() === '') {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Title is required'))\n\t\t\t}\n\t\t\tif (typeof this.backgroundImageUrl === 'string'\n\t\t\t\t&& this.backgroundImageUrl.trim() !== ''\n\t\t\t\t&& /^(https?:\\/\\/|data:|blob:|\\/)/i.test(this.backgroundImageUrl.trim()) === false) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Background image must be a URL or an uploaded image'))\n\t\t\t}\n\t\t\tconst labelEmpty = typeof this.ctaLabel !== 'string' || this.ctaLabel.trim() === ''\n\t\t\tconst urlEmpty = typeof this.ctaUrl !== 'string' || this.ctaUrl.trim() === ''\n\t\t\tif (labelEmpty !== urlEmpty) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Call-to-action requires both label and URL'))\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-header-widget-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-header-widget-form__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__label {\n\tfont-weight: 500;\n}\n\n.cn-header-widget-form__color-label {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__upload-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-header-widget-form__upload-label {\n\tdisplay: inline-flex;\n\tcursor: pointer;\n}\n\n.cn-header-widget-form__pending {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-header-widget-form__file-input {\n\tdisplay: none;\n}\n\n.cn-header-widget-form__upload-button {\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-hover);\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__upload-label:hover .cn-header-widget-form__upload-button {\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-header-widget-form__error {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-error);\n}\n\n.cn-header-widget-form__color {\n\twidth: 48px;\n\theight: 32px;\n\tpadding: 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n\tbackground: transparent;\n}\n\n.cn-header-widget-form__range {\n\twidth: 100%;\n}\n\n.cn-header-widget-form__fieldset {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 12px;\n}\n\n.cn-header-widget-form__legend {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tpadding: 0 4px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_createBlock","_createVNode","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnHeaderWidgetForm.vue3.js","sources":["../../../../src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue?vue&type=template&id=3741270f&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-form\">\n\t\t<NcTextField\n\t\t\t:modelValue=\"title\"\n\t\t\t:label=\"t('nextcloud-vue', 'Title')\"\n\t\t\t:placeholder=\"t('nextcloud-vue', 'Header title')\"\n\t\t\trequired\n\t\t\t@update:modelValue=\"updateField('title', $event)\" />\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"subtitle\"\n\t\t\t:label=\"t('nextcloud-vue', 'Subtitle (optional)')\"\n\t\t\t:placeholder=\"t('nextcloud-vue', 'Optional subtitle')\"\n\t\t\t@update:modelValue=\"updateField('subtitle', $event)\" />\n\n\t\t<NcCheckboxRadioSwitch\n\t\t\t:modelValue=\"greeting\"\n\t\t\t@update:modelValue=\"updateField('greeting', $event)\">\n\t\t\t{{ t('nextcloud-vue', 'Greet the user by name instead of showing the title') }}\n\t\t</NcCheckboxRadioSwitch>\n\t\t<NcCheckboxRadioSwitch\n\t\t\t:modelValue=\"showDate\"\n\t\t\t@update:modelValue=\"updateField('showDate', $event)\">\n\t\t\t{{ t('nextcloud-vue', 'Show the date of today above the heading') }}\n\t\t</NcCheckboxRadioSwitch>\n\t\t<NcCheckboxRadioSwitch\n\t\t\t:modelValue=\"plain\"\n\t\t\t@update:modelValue=\"updateField('plain', $event)\">\n\t\t\t{{ t('nextcloud-vue', 'Plain header without a coloured background') }}\n\t\t</NcCheckboxRadioSwitch>\n\n\t\t<!-- Pick a background image. Selection does NOT upload — the file is held\n\t\t and only uploaded when the host modal calls commit() on submit, so\n\t\t re-picking or cancelling writes nothing. Uploads are the reliable\n\t\t path: external image URLs are often blocked by the Nextcloud\n\t\t Content-Security-Policy, so only the background colour would show. -->\n\t\t<div class=\"cn-header-widget-form__upload-row\">\n\t\t\t<label class=\"cn-header-widget-form__upload-label\">\n\t\t\t\t<input\n\t\t\t\t\tref=\"fileInput\"\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\taccept=\"image/*\"\n\t\t\t\t\tclass=\"cn-header-widget-form__file-input\"\n\t\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t\t@change=\"handleFileSelect\">\n\t\t\t\t<span class=\"cn-header-widget-form__upload-button\">\n\t\t\t\t\t{{ pendingFile ? t('nextcloud-vue', 'Change background image') : t('nextcloud-vue', 'Choose background image') }}\n\t\t\t\t</span>\n\t\t\t</label>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"pendingFile\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t@click=\"clearPendingFile\">\n\t\t\t\t{{ t('nextcloud-vue', 'Remove') }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<p v-if=\"pendingFile\" class=\"cn-header-widget-form__pending\">\n\t\t\t{{ t('nextcloud-vue', 'Ready to upload on save: {name}', { name: pendingFile.name }) }}\n\t\t</p>\n\t\t<p v-if=\"uploadError\" class=\"cn-header-widget-form__error\" role=\"alert\">\n\t\t\t{{ uploadError }}\n\t\t</p>\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"backgroundImageUrl\"\n\t\t\t:label=\"t('nextcloud-vue', 'Background image URL')\"\n\t\t\tplaceholder=\"https://example.com/banner.jpg\"\n\t\t\t:disabled=\"!!pendingFile\"\n\t\t\t@update:modelValue=\"updateField('backgroundImageUrl', $event)\" />\n\n\t\t<label class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Background color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"backgroundColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('backgroundColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('backgroundColor', '')\" />\n\t\t</label>\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"overlayMode\"\n\t\t\t:options=\"overlayModeOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Overlay mode')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('overlayMode', $event)\" />\n\n\t\t<label v-if=\"overlayMode !== 'none'\" class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Overlay color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"overlayColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('overlayColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('overlayColor', '')\" />\n\t\t</label>\n\n\t\t<label v-if=\"overlayMode === 'tint'\" class=\"cn-header-widget-form__field\">\n\t\t\t<span class=\"cn-header-widget-form__label\">\n\t\t\t\t{{ t('nextcloud-vue', 'Overlay opacity') }} ({{ overlayOpacity.toFixed(2) }})\n\t\t\t</span>\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\tmin=\"0\"\n\t\t\t\tmax=\"1\"\n\t\t\t\tstep=\"0.05\"\n\t\t\t\t:value=\"overlayOpacity\"\n\t\t\t\tclass=\"cn-header-widget-form__range\"\n\t\t\t\t@input=\"updateField('overlayOpacity', parseFloat($event.target.value))\">\n\t\t</label>\n\n\t\t<label class=\"cn-header-widget-form__color-label\">\n\t\t\t{{ t('nextcloud-vue', 'Text color') }}\n\t\t\t<CnColorPicker\n\t\t\t\t:value=\"textColor\"\n\t\t\t\tclearable\n\t\t\t\t@input=\"updateField('textColor', $event.hex)\"\n\t\t\t\t@clear=\"updateField('textColor', '')\" />\n\t\t</label>\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"textAlign\"\n\t\t\t:options=\"textAlignOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Text alignment')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('textAlign', $event)\" />\n\n\t\t<NcSelect\n\t\t\t:modelValue=\"verticalAlign\"\n\t\t\t:options=\"verticalAlignOptions\"\n\t\t\t:inputLabel=\"t('nextcloud-vue', 'Vertical alignment')\"\n\t\t\t:reduce=\"(option) => option.value\"\n\t\t\tlabel=\"label\"\n\t\t\t:clearable=\"false\"\n\t\t\t@update:modelValue=\"updateField('verticalAlign', $event)\" />\n\n\t\t<!-- The banner fills its dashboard grid cell, so a fixed Height preset\n\t\t no longer changes anything — resize the widget on the grid instead.\n\t\t (The `height` field is kept in the saved content for back-compat\n\t\t but is no longer surfaced as a control.) -->\n\n\t\t<fieldset class=\"cn-header-widget-form__fieldset\">\n\t\t\t<legend class=\"cn-header-widget-form__legend\">\n\t\t\t\t{{ t('nextcloud-vue', 'Call-to-action button (optional)') }}\n\t\t\t</legend>\n\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"ctaLabel\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Button text')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Sign up')\"\n\t\t\t\t@update:modelValue=\"updateCta('label', $event)\" />\n\n\t\t\t<NcTextField\n\t\t\t\t:modelValue=\"ctaUrl\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Target URL')\"\n\t\t\t\tplaceholder=\"https://...\"\n\t\t\t\t@update:modelValue=\"updateCta('url', $event)\" />\n\n\t\t\t<NcSelect\n\t\t\t\t:modelValue=\"ctaStyle\"\n\t\t\t\t:options=\"ctaStyleOptions\"\n\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Button style')\"\n\t\t\t\t:reduce=\"(option) => option.value\"\n\t\t\t\tlabel=\"label\"\n\t\t\t\t:clearable=\"false\"\n\t\t\t\t@update:modelValue=\"updateCta('style', $event)\" />\n\t\t</fieldset>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnColorPicker from '../CnColorPicker/CnColorPicker.vue'\nimport { embedAsDataUrl, extractTransportUrl, readFileAsDataUrl, warnUploadFnDeprecated } from '../../utils/widgetUpload.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 DEFAULT_CONTENT = Object.freeze({\n\ttitle: '',\n\tsubtitle: '',\n\tbackgroundImageUrl: '',\n\tbackgroundImageFileId: null,\n\tbackgroundColor: '',\n\toverlayMode: 'none',\n\toverlayColor: '',\n\toverlayOpacity: 0.4,\n\ttextColor: '',\n\ttextAlign: 'center',\n\tverticalAlign: 'middle',\n\theight: 'medium',\n\tcta: null,\n})\n\n/**\n * CnHeaderWidgetForm — the `CnAddWidgetModal` sub-form for creating or editing\n * a `header` widget placement (renderer: `CnHeaderWidget`).\n *\n * Controls cover every persisted field: title, subtitle, background image URL,\n * background colour, overlay mode/colour/opacity, text colour/alignment/\n * vertical alignment, height preset, and an optional call-to-action with\n * label, URL, and style. `validate()` requires `title`, requires any provided\n * `backgroundImageUrl` to be http(s), and flags a half-filled CTA (label XOR\n * url). Emits `update:content` with the assembled blob on change.\n */\nexport default {\n\tname: 'CnHeaderWidgetForm',\n\n\tcomponents: {\n\t\tNcTextField,\n\t\tNcSelect,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tCnColorPicker,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited, or `null` in create mode.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Initial content values — used when not editing and the parent\n\t\t * supplies registry defaults.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({ ...DEFAULT_CONTENT }),\n\t\t},\n\n\t\t/**\n\t\t * Optional raw-file upload transport: `async (file: File) => ({ url })`.\n\t\t * Named `fileUploadFn` (not `uploadFn`) to match `CnAddWidgetModal`'s\n\t\t * File-typed sub-form transport. Called by `commit()` on submit (never on\n\t\t * file selection) with the raw picked `File`; the returned hosted URL is\n\t\t * stored as `backgroundImageUrl`. When omitted, the file is embedded as a\n\t\t * data URL on commit instead (same-origin, so it isn't blocked by the CSP\n\t\t * the way external http(s) URLs are) — but only when the raw file is ≤ 1 MB\n\t\t * (~1.37 MB once base64-encoded and stored) so a huge inline blob can't\n\t\t * freeze the tab. Wire a transport for anything larger.\n\t\t *\n\t\t * @type {((file: File) => Promise<{ url: string }>)|null}\n\t\t */\n\t\tfileUploadFn: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Legacy base64 upload transport, superseded by `fileUploadFn`.\n\t\t *\n\t\t * @deprecated Use `fileUploadFn` instead. Legacy base64 transport\n\t\t * `async (dataUrl: string) => ({ url })`, kept for backward compatibility.\n\t\t * When `fileUploadFn` is not set but this is, `commit()` reads the file to\n\t\t * a data URL and hands that to this function (emitting a one-time\n\t\t * console.warn). `fileUploadFn` takes precedence when both are provided.\n\t\t *\n\t\t * @type {((dataUrl: string) => Promise<{ url: string }>)|null}\n\t\t */\n\t\tuploadFn: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the assembled content blob on every field change.\n\t\t *\n\t\t * @event update:content\n\t\t * @type {object}\n\t\t */\n\t\t'update:content',\n\t],\n\n\tdata() {\n\t\tconst initial = (this.editingWidget && this.editingWidget.content) || this.value || {}\n\t\tconst cta = (initial.cta && typeof initial.cta === 'object') ? initial.cta : null\n\t\treturn {\n\t\t\ttitle: typeof initial.title === 'string' ? initial.title : DEFAULT_CONTENT.title,\n\t\t\tsubtitle: typeof initial.subtitle === 'string' ? initial.subtitle : DEFAULT_CONTENT.subtitle,\n\t\t\tgreeting: initial.greeting === true || initial.greeting === 'full' || initial.greeting === 'first',\n\t\t\t// `\"full\"` (whole display name) is a manifest choice; kept as written.\n\t\t\tgreetingMode: initial.greeting === 'full' ? 'full' : true,\n\t\t\tshowDate: initial.showDate === true,\n\t\t\tplain: initial.plain === true,\n\t\t\tbackgroundImageUrl: typeof initial.backgroundImageUrl === 'string'\n\t\t\t\t? initial.backgroundImageUrl\n\t\t\t\t: DEFAULT_CONTENT.backgroundImageUrl,\n\n\t\t\tbackgroundImageFileId: (typeof initial.backgroundImageFileId === 'number')\n\t\t\t\t? initial.backgroundImageFileId\n\t\t\t\t: DEFAULT_CONTENT.backgroundImageFileId,\n\n\t\t\tbackgroundColor: typeof initial.backgroundColor === 'string'\n\t\t\t\t? initial.backgroundColor\n\t\t\t\t: DEFAULT_CONTENT.backgroundColor,\n\n\t\t\toverlayMode: ALLOWED_OVERLAY_MODES.includes(initial.overlayMode)\n\t\t\t\t? initial.overlayMode\n\t\t\t\t: DEFAULT_CONTENT.overlayMode,\n\n\t\t\toverlayColor: typeof initial.overlayColor === 'string'\n\t\t\t\t? initial.overlayColor\n\t\t\t\t: DEFAULT_CONTENT.overlayColor,\n\n\t\t\toverlayOpacity: typeof initial.overlayOpacity === 'number'\n\t\t\t\t? initial.overlayOpacity\n\t\t\t\t: DEFAULT_CONTENT.overlayOpacity,\n\n\t\t\ttextColor: typeof initial.textColor === 'string'\n\t\t\t\t? initial.textColor\n\t\t\t\t: DEFAULT_CONTENT.textColor,\n\n\t\t\ttextAlign: ALLOWED_TEXT_ALIGN.includes(initial.textAlign)\n\t\t\t\t? initial.textAlign\n\t\t\t\t: DEFAULT_CONTENT.textAlign,\n\n\t\t\tverticalAlign: ALLOWED_VERTICAL_ALIGN.includes(initial.verticalAlign)\n\t\t\t\t? initial.verticalAlign\n\t\t\t\t: DEFAULT_CONTENT.verticalAlign,\n\n\t\t\theight: ALLOWED_HEIGHTS.includes(initial.height)\n\t\t\t\t? initial.height\n\t\t\t\t: DEFAULT_CONTENT.height,\n\n\t\t\tctaLabel: cta && typeof cta.label === 'string' ? cta.label : '',\n\t\t\tctaUrl: cta && typeof cta.url === 'string' ? cta.url : '',\n\t\t\tctaStyle: (cta && ALLOWED_CTA_STYLES.includes(cta.style)) ? cta.style : 'primary',\n\t\t\tuploading: false,\n\t\t\tuploadError: '',\n\t\t\t// The picked-but-not-yet-uploaded file. Upload is deferred to commit()\n\t\t\t// so nothing is written on selection.\n\t\t\tpendingFile: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Overlay-mode select options. */\n\t\toverlayModeOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'none', label: t('nextcloud-vue', 'None') },\n\t\t\t\t{ value: 'tint', label: t('nextcloud-vue', 'Tinted overlay') },\n\t\t\t\t{ value: 'gradient-bottom', label: t('nextcloud-vue', 'Gradient bottom') },\n\t\t\t]\n\t\t},\n\n\t\t/** Text-alignment select options. */\n\t\ttextAlignOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'left', label: t('nextcloud-vue', 'Left') },\n\t\t\t\t{ value: 'center', label: t('nextcloud-vue', 'Center') },\n\t\t\t\t{ value: 'right', label: t('nextcloud-vue', 'Right') },\n\t\t\t]\n\t\t},\n\n\t\t/** Vertical-alignment select options. */\n\t\tverticalAlignOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'top', label: t('nextcloud-vue', 'Top') },\n\t\t\t\t{ value: 'middle', label: t('nextcloud-vue', 'Middle') },\n\t\t\t\t{ value: 'bottom', label: t('nextcloud-vue', 'Bottom') },\n\t\t\t]\n\t\t},\n\n\t\t/** Height-preset select options. */\n\t\theightOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'small', label: t('nextcloud-vue', 'Small (120px)') },\n\t\t\t\t{ value: 'medium', label: t('nextcloud-vue', 'Medium (200px)') },\n\t\t\t\t{ value: 'large', label: t('nextcloud-vue', 'Large (320px)') },\n\t\t\t\t{ value: 'xlarge', label: t('nextcloud-vue', 'Extra large (480px)') },\n\t\t\t]\n\t\t},\n\n\t\t/** CTA-style select options. */\n\t\tctaStyleOptions() {\n\t\t\treturn [\n\t\t\t\t{ value: 'primary', label: t('nextcloud-vue', 'Primary') },\n\t\t\t\t{ value: 'secondary', label: t('nextcloud-vue', 'Secondary') },\n\t\t\t\t{ value: 'ghost', label: t('nextcloud-vue', 'Ghost') },\n\t\t\t]\n\t\t},\n\n\t\t/** The assembled CTA object, or null when both fields are empty. */\n\t\tctaPayload() {\n\t\t\tconst label = typeof this.ctaLabel === 'string' ? this.ctaLabel.trim() : ''\n\t\t\tconst url = typeof this.ctaUrl === 'string' ? this.ctaUrl.trim() : ''\n\t\t\tif (label === '' && url === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tlabel,\n\t\t\t\turl,\n\t\t\t\tstyle: this.ctaStyle,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The greeting keys, written only when they are on, so a header that\n\t\t * never used them keeps the content blob it always had.\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\tgreetingContent() {\n\t\t\tconst content = {}\n\t\t\tif (this.greeting) {\n\t\t\t\tcontent.greeting = this.greetingMode\n\t\t\t}\n\t\t\tif (this.showDate) {\n\t\t\t\tcontent.showDate = true\n\t\t\t}\n\t\t\tif (this.plain) {\n\t\t\t\tcontent.plain = true\n\t\t\t}\n\t\t\treturn content\n\t\t},\n\n\t\t/**\n\t\t * The full content blob assembled from the current field values.\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\tassembledContent() {\n\t\t\treturn {\n\t\t\t\t...this.greetingContent,\n\t\t\t\ttitle: this.title,\n\t\t\t\tsubtitle: this.subtitle,\n\t\t\t\tbackgroundImageUrl: this.backgroundImageUrl,\n\t\t\t\tbackgroundImageFileId: this.backgroundImageFileId,\n\t\t\t\tbackgroundColor: this.backgroundColor,\n\t\t\t\toverlayMode: this.overlayMode,\n\t\t\t\toverlayColor: this.overlayColor,\n\t\t\t\toverlayOpacity: this.overlayOpacity,\n\t\t\t\ttextColor: this.textColor,\n\t\t\t\ttextAlign: this.textAlign,\n\t\t\t\tverticalAlign: this.verticalAlign,\n\t\t\t\theight: this.height,\n\t\t\t\tcta: this.ctaPayload,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Set a top-level field and re-emit the assembled payload.\n\t\t *\n\t\t * @param {string} field one of the top-level content keys.\n\t\t * @param {unknown} value the new value.\n\t\t * @return {void}\n\t\t */\n\t\tupdateField(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Hold the picked file for a deferred upload. Does NOT upload — that\n\t\t * happens in commit() when the host modal submits, so re-picking or\n\t\t * cancelling writes nothing.\n\t\t *\n\t\t * @param {Event} event the file-input change event.\n\t\t * @return {void}\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\thandleFileSelect(event) {\n\t\t\tconst file = event.target.files && event.target.files[0]\n\t\t\tif (!file) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tthis.pendingFile = file\n\t\t\tthis.resetFileInput()\n\t\t},\n\n\t\t/**\n\t\t * Upload the pending file (if any) and store it as backgroundImageUrl.\n\t\t * Called by the host modal on submit. A no-op when no file is pending, so\n\t\t * editing without changing the image keeps the existing value.\n\t\t *\n\t\t * @return {Promise<void>} resolves once the URL is set.\n\t\t * @throws {Error} when the upload fails, so the modal can block submit.\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\tasync commit() {\n\t\t\tif (this.pendingFile === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.uploadError = ''\n\t\t\tthis.uploading = true\n\t\t\ttry {\n\t\t\t\tlet resolvedUrl\n\t\t\t\tif (typeof this.fileUploadFn === 'function') {\n\t\t\t\t\tresolvedUrl = extractTransportUrl(await this.fileUploadFn(this.pendingFile))\n\t\t\t\t} else if (typeof this.uploadFn === 'function') {\n\t\t\t\t\t// Deprecated path: the legacy uploadFn expects a base64 data URL.\n\t\t\t\t\twarnUploadFnDeprecated('CnHeaderWidgetForm')\n\t\t\t\t\tconst dataUrl = await readFileAsDataUrl(this.pendingFile)\n\t\t\t\t\tresolvedUrl = extractTransportUrl(await this.uploadFn(dataUrl))\n\t\t\t\t} else {\n\t\t\t\t\tresolvedUrl = await embedAsDataUrl(this.pendingFile)\n\t\t\t\t}\n\t\t\t\tthis.pendingFile = null\n\t\t\t\tthis.updateField('backgroundImageUrl', resolvedUrl)\n\t\t\t} catch (err) {\n\t\t\t\tthis.uploadError = (err && err.message) || t('nextcloud-vue', 'Failed to upload image')\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('Header image upload failed:', err)\n\t\t\t\tthrow err\n\t\t\t} finally {\n\t\t\t\tthis.uploading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Discard the pending file, re-enabling the URL field. Called by the\n\t\t * Remove button.\n\t\t *\n\t\t * @return {void}\n\t\t *\n\t\t * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n\t\t */\n\t\tclearPendingFile() {\n\t\t\tthis.pendingFile = null\n\t\t\tthis.uploadError = ''\n\t\t\tthis.resetFileInput()\n\t\t},\n\n\t\t/**\n\t\t * Clear the native file input so re-selecting the same file re-fires.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetFileInput() {\n\t\t\tif (this.$refs.fileInput) {\n\t\t\t\tthis.$refs.fileInput.value = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Set a CTA sub-field (label, url, style) and re-emit.\n\t\t *\n\t\t * @param {string} field one of: label, url, style.\n\t\t * @param {string} value the new value.\n\t\t * @return {void}\n\t\t */\n\t\tupdateCta(field, value) {\n\t\t\tif (field === 'label') {\n\t\t\t\tthis.ctaLabel = value\n\t\t\t} else if (field === 'url') {\n\t\t\t\tthis.ctaUrl = value\n\t\t\t} else if (field === 'style') {\n\t\t\t\tthis.ctaStyle = value\n\t\t\t}\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Validate the form; an empty array means valid. Requires a title,\n\t\t * an http(s) background image URL when provided, and a complete CTA\n\t\t * when either CTA field is filled.\n\t\t *\n\t\t * @return {string[]} the validation errors.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = []\n\t\t\t// A greeting is the heading, so a greeting header needs no title.\n\t\t\tif (!this.greeting && (typeof this.title !== 'string' || this.title.trim() === '')) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Title is required'))\n\t\t\t}\n\t\t\tif (typeof this.backgroundImageUrl === 'string'\n\t\t\t\t&& this.backgroundImageUrl.trim() !== ''\n\t\t\t\t&& /^(https?:\\/\\/|data:|blob:|\\/)/i.test(this.backgroundImageUrl.trim()) === false) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Background image must be a URL or an uploaded image'))\n\t\t\t}\n\t\t\tconst labelEmpty = typeof this.ctaLabel !== 'string' || this.ctaLabel.trim() === ''\n\t\t\tconst urlEmpty = typeof this.ctaUrl !== 'string' || this.ctaUrl.trim() === ''\n\t\t\tif (labelEmpty !== urlEmpty) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'Call-to-action requires both label and URL'))\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-header-widget-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-header-widget-form__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__label {\n\tfont-weight: 500;\n}\n\n.cn-header-widget-form__color-label {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__upload-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-header-widget-form__upload-label {\n\tdisplay: inline-flex;\n\tcursor: pointer;\n}\n\n.cn-header-widget-form__pending {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-header-widget-form__file-input {\n\tdisplay: none;\n}\n\n.cn-header-widget-form__upload-button {\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-hover);\n\tfont-size: 14px;\n}\n\n.cn-header-widget-form__upload-label:hover .cn-header-widget-form__upload-button {\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-header-widget-form__error {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-error);\n}\n\n.cn-header-widget-form__color {\n\twidth: 48px;\n\theight: 32px;\n\tpadding: 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n\tbackground: transparent;\n}\n\n.cn-header-widget-form__range {\n\twidth: 100%;\n}\n\n.cn-header-widget-form__fieldset {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 12px;\n}\n\n.cn-header-widget-form__legend {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tpadding: 0 4px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_createBlock","_createTextVNode"],"mappings":"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"}
|