@conduction/nextcloud-vue 2.69.0 → 2.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +25 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +155 -152
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +77 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +8 -8
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +151 -13
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +28 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +4 -2
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +25 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js +32 -17
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js +14 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js.map +1 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +43 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +40 -43
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +35 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +54 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +6 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +28 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +3 -2
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +12 -0
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +9 -0
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +54 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +26 -8
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +165 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +69 -26
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +17 -3
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +25 -24
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +27 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +7 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +24 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +11 -0
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +4 -2
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +15 -2
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +31 -32
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useWalkthrough.js +11 -3
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +198 -0
- package/dist/esm/utils/capabilityComparison.js +29 -1
- package/dist/esm/utils/capabilityComparison.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +54 -4
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/userAutocomplete.js +69 -3
- package/dist/esm/utils/userAutocomplete.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2778 -1630
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +198 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +2 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +28 -2
- package/src/components/CnAppNav/CnAppNav.vue +85 -8
- package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +45 -0
- package/src/components/CnCalendarWidget/CnCalendarWidget.vue +32 -4
- package/src/components/CnCapabilityTable/CnCapabilityTable.vue +14 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.md +2 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +54 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +46 -19
- package/src/components/CnDataTable/CnDataTable.vue +35 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +57 -3
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +30 -1
- package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +12 -0
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +9 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +95 -8
- package/src/components/CnIndexPage/CnIndexPage.vue +194 -7
- package/src/components/CnNextStepCard/CnNextStepCard.vue +11 -0
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +4 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +34 -7
- package/src/components/CnStagesWidget/CnStagesWidget.vue +19 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +36 -0
- package/src/components/CnStatsBlock/CnStatsBlock.md +2 -0
- package/src/components/CnStatsBlock/CnStatsBlock.vue +25 -2
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +13 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +24 -3
- package/src/composables/useWalkthrough.js +11 -3
- package/src/css/dashboard.css +44 -0
- package/src/css/detail-page.css +10 -0
- package/src/css/index-page.css +12 -0
- package/src/css/kpi-card.css +15 -0
- package/src/schemas/app-manifest-v2.schema.json +47 -5
- package/src/utils/capabilityComparison.js +28 -0
- package/src/utils/columnFilters.js +54 -4
- package/src/utils/userAutocomplete.js +68 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -513,8 +513,10 @@
|
|
|
513
513
|
:manifest="manifest"
|
|
514
514
|
:seenVersion="walkthroughSeenVersion"
|
|
515
515
|
:resume="walkthroughResume"
|
|
516
|
+
:autoStart="!walkthroughPaused"
|
|
516
517
|
:translate="translate"
|
|
517
518
|
@complete="onWalkthroughComplete"
|
|
519
|
+
@pause="onWalkthroughPause"
|
|
518
520
|
@progress="onWalkthroughProgress" />
|
|
519
521
|
</slot>
|
|
520
522
|
<!--
|
|
@@ -934,8 +936,11 @@ export default {
|
|
|
934
936
|
* Restart entry for the product walkthrough (ADR-043). Descendants
|
|
935
937
|
* (a menu/settings "Replay walkthrough" entry, or a manifest menu
|
|
936
938
|
* `action: "replay-walkthrough"`) call this to re-run a tour. With no
|
|
937
|
-
* `tourId` the first declared tour is used.
|
|
939
|
+
* `tourId` the first declared tour is used. An unfinished tour
|
|
940
|
+
* continues where the user was (a paused tour, else the saved
|
|
941
|
+
* step); "Start over" in the user settings goes back to step 1.
|
|
938
942
|
*
|
|
943
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
|
|
939
944
|
* @param {string} [tourId] The tour to restart.
|
|
940
945
|
* @return {void}
|
|
941
946
|
*/
|
|
@@ -945,9 +950,18 @@ export default {
|
|
|
945
950
|
}
|
|
946
951
|
const wt = useWalkthrough(this.appId, this.manifest)
|
|
947
952
|
const id = tourId || (this.manifest.walkthrough.tours[0] && this.manifest.walkthrough.tours[0].id)
|
|
948
|
-
if (id) {
|
|
949
|
-
|
|
953
|
+
if (!id) {
|
|
954
|
+
return
|
|
955
|
+
}
|
|
956
|
+
const progress = this.walkthroughProgressValue
|
|
957
|
+
this.unpauseWalkthroughProgress()
|
|
958
|
+
if (wt.paused.value && wt.activeTour.value && wt.activeTour.value.id === id && wt.resumePaused()) {
|
|
959
|
+
return
|
|
960
|
+
}
|
|
961
|
+
if (progress && progress.tourId === id && wt.resumeAt(id, progress.stepId)) {
|
|
962
|
+
return
|
|
950
963
|
}
|
|
964
|
+
wt.restart(id)
|
|
951
965
|
},
|
|
952
966
|
|
|
953
967
|
/**
|
|
@@ -1528,7 +1542,7 @@ export default {
|
|
|
1528
1542
|
},
|
|
1529
1543
|
},
|
|
1530
1544
|
|
|
1531
|
-
emits: ['setup-complete', 'walkthrough-complete'],
|
|
1545
|
+
emits: ['setup-complete', 'setup-wizard-dismissed', 'walkthrough-complete'],
|
|
1532
1546
|
|
|
1533
1547
|
/**
|
|
1534
1548
|
* Component-instance state for the capabilities guard.
|
|
@@ -2599,9 +2613,21 @@ export default {
|
|
|
2599
2613
|
} catch {
|
|
2600
2614
|
// No URL token; fall through to the remembered progress.
|
|
2601
2615
|
}
|
|
2602
|
-
// An unfinished tour continues at the step the user reached
|
|
2616
|
+
// An unfinished tour continues at the step the user reached,
|
|
2617
|
+
// unless the user paused it: then it waits for "Continue".
|
|
2603
2618
|
const progress = this.walkthroughProgressValue
|
|
2604
|
-
return progress ? { tourId: progress.tourId, stepId: progress.stepId } : null
|
|
2619
|
+
return progress && !progress.paused ? { tourId: progress.tourId, stepId: progress.stepId } : null
|
|
2620
|
+
},
|
|
2621
|
+
|
|
2622
|
+
/**
|
|
2623
|
+
* Whether the user paused the tour (X, ESC or the dim). A paused tour
|
|
2624
|
+
* stays hidden across page loads until the user picks "Continue".
|
|
2625
|
+
*
|
|
2626
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
|
|
2627
|
+
* @return {boolean} True while the saved progress is paused.
|
|
2628
|
+
*/
|
|
2629
|
+
walkthroughPaused() {
|
|
2630
|
+
return !!(this.walkthroughProgressValue && this.walkthroughProgressValue.paused)
|
|
2605
2631
|
},
|
|
2606
2632
|
|
|
2607
2633
|
phase() {
|
|
@@ -3281,7 +3307,7 @@ export default {
|
|
|
3281
3307
|
// the server keeps reporting it unmet — so without this the wizard
|
|
3282
3308
|
// auto-opens again on every visit. Cancel must not be the only way to
|
|
3283
3309
|
// get peace.
|
|
3284
|
-
this.persistSetupWizardDismissal()
|
|
3310
|
+
this.persistSetupWizardDismissal(true)
|
|
3285
3311
|
if (this.setupState && typeof this.setupState.refresh === 'function') {
|
|
3286
3312
|
this.setupState.refresh()
|
|
3287
3313
|
}
|
|
@@ -3302,7 +3328,7 @@ export default {
|
|
|
3302
3328
|
* @return {void}
|
|
3303
3329
|
*/
|
|
3304
3330
|
dismissSetupWizard() {
|
|
3305
|
-
this.persistSetupWizardDismissal()
|
|
3331
|
+
this.persistSetupWizardDismissal(false)
|
|
3306
3332
|
this.setupWizardOpen = false
|
|
3307
3333
|
},
|
|
3308
3334
|
|
|
@@ -3325,7 +3351,7 @@ export default {
|
|
|
3325
3351
|
* @return {boolean}
|
|
3326
3352
|
*/
|
|
3327
3353
|
isSetupWizardDismissed() {
|
|
3328
|
-
if (this.setupWizardDismissed) {
|
|
3354
|
+
if (this.setupWizardDismissed || this.isSetupWizardDismissedOnServer()) {
|
|
3329
3355
|
return true
|
|
3330
3356
|
}
|
|
3331
3357
|
try {
|
|
@@ -3336,18 +3362,87 @@ export default {
|
|
|
3336
3362
|
},
|
|
3337
3363
|
|
|
3338
3364
|
/**
|
|
3339
|
-
*
|
|
3340
|
-
*
|
|
3365
|
+
* Whether the app's setup status says the wizard was closed, so it
|
|
3366
|
+
* stays closed in every browser and on every device. The status
|
|
3367
|
+
* (`GET /api/setup/status`) may carry `dismissed: true`, or the
|
|
3368
|
+
* `setup.version` it was closed at (`dismissed: 2`), which re-opens the
|
|
3369
|
+
* wizard after a version bump. An app whose status carries neither
|
|
3370
|
+
* keeps today's per-browser behaviour. An app may also answer the
|
|
3371
|
+
* outstanding steps itself when it records the close (OpenRegister's
|
|
3372
|
+
* `dismiss-setup`), which needs nothing here.
|
|
3373
|
+
*
|
|
3374
|
+
* @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
|
|
3375
|
+
* @return {boolean} True when the server recorded the close for this version.
|
|
3376
|
+
*/
|
|
3377
|
+
isSetupWizardDismissedOnServer() {
|
|
3378
|
+
const s = this.setupState
|
|
3379
|
+
const status = s && s.status && s.status.value
|
|
3380
|
+
const dismissed = status && typeof status === 'object' ? status.dismissed : undefined
|
|
3381
|
+
if (dismissed === true) {
|
|
3382
|
+
return true
|
|
3383
|
+
}
|
|
3384
|
+
if (typeof dismissed === 'number' && Number.isFinite(dismissed)) {
|
|
3385
|
+
const version = (this.manifest && this.manifest.setup && this.manifest.setup.version) || 0
|
|
3386
|
+
return dismissed >= version
|
|
3387
|
+
}
|
|
3388
|
+
return false
|
|
3389
|
+
},
|
|
3390
|
+
|
|
3391
|
+
/**
|
|
3392
|
+
* The setup action that records a closed wizard on the server, from
|
|
3393
|
+
* `manifest.setup.dismissAction`, or '' when the app declares none.
|
|
3394
|
+
*
|
|
3395
|
+
* @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
|
|
3396
|
+
* @return {string} The action id.
|
|
3397
|
+
*/
|
|
3398
|
+
setupDismissAction() {
|
|
3399
|
+
const action = this.manifest && this.manifest.setup && this.manifest.setup.dismissAction
|
|
3400
|
+
return typeof action === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(action) ? action : ''
|
|
3401
|
+
},
|
|
3402
|
+
|
|
3403
|
+
/**
|
|
3404
|
+
* Record the setup wizard as closed for this manifest `setup.version`
|
|
3405
|
+
* so it doesn't auto-open again: in this browser (localStorage) and,
|
|
3406
|
+
* when the manifest declares `setup.dismissAction`, on the server
|
|
3407
|
+
* through the app's own setup action endpoint
|
|
3408
|
+
* (`POST /apps/{appId}/api/setup/action/{dismissAction}`, admin-only
|
|
3409
|
+
* like every setup endpoint, so the record is per instance). Then
|
|
3410
|
+
* emits `setup-wizard-dismissed`. Once per page load: finishing
|
|
3411
|
+
* records it, and the result screen's Close that follows does not
|
|
3412
|
+
* post again.
|
|
3341
3413
|
*
|
|
3414
|
+
* @spec openspec/changes/setup-wizard-close-on-server/specs/cn-setup-wizard/spec.md
|
|
3415
|
+
* @param {boolean} [finished] True when the user finished the wizard, false when they closed it.
|
|
3342
3416
|
* @return {void}
|
|
3343
3417
|
*/
|
|
3344
|
-
persistSetupWizardDismissal() {
|
|
3418
|
+
persistSetupWizardDismissal(finished = false) {
|
|
3345
3419
|
try {
|
|
3346
3420
|
window.localStorage.setItem(this.setupWizardDismissKey(), '1')
|
|
3347
3421
|
} catch {
|
|
3348
3422
|
// Best-effort persistence (private mode / no storage).
|
|
3349
3423
|
}
|
|
3424
|
+
if (this.setupWizardDismissed) {
|
|
3425
|
+
return
|
|
3426
|
+
}
|
|
3350
3427
|
this.setupWizardDismissed = true
|
|
3428
|
+
const action = this.setupDismissAction()
|
|
3429
|
+
if (action) {
|
|
3430
|
+
try {
|
|
3431
|
+
Promise.resolve(axios.post(generateUrl(`/apps/${this.appId}/api/setup/action/${action}`), { finished: finished === true }))
|
|
3432
|
+
.catch(() => { /* the local record still holds in this browser */ })
|
|
3433
|
+
} catch {
|
|
3434
|
+
// Never let a failed record break the close.
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
/**
|
|
3438
|
+
* @event setup-wizard-dismissed Emitted once when the user closes or finishes the setup wizard.
|
|
3439
|
+
* @type {{ appId: string, version: number, finished: boolean }}
|
|
3440
|
+
*/
|
|
3441
|
+
this.$emit('setup-wizard-dismissed', {
|
|
3442
|
+
appId: this.appId,
|
|
3443
|
+
version: (this.manifest && this.manifest.setup && this.manifest.setup.version) || 0,
|
|
3444
|
+
finished: finished === true,
|
|
3445
|
+
})
|
|
3351
3446
|
},
|
|
3352
3447
|
|
|
3353
3448
|
/**
|
|
@@ -3434,6 +3529,49 @@ export default {
|
|
|
3434
3529
|
persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
|
|
3435
3530
|
},
|
|
3436
3531
|
|
|
3532
|
+
/**
|
|
3533
|
+
* Remember that the user paused the tour, at the step they were on,
|
|
3534
|
+
* so the next page load keeps it hidden until they pick "Continue".
|
|
3535
|
+
*
|
|
3536
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
|
|
3537
|
+
* @param {object} [where] `{ tourId, stepId, index }` from CnWalkthrough.
|
|
3538
|
+
* @return {void}
|
|
3539
|
+
*/
|
|
3540
|
+
onWalkthroughPause(where) {
|
|
3541
|
+
const current = this.walkthroughProgressValue || {}
|
|
3542
|
+
const tourId = (where && where.tourId) || current.tourId
|
|
3543
|
+
if (!tourId) {
|
|
3544
|
+
return
|
|
3545
|
+
}
|
|
3546
|
+
const value = {
|
|
3547
|
+
tourId,
|
|
3548
|
+
stepId: (where && where.tourId) ? (where.stepId || '') : (current.stepId || ''),
|
|
3549
|
+
index: (where && where.tourId && Number.isInteger(where.index)) ? where.index : (current.index || 0),
|
|
3550
|
+
version: String((this.manifest && this.manifest.version) || ''),
|
|
3551
|
+
paused: true,
|
|
3552
|
+
}
|
|
3553
|
+
this.walkthroughProgressValue = value
|
|
3554
|
+
persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
|
|
3555
|
+
},
|
|
3556
|
+
|
|
3557
|
+
/**
|
|
3558
|
+
* Clear the paused mark when the user continues the tour. The step
|
|
3559
|
+
* stays; the next step change writes fresh progress anyway.
|
|
3560
|
+
*
|
|
3561
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
|
|
3562
|
+
* @return {void}
|
|
3563
|
+
*/
|
|
3564
|
+
unpauseWalkthroughProgress() {
|
|
3565
|
+
const progress = this.walkthroughProgressValue
|
|
3566
|
+
if (!progress || !progress.paused) {
|
|
3567
|
+
return
|
|
3568
|
+
}
|
|
3569
|
+
const value = { ...progress }
|
|
3570
|
+
delete value.paused
|
|
3571
|
+
this.walkthroughProgressValue = value
|
|
3572
|
+
persistWalkthroughProgress(this.appId, this.walkthroughConfigKey, value)
|
|
3573
|
+
},
|
|
3574
|
+
|
|
3437
3575
|
/**
|
|
3438
3576
|
* Continue an unfinished tour from the user-settings dialog: show a
|
|
3439
3577
|
* paused tour again, or start the remembered tour at its step.
|
|
@@ -3448,10 +3586,11 @@ export default {
|
|
|
3448
3586
|
}
|
|
3449
3587
|
setTimeout(() => {
|
|
3450
3588
|
const wt = useWalkthrough(this.appId, this.manifest)
|
|
3589
|
+
const progress = this.walkthroughProgressValue
|
|
3590
|
+
this.unpauseWalkthroughProgress()
|
|
3451
3591
|
if (wt.resumePaused()) {
|
|
3452
3592
|
return
|
|
3453
3593
|
}
|
|
3454
|
-
const progress = this.walkthroughProgressValue
|
|
3455
3594
|
if (progress) {
|
|
3456
3595
|
wt.resumeAt(progress.tourId, progress.stepId)
|
|
3457
3596
|
}
|
|
@@ -3478,6 +3617,7 @@ export default {
|
|
|
3478
3617
|
setTimeout(() => {
|
|
3479
3618
|
const id = this.manifest.walkthrough.tours[0] && this.manifest.walkthrough.tours[0].id
|
|
3480
3619
|
if (id) {
|
|
3620
|
+
this.unpauseWalkthroughProgress()
|
|
3481
3621
|
useWalkthrough(this.appId, this.manifest).restart(id)
|
|
3482
3622
|
}
|
|
3483
3623
|
}, 50)
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
v-if="crumbs.length > 0"
|
|
20
20
|
:aria-label="ariaLabel"
|
|
21
21
|
:rootIcon="rootText ? '' : undefined"
|
|
22
|
+
:class="{ 'cn-breadcrumbs--text-separator': separator !== '' }"
|
|
23
|
+
:style="separatorStyle"
|
|
22
24
|
data-testid="cn-breadcrumbs">
|
|
23
25
|
<!-- `name` falls back to '' because NcBreadcrumb declares it a REQUIRED
|
|
24
26
|
String and warns per render on the label-less icon-only crumb. The
|
|
@@ -111,6 +113,34 @@ export default {
|
|
|
111
113
|
type: Boolean,
|
|
112
114
|
default: false,
|
|
113
115
|
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Draw this text between the crumbs (for example `/`) instead of
|
|
119
|
+
* NcBreadcrumbs' chevron. Empty (the default) keeps the chevron. The
|
|
120
|
+
* separator is decoration: it is not read out.
|
|
121
|
+
*
|
|
122
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator
|
|
123
|
+
*/
|
|
124
|
+
separator: {
|
|
125
|
+
type: String,
|
|
126
|
+
default: '',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
computed: {
|
|
131
|
+
/**
|
|
132
|
+
* The separator handed to the stylesheet as a CSS string, or nothing
|
|
133
|
+
* without a `separator`.
|
|
134
|
+
*
|
|
135
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator
|
|
136
|
+
* @return {object|undefined}
|
|
137
|
+
*/
|
|
138
|
+
separatorStyle() {
|
|
139
|
+
if (this.separator === '') {
|
|
140
|
+
return undefined
|
|
141
|
+
}
|
|
142
|
+
return { '--cn-breadcrumbs-separator': JSON.stringify(this.separator) }
|
|
143
|
+
},
|
|
114
144
|
},
|
|
115
145
|
|
|
116
146
|
methods: {
|
|
@@ -127,3 +157,18 @@ export default {
|
|
|
127
157
|
},
|
|
128
158
|
}
|
|
129
159
|
</script>
|
|
160
|
+
|
|
161
|
+
<style scoped>
|
|
162
|
+
/* `separator`: a text separator in place of the chevron. The chevron's own
|
|
163
|
+
element stays (it carries NcBreadcrumbs' spacing and its "no separator after
|
|
164
|
+
the last crumb" rule); only its icon gives way to the text. */
|
|
165
|
+
.cn-breadcrumbs--text-separator :deep(.vue-crumb__separator svg) {
|
|
166
|
+
display: none;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.cn-breadcrumbs--text-separator :deep(.vue-crumb__separator)::before {
|
|
170
|
+
content: var(--cn-breadcrumbs-separator);
|
|
171
|
+
padding-inline: var(--cn-breadcrumbs-separator-gap, 0);
|
|
172
|
+
color: var(--color-text-maxcontrast);
|
|
173
|
+
}
|
|
174
|
+
</style>
|
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
7
|
<div class="cn-calendar-widget">
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
<!-- `content.showTitle: false` and `content.showViewModes: false` drop
|
|
9
|
+
the sub-heading and the Month / Week / Agenda buttons, for an agenda
|
|
10
|
+
placement whose card already names it; without both the header
|
|
11
|
+
row goes. -->
|
|
12
|
+
<header v-if="showsTitle || showsViewModes" class="cn-calendar-widget__header">
|
|
13
|
+
<span v-if="showsTitle" class="cn-calendar-widget__title">{{ t('nextcloud-vue', 'Calendar') }}</span>
|
|
14
|
+
<div v-if="showsViewModes" class="cn-calendar-widget__modes">
|
|
11
15
|
<button
|
|
12
16
|
v-for="mode in viewModes"
|
|
13
17
|
:key="mode"
|
|
@@ -160,7 +164,10 @@ export default {
|
|
|
160
164
|
|
|
161
165
|
props: {
|
|
162
166
|
/**
|
|
163
|
-
* Persisted widget content `{viewMode, daysAhead, colorByCalendar
|
|
167
|
+
* Persisted widget content `{viewMode, daysAhead, colorByCalendar,
|
|
168
|
+
* showTitle, showViewModes}`. `showTitle: false` drops the "Calendar"
|
|
169
|
+
* sub-heading and `showViewModes: false` the Month / Week / Agenda
|
|
170
|
+
* buttons (both shown by default).
|
|
164
171
|
*
|
|
165
172
|
* @type {object}
|
|
166
173
|
*/
|
|
@@ -197,6 +204,27 @@ export default {
|
|
|
197
204
|
},
|
|
198
205
|
|
|
199
206
|
computed: {
|
|
207
|
+
/**
|
|
208
|
+
* Whether the "Calendar" sub-heading renders (`content.showTitle`).
|
|
209
|
+
*
|
|
210
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-agenda-placement-can-drop-the-calendar-chrome
|
|
211
|
+
* @return {boolean}
|
|
212
|
+
*/
|
|
213
|
+
showsTitle() {
|
|
214
|
+
return !(this.content && this.content.showTitle === false)
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Whether the Month / Week / Agenda buttons render
|
|
219
|
+
* (`content.showViewModes`).
|
|
220
|
+
*
|
|
221
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-agenda-placement-can-drop-the-calendar-chrome
|
|
222
|
+
* @return {boolean}
|
|
223
|
+
*/
|
|
224
|
+
showsViewModes() {
|
|
225
|
+
return !(this.content && this.content.showViewModes === false)
|
|
226
|
+
},
|
|
227
|
+
|
|
200
228
|
/**
|
|
201
229
|
* The available view modes.
|
|
202
230
|
*
|
|
@@ -187,6 +187,7 @@ import {
|
|
|
187
187
|
hasProviders,
|
|
188
188
|
RATINGS,
|
|
189
189
|
resolveProvider,
|
|
190
|
+
warnCapabilityComparisonDeprecated,
|
|
190
191
|
} from '../../utils/capabilityComparison.js'
|
|
191
192
|
|
|
192
193
|
/**
|
|
@@ -212,6 +213,13 @@ export default {
|
|
|
212
213
|
* document without them renders the table this component's consumers
|
|
213
214
|
* already had.
|
|
214
215
|
*
|
|
216
|
+
* Deprecated, with the whole component: publish the comparison on the
|
|
217
|
+
* app's docs site and link to it (as dossiq #3312 and pipelinq #2195
|
|
218
|
+
* did). Still rendered; removal waits for a major version. A
|
|
219
|
+
* development build warns once when a comparison is given.
|
|
220
|
+
*
|
|
221
|
+
* @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.
|
|
222
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated
|
|
215
223
|
* @type {object}
|
|
216
224
|
*/
|
|
217
225
|
comparison: {
|
|
@@ -437,6 +445,12 @@ export default {
|
|
|
437
445
|
}
|
|
438
446
|
},
|
|
439
447
|
|
|
448
|
+
mounted() {
|
|
449
|
+
if (this.comparison) {
|
|
450
|
+
warnCapabilityComparisonDeprecated('CnCapabilityTable')
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
|
|
440
454
|
methods: {
|
|
441
455
|
/**
|
|
442
456
|
* Debounce the reader's typing into `query`.
|
|
@@ -158,3 +158,5 @@ explicit event when you are writing new code:
|
|
|
158
158
|
@item-activate="({ item, clientX, clientY }) => openMenu(item, clientX, clientY)"
|
|
159
159
|
@layout-change="onLayoutChange" />
|
|
160
160
|
```
|
|
161
|
+
|
|
162
|
+
`float` (Boolean, default `true`): GridStack's `float`. `false` packs items upward, so a size-to-content cell that shrinks, or a widget that is not shown, closes its gap.
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
v-for="(item, index) in layout"
|
|
32
32
|
:key="resolveItemKey(item)"
|
|
33
33
|
class="grid-stack-item"
|
|
34
|
+
:class="{ 'cn-dashboard-grid__item--fit': sizeToContentAttr(item) !== undefined }"
|
|
34
35
|
role="group"
|
|
35
36
|
:aria-label="resolveItemLabel(item, index)"
|
|
36
37
|
:aria-describedby="itemDescribedBy"
|
|
@@ -255,6 +256,19 @@ export default {
|
|
|
255
256
|
type: Boolean,
|
|
256
257
|
default: true,
|
|
257
258
|
},
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* GridStack's `float`. True (the default) keeps every item on the row
|
|
262
|
+
* it was placed on, with empty rows above it if need be. False packs
|
|
263
|
+
* items upward, so a size-to-content cell that shrinks, or a widget
|
|
264
|
+
* that is not shown, closes its gap instead of leaving a band.
|
|
265
|
+
*
|
|
266
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-dashboard-closes-the-gap-a-shrinking-or-hidden-widget-leaves
|
|
267
|
+
*/
|
|
268
|
+
float: {
|
|
269
|
+
type: Boolean,
|
|
270
|
+
default: true,
|
|
271
|
+
},
|
|
258
272
|
},
|
|
259
273
|
|
|
260
274
|
emits: [
|
|
@@ -278,6 +292,8 @@ export default {
|
|
|
278
292
|
return {
|
|
279
293
|
grid: null,
|
|
280
294
|
contentObserver: null,
|
|
295
|
+
/** True while the grid moves an item to where the layout put it. */
|
|
296
|
+
applyingLayout: false,
|
|
281
297
|
domId: `cn-dashboard-grid-${++domIdCounter}`,
|
|
282
298
|
/** Text currently held in the polite live region. */
|
|
283
299
|
announcement: '',
|
|
@@ -485,7 +501,7 @@ export default {
|
|
|
485
501
|
column: this.columns,
|
|
486
502
|
cellHeight: this.cellHeight,
|
|
487
503
|
margin: this.margin,
|
|
488
|
-
float:
|
|
504
|
+
float: this.float,
|
|
489
505
|
animate: true,
|
|
490
506
|
disableDrag: !this.editable,
|
|
491
507
|
disableResize: !this.editable,
|
|
@@ -558,6 +574,10 @@ export default {
|
|
|
558
574
|
// switched off as the grid narrows, and back on as it widens.
|
|
559
575
|
this.syncInteractivity()
|
|
560
576
|
|
|
577
|
+
if (this.applyingLayout) {
|
|
578
|
+
return
|
|
579
|
+
}
|
|
580
|
+
|
|
561
581
|
// GridStack fires `change` for its own responsive rescale too, and at a
|
|
562
582
|
// 12 → 1 breakpoint every item reads `gridX: 0, gridWidth: 1`. Emitting
|
|
563
583
|
// that has the host persist the reflow AS the authored layout, which
|
|
@@ -957,7 +977,29 @@ export default {
|
|
|
957
977
|
const node = this.grid.engine.nodes.find((n) => String(n.id) === String(item.id))
|
|
958
978
|
if (!node) {
|
|
959
979
|
this.grid.makeWidget(el)
|
|
960
|
-
} else if (node.el
|
|
980
|
+
} else if (node.el === el) {
|
|
981
|
+
// The layout MOVED an item the grid already tracks: the
|
|
982
|
+
// page re-compacts its display layout when a widget's
|
|
983
|
+
// condition hides it, and the item below was given a new
|
|
984
|
+
// gridY that never reached GridStack, so a band stayed
|
|
985
|
+
// where the hidden widget had been. A drag reports the
|
|
986
|
+
// grid's own position back, which matches and does
|
|
987
|
+
// nothing here.
|
|
988
|
+
const x = item.gridX ?? node.x
|
|
989
|
+
const y = item.gridY ?? node.y
|
|
990
|
+
if (x !== node.x || y !== node.y) {
|
|
991
|
+
// The layout said where it goes; GridStack's `change`
|
|
992
|
+
// for it is not a user's arrangement and is not
|
|
993
|
+
// reported back (it would write the display-only
|
|
994
|
+
// compaction into the authored layout).
|
|
995
|
+
this.applyingLayout = true
|
|
996
|
+
try {
|
|
997
|
+
this.grid.update(el, { x, y })
|
|
998
|
+
} finally {
|
|
999
|
+
this.applyingLayout = false
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
} else {
|
|
961
1003
|
// Drop the stale node without touching its detached DOM,
|
|
962
1004
|
// then adopt the new element so GridStack sizes/places it.
|
|
963
1005
|
this.grid.removeWidget(node.el, false, false)
|
|
@@ -1016,6 +1058,16 @@ export default {
|
|
|
1016
1058
|
border-radius: var(--border-radius-container-large, 16px);
|
|
1017
1059
|
}
|
|
1018
1060
|
|
|
1061
|
+
/* A size-to-content cell (`sizeToContent`) is measured by GridStack from the
|
|
1062
|
+
content's first child, and a widget wrapper there filled the cell
|
|
1063
|
+
(`height: 100%`), so the measure returned the cell's own height and the
|
|
1064
|
+
cell never shrank: "Waiting for me" stayed a tall card around one row. In
|
|
1065
|
+
such a cell the child takes its own height. A cell without the key keeps
|
|
1066
|
+
the filling child. */
|
|
1067
|
+
.cn-dashboard-grid__item--fit > :deep(.grid-stack-item-content > *) {
|
|
1068
|
+
height: auto;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1019
1071
|
:deep(.grid-stack-item-content) {
|
|
1020
1072
|
background: var(--color-main-background);
|
|
1021
1073
|
border: none;
|
|
@@ -305,6 +305,7 @@
|
|
|
305
305
|
:columns="columns"
|
|
306
306
|
:cellHeight="cellHeight"
|
|
307
307
|
:margin="gridMargin"
|
|
308
|
+
:float="gridFloat"
|
|
308
309
|
@layoutChange="section.view ? onViewLayoutChange($event) : onLayoutChange($event)">
|
|
309
310
|
<template #widget="{ item }">
|
|
310
311
|
<!-- In-app edit overlay (ADR-041): a single launchpad-style
|
|
@@ -329,24 +330,26 @@
|
|
|
329
330
|
v-if="missingRequiredApp(item)"
|
|
330
331
|
:title="getWidgetTitle(item)"
|
|
331
332
|
:showTitle="widgetShowTitle(item)">
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
333
|
+
<!-- Compact on purpose: a two-row tile is about 150px, less
|
|
334
|
+
a title row. NcEmptyContent's 64px icon, 20px name,
|
|
335
|
+
description and button did not fit, so the icon ran out
|
|
336
|
+
at the top and the button at the bottom. The button
|
|
337
|
+
already says "Install {app}", so there is no separate
|
|
338
|
+
name, and the text keeps to two lines. -->
|
|
339
|
+
<div class="cn-dashboard-page__requires-app cn-requires-app cn-requires-app--compact"
|
|
340
|
+
data-testid="cn-requires-app">
|
|
341
|
+
<p class="cn-requires-app__description">
|
|
342
|
+
{{ t('nextcloud-vue', 'This widget shows data from another app that isn\'t installed yet.') }}
|
|
343
|
+
</p>
|
|
344
|
+
<NcButton
|
|
345
|
+
variant="primary"
|
|
346
|
+
:href="appInstallUrl(missingRequiredApp(item))">
|
|
347
|
+
<template #icon>
|
|
348
|
+
<Download :size="18" />
|
|
349
|
+
</template>
|
|
350
|
+
{{ installAppLabel(missingRequiredApp(item)) }}
|
|
351
|
+
</NcButton>
|
|
352
|
+
</div>
|
|
350
353
|
</CnWidgetWrapper>
|
|
351
354
|
|
|
352
355
|
<!-- Tile widget -->
|
|
@@ -1030,6 +1033,19 @@ export default {
|
|
|
1030
1033
|
default: true,
|
|
1031
1034
|
},
|
|
1032
1035
|
|
|
1036
|
+
/**
|
|
1037
|
+
* GridStack's `float` for this dashboard (manifest
|
|
1038
|
+
* `config.gridFloat`). True (the default) keeps every widget on its
|
|
1039
|
+
* row. False packs widgets upward, so a size-to-content widget that
|
|
1040
|
+
* shrinks closes the gap under it instead of leaving an empty band.
|
|
1041
|
+
*
|
|
1042
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-dashboard-closes-the-gap-a-shrinking-or-hidden-widget-leaves
|
|
1043
|
+
*/
|
|
1044
|
+
gridFloat: {
|
|
1045
|
+
type: Boolean,
|
|
1046
|
+
default: true,
|
|
1047
|
+
},
|
|
1048
|
+
|
|
1033
1049
|
/**
|
|
1034
1050
|
* Widget definitions array. Each widget defines metadata for rendering.
|
|
1035
1051
|
*
|
|
@@ -3602,6 +3618,12 @@ export default {
|
|
|
3602
3618
|
|
|
3603
3619
|
getWidgetButtons(item) {
|
|
3604
3620
|
const def = this.getWidgetDef(item.widgetId)
|
|
3621
|
+
// `showButtons: false` on the placement or the definition drops
|
|
3622
|
+
// the footer links ("More events"), for a placement whose card
|
|
3623
|
+
// says enough without them (zuiddrecht-pixel-gaps-3).
|
|
3624
|
+
if (item?.showButtons === false || def?.showButtons === false) {
|
|
3625
|
+
return []
|
|
3626
|
+
}
|
|
3605
3627
|
return def?.buttons || []
|
|
3606
3628
|
},
|
|
3607
3629
|
|
|
@@ -3905,11 +3927,16 @@ export default {
|
|
|
3905
3927
|
// library's own defaults, so a manifest need not set them — but a
|
|
3906
3928
|
// manifest that DOES set them must reach the component, or the
|
|
3907
3929
|
// declaration is a silent no-op that reads like configuration.
|
|
3908
|
-
for (const key of ['countLabel', 'variant', 'showZeroCount', 'horizontal', 'vertical', 'filled', 'route', 'iconClass', 'format']) {
|
|
3930
|
+
for (const key of ['countLabel', 'variant', 'showZeroCount', 'horizontal', 'vertical', 'filled', 'route', 'iconClass', 'format', 'layout']) {
|
|
3909
3931
|
if (props[key] !== undefined) {
|
|
3910
3932
|
out[key] = props[key]
|
|
3911
3933
|
}
|
|
3912
3934
|
}
|
|
3935
|
+
// `content.layout: "stacked"`, the key a stat tile takes, works on
|
|
3936
|
+
// a stats block too (zuiddrecht-pixel-gaps-3).
|
|
3937
|
+
if (out.layout === undefined && typeof content.layout === 'string') {
|
|
3938
|
+
out.layout = content.layout
|
|
3939
|
+
}
|
|
3913
3940
|
// `countLabel` is the unit beside the number ("0 cases", "0 tasks").
|
|
3914
3941
|
// It is manifest-authored prose and was being forwarded raw, so a
|
|
3915
3942
|
// Dutch account read "0 cases" beside a translated widget title.
|
|
@@ -361,6 +361,40 @@ import { CnLockIndicator } from '../CnLockIndicator/index.js'
|
|
|
361
361
|
// consuming app does not pull in the library's global css/index.css.
|
|
362
362
|
import '../../css/table.css'
|
|
363
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Fill a `secondary` template such as `"{identifier} · {requester}"`.
|
|
366
|
+
*
|
|
367
|
+
* A field without a value takes its separator with it: the text between two
|
|
368
|
+
* fields belongs to the field after it, so an empty requester leaves
|
|
369
|
+
* "2026-0002", not "2026-0002 ·", and an empty first field does not leave a
|
|
370
|
+
* leading separator either. Text before the first field and after the last
|
|
371
|
+
* one is kept as written. A template whose fields all have values fills
|
|
372
|
+
* exactly as before.
|
|
373
|
+
*
|
|
374
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-empty-field-takes-its-separator-with-it
|
|
375
|
+
* @param {string} template The template, with `{field}` placeholders.
|
|
376
|
+
* @param {(key: string) => string} valueOf The text of a field ('' when empty).
|
|
377
|
+
* @return {string} The filled template.
|
|
378
|
+
*/
|
|
379
|
+
function fillSecondaryTemplate(template, valueOf) {
|
|
380
|
+
// split() with a capture group alternates literal, field, literal, …
|
|
381
|
+
const parts = template.split(/\{([^}]+)\}/)
|
|
382
|
+
const lead = parts[0]
|
|
383
|
+
const tail = parts[parts.length - 1]
|
|
384
|
+
let body = ''
|
|
385
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
386
|
+
const value = valueOf(parts[i].trim())
|
|
387
|
+
if (value === '') {
|
|
388
|
+
continue
|
|
389
|
+
}
|
|
390
|
+
// The literal before this field: the separator, unless nothing has
|
|
391
|
+
// been written yet (then there is nothing to separate from).
|
|
392
|
+
const separator = i === 1 ? '' : parts[i - 1]
|
|
393
|
+
body += (body === '' ? '' : separator) + value
|
|
394
|
+
}
|
|
395
|
+
return lead + body + tail
|
|
396
|
+
}
|
|
397
|
+
|
|
364
398
|
/**
|
|
365
399
|
* CnDataTable — Generic sortable data table for list views.
|
|
366
400
|
*
|
|
@@ -1451,7 +1485,7 @@ export default {
|
|
|
1451
1485
|
}
|
|
1452
1486
|
const asText = (v) => (v === null || v === undefined ? '' : String(v))
|
|
1453
1487
|
if (spec.includes('{')) {
|
|
1454
|
-
const filled = spec
|
|
1488
|
+
const filled = fillSecondaryTemplate(spec, (key) => asText(this.getCellValue(row, key)))
|
|
1455
1489
|
// A template whose every field is empty leaves only separators:
|
|
1456
1490
|
// nothing worth a line.
|
|
1457
1491
|
return /[\p{L}\p{N}]/u.test(filled) ? filled.trim() : ''
|