@conduction/nextcloud-vue 2.61.0 → 2.63.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/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +394 -27
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +79 -2
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +38 -25
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +21 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +4 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +30 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +256 -23
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +119 -51
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +53 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +22 -5
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnStorePage/CnStorePage.vue2.js.map +1 -1
- package/dist/esm/components/CnStorePage/CnStorePage.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWikiPage/CnWikiPage.vue2.js.map +1 -1
- package/dist/esm/components/CnWikiPage/CnWikiPage.vue3.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +65 -0
- package/dist/esm/composables/useDependencyCheck.js.map +1 -0
- package/dist/esm/composables/useWalkthrough.js +218 -6
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +48 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +48 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +557 -9
- package/dist/esm/store/useObjectStore.js +8 -0
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +311 -0
- package/dist/esm/utils/columnFilters.js.map +1 -0
- package/dist/esm/utils/groupAutocomplete.js +123 -0
- package/dist/esm/utils/groupAutocomplete.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/pickerOptions.js +198 -0
- package/dist/esm/utils/pickerOptions.js.map +1 -0
- package/dist/esm/utils/schema.js +83 -7
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/timelineEvents.js +174 -0
- package/dist/esm/utils/timelineEvents.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/esm/utils/walkthroughSignals.js +88 -0
- package/dist/esm/utils/walkthroughSignals.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +9779 -5236
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +557 -9
- package/l10n/en.json +48 -0
- package/l10n/nl.json +48 -0
- package/package.json +3 -2
- package/scripts/manifest-schema-hash.js +41 -0
- package/scripts/record-manifest-schema-hash.js +52 -0
- package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
- package/src/components/CnAdminActionCard/index.js +4 -0
- package/src/components/CnAppNav/CnAppNav.vue +394 -27
- package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
- package/src/components/CnBannerWidget/CnBannerWidget.vue +113 -2
- package/src/components/CnBrandStripe/CnBrandStripe.md +16 -0
- package/src/components/CnBrandStripe/CnBrandStripe.vue +58 -5
- package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
- package/src/components/CnDashboardPage/CnDashboardPage.vue +32 -8
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
- package/src/components/CnDataTable/CnDataTable.md +4 -1
- package/src/components/CnDataTable/CnDataTable.vue +302 -21
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
- package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
- package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
- package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
- package/src/components/CnIndexPage/selfModeActions.js +3 -3
- package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +35 -2
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +65 -0
- package/src/components/CnReportsPage/CnReportsPage.vue +7 -0
- package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
- package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
- package/src/components/CnStorePage/CnStorePage.vue +7 -0
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
- package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
- package/src/components/CnTimelineWidget/index.js +4 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWikiPage/CnWikiPage.vue +7 -0
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +2 -0
- package/src/composables/useDependencyCheck.js +61 -0
- package/src/composables/useWalkthrough.js +217 -5
- package/src/css/dashboard.css +49 -0
- package/src/css/detail-page.css +19 -0
- package/src/css/table.css +82 -2
- package/src/index.js +2 -0
- package/src/public/index.js +5 -0
- package/src/schemas/app-manifest-v2.schema.json +23 -1
- package/src/store/useObjectStore.js +8 -0
- package/src/utils/columnFilters.js +324 -0
- package/src/utils/groupAutocomplete.js +119 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/pickerOptions.js +194 -0
- package/src/utils/schema.js +83 -7
- package/src/utils/timelineEvents.js +171 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/walkthroughSignals.js +94 -0
|
@@ -509,7 +509,7 @@
|
|
|
509
509
|
:resume="walkthroughResume"
|
|
510
510
|
:translate="translate"
|
|
511
511
|
@complete="onWalkthroughComplete"
|
|
512
|
-
@
|
|
512
|
+
@progress="onWalkthroughProgress" />
|
|
513
513
|
</slot>
|
|
514
514
|
<!--
|
|
515
515
|
User-settings modal. Always mounted so descendants can
|
|
@@ -554,12 +554,27 @@
|
|
|
554
554
|
<p class="cn-app-root__walkthrough-hint">
|
|
555
555
|
{{ restartWalkthroughHint }}
|
|
556
556
|
</p>
|
|
557
|
-
<
|
|
558
|
-
<
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
557
|
+
<div class="cn-app-root__walkthrough-actions">
|
|
558
|
+
<NcButton
|
|
559
|
+
v-if="walkthroughProgressValue"
|
|
560
|
+
variant="primary"
|
|
561
|
+
data-testid="cn-walkthrough-continue"
|
|
562
|
+
@click="continueWalkthroughFromSettings">
|
|
563
|
+
<template #icon>
|
|
564
|
+
<Play :size="20" />
|
|
565
|
+
</template>
|
|
566
|
+
{{ continueWalkthroughLabel }}
|
|
567
|
+
</NcButton>
|
|
568
|
+
<NcButton
|
|
569
|
+
variant="secondary"
|
|
570
|
+
data-testid="cn-walkthrough-restart"
|
|
571
|
+
@click="restartWalkthroughFromSettings">
|
|
572
|
+
<template #icon>
|
|
573
|
+
<Restart :size="20" />
|
|
574
|
+
</template>
|
|
575
|
+
{{ walkthroughProgressValue ? startOverWalkthroughLabel : restartWalkthroughLabel }}
|
|
576
|
+
</NcButton>
|
|
577
|
+
</div>
|
|
563
578
|
</NcAppSettingsSection>
|
|
564
579
|
</slot>
|
|
565
580
|
<!--
|
|
@@ -638,11 +653,13 @@
|
|
|
638
653
|
import { getCurrentUser } from '@nextcloud/auth'
|
|
639
654
|
import axios from '@nextcloud/axios'
|
|
640
655
|
import { loadState } from '@nextcloud/initial-state'
|
|
656
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
641
657
|
import { generateUrl } from '@nextcloud/router'
|
|
642
658
|
import { NcAppContent, NcAppSettingsDialog, NcAppSettingsSection, NcButton, NcContent, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
|
|
643
659
|
import { computed, reactive, shallowRef, watch } from 'vue'
|
|
644
660
|
import DatabaseSearchOutline from 'vue-material-design-icons/DatabaseSearchOutline.vue'
|
|
645
661
|
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
|
|
662
|
+
import Play from 'vue-material-design-icons/Play.vue'
|
|
646
663
|
import Restart from 'vue-material-design-icons/Restart.vue'
|
|
647
664
|
import CnAiCompanion from '../CnAiCompanion/CnAiCompanion.vue'
|
|
648
665
|
import CnAppLoading from '../CnAppLoading/CnAppLoading.vue'
|
|
@@ -667,9 +684,12 @@ import { useSupportDialog } from '../../composables/useSupportDialog.js'
|
|
|
667
684
|
import { provideTenantContext } from '../../composables/useTenantContext.js'
|
|
668
685
|
import { useUserPreferences } from '../../composables/useUserPreferences.js'
|
|
669
686
|
import {
|
|
687
|
+
loadWalkthroughProgress,
|
|
670
688
|
loadWalkthroughSeenVersion,
|
|
671
689
|
normaliseSeenVersion,
|
|
690
|
+
persistWalkthroughProgress,
|
|
672
691
|
persistWalkthroughSeenVersion,
|
|
692
|
+
readLocalWalkthroughProgress,
|
|
673
693
|
readLocalWalkthroughSeenVersion,
|
|
674
694
|
useWalkthrough,
|
|
675
695
|
} from '../../composables/useWalkthrough.js'
|
|
@@ -749,6 +769,7 @@ export default {
|
|
|
749
769
|
NcNoteCard,
|
|
750
770
|
DatabaseSearchOutline,
|
|
751
771
|
OpenInNew,
|
|
772
|
+
Play,
|
|
752
773
|
Restart,
|
|
753
774
|
CnAppNav,
|
|
754
775
|
CnAppLoading,
|
|
@@ -1776,6 +1797,15 @@ export default {
|
|
|
1776
1797
|
* @type {string}
|
|
1777
1798
|
*/
|
|
1778
1799
|
walkthroughSeenVersionValue: readLocalWalkthroughSeenVersion(this.appId),
|
|
1800
|
+
/**
|
|
1801
|
+
* Where the user is in an unfinished tour (`{ tourId, stepId,
|
|
1802
|
+
* index, version }`), or null. Seeded from the local mirror, then
|
|
1803
|
+
* replaced by the per-user `<completionConfigKey>-progress`
|
|
1804
|
+
* preference. The tour resumes at this step on the next visit.
|
|
1805
|
+
*
|
|
1806
|
+
* @type {object|null}
|
|
1807
|
+
*/
|
|
1808
|
+
walkthroughProgressValue: readLocalWalkthroughProgress(this.appId),
|
|
1779
1809
|
/**
|
|
1780
1810
|
* Whether `walkthroughSeenVersionValue` is settled. Starts `true`
|
|
1781
1811
|
* when the manifest declares no `completionConfigKey` (the local
|
|
@@ -2529,13 +2559,15 @@ export default {
|
|
|
2529
2559
|
try {
|
|
2530
2560
|
const p = new URLSearchParams(window.location.search)
|
|
2531
2561
|
const tourId = p.get('cn_resume_tour')
|
|
2532
|
-
if (
|
|
2533
|
-
return
|
|
2562
|
+
if (tourId) {
|
|
2563
|
+
return { tourId, stepId: p.get('cn_resume_step') || '' }
|
|
2534
2564
|
}
|
|
2535
|
-
return { tourId, stepId: p.get('cn_resume_step') || '' }
|
|
2536
2565
|
} catch {
|
|
2537
|
-
|
|
2566
|
+
// No URL token; fall through to the remembered progress.
|
|
2538
2567
|
}
|
|
2568
|
+
// An unfinished tour continues at the step the user reached.
|
|
2569
|
+
const progress = this.walkthroughProgressValue
|
|
2570
|
+
return progress ? { tourId: progress.tourId, stepId: progress.stepId } : null
|
|
2539
2571
|
},
|
|
2540
2572
|
|
|
2541
2573
|
phase() {
|
|
@@ -2778,6 +2810,24 @@ export default {
|
|
|
2778
2810
|
return this.translate('Restart walkthrough')
|
|
2779
2811
|
},
|
|
2780
2812
|
|
|
2813
|
+
/**
|
|
2814
|
+
* Label for continuing an unfinished tour at the remembered step.
|
|
2815
|
+
*
|
|
2816
|
+
* @return {string}
|
|
2817
|
+
*/
|
|
2818
|
+
continueWalkthroughLabel() {
|
|
2819
|
+
return t('nextcloud-vue', 'Continue where you left off')
|
|
2820
|
+
},
|
|
2821
|
+
|
|
2822
|
+
/**
|
|
2823
|
+
* Label for restarting an unfinished tour from the first step.
|
|
2824
|
+
*
|
|
2825
|
+
* @return {string}
|
|
2826
|
+
*/
|
|
2827
|
+
startOverWalkthroughLabel() {
|
|
2828
|
+
return t('nextcloud-vue', 'Start over')
|
|
2829
|
+
},
|
|
2830
|
+
|
|
2781
2831
|
/**
|
|
2782
2832
|
* Resolve the active modal's Vue component from the registry.
|
|
2783
2833
|
* Returns null when no modal is open or the key no longer resolves.
|
|
@@ -3281,10 +3331,12 @@ export default {
|
|
|
3281
3331
|
return
|
|
3282
3332
|
}
|
|
3283
3333
|
try {
|
|
3284
|
-
|
|
3285
|
-
this.appId,
|
|
3286
|
-
this.walkthroughConfigKey,
|
|
3287
|
-
)
|
|
3334
|
+
const [seen, progress] = await Promise.all([
|
|
3335
|
+
loadWalkthroughSeenVersion(this.appId, this.walkthroughConfigKey),
|
|
3336
|
+
loadWalkthroughProgress(this.appId, this.walkthroughConfigKey),
|
|
3337
|
+
])
|
|
3338
|
+
this.walkthroughSeenVersionValue = seen
|
|
3339
|
+
this.walkthroughProgressValue = progress
|
|
3288
3340
|
} finally {
|
|
3289
3341
|
this.walkthroughSeenResolved = true
|
|
3290
3342
|
}
|
|
@@ -3308,12 +3360,68 @@ export default {
|
|
|
3308
3360
|
// Fire-and-forget: the loader never rejects and the local mirror is
|
|
3309
3361
|
// already written, so a failed PUT must not surface as an error.
|
|
3310
3362
|
persistWalkthroughSeenVersion(this.appId, this.walkthroughConfigKey, v)
|
|
3363
|
+
// A finished or skipped tour has nothing to continue.
|
|
3364
|
+
if (this.walkthroughProgressValue) {
|
|
3365
|
+
this.walkthroughProgressValue = null
|
|
3366
|
+
persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, null)
|
|
3367
|
+
}
|
|
3311
3368
|
/**
|
|
3312
3369
|
* @event walkthrough-complete Emitted when the walkthrough finishes or is dismissed.
|
|
3313
3370
|
*/
|
|
3314
3371
|
this.$emit('walkthrough-complete')
|
|
3315
3372
|
},
|
|
3316
3373
|
|
|
3374
|
+
/**
|
|
3375
|
+
* Remember where the user is in the tour, so a reload or a restart
|
|
3376
|
+
* entry continues there (local mirror + per-user preference).
|
|
3377
|
+
*
|
|
3378
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
3379
|
+
* @param {object} progress `{ tourId, stepId, index }` from CnWalkthrough.
|
|
3380
|
+
* @return {void}
|
|
3381
|
+
*/
|
|
3382
|
+
onWalkthroughProgress(progress) {
|
|
3383
|
+
if (!progress || !progress.tourId) {
|
|
3384
|
+
return
|
|
3385
|
+
}
|
|
3386
|
+
// The first step of a fresh tour is where a restart lands anyway,
|
|
3387
|
+
// so it costs no request.
|
|
3388
|
+
if (progress.index === 0 && !this.walkthroughProgressValue) {
|
|
3389
|
+
return
|
|
3390
|
+
}
|
|
3391
|
+
const value = {
|
|
3392
|
+
tourId: progress.tourId,
|
|
3393
|
+
stepId: progress.stepId || '',
|
|
3394
|
+
index: progress.index || 0,
|
|
3395
|
+
version: String((this.manifest && this.manifest.version) || ''),
|
|
3396
|
+
}
|
|
3397
|
+
this.walkthroughProgressValue = value
|
|
3398
|
+
persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
|
|
3399
|
+
},
|
|
3400
|
+
|
|
3401
|
+
/**
|
|
3402
|
+
* Continue an unfinished tour from the user-settings dialog: show a
|
|
3403
|
+
* paused tour again, or start the remembered tour at its step.
|
|
3404
|
+
*
|
|
3405
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
3406
|
+
* @return {void}
|
|
3407
|
+
*/
|
|
3408
|
+
continueWalkthroughFromSettings() {
|
|
3409
|
+
this.userSettingsOpen = false
|
|
3410
|
+
if (!this.walkthroughEnabled) {
|
|
3411
|
+
return
|
|
3412
|
+
}
|
|
3413
|
+
setTimeout(() => {
|
|
3414
|
+
const wt = useWalkthrough(this.appId, this.manifest)
|
|
3415
|
+
if (wt.resumePaused()) {
|
|
3416
|
+
return
|
|
3417
|
+
}
|
|
3418
|
+
const progress = this.walkthroughProgressValue
|
|
3419
|
+
if (progress) {
|
|
3420
|
+
wt.resumeAt(progress.tourId, progress.stepId)
|
|
3421
|
+
}
|
|
3422
|
+
}, 50)
|
|
3423
|
+
},
|
|
3424
|
+
|
|
3317
3425
|
/**
|
|
3318
3426
|
* Replay the product tour from the user-settings dialog. Closes the
|
|
3319
3427
|
* dialog first, then restarts the first declared tour on the next tick
|
|
@@ -3646,6 +3754,12 @@ export default {
|
|
|
3646
3754
|
color: var(--color-text-maxcontrast);
|
|
3647
3755
|
}
|
|
3648
3756
|
|
|
3757
|
+
.cn-app-root__walkthrough-actions {
|
|
3758
|
+
display: flex;
|
|
3759
|
+
flex-wrap: wrap;
|
|
3760
|
+
gap: 8px;
|
|
3761
|
+
}
|
|
3762
|
+
|
|
3649
3763
|
.cn-app-root__integrations-hint {
|
|
3650
3764
|
margin-bottom: 12px;
|
|
3651
3765
|
color: var(--color-text-maxcontrast);
|
|
@@ -36,6 +36,16 @@
|
|
|
36
36
|
</component>
|
|
37
37
|
</div>
|
|
38
38
|
</section>
|
|
39
|
+
<!-- The count could not be read. One quiet line, not the card and not an
|
|
40
|
+
alarm: hidden would read as "nothing to report", which is not known. -->
|
|
41
|
+
<p
|
|
42
|
+
v-else-if="checkFailed"
|
|
43
|
+
class="cn-banner-widget cn-banner-widget--unchecked"
|
|
44
|
+
:title="failureTooltip"
|
|
45
|
+
data-testid="cn-banner-widget-unchecked">
|
|
46
|
+
<span class="cn-banner-widget__unchecked-text">{{ uncheckedText }}</span>
|
|
47
|
+
<span class="cn-banner-widget__unchecked-reason">{{ failureTooltip }}</span>
|
|
48
|
+
</p>
|
|
39
49
|
<div v-else-if="visible" class="cn-banner-widget">
|
|
40
50
|
<NcNoteCard :type="resolvedVariant" class="cn-banner-widget__card">
|
|
41
51
|
<component
|
|
@@ -52,6 +62,7 @@
|
|
|
52
62
|
</template>
|
|
53
63
|
|
|
54
64
|
<script>
|
|
65
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
55
66
|
import { NcNoteCard } from '@nextcloud/vue'
|
|
56
67
|
import { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'
|
|
57
68
|
import { safeHref } from '../../utils/safeHref.js'
|
|
@@ -97,6 +108,12 @@ const MAX_ACTIONS = 2
|
|
|
97
108
|
* condition evaluates true — a failed fetch never breaks (or spams) a
|
|
98
109
|
* dashboard.
|
|
99
110
|
*
|
|
111
|
+
* An attention card (`layout: "attention"`) whose `visibleWhen` request
|
|
112
|
+
* FAILS renders one quiet line instead, "Could not check", with the reason as
|
|
113
|
+
* a tooltip. A hidden card reads as "nothing needs attention", and a failed
|
|
114
|
+
* count does not know that. A request that succeeds with a value that does
|
|
115
|
+
* not meet the condition still renders nothing.
|
|
116
|
+
*
|
|
100
117
|
* `layout: "attention"` turns the banner into an attention card: a card with
|
|
101
118
|
* a coloured edge by severity, a `kicker` label, a `title`, a `reason` line
|
|
102
119
|
* and up to two `actions` (the first is the primary one unless an action says
|
|
@@ -238,7 +255,9 @@ export default {
|
|
|
238
255
|
* the banner renders from this verdict instead of fetching again —
|
|
239
256
|
* no duplicate request and no hidden-until-self-evaluated flash —
|
|
240
257
|
* and `value` feeds the `{value}` text placeholder. `null` (the
|
|
241
|
-
* default) keeps the banner self-evaluating.
|
|
258
|
+
* default) keeps the banner self-evaluating. A host whose request
|
|
259
|
+
* failed passes `{ met: false, value: null, failed: true, reason }`,
|
|
260
|
+
* and an attention card then says it could not check.
|
|
242
261
|
*
|
|
243
262
|
* @type {object|null}
|
|
244
263
|
*/
|
|
@@ -259,6 +278,13 @@ export default {
|
|
|
259
278
|
'action',
|
|
260
279
|
],
|
|
261
280
|
|
|
281
|
+
/**
|
|
282
|
+
* The banner's own state: the evaluated condition, the value it read,
|
|
283
|
+
* and whether the request for it failed.
|
|
284
|
+
*
|
|
285
|
+
* @return {object} The state.
|
|
286
|
+
* @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check
|
|
287
|
+
*/
|
|
262
288
|
data() {
|
|
263
289
|
return {
|
|
264
290
|
/** Id that ties the attention card's region to its title. */
|
|
@@ -271,6 +297,10 @@ export default {
|
|
|
271
297
|
* the text wherever it says `{value}`.
|
|
272
298
|
*/
|
|
273
299
|
conditionValue: null,
|
|
300
|
+
/** Whether the visibleWhen request itself failed (not: answered "no"). */
|
|
301
|
+
conditionFailed: false,
|
|
302
|
+
/** Why it failed, as the request reported it. */
|
|
303
|
+
conditionFailureReason: '',
|
|
274
304
|
}
|
|
275
305
|
},
|
|
276
306
|
|
|
@@ -419,6 +449,52 @@ export default {
|
|
|
419
449
|
return this.conditionMet === true
|
|
420
450
|
},
|
|
421
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Whether to say the check failed: an attention card, with words to
|
|
454
|
+
* show, whose `visibleWhen` request failed. A plain banner stays
|
|
455
|
+
* hidden on failure, as it always did.
|
|
456
|
+
*
|
|
457
|
+
* @return {boolean}
|
|
458
|
+
* @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check
|
|
459
|
+
*/
|
|
460
|
+
checkFailed() {
|
|
461
|
+
const title = this.title || (this.content && this.content.title) || ''
|
|
462
|
+
return this.isAttention
|
|
463
|
+
&& !!this.resolvedVisibleWhen
|
|
464
|
+
&& this.conditionFailed
|
|
465
|
+
&& (title !== '' || this.resolvedText !== '')
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* The one line shown when the check failed. It names the card by its
|
|
470
|
+
* title, unless the title needs the value that could not be read.
|
|
471
|
+
*
|
|
472
|
+
* @return {string}
|
|
473
|
+
* @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check
|
|
474
|
+
*/
|
|
475
|
+
uncheckedText() {
|
|
476
|
+
const title = this.title || (this.content && this.content.title) || this.resolvedText
|
|
477
|
+
if (!title || title.includes('{value}')) {
|
|
478
|
+
return t('nextcloud-vue', 'Could not check')
|
|
479
|
+
}
|
|
480
|
+
// `escape: false`: the result is rendered through `{{ }}`, which escapes.
|
|
481
|
+
return t('nextcloud-vue', 'Could not check: {subject}', { subject: title }, undefined, { escape: false })
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* The reason the check failed, for the tooltip and for assistive
|
|
486
|
+
* technology.
|
|
487
|
+
*
|
|
488
|
+
* @return {string}
|
|
489
|
+
* @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check
|
|
490
|
+
*/
|
|
491
|
+
failureTooltip() {
|
|
492
|
+
if (!this.conditionFailureReason) {
|
|
493
|
+
return t('nextcloud-vue', 'The check failed.')
|
|
494
|
+
}
|
|
495
|
+
return t('nextcloud-vue', 'The check failed: {reason}', { reason: this.conditionFailureReason }, undefined, { escape: false })
|
|
496
|
+
},
|
|
497
|
+
|
|
422
498
|
/** Whether the banner navigates on click (route set + router present). */
|
|
423
499
|
clickable() {
|
|
424
500
|
return !!this.resolvedRoute && !!this.$router
|
|
@@ -493,27 +569,36 @@ export default {
|
|
|
493
569
|
* modes — the Wave-1 banner shape is the canonical one, extracted to
|
|
494
570
|
* `utils/visibleWhen.js` in Wave 3 so manifest actions reuse it),
|
|
495
571
|
* keeping the read VALUE for the `{value}` text placeholder.
|
|
496
|
-
* Fail-safe: any fetch/shape error leaves the banner hidden.
|
|
572
|
+
* Fail-safe: any fetch/shape error leaves the banner hidden. The
|
|
573
|
+
* failure is kept (`conditionFailed`, with its reason) so an
|
|
574
|
+
* attention card can say it could not check.
|
|
497
575
|
*
|
|
498
576
|
* @return {Promise<void>}
|
|
577
|
+
* @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check
|
|
499
578
|
*/
|
|
500
579
|
async evaluateCondition() {
|
|
501
580
|
const outcome = this.resolvedConditionOutcome
|
|
502
581
|
if (outcome) {
|
|
503
582
|
this.conditionMet = outcome.met === true
|
|
504
583
|
this.conditionValue = outcome.value !== undefined ? outcome.value : null
|
|
584
|
+
this.conditionFailed = outcome.failed === true
|
|
585
|
+
this.conditionFailureReason = outcome.failed === true && typeof outcome.reason === 'string' ? outcome.reason : ''
|
|
505
586
|
return
|
|
506
587
|
}
|
|
507
588
|
const cond = this.resolvedVisibleWhen
|
|
508
589
|
if (!cond) {
|
|
509
590
|
this.conditionMet = null
|
|
510
591
|
this.conditionValue = null
|
|
592
|
+
this.conditionFailed = false
|
|
593
|
+
this.conditionFailureReason = ''
|
|
511
594
|
return
|
|
512
595
|
}
|
|
513
596
|
try {
|
|
514
597
|
const value = await readVisibleWhenValue(cond)
|
|
515
598
|
this.conditionValue = value
|
|
516
599
|
this.conditionMet = compareVisibleWhen(value, cond.op || 'eq', cond.value)
|
|
600
|
+
this.conditionFailed = false
|
|
601
|
+
this.conditionFailureReason = ''
|
|
517
602
|
} catch (error) {
|
|
518
603
|
// Still hidden: a banner that cannot tell whether it applies
|
|
519
604
|
// should not claim attention. But not silently, because a
|
|
@@ -522,6 +607,8 @@ export default {
|
|
|
522
607
|
console.warn('[CnBannerWidget] visibleWhen could not be evaluated, banner hidden:', error?.message || error)
|
|
523
608
|
this.conditionMet = false
|
|
524
609
|
this.conditionValue = null
|
|
610
|
+
this.conditionFailed = true
|
|
611
|
+
this.conditionFailureReason = typeof error?.message === 'string' ? error.message : String(error ?? '')
|
|
525
612
|
}
|
|
526
613
|
},
|
|
527
614
|
|
|
@@ -557,6 +644,30 @@ export default {
|
|
|
557
644
|
color: inherit;
|
|
558
645
|
}
|
|
559
646
|
|
|
647
|
+
/* The check failed. Muted text, no fill, no severity colour: it is a note
|
|
648
|
+
that something is not known, not a warning. */
|
|
649
|
+
.cn-banner-widget--unchecked {
|
|
650
|
+
box-sizing: border-box;
|
|
651
|
+
margin: 0;
|
|
652
|
+
padding: 8px 12px;
|
|
653
|
+
color: var(--color-text-maxcontrast);
|
|
654
|
+
font-size: 0.9em;
|
|
655
|
+
cursor: help;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/* The reason, for assistive technology; sighted readers get the tooltip. */
|
|
659
|
+
.cn-banner-widget__unchecked-reason {
|
|
660
|
+
position: absolute;
|
|
661
|
+
width: 1px;
|
|
662
|
+
height: 1px;
|
|
663
|
+
margin: -1px;
|
|
664
|
+
padding: 0;
|
|
665
|
+
overflow: hidden;
|
|
666
|
+
clip-path: inset(50%);
|
|
667
|
+
white-space: nowrap;
|
|
668
|
+
border: 0;
|
|
669
|
+
}
|
|
670
|
+
|
|
560
671
|
/* Attention card. The severity edge is an inset shadow, so it takes no width. */
|
|
561
672
|
.cn-banner-widget--attention {
|
|
562
673
|
--cn-banner-accent: var(--color-primary-element);
|
|
@@ -20,3 +20,19 @@ Down the side of a panel, with `orientation="vertical"`:
|
|
|
20
20
|
<span>Content beside the stripe</span>
|
|
21
21
|
</div>
|
|
22
22
|
```
|
|
23
|
+
|
|
24
|
+
A theme whose motif is not three bands names an image. Here the canal of a school: a blue line, its bank and the water:
|
|
25
|
+
|
|
26
|
+
```vue
|
|
27
|
+
<div style="--cn-brand-stripe-image: linear-gradient(to bottom, #1f4fd8 0 4px, transparent 4px 7px, #1fb5a8 7px 9px); --cn-brand-stripe-height: 9px">
|
|
28
|
+
<CnBrandStripe />
|
|
29
|
+
</div>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
On a dark band, such as a footer, `variant="inverse"` draws the theme's `--cn-brand-stripe-image-inverse`, else the one image, else the bands:
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<div style="background: #14338f; padding-bottom: 16px; --cn-brand-stripe-image: linear-gradient(to bottom, #1f4fd8 0 4px, transparent 4px 7px, #1fb5a8 7px 9px); --cn-brand-stripe-image-inverse: linear-gradient(to bottom, #1f4fd8 0 4px, #ffffff 4px 7px, #1fb5a8 7px 9px); --cn-brand-stripe-height: 9px">
|
|
36
|
+
<CnBrandStripe variant="inverse" />
|
|
37
|
+
</div>
|
|
38
|
+
```
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
<template>
|
|
6
6
|
<div
|
|
7
7
|
class="cn-brand-stripe"
|
|
8
|
-
:class="{
|
|
8
|
+
:class="{
|
|
9
|
+
'cn-brand-stripe--vertical': orientation === 'vertical',
|
|
10
|
+
'cn-brand-stripe--inverse': variant === 'inverse',
|
|
11
|
+
}"
|
|
9
12
|
aria-hidden="true"
|
|
10
13
|
data-testid="cn-brand-stripe">
|
|
11
14
|
<span class="cn-brand-stripe__band cn-brand-stripe__band--1" />
|
|
@@ -17,7 +20,8 @@
|
|
|
17
20
|
<script>
|
|
18
21
|
/**
|
|
19
22
|
* CnBrandStripe draws the organisation's brand stripe: up to three coloured
|
|
20
|
-
* bands next to each other,
|
|
23
|
+
* bands next to each other, or the organisation's own motif, for the top of a
|
|
24
|
+
* page, a card or a sidebar.
|
|
21
25
|
*
|
|
22
26
|
* The theme decides what it looks like, through the library's own custom
|
|
23
27
|
* properties: `--cn-brand-stripe-color-1/2/3` (the colours, start to end),
|
|
@@ -26,6 +30,12 @@
|
|
|
26
30
|
* tokens onto these; the library never reads a theme's tokens. Without them the
|
|
27
31
|
* stripe is one band in the Nextcloud primary colour, 4px high.
|
|
28
32
|
*
|
|
33
|
+
* A theme whose motif is not three bands (hanging twigs, a canal and its bank,
|
|
34
|
+
* a slanted cut) names it in `--cn-brand-stripe-image`, any background-image
|
|
35
|
+
* value; the stripe then draws that image in the same box instead of the
|
|
36
|
+
* bands. `--cn-brand-stripe-image-inverse` is the same motif for a dark band;
|
|
37
|
+
* the `inverse` variant draws it, else the one image, else the bands.
|
|
38
|
+
*
|
|
29
39
|
* The stripe is decoration: it carries no meaning and is hidden from
|
|
30
40
|
* assistive technology.
|
|
31
41
|
*
|
|
@@ -49,6 +59,19 @@ export default {
|
|
|
49
59
|
default: 'horizontal',
|
|
50
60
|
validator: (v) => ['horizontal', 'vertical'].includes(v),
|
|
51
61
|
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The ground the stripe sits on: `default` (a light surface) or
|
|
65
|
+
* `inverse` (a dark band such as a footer), which draws the theme's
|
|
66
|
+
* `--cn-brand-stripe-image-inverse` when it names one.
|
|
67
|
+
*
|
|
68
|
+
* @type {'default'|'inverse'}
|
|
69
|
+
*/
|
|
70
|
+
variant: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: 'default',
|
|
73
|
+
validator: (v) => ['default', 'inverse'].includes(v),
|
|
74
|
+
},
|
|
52
75
|
},
|
|
53
76
|
}
|
|
54
77
|
</script>
|
|
@@ -56,19 +79,52 @@ export default {
|
|
|
56
79
|
<!--
|
|
57
80
|
The stripe reads the library's own `--cn-brand-stripe-*` properties, each
|
|
58
81
|
with a Nextcloud fallback. A theming app sets them from its own tokens.
|
|
82
|
+
|
|
83
|
+
THE ROOT PAINTS, NOT THE BANDS. The three bands are one background on the
|
|
84
|
+
root, a gradient with hard stops computed from the ratios, so a theme's
|
|
85
|
+
motif image can take its place with one `var()` fallback. A band painted on
|
|
86
|
+
its own element could not be switched off by the presence of an image: CSS
|
|
87
|
+
cannot ask whether a custom property is set. The band elements stay, empty,
|
|
88
|
+
so markup and tests that count them keep working.
|
|
59
89
|
-->
|
|
60
90
|
<style scoped>
|
|
61
91
|
.cn-brand-stripe {
|
|
92
|
+
--cn-brand-stripe-first: var(--cn-brand-stripe-ratio-1, 1);
|
|
93
|
+
--cn-brand-stripe-second: var(--cn-brand-stripe-ratio-2, 1);
|
|
94
|
+
--cn-brand-stripe-total: calc(
|
|
95
|
+
var(--cn-brand-stripe-first) + var(--cn-brand-stripe-second) + var(--cn-brand-stripe-ratio-3, 1)
|
|
96
|
+
);
|
|
97
|
+
--cn-brand-stripe-stop-1: calc(var(--cn-brand-stripe-first) / var(--cn-brand-stripe-total) * 100%);
|
|
98
|
+
--cn-brand-stripe-stop-2: calc(
|
|
99
|
+
(var(--cn-brand-stripe-first) + var(--cn-brand-stripe-second)) / var(--cn-brand-stripe-total) * 100%
|
|
100
|
+
);
|
|
101
|
+
--cn-brand-stripe-direction: to right;
|
|
102
|
+
--cn-brand-stripe-bands: linear-gradient(
|
|
103
|
+
var(--cn-brand-stripe-direction),
|
|
104
|
+
var(--cn-brand-stripe-color-1, var(--color-primary-element)) 0 var(--cn-brand-stripe-stop-1),
|
|
105
|
+
var(--cn-brand-stripe-color-2, var(--color-primary-element)) var(--cn-brand-stripe-stop-1) var(--cn-brand-stripe-stop-2),
|
|
106
|
+
var(--cn-brand-stripe-color-3, var(--color-primary-element)) var(--cn-brand-stripe-stop-2) 100%
|
|
107
|
+
);
|
|
62
108
|
display: flex;
|
|
63
109
|
flex: none;
|
|
64
110
|
width: 100%;
|
|
65
111
|
height: var(--cn-brand-stripe-height, 4px);
|
|
112
|
+
background-image: var(--cn-brand-stripe-image, var(--cn-brand-stripe-bands));
|
|
113
|
+
background-repeat: repeat-x;
|
|
114
|
+
background-size: auto 100%;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.cn-brand-stripe--inverse {
|
|
118
|
+
background-image: var(--cn-brand-stripe-image-inverse, var(--cn-brand-stripe-image, var(--cn-brand-stripe-bands)));
|
|
66
119
|
}
|
|
67
120
|
|
|
68
121
|
.cn-brand-stripe--vertical {
|
|
122
|
+
--cn-brand-stripe-direction: to bottom;
|
|
69
123
|
flex-direction: column;
|
|
70
124
|
width: var(--cn-brand-stripe-height, 4px);
|
|
71
125
|
height: 100%;
|
|
126
|
+
background-repeat: repeat-y;
|
|
127
|
+
background-size: 100% auto;
|
|
72
128
|
}
|
|
73
129
|
|
|
74
130
|
.cn-brand-stripe__band {
|
|
@@ -78,17 +134,14 @@ export default {
|
|
|
78
134
|
|
|
79
135
|
.cn-brand-stripe__band--1 {
|
|
80
136
|
flex-grow: var(--cn-brand-stripe-ratio-1, 1);
|
|
81
|
-
background-color: var(--cn-brand-stripe-color-1, var(--color-primary-element));
|
|
82
137
|
}
|
|
83
138
|
|
|
84
139
|
.cn-brand-stripe__band--2 {
|
|
85
140
|
flex-grow: var(--cn-brand-stripe-ratio-2, 1);
|
|
86
|
-
background-color: var(--cn-brand-stripe-color-2, var(--color-primary-element));
|
|
87
141
|
}
|
|
88
142
|
|
|
89
143
|
.cn-brand-stripe__band--3 {
|
|
90
144
|
flex-grow: var(--cn-brand-stripe-ratio-3, 1);
|
|
91
|
-
background-color: var(--cn-brand-stripe-color-3, var(--color-primary-element));
|
|
92
145
|
}
|
|
93
146
|
|
|
94
147
|
@media print {
|
|
@@ -15,39 +15,51 @@
|
|
|
15
15
|
</NcNoteCard>
|
|
16
16
|
|
|
17
17
|
<div v-else class="cn-choice-cards__grid" :style="gridStyle">
|
|
18
|
-
|
|
18
|
+
<!-- One cell per option: the card's label, and below it whatever the
|
|
19
|
+
`#option-actions` slot adds. The slot sits OUTSIDE the label on
|
|
20
|
+
purpose: a button inside a label is interactive content inside
|
|
21
|
+
interactive content, and a click on it would toggle the radio. -->
|
|
22
|
+
<div
|
|
19
23
|
v-for="option in normalizedOptions"
|
|
20
24
|
:key="String(option.value)"
|
|
21
|
-
class="cn-choice-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
25
|
+
class="cn-choice-cards__cell">
|
|
26
|
+
<label
|
|
27
|
+
class="cn-choice-cards__option"
|
|
28
|
+
:class="{ 'cn-choice-cards__option--selected': isSelected(option) }">
|
|
29
|
+
<!-- A REAL, VISIBLE input, not a styled div with a click handler.
|
|
30
|
+
It carries the checked state to assistive technology and to
|
|
31
|
+
Windows high-contrast mode for free, and it keeps the
|
|
32
|
+
selection perceivable without colour (WCAG 1.4.1) — the
|
|
33
|
+
card's active border alone would not be. -->
|
|
34
|
+
<input
|
|
35
|
+
class="cn-choice-cards__input"
|
|
36
|
+
:type="multiple ? 'checkbox' : 'radio'"
|
|
37
|
+
:name="groupName"
|
|
38
|
+
:value="String(option.value)"
|
|
39
|
+
:checked="isSelected(option)"
|
|
40
|
+
:disabled="disabled"
|
|
41
|
+
@change="onToggle(option)">
|
|
42
|
+
<CnCard
|
|
43
|
+
class="cn-choice-cards__card"
|
|
44
|
+
:title="option.label"
|
|
45
|
+
titleTag="span"
|
|
46
|
+
:description="option.description"
|
|
47
|
+
:stats="option.stats || []"
|
|
48
|
+
:tags="option.tags || []"
|
|
49
|
+
:active="isSelected(option)"
|
|
50
|
+
activeVariant="primary"
|
|
51
|
+
:descriptionLines="descriptionLines">
|
|
52
|
+
<template v-if="option.icon" #icon>
|
|
53
|
+
<CnIcon :name="option.icon" :size="20" />
|
|
54
|
+
</template>
|
|
55
|
+
</CnCard>
|
|
56
|
+
</label>
|
|
57
|
+
<div v-if="$slots['option-actions']" class="cn-choice-cards__actions">
|
|
58
|
+
<!-- @slot option-actions Content below one card, outside its label
|
|
59
|
+
(e.g. a per-option Load button). Scope: `{ option, selected }`. -->
|
|
60
|
+
<slot name="option-actions" :option="option" :selected="isSelected(option)" />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
51
63
|
</div>
|
|
52
64
|
</fieldset>
|
|
53
65
|
</template>
|
|
@@ -298,6 +310,19 @@ export default {
|
|
|
298
310
|
padding: 12px;
|
|
299
311
|
}
|
|
300
312
|
|
|
313
|
+
.cn-choice-cards__cell {
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-direction: column;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
min-inline-size: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.cn-choice-cards__actions {
|
|
321
|
+
display: flex;
|
|
322
|
+
flex-direction: column;
|
|
323
|
+
gap: 4px;
|
|
324
|
+
}
|
|
325
|
+
|
|
301
326
|
.cn-choice-cards__option {
|
|
302
327
|
position: relative;
|
|
303
328
|
display: block;
|