@conduction/nextcloud-vue 2.56.0-beta.6 → 2.56.0-beta.8
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
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<section
|
|
8
|
+
v-if="normalisedItems.length > 0"
|
|
9
|
+
class="cn-next-step-card"
|
|
10
|
+
data-testid="cn-next-step-card"
|
|
11
|
+
:aria-labelledby="title ? titleId : null"
|
|
12
|
+
:aria-label="title ? null : fallbackLabel">
|
|
13
|
+
<div class="cn-next-step-card__main">
|
|
14
|
+
<component
|
|
15
|
+
:is="titleTag"
|
|
16
|
+
v-if="title"
|
|
17
|
+
:id="titleId"
|
|
18
|
+
class="cn-next-step-card__title">
|
|
19
|
+
{{ title }}
|
|
20
|
+
</component>
|
|
21
|
+
<ul class="cn-next-step-card__list">
|
|
22
|
+
<li
|
|
23
|
+
v-for="(item, index) in normalisedItems"
|
|
24
|
+
:key="`${index}-${item.label}`"
|
|
25
|
+
class="cn-next-step-card__item"
|
|
26
|
+
:class="{
|
|
27
|
+
'cn-next-step-card__item--done': item.done,
|
|
28
|
+
'cn-next-step-card__item--current': index === currentIndex,
|
|
29
|
+
}"
|
|
30
|
+
data-testid="cn-next-step-item">
|
|
31
|
+
<span class="cn-next-step-card__marker" aria-hidden="true">
|
|
32
|
+
<Check v-if="item.done" :size="14" />
|
|
33
|
+
</span>
|
|
34
|
+
<span class="cn-next-step-card__label">{{ item.label }}</span>
|
|
35
|
+
<span v-if="item.hint" class="cn-next-step-card__hint">{{ item.hint }}</span>
|
|
36
|
+
<!-- The state in words. The marker is a shape and a colour, and
|
|
37
|
+
neither reaches a screen reader or a reader who cannot tell
|
|
38
|
+
the two colours apart. -->
|
|
39
|
+
<span class="cn-next-step-card__state">{{ item.done ? doneLabel : todoLabel }}</span>
|
|
40
|
+
</li>
|
|
41
|
+
</ul>
|
|
42
|
+
</div>
|
|
43
|
+
<div v-if="actionLabel || $slots.action || after" class="cn-next-step-card__side">
|
|
44
|
+
<!-- @slot action Replace the primary button, for a host that renders its own control. -->
|
|
45
|
+
<slot name="action">
|
|
46
|
+
<NcButton
|
|
47
|
+
v-if="actionLabel"
|
|
48
|
+
:id="actionId || null"
|
|
49
|
+
variant="primary"
|
|
50
|
+
:disabled="actionDisabled"
|
|
51
|
+
data-testid="cn-next-step-action"
|
|
52
|
+
@click="$emit('action')">
|
|
53
|
+
{{ actionLabel }}
|
|
54
|
+
</NcButton>
|
|
55
|
+
</slot>
|
|
56
|
+
<span v-if="after" class="cn-next-step-card__after">{{ after }}</span>
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script>
|
|
62
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
63
|
+
import { NcButton } from '@nextcloud/vue'
|
|
64
|
+
import Check from 'vue-material-design-icons/Check.vue'
|
|
65
|
+
|
|
66
|
+
let nextStepCardUid = 0
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* CnNextStepCard: what this record needs now, next to the button that does it.
|
|
70
|
+
*
|
|
71
|
+
* A short checklist for the current step and one primary action. It answers
|
|
72
|
+
* "what do I do next?" on a record whose menu would otherwise make the handler
|
|
73
|
+
* guess. Renders nothing when there is no checklist, so a step that needs no
|
|
74
|
+
* explaining shows no empty card.
|
|
75
|
+
*
|
|
76
|
+
* ```vue
|
|
77
|
+
* <CnNextStepCard
|
|
78
|
+
* title="What now? Step 2: handling"
|
|
79
|
+
* :items="[
|
|
80
|
+
* { label: 'Receipt confirmed', done: true },
|
|
81
|
+
* { label: 'Review the open documents', hint: '2 of 5 done' },
|
|
82
|
+
* { label: 'Draft the decision' },
|
|
83
|
+
* ]"
|
|
84
|
+
* action-label="Continue reviewing"
|
|
85
|
+
* after="Then: step 3, decision"
|
|
86
|
+
* @action="onContinue" />
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* CnDetailPage renders this card from its `nextStep` config, and the
|
|
90
|
+
* `next-step` detail widget type renders it inside a grid.
|
|
91
|
+
*/
|
|
92
|
+
export default {
|
|
93
|
+
name: 'CnNextStepCard',
|
|
94
|
+
|
|
95
|
+
components: {
|
|
96
|
+
Check,
|
|
97
|
+
NcButton,
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
props: {
|
|
101
|
+
/** Heading of the card, for example "What now? Step 2: handling". */
|
|
102
|
+
title: {
|
|
103
|
+
type: String,
|
|
104
|
+
default: '',
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/** Element the heading renders as. Pick the level that fits the page outline. */
|
|
108
|
+
titleTag: {
|
|
109
|
+
type: String,
|
|
110
|
+
default: 'h3',
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The checklist: `{ label, done?, hint? }` per item. The first item
|
|
115
|
+
* that is not done is emphasised as the one to pick up now.
|
|
116
|
+
*/
|
|
117
|
+
items: {
|
|
118
|
+
type: Array,
|
|
119
|
+
default: () => [],
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/** Label of the primary button. Empty renders no button. */
|
|
123
|
+
actionLabel: {
|
|
124
|
+
type: String,
|
|
125
|
+
default: '',
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/** DOM id for the primary button, so a skip link can land on it. */
|
|
129
|
+
actionId: {
|
|
130
|
+
type: String,
|
|
131
|
+
default: '',
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/** Disable the primary button, for example while its request is in flight. */
|
|
135
|
+
actionDisabled: {
|
|
136
|
+
type: Boolean,
|
|
137
|
+
default: false,
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/** One line under the button saying what follows, for example "Then: step 3, decision". */
|
|
141
|
+
after: {
|
|
142
|
+
type: String,
|
|
143
|
+
default: '',
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/** Text state of a done item, read by screen readers. */
|
|
147
|
+
doneLabel: {
|
|
148
|
+
type: String,
|
|
149
|
+
default: () => t('nextcloud-vue', 'Done'),
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
/** Text state of an open item, read by screen readers. */
|
|
153
|
+
todoLabel: {
|
|
154
|
+
type: String,
|
|
155
|
+
default: () => t('nextcloud-vue', 'To do'),
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
emits: [
|
|
160
|
+
/** The primary button was pressed. */
|
|
161
|
+
'action',
|
|
162
|
+
],
|
|
163
|
+
|
|
164
|
+
data() {
|
|
165
|
+
nextStepCardUid += 1
|
|
166
|
+
return {
|
|
167
|
+
titleId: `cn-next-step-title-${nextStepCardUid}`,
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
computed: {
|
|
172
|
+
/**
|
|
173
|
+
* The items that can render: an object with a label.
|
|
174
|
+
*
|
|
175
|
+
* @return {Array<{label: string, done: boolean, hint: string}>} The checklist.
|
|
176
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
177
|
+
*/
|
|
178
|
+
normalisedItems() {
|
|
179
|
+
return (Array.isArray(this.items) ? this.items : [])
|
|
180
|
+
.filter((item) => item && typeof item.label === 'string' && item.label !== '')
|
|
181
|
+
.map((item) => ({
|
|
182
|
+
label: item.label,
|
|
183
|
+
done: item.done === true,
|
|
184
|
+
hint: typeof item.hint === 'string' ? item.hint : '',
|
|
185
|
+
}))
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Index of the first open item, the one to pick up now.
|
|
190
|
+
*
|
|
191
|
+
* @return {number} The index, or -1 when everything is done.
|
|
192
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
193
|
+
*/
|
|
194
|
+
currentIndex() {
|
|
195
|
+
return this.normalisedItems.findIndex((item) => !item.done)
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Accessible name for a card without a title.
|
|
200
|
+
*
|
|
201
|
+
* @return {string} The label.
|
|
202
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
203
|
+
*/
|
|
204
|
+
fallbackLabel() {
|
|
205
|
+
return t('nextcloud-vue', 'Next step')
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<style scoped>
|
|
212
|
+
.cn-next-step-card {
|
|
213
|
+
align-items: center;
|
|
214
|
+
background: var(--color-main-background);
|
|
215
|
+
border: 1px solid var(--color-border);
|
|
216
|
+
border-radius: var(--border-radius-large);
|
|
217
|
+
/* The edge says "this is the thing to act on". An inset shadow, so it
|
|
218
|
+
follows the card's rounded corners instead of squaring them off. */
|
|
219
|
+
box-shadow: inset 4px 0 0 var(--color-primary-element);
|
|
220
|
+
display: flex;
|
|
221
|
+
flex-wrap: wrap;
|
|
222
|
+
gap: calc(4 * var(--default-grid-baseline)) calc(8 * var(--default-grid-baseline));
|
|
223
|
+
padding: calc(5 * var(--default-grid-baseline)) calc(6 * var(--default-grid-baseline));
|
|
224
|
+
padding-inline-start: calc(7 * var(--default-grid-baseline));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.cn-next-step-card__main {
|
|
228
|
+
display: flex;
|
|
229
|
+
flex: 1 1 420px;
|
|
230
|
+
flex-direction: column;
|
|
231
|
+
gap: calc(2.5 * var(--default-grid-baseline));
|
|
232
|
+
min-width: 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.cn-next-step-card__title {
|
|
236
|
+
color: var(--color-main-text);
|
|
237
|
+
font-size: 0.85em;
|
|
238
|
+
font-weight: 700;
|
|
239
|
+
letter-spacing: 0.06em;
|
|
240
|
+
margin: 0;
|
|
241
|
+
text-transform: uppercase;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.cn-next-step-card__list {
|
|
245
|
+
display: grid;
|
|
246
|
+
gap: calc(2 * var(--default-grid-baseline));
|
|
247
|
+
list-style: none;
|
|
248
|
+
margin: 0;
|
|
249
|
+
padding: 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.cn-next-step-card__item {
|
|
253
|
+
align-items: center;
|
|
254
|
+
color: var(--color-text-maxcontrast);
|
|
255
|
+
display: flex;
|
|
256
|
+
flex-wrap: wrap;
|
|
257
|
+
gap: calc(2.5 * var(--default-grid-baseline));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.cn-next-step-card__item--current {
|
|
261
|
+
color: var(--color-main-text);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.cn-next-step-card__item--current .cn-next-step-card__label {
|
|
265
|
+
font-weight: 700;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.cn-next-step-card__marker {
|
|
269
|
+
align-items: center;
|
|
270
|
+
border: 2px solid var(--color-border-maxcontrast);
|
|
271
|
+
border-radius: 50%;
|
|
272
|
+
box-sizing: border-box;
|
|
273
|
+
display: inline-flex;
|
|
274
|
+
flex: none;
|
|
275
|
+
height: 20px;
|
|
276
|
+
justify-content: center;
|
|
277
|
+
width: 20px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.cn-next-step-card__item--current .cn-next-step-card__marker {
|
|
281
|
+
border-color: var(--color-primary-element);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.cn-next-step-card__item--done .cn-next-step-card__marker {
|
|
285
|
+
background: var(--color-primary-element);
|
|
286
|
+
border-color: var(--color-primary-element);
|
|
287
|
+
color: var(--color-primary-element-text);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.cn-next-step-card__hint {
|
|
291
|
+
color: var(--color-text-maxcontrast);
|
|
292
|
+
font-weight: 400;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* Read, not seen. */
|
|
296
|
+
.cn-next-step-card__state {
|
|
297
|
+
border: 0;
|
|
298
|
+
clip-path: inset(50%);
|
|
299
|
+
height: 1px;
|
|
300
|
+
margin: -1px;
|
|
301
|
+
overflow: hidden;
|
|
302
|
+
padding: 0;
|
|
303
|
+
position: absolute;
|
|
304
|
+
white-space: nowrap;
|
|
305
|
+
width: 1px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.cn-next-step-card__side {
|
|
309
|
+
align-items: flex-start;
|
|
310
|
+
display: flex;
|
|
311
|
+
flex-direction: column;
|
|
312
|
+
gap: calc(2 * var(--default-grid-baseline));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.cn-next-step-card__after {
|
|
316
|
+
color: var(--color-text-maxcontrast);
|
|
317
|
+
font-size: 0.9em;
|
|
318
|
+
}
|
|
319
|
+
</style>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<CnNextStepCard
|
|
8
|
+
v-if="step"
|
|
9
|
+
:title="step.title"
|
|
10
|
+
:items="step.items"
|
|
11
|
+
:after="step.after" />
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import CnNextStepCard from './CnNextStepCard.vue'
|
|
16
|
+
import { resolveNextStep } from '../../utils/detailActionModel.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* CnNextStepWidget: the `next-step` detail widget type.
|
|
20
|
+
*
|
|
21
|
+
* Renders CnNextStepCard for the bound record's stage from
|
|
22
|
+
* `content: { field?, stages: { <stage>: { title?, checklist, after? } } }`.
|
|
23
|
+
* The checklist only: the page's own primary button stays where the page
|
|
24
|
+
* puts it. Resolved by its registry key, not exported.
|
|
25
|
+
*/
|
|
26
|
+
export default {
|
|
27
|
+
name: 'CnNextStepWidget',
|
|
28
|
+
|
|
29
|
+
components: {
|
|
30
|
+
CnNextStepCard,
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
props: {
|
|
34
|
+
/** The widget's content: `{ field?, title?, stages }`. */
|
|
35
|
+
content: {
|
|
36
|
+
type: Object,
|
|
37
|
+
default: () => ({}),
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/** The bound record. */
|
|
41
|
+
objectData: {
|
|
42
|
+
type: Object,
|
|
43
|
+
default: null,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
computed: {
|
|
48
|
+
/**
|
|
49
|
+
* The card for the record's stage.
|
|
50
|
+
*
|
|
51
|
+
* @return {object|null} The resolved card, or null when the stage declares none.
|
|
52
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
53
|
+
*/
|
|
54
|
+
step() {
|
|
55
|
+
return resolveNextStep(this.content, this.objectData)
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
@@ -456,6 +456,13 @@ export default {
|
|
|
456
456
|
* identity when used standalone (no CnAppRoot ancestor).
|
|
457
457
|
*/
|
|
458
458
|
cnTranslate: { default: () => (key) => key },
|
|
459
|
+
/**
|
|
460
|
+
* Formatter registry, provided by CnAppRoot (`cnFormatters`: the
|
|
461
|
+
* library's built-ins plus the app's `formatters`). A field override
|
|
462
|
+
* `{ formatter: '<id>' }` renders the value through the same function a
|
|
463
|
+
* table column with that `formatter` uses. Empty when used standalone.
|
|
464
|
+
*/
|
|
465
|
+
cnFormatters: { default: () => ({}) },
|
|
459
466
|
/**
|
|
460
467
|
* The host surface's panel-action channel, when this widget is rendered
|
|
461
468
|
* in a tab panel. Null everywhere else, which is every other surface.
|
|
@@ -641,6 +648,11 @@ export default {
|
|
|
641
648
|
* - `editable` (boolean) — Override editability (default: based on schema readOnly)
|
|
642
649
|
* - `label` (string) — Override the display label
|
|
643
650
|
* - `widget` (string) — Override the widget type for editing
|
|
651
|
+
* - `formatter` (string) — Display the value through the formatter
|
|
652
|
+
* registered under this id on CnAppRoot (`cnFormatters`), called as
|
|
653
|
+
* `(value, object, property)` like a table cell's formatter. An id
|
|
654
|
+
* nobody registered, or a formatter that throws, keeps the default
|
|
655
|
+
* rendering. Editing still writes the stored value.
|
|
644
656
|
*
|
|
645
657
|
* Accepts an Array too: an empty overrides map (`{}`) round-trips through
|
|
646
658
|
* PHP/JSON as an empty Array (`[]`), so a persisted manifest can deliver
|
|
@@ -917,12 +929,46 @@ export default {
|
|
|
917
929
|
}
|
|
918
930
|
},
|
|
919
931
|
|
|
932
|
+
/**
|
|
933
|
+
* Display values of the fields whose override names a registered
|
|
934
|
+
* formatter, keyed by field key. A field is left out when its formatter
|
|
935
|
+
* id is not registered or the formatter throws, so it falls back to the
|
|
936
|
+
* default rendering.
|
|
937
|
+
*
|
|
938
|
+
* @return {Record<string, string>}
|
|
939
|
+
*/
|
|
940
|
+
formatterDisplayValues() {
|
|
941
|
+
const values = {}
|
|
942
|
+
for (const field of this.resolvedFields) {
|
|
943
|
+
const fn = field.formatter && this.cnFormatters && this.cnFormatters[field.formatter]
|
|
944
|
+
if (typeof fn !== 'function') {
|
|
945
|
+
continue
|
|
946
|
+
}
|
|
947
|
+
const raw = field.key in this.dirtyFields
|
|
948
|
+
? this.dirtyFields[field.key]
|
|
949
|
+
: (this.objectData || {})[field.key]
|
|
950
|
+
const prop = (this.schema.properties && this.schema.properties[field.key]) || {}
|
|
951
|
+
try {
|
|
952
|
+
const formatted = fn(raw, this.objectData || {}, prop)
|
|
953
|
+
values[field.key] = (formatted === null || formatted === undefined || formatted === '') ? '—' : String(formatted)
|
|
954
|
+
} catch (e) {
|
|
955
|
+
// eslint-disable-next-line no-console
|
|
956
|
+
console.warn(`[CnObjectDataWidget] formatter "${field.formatter}" threw; falling back`, e)
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return values
|
|
960
|
+
},
|
|
961
|
+
|
|
920
962
|
/**
|
|
921
963
|
* Formatted display values for each field.
|
|
922
964
|
*/
|
|
923
965
|
displayValues() {
|
|
924
966
|
const values = {}
|
|
925
967
|
for (const field of this.resolvedFields) {
|
|
968
|
+
if (field.key in this.formatterDisplayValues) {
|
|
969
|
+
values[field.key] = this.formatterDisplayValues[field.key]
|
|
970
|
+
continue
|
|
971
|
+
}
|
|
926
972
|
// Show pending edit value if dirty
|
|
927
973
|
const raw = field.key in this.dirtyFields
|
|
928
974
|
? this.dirtyFields[field.key]
|
|
@@ -1191,6 +1237,9 @@ export default {
|
|
|
1191
1237
|
* @return {'object-array'|'scalar-array'|'object'|'scalar'} The value kind.
|
|
1192
1238
|
*/
|
|
1193
1239
|
fieldValueKind(field) {
|
|
1240
|
+
if (field.key in this.formatterDisplayValues) {
|
|
1241
|
+
return 'scalar'
|
|
1242
|
+
}
|
|
1194
1243
|
const prop = ((this.schema && this.schema.properties) || {})[field.key]
|
|
1195
1244
|
if (this.isRelationField(prop)) {
|
|
1196
1245
|
return 'scalar'
|
|
@@ -268,6 +268,9 @@ export default {
|
|
|
268
268
|
gridColumn: Number.isFinite(o.gridColumn) ? o.gridColumn : 1,
|
|
269
269
|
widget: o.widget || 'auto',
|
|
270
270
|
editable: o.editable !== false,
|
|
271
|
+
// Not edited here, but carried through, so saving the
|
|
272
|
+
// cog editor does not drop a manifest-authored formatter.
|
|
273
|
+
formatter: typeof o.formatter === 'string' ? o.formatter : '',
|
|
271
274
|
}
|
|
272
275
|
})
|
|
273
276
|
},
|
|
@@ -386,6 +389,9 @@ export default {
|
|
|
386
389
|
if (row.editable === false) {
|
|
387
390
|
o.editable = false
|
|
388
391
|
}
|
|
392
|
+
if (row.formatter) {
|
|
393
|
+
o.formatter = row.formatter
|
|
394
|
+
}
|
|
389
395
|
if (Object.keys(o).length) {
|
|
390
396
|
out[row.key] = o
|
|
391
397
|
}
|
|
@@ -50,11 +50,27 @@
|
|
|
50
50
|
<template #item="{ element: card }">
|
|
51
51
|
<div
|
|
52
52
|
class="cn-object-kanban__card"
|
|
53
|
-
:class="{
|
|
53
|
+
:class="{
|
|
54
|
+
'cn-object-kanban__card--pending': isPending(card),
|
|
55
|
+
'cn-object-kanban__card--overdue': dueState(card) === 'overdue',
|
|
56
|
+
'cn-object-kanban__card--soon': dueState(card) === 'soon',
|
|
57
|
+
}">
|
|
54
58
|
<!-- @slot card Fully replace the default card rendering. -->
|
|
55
59
|
<!-- @binding {object} object The card's object. -->
|
|
56
60
|
<!-- @binding {object} column The column the card currently sits in. -->
|
|
57
|
-
|
|
61
|
+
<!-- @binding {string|null} dueState The card's due state under `dueRule`: `overdue`, `soon`, `ok`, or null without a rule. -->
|
|
62
|
+
<slot
|
|
63
|
+
name="card"
|
|
64
|
+
:object="card"
|
|
65
|
+
:column="column"
|
|
66
|
+
:dueState="dueState(card)">
|
|
67
|
+
<span
|
|
68
|
+
v-if="dueLabel(card)"
|
|
69
|
+
class="cn-object-kanban__due"
|
|
70
|
+
:class="`cn-object-kanban__due--${dueState(card)}`"
|
|
71
|
+
data-testid="cn-kanban-due">
|
|
72
|
+
{{ dueLabel(card) }}
|
|
73
|
+
</span>
|
|
58
74
|
<div class="cn-object-kanban__card-title" @click="onCardClick(card)">
|
|
59
75
|
{{ cardTitle(card) }}
|
|
60
76
|
</div>
|
|
@@ -92,6 +108,7 @@ import { translate as t } from '@nextcloud/l10n'
|
|
|
92
108
|
import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
|
|
93
109
|
import draggable from 'vuedraggable'
|
|
94
110
|
import ViewColumn from 'vue-material-design-icons/ViewColumn.vue'
|
|
111
|
+
import { dueStateForRow } from '../../utils/dueRule.js'
|
|
95
112
|
import { CnCellRenderer } from '../CnCellRenderer/index.js'
|
|
96
113
|
|
|
97
114
|
/**
|
|
@@ -225,6 +242,22 @@ export default {
|
|
|
225
242
|
type: String,
|
|
226
243
|
default: 'id',
|
|
227
244
|
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Marks late cards: `{ field, soonDays? }`. `field` is the dot-path to
|
|
248
|
+
* a card's due date. A date before today gets an error edge and the
|
|
249
|
+
* label "Overdue"; a date within `soonDays` (default 3) gets "Due
|
|
250
|
+
* soon" in the warning colour. `null` (the default) marks nothing.
|
|
251
|
+
*
|
|
252
|
+
* The same rule shape the table's date cell uses, so a list and its
|
|
253
|
+
* board agree on what late means.
|
|
254
|
+
*
|
|
255
|
+
* @type {{field: string, soonDays?: number}|null}
|
|
256
|
+
*/
|
|
257
|
+
dueRule: {
|
|
258
|
+
type: Object,
|
|
259
|
+
default: null,
|
|
260
|
+
},
|
|
228
261
|
},
|
|
229
262
|
|
|
230
263
|
emits: ['card-click', 'load-more', 'move', 'move-rejected'],
|
|
@@ -240,6 +273,8 @@ export default {
|
|
|
240
273
|
// objectId -> { fromValue, toValue, card, originIndex }
|
|
241
274
|
pendingMoves: {},
|
|
242
275
|
dragOriginValue: null,
|
|
276
|
+
// The day late marking measures from.
|
|
277
|
+
today: new Date(),
|
|
243
278
|
}
|
|
244
279
|
},
|
|
245
280
|
|
|
@@ -294,6 +329,33 @@ export default {
|
|
|
294
329
|
methods: {
|
|
295
330
|
t,
|
|
296
331
|
|
|
332
|
+
/**
|
|
333
|
+
* A card's due state under `dueRule`.
|
|
334
|
+
*
|
|
335
|
+
* @param {object} card The card's row.
|
|
336
|
+
* @return {'overdue'|'soon'|'ok'|null} The state, or null when no rule applies.
|
|
337
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
|
|
338
|
+
*/
|
|
339
|
+
dueState(card) {
|
|
340
|
+
return dueStateForRow(card, this.dueRule, this.today)
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The words a late card carries. Colour alone would not reach a reader
|
|
345
|
+
* who cannot see it, or cannot tell red from amber.
|
|
346
|
+
*
|
|
347
|
+
* @param {object} card The card's row.
|
|
348
|
+
* @return {string} "Overdue", "Due soon", or '' for a card that is fine.
|
|
349
|
+
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
|
|
350
|
+
*/
|
|
351
|
+
dueLabel(card) {
|
|
352
|
+
const state = this.dueState(card)
|
|
353
|
+
if (state === 'overdue') {
|
|
354
|
+
return t('nextcloud-vue', 'Overdue')
|
|
355
|
+
}
|
|
356
|
+
return state === 'soon' ? t('nextcloud-vue', 'Due soon') : ''
|
|
357
|
+
},
|
|
358
|
+
|
|
297
359
|
/**
|
|
298
360
|
* Rebuild `localColumns` from either the `columns` prop (backend-paginated
|
|
299
361
|
* mode) or `objects` + `groupByField` (locally-derived mode).
|
|
@@ -703,6 +765,28 @@ export default {
|
|
|
703
765
|
opacity: 0.6;
|
|
704
766
|
}
|
|
705
767
|
|
|
768
|
+
/* A late card. The label carries the meaning; the edge helps a sighted
|
|
769
|
+
reader find it. An inset shadow, so the card keeps its size. */
|
|
770
|
+
.cn-object-kanban__card--overdue {
|
|
771
|
+
border-color: var(--color-error);
|
|
772
|
+
box-shadow: inset 3px 0 0 var(--color-error);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.cn-object-kanban__due {
|
|
776
|
+
display: block;
|
|
777
|
+
font-size: 0.85em;
|
|
778
|
+
font-weight: 700;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.cn-object-kanban__due--overdue {
|
|
782
|
+
color: var(--color-error-text, var(--color-error));
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.cn-object-kanban__due--soon {
|
|
786
|
+
color: var(--color-warning-text, var(--color-main-text));
|
|
787
|
+
font-weight: 600;
|
|
788
|
+
}
|
|
789
|
+
|
|
706
790
|
.cn-object-kanban__card-title {
|
|
707
791
|
font-weight: 500;
|
|
708
792
|
cursor: pointer;
|
|
@@ -57,6 +57,7 @@ export const defaultPageTypes = {
|
|
|
57
57
|
map: defineAsyncComponent(() => import('../CnMapPage/CnMapPage.vue').then((m) => m.default)),
|
|
58
58
|
roadmap: defineAsyncComponent(() => import('../CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue').then((m) => m.default)),
|
|
59
59
|
reports: defineAsyncComponent(() => import('../CnReportsPage/CnReportsPage.vue').then((m) => m.default)),
|
|
60
|
+
links: defineAsyncComponent(() => import('../CnLinkCardsPage/CnLinkCardsPage.vue').then((m) => m.default)),
|
|
60
61
|
store: defineAsyncComponent(() => import('../CnStorePage/CnStorePage.vue').then((m) => m.default)),
|
|
61
62
|
search: defineAsyncComponent(() => import('../CnSearchPage/CnSearchPage.vue').then((m) => m.default)),
|
|
62
63
|
wiki: defineAsyncComponent(() => import('../CnWikiPage/CnWikiPage.vue').then((m) => m.default)),
|
|
@@ -45,6 +45,8 @@ In `manifest.json` (the usual entry-point — CnIndexPage mounts the bar for you
|
|
|
45
45
|
| `filter` | Object | yes | Filter map merged into the fetch when this tab is active. Same value syntax as `config.filter`: literals pass through; `"@route.<name>"` / `":<name>"` resolve from `$route.params`. |
|
|
46
46
|
| `default` | Boolean | no | Pre-selected on mount; first tab with `default:true` (else index 0) is active |
|
|
47
47
|
| `icon` | String | no | Optional MDI icon name shown next to the label |
|
|
48
|
+
| `showCount` | Boolean | no | On a CnIndexPage: show how many records this tab matches. Tabs that filter the same single field share one grouped request. |
|
|
49
|
+
| `count` | Number | no | A count you supply yourself, shown after the label. |
|
|
48
50
|
|
|
49
51
|
## Props
|
|
50
52
|
|
|
@@ -57,6 +59,7 @@ In `manifest.json` (the usual entry-point — CnIndexPage mounts the bar for you
|
|
|
57
59
|
- `selectLabel` (String, default `'Filter'`) — accessible label / placeholder for the dropdown control.
|
|
58
60
|
- `placeholder` (String) — placeholder text for the dropdown (falls back to `selectLabel`).
|
|
59
61
|
- `maxVisible` (Number, default `0`) — chips mode only: how many pills render inline before the rest move behind one more chip, a `⋯` pill that opens a small panel of the hidden lenses (also as chips). `0` renders every tab. The visible set is the first `maxVisible` entries of `tabs`, so their declared order decides what stays in the strip; an active hidden tab moves its label and fill onto that chip. Manifest: `config.quickFilterMaxVisible`.
|
|
62
|
+
- `counts` (Object or Array, default `null`): how many records each tab matches, keyed by tab index. A tab with a number shows it after its label. CnIndexPage fills this for tabs that set `showCount`.
|
|
60
63
|
|
|
61
64
|
### Events
|
|
62
65
|
|