@conduction/nextcloud-vue 2.92.0 → 2.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +2 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +28 -8
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +37 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +57 -4
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +44 -17
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +44 -2
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +140 -59
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +139 -3
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js +60 -12
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +111 -42
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +117 -73
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +34 -39
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +7 -7
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +5 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +6 -4
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +46 -0
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js +6 -2
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +82 -9
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +104 -90
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +9 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js.map +1 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +355 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +462 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +21 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +402 -391
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
- package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +141 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +22 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +20 -14
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue.js +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +2 -0
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +15 -5
- package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js +8 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +114 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js +45 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js +38 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js +12 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue2.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue3.js +56 -51
- package/dist/esm/components/CnPagination/CnPagination.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +3 -3
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue.js +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js +8 -30
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js.map +1 -1
- package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +24 -0
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +3 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +39 -2
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +85 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +37 -9
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +9 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +164 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +93 -0
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -3
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +32 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +31 -20
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/l10n/en.json.js +24 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +24 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1190 -410
- package/dist/esm/utils/dueText.js +49 -8
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/featureCatalog.js +135 -0
- package/dist/esm/utils/featureCatalog.js.map +1 -0
- package/dist/esm/utils/fillTemplate.js +74 -3
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/headerMeta.js +62 -12
- package/dist/esm/utils/headerMeta.js.map +1 -1
- package/dist/esm/utils/roadmapIssues.js +86 -0
- package/dist/esm/utils/roadmapIssues.js.map +1 -0
- package/dist/esm/utils/userDisplayName.js +47 -0
- package/dist/esm/utils/userDisplayName.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +16229 -13423
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1190 -410
- package/l10n/en.json +24 -1
- package/l10n/nl.json +24 -1
- package/package.json +1 -1
- package/src/components/CnActionsMenu/CnActionsMenu.vue +3 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +39 -1
- package/src/components/CnAppRoot/CnAppRoot.md +2 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +66 -5
- package/src/components/CnChoiceCards/CnChoiceCards.md +27 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +168 -3
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +222 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +141 -8
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +41 -46
- package/src/components/CnDetailPage/CnDetailPage.vue +5 -3
- package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
- package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +51 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +98 -13
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +1053 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +606 -562
- package/src/components/CnIndexPage/CnIndexPage.vue +144 -7
- package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +23 -2
- package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +5 -0
- package/src/components/CnPageHeader/CnHeaderTrail.vue +140 -0
- package/src/components/CnPageHeader/CnPageHeader.md +11 -0
- package/src/components/CnPageHeader/CnPageHeader.vue +42 -0
- package/src/components/CnPagination/CnPagination.vue +10 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +3 -3
- package/src/components/CnRoadmapTab/CnRoadmapTab.vue +6 -28
- package/src/components/CnSettingsPage/CnSettingsPage.vue +26 -0
- package/src/components/CnTabs/CnTabs.vue +39 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +106 -13
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +305 -0
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +8 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +43 -2
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +61 -0
- package/src/css/look-board-detail.css +8 -0
- package/src/css/look-board-index.css +27 -1
- package/src/css/look-board.css +60 -0
- package/src/css/page-header.css +16 -0
- package/src/schemas/app-manifest-v2.schema.json +30 -17
- package/src/utils/dueText.js +49 -8
- package/src/utils/featureCatalog.js +132 -0
- package/src/utils/fillTemplate.js +73 -2
- package/src/utils/headerMeta.js +62 -12
- package/src/utils/roadmapIssues.js +82 -0
- package/src/utils/userDisplayName.js +54 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
:title="title"
|
|
48
48
|
:description="resolvedDescription"
|
|
49
49
|
:icon="icon"
|
|
50
|
+
:breadcrumb="breadcrumb"
|
|
51
|
+
:meta="headerMeta"
|
|
50
52
|
:visuallyHidden="!showTitle">
|
|
51
53
|
<!-- Board look: the buildiq square, then the page Save as the
|
|
52
54
|
last header button. -->
|
|
@@ -475,6 +477,30 @@ export default {
|
|
|
475
477
|
default: () => t('nextcloud-vue', 'Settings'),
|
|
476
478
|
},
|
|
477
479
|
|
|
480
|
+
/**
|
|
481
|
+
* A breadcrumb under the title (manifest `config.breadcrumb`): one
|
|
482
|
+
* crumb `{ label, route?, params?, href?, icon?, separator? }` or a
|
|
483
|
+
* list; the title follows as the current crumb. Drawn as the detail
|
|
484
|
+
* header's breadcrumb row. Null (the default) draws none.
|
|
485
|
+
*
|
|
486
|
+
* @type {object|Array<object>|null}
|
|
487
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
488
|
+
*/
|
|
489
|
+
breadcrumb: {
|
|
490
|
+
type: [Object, Array],
|
|
491
|
+
default: null,
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* A note after the breadcrumb and a middle dot (manifest `config.headerMeta`).
|
|
496
|
+
*
|
|
497
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
498
|
+
*/
|
|
499
|
+
headerMeta: {
|
|
500
|
+
type: String,
|
|
501
|
+
default: '',
|
|
502
|
+
},
|
|
503
|
+
|
|
478
504
|
/** Description shown under the title when `showTitle` is set. */
|
|
479
505
|
description: {
|
|
480
506
|
type: String,
|
|
@@ -263,6 +263,21 @@ export default defineComponent({
|
|
|
263
263
|
default: '',
|
|
264
264
|
},
|
|
265
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Leave the `overflow` tabs to the host's `#nav-end` menu instead of a
|
|
268
|
+
* "More" menu of the strip's own. The strip then draws only the tabs
|
|
269
|
+
* that are not `overflow`, and an overflow tab that is selected joins
|
|
270
|
+
* it just before the last of them (board DqZaakBesluiten: Overview,
|
|
271
|
+
* Files, Contact, Tasks, Decisions, History). The host lists the
|
|
272
|
+
* overflow tabs and selects one through its `CnTab`'s `active`.
|
|
273
|
+
*
|
|
274
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu
|
|
275
|
+
*/
|
|
276
|
+
overflowInNavEnd: {
|
|
277
|
+
type: Boolean,
|
|
278
|
+
default: false,
|
|
279
|
+
},
|
|
280
|
+
|
|
266
281
|
/** Name of the menu that lists the `overflow` tabs. */
|
|
267
282
|
moreLabel: {
|
|
268
283
|
type: String,
|
|
@@ -377,14 +392,36 @@ export default defineComponent({
|
|
|
377
392
|
*
|
|
378
393
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
379
394
|
*/
|
|
380
|
-
const visibleTabs = computed(() =>
|
|
395
|
+
const visibleTabs = computed(() => {
|
|
396
|
+
if (props.overflowInNavEnd) {
|
|
397
|
+
return navEndStrip()
|
|
398
|
+
}
|
|
399
|
+
return tabs.filter((tab) => boardStrip.value || !tab.overflow || tab.uid === activeUid.value)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The strip under `overflowInNavEnd`: the tabs that are not
|
|
404
|
+
* `overflow`, with a selected overflow tab placed before the last.
|
|
405
|
+
*
|
|
406
|
+
* @return {object[]} The tabs to draw, in order.
|
|
407
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-an-opened-overflow-tab-joins-the-strip-before-the-last-tab
|
|
408
|
+
*/
|
|
409
|
+
function navEndStrip() {
|
|
410
|
+
const pinned = tabs.filter((tab) => !tab.overflow)
|
|
411
|
+
const opened = tabs.find((tab) => tab.overflow && tab.uid === activeUid.value)
|
|
412
|
+
if (!opened) {
|
|
413
|
+
return pinned
|
|
414
|
+
}
|
|
415
|
+
const at = Math.max(0, pinned.length - 1)
|
|
416
|
+
return [...pinned.slice(0, at), opened, ...pinned.slice(at)]
|
|
417
|
+
}
|
|
381
418
|
|
|
382
419
|
/**
|
|
383
420
|
* The tabs listed under "More": overflow tabs that are not selected.
|
|
384
421
|
*
|
|
385
422
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
386
423
|
*/
|
|
387
|
-
const overflowTabs = computed(() => (boardStrip.value ? [] : tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value)))
|
|
424
|
+
const overflowTabs = computed(() => (boardStrip.value || props.overflowInNavEnd ? [] : tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value)))
|
|
388
425
|
|
|
389
426
|
/**
|
|
390
427
|
* Whether a tab declares a count. `0` is a count; null and '' are not.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
:aria-label="stripLabel"
|
|
10
10
|
:moreLabel="moreLabel"
|
|
11
11
|
:variant="stripVariant"
|
|
12
|
+
:overflowInNavEnd="isBoard"
|
|
12
13
|
class="cn-tabs-widget__tabs"
|
|
13
14
|
@update:activeIndex="onTabChange">
|
|
14
15
|
<!-- One Actions menu for the whole widget, bound to whichever child
|
|
@@ -27,10 +28,11 @@
|
|
|
27
28
|
{{ cnTranslate(activeHeaderLink.label) }}
|
|
28
29
|
</component>
|
|
29
30
|
<CnActionsMenu
|
|
30
|
-
v-if="showActions"
|
|
31
|
-
:showRefresh="showRefresh"
|
|
32
|
-
:showRequestFeature="showRequestFeature"
|
|
33
|
-
:showDocumentation="showDocumentation"
|
|
31
|
+
v-if="showActions || menuTabs.length"
|
|
32
|
+
:showRefresh="showActions && showRefresh"
|
|
33
|
+
:showRequestFeature="showActions && showRequestFeature"
|
|
34
|
+
:showDocumentation="showActions && showDocumentation"
|
|
35
|
+
:showReportBug="showActions"
|
|
34
36
|
:documentationUrl="documentationUrl"
|
|
35
37
|
:widgetId="activeWidgetId"
|
|
36
38
|
:title="activeTitle"
|
|
@@ -38,6 +40,20 @@
|
|
|
38
40
|
:surface="`widget:${activeWidgetId}`"
|
|
39
41
|
refreshChannel="cn:widget:refresh"
|
|
40
42
|
testidBase="cn-tabs-widget">
|
|
43
|
+
<!-- Board look: the tabs that are not in the strip, at the
|
|
44
|
+
top of the menu, in place of a "More" button. -->
|
|
45
|
+
<template v-if="menuTabs.length" #lead-items>
|
|
46
|
+
<NcActionButton
|
|
47
|
+
v-for="entry in menuTabs"
|
|
48
|
+
:key="entry.key"
|
|
49
|
+
:disabled="entry.pending || null"
|
|
50
|
+
:closeAfterClick="true"
|
|
51
|
+
data-testid="cn-tabs-widget-menu-tab"
|
|
52
|
+
@click="openMenuTab(entry)">
|
|
53
|
+
{{ menuTabLabel(entry) }}
|
|
54
|
+
</NcActionButton>
|
|
55
|
+
<NcActionSeparator v-if="showActions" />
|
|
56
|
+
</template>
|
|
41
57
|
<!-- The open panel's own items, below the built-in trio.
|
|
42
58
|
A panel draws no header, so these would otherwise have
|
|
43
59
|
nowhere to go: the data widget's Metadata and full edit
|
|
@@ -51,7 +67,7 @@
|
|
|
51
67
|
tab mounted, so several panels publish at once and an
|
|
52
68
|
unfiltered list would offer actions for a sheet nobody
|
|
53
69
|
is looking at. -->
|
|
54
|
-
<template v-if="activePanelActions.length" #action-items>
|
|
70
|
+
<template v-if="showActions && activePanelActions.length" #action-items>
|
|
55
71
|
<NcActionButton
|
|
56
72
|
v-for="action in activePanelActions"
|
|
57
73
|
:key="action.key"
|
|
@@ -175,7 +191,7 @@
|
|
|
175
191
|
<script>
|
|
176
192
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
177
193
|
import { translate as t } from '@nextcloud/l10n'
|
|
178
|
-
import { NcActionButton, NcLoadingIcon } from '@nextcloud/vue'
|
|
194
|
+
import { NcActionButton, NcActionSeparator, NcLoadingIcon } from '@nextcloud/vue'
|
|
179
195
|
import CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'
|
|
180
196
|
import CnEmptyContent from '../CnEmptyContent/CnEmptyContent.vue'
|
|
181
197
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
@@ -184,6 +200,7 @@ import CnTabs from '../CnTabs/CnTabs.vue'
|
|
|
184
200
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
185
201
|
import { headerLinkTarget, widgetHeaderLink } from '../../utils/contentHeaderLink.js'
|
|
186
202
|
import { resolveTabCount } from '../../utils/detailActionModel.js'
|
|
203
|
+
import { detailWidgetVisible } from '../../utils/detailWidgetVisibility.js'
|
|
187
204
|
import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
|
|
188
205
|
import { isActivityWidget } from '../../utils/headerMeta.js'
|
|
189
206
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
|
|
@@ -251,6 +268,7 @@ export default {
|
|
|
251
268
|
CnTab,
|
|
252
269
|
CnTabs,
|
|
253
270
|
NcActionButton,
|
|
271
|
+
NcActionSeparator,
|
|
254
272
|
CnEmptyContent,
|
|
255
273
|
NcLoadingIcon,
|
|
256
274
|
},
|
|
@@ -364,7 +382,8 @@ export default {
|
|
|
364
382
|
* `layout: [{ widgetId, gridWidth }]` sets a width (of 12) per widget, as
|
|
365
383
|
* the page grid does. Widgets wrap onto the next row.
|
|
366
384
|
*
|
|
367
|
-
* `maxVisibleTabs` caps the strip: later tabs go under "More"
|
|
385
|
+
* `maxVisibleTabs` caps the strip: later tabs go under "More" (in the
|
|
386
|
+
* board look: into the Actions menu, with History kept in the strip).
|
|
368
387
|
* `hideEmpty: true` moves a tab whose count is 0 there too.
|
|
369
388
|
* `moreLabel` names that menu. `variant: "segmented"` draws the strip
|
|
370
389
|
* as a pill switch (CnTabs' segmented variant); `line` is the default.
|
|
@@ -552,16 +571,34 @@ export default {
|
|
|
552
571
|
return [...(forId.host || []), ...(forId.widget || [])]
|
|
553
572
|
},
|
|
554
573
|
|
|
574
|
+
/**
|
|
575
|
+
* The tabs the board look lists at the top of the Actions menu: the
|
|
576
|
+
* overflow tabs, less the one that is open (it is in the strip).
|
|
577
|
+
* Empty outside the board look, where CnTabs draws "More".
|
|
578
|
+
*
|
|
579
|
+
* @return {object[]} Resolved tabs, in strip order.
|
|
580
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu
|
|
581
|
+
*/
|
|
582
|
+
menuTabs() {
|
|
583
|
+
if (!this.isBoard) {
|
|
584
|
+
return []
|
|
585
|
+
}
|
|
586
|
+
return this.resolvedTabs.filter((entry, index) => entry.overflow && index !== this.activeIndex)
|
|
587
|
+
},
|
|
588
|
+
|
|
555
589
|
/**
|
|
556
590
|
* The configured tabs, each paired with the widget definition it names.
|
|
557
591
|
*
|
|
558
592
|
* A tab goes under the strip's "More" menu when it says `overflow`, when
|
|
559
593
|
* `content.hideEmpty` is set and its count is 0, or when
|
|
560
594
|
* `content.maxVisibleTabs` places are already taken. `count` is a
|
|
561
|
-
* literal, or read off the record through `countField`.
|
|
595
|
+
* literal, or read off the record through `countField`. In the board
|
|
596
|
+
* look those tabs are listed in the Actions menu instead, and the
|
|
597
|
+
* activity (History) tab always stays in the strip.
|
|
562
598
|
*
|
|
563
599
|
* @return {object[]} `{ key, widgetId, label, icon, widget, count, overflow }` per tab.
|
|
564
600
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
|
|
601
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu
|
|
565
602
|
*/
|
|
566
603
|
resolvedTabs() {
|
|
567
604
|
const authored = Array.isArray(this.content?.tabs) ? this.content.tabs : []
|
|
@@ -580,9 +617,12 @@ export default {
|
|
|
580
617
|
const originalIndex = ordered.map(({ index }) => index)
|
|
581
618
|
const max = Number(this.content?.maxVisibleTabs)
|
|
582
619
|
const hideEmpty = this.content?.hideEmpty === true
|
|
620
|
+
// The board keeps its History tab in the strip, so it holds one of
|
|
621
|
+
// the `maxVisibleTabs` places from the start.
|
|
622
|
+
const pinActivity = this.isBoard && tabs.some((tab, position) => isActivityTab(tab) && this.tabVisibility(tab, originalIndex[position]) !== 'hidden')
|
|
583
623
|
// How many tabs have taken a place in the strip so far. A tab that
|
|
584
624
|
// is already going under "More" does not use one up.
|
|
585
|
-
let placed = 0
|
|
625
|
+
let placed = pinActivity ? 1 : 0
|
|
586
626
|
const resolved = []
|
|
587
627
|
tabs.forEach((tab, position) => {
|
|
588
628
|
const index = originalIndex[position]
|
|
@@ -596,7 +636,7 @@ export default {
|
|
|
596
636
|
const ids = this.tabWidgetIds(tab)
|
|
597
637
|
const widgets = ids.length > 1
|
|
598
638
|
? ids.map((id) => this.availableWidgets.find((w) => w && w.id === id) || null)
|
|
599
|
-
.filter((w) => w && !this.hiddenByWorkspace(w))
|
|
639
|
+
.filter((w) => w && !this.hiddenByWorkspace(w) && !this.hiddenByRecord(w))
|
|
600
640
|
: []
|
|
601
641
|
const widget = ids.length > 1
|
|
602
642
|
? (widgets[0] || null)
|
|
@@ -605,9 +645,9 @@ export default {
|
|
|
605
645
|
const declared = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)
|
|
606
646
|
const counted = this.widgetCounts[index]
|
|
607
647
|
const count = declared === null && typeof counted === 'number' ? counted : declared
|
|
608
|
-
|
|
609
|
-
let overflow = !
|
|
610
|
-
if (!overflow && !
|
|
648
|
+
const pinned = pinActivity && isActivityTab(tab)
|
|
649
|
+
let overflow = !pinned && ((tab && tab.overflow === true) || (hideEmpty && count === 0))
|
|
650
|
+
if (!overflow && !pinned) {
|
|
611
651
|
if (Number.isFinite(max) && max > 0 && placed >= max) {
|
|
612
652
|
overflow = true
|
|
613
653
|
} else {
|
|
@@ -868,6 +908,26 @@ export default {
|
|
|
868
908
|
return !evaluateWorkspaceCondition(cond, 'value' in bag ? bag.value : bag)
|
|
869
909
|
},
|
|
870
910
|
|
|
911
|
+
/**
|
|
912
|
+
* Whether the record hides a widget of a stack on a detail page: its
|
|
913
|
+
* own `visibleWhen` reads a field of the record or of a record the
|
|
914
|
+
* page's `visibilityContext` names (`@context.<name>`), and does not
|
|
915
|
+
* hold, as a grid widget's would not. Hidden while those records
|
|
916
|
+
* load. Off a detail page (no record), and for a condition that needs
|
|
917
|
+
* a request, nothing is hidden here.
|
|
918
|
+
*
|
|
919
|
+
* @param {object} widget The widget definition.
|
|
920
|
+
* @return {boolean} True when the widget must not draw.
|
|
921
|
+
* @spec openspec/changes/tab-stack-child-visible-when/specs/tab-stack-child-visible-when/spec.md#requirement-a-widget-in-a-tab-stack-on-a-detail-page-follows-its-own-condition
|
|
922
|
+
*/
|
|
923
|
+
hiddenByRecord(widget) {
|
|
924
|
+
const cond = widget.visibleWhen
|
|
925
|
+
if (!this.objectData || !cond || typeof cond !== 'object' || isWorkspaceCondition(cond) || !isLocallyDecidableVisibleWhen(cond)) {
|
|
926
|
+
return false
|
|
927
|
+
}
|
|
928
|
+
return !detailWidgetVisible(widget, this.conditionData)
|
|
929
|
+
},
|
|
930
|
+
|
|
871
931
|
/**
|
|
872
932
|
* The grid a stack lays its widgets out in, as one column span (of 12)
|
|
873
933
|
* per widget in order, or null for the plain vertical stack. A tab says
|
|
@@ -1139,6 +1199,39 @@ export default {
|
|
|
1139
1199
|
}
|
|
1140
1200
|
},
|
|
1141
1201
|
|
|
1202
|
+
/**
|
|
1203
|
+
* Open a tab picked from the Actions menu, then move focus to it in
|
|
1204
|
+
* the strip, so a keyboard user lands on the tab they chose.
|
|
1205
|
+
*
|
|
1206
|
+
* @param {object} entry The resolved tab.
|
|
1207
|
+
* @return {void}
|
|
1208
|
+
* @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu
|
|
1209
|
+
*/
|
|
1210
|
+
openMenuTab(entry) {
|
|
1211
|
+
const index = this.resolvedTabs.indexOf(entry)
|
|
1212
|
+
if (index < 0) {
|
|
1213
|
+
return
|
|
1214
|
+
}
|
|
1215
|
+
this.activeIndex = index
|
|
1216
|
+
this.$nextTick(() => {
|
|
1217
|
+
const button = this.$el?.querySelector?.('[role="tab"][aria-selected="true"]')
|
|
1218
|
+
if (button && typeof button.focus === 'function') {
|
|
1219
|
+
button.focus()
|
|
1220
|
+
}
|
|
1221
|
+
})
|
|
1222
|
+
},
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* A menu tab's text: its label, and its count when it has one.
|
|
1226
|
+
*
|
|
1227
|
+
* @param {object} entry The resolved tab.
|
|
1228
|
+
* @return {string} The menu label.
|
|
1229
|
+
*/
|
|
1230
|
+
menuTabLabel(entry) {
|
|
1231
|
+
const hasCount = entry.count !== null && entry.count !== undefined && entry.count !== ''
|
|
1232
|
+
return hasCount ? `${entry.label} (${entry.count})` : entry.label
|
|
1233
|
+
},
|
|
1234
|
+
|
|
1142
1235
|
/**
|
|
1143
1236
|
* Empty-state text for a tab whose widget id resolves to nothing.
|
|
1144
1237
|
*
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<CnDialog
|
|
7
|
+
class="cn-user-settings-dialog"
|
|
8
|
+
:name="title"
|
|
9
|
+
look="board"
|
|
10
|
+
width="wizard"
|
|
11
|
+
:eyebrow="eyebrow"
|
|
12
|
+
:subtitle="subtitle"
|
|
13
|
+
:open="open"
|
|
14
|
+
:style="eyebrowStyle"
|
|
15
|
+
data-testid="cn-user-settings-dialog"
|
|
16
|
+
@update:open="onUpdateOpen"
|
|
17
|
+
@closing="close">
|
|
18
|
+
<div class="cn-user-settings-dialog__body">
|
|
19
|
+
<nav
|
|
20
|
+
v-if="sections.length > 0"
|
|
21
|
+
class="cn-user-settings-dialog__nav"
|
|
22
|
+
:aria-label="navLabel"
|
|
23
|
+
data-testid="cn-user-settings-nav">
|
|
24
|
+
<ul>
|
|
25
|
+
<li v-for="section in sections" :key="section.id">
|
|
26
|
+
<a
|
|
27
|
+
:href="`#settings-section_${section.id}`"
|
|
28
|
+
class="cn-user-settings-dialog__nav-link"
|
|
29
|
+
:class="{ 'cn-user-settings-dialog__nav-link--active': section.id === selected }"
|
|
30
|
+
:aria-current="section.id === selected ? 'true' : undefined"
|
|
31
|
+
:data-testid="`cn-user-settings-nav-${section.id}`"
|
|
32
|
+
@click.prevent="selectSection(section.id)">
|
|
33
|
+
<span v-if="section.icon" class="cn-user-settings-dialog__nav-icon" aria-hidden="true">
|
|
34
|
+
<CnVNodes :vnodes="section.icon" />
|
|
35
|
+
</span>
|
|
36
|
+
<span>{{ section.name }}</span>
|
|
37
|
+
</a>
|
|
38
|
+
</li>
|
|
39
|
+
</ul>
|
|
40
|
+
</nav>
|
|
41
|
+
<div class="cn-user-settings-dialog__sections">
|
|
42
|
+
<!-- @slot default NcAppSettingsSection children; each registers itself in the navigation. -->
|
|
43
|
+
<slot />
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<template #actions>
|
|
47
|
+
<p class="cn-user-settings-dialog__note" data-testid="cn-user-settings-note">
|
|
48
|
+
{{ footerNote }}
|
|
49
|
+
</p>
|
|
50
|
+
<NcButton variant="secondary" data-testid="cn-user-settings-close" @click="close">
|
|
51
|
+
{{ closeLabel }}
|
|
52
|
+
</NcButton>
|
|
53
|
+
</template>
|
|
54
|
+
</CnDialog>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<script>
|
|
58
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
59
|
+
import { NcButton } from '@nextcloud/vue'
|
|
60
|
+
import { markRaw } from 'vue'
|
|
61
|
+
import CnDialog from '../CnDialog/CnDialog.vue'
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The key NcAppSettingsSection injects to register itself with its dialog
|
|
65
|
+
* (`Symbol.for`, so the same symbol `@nextcloud/vue` provides).
|
|
66
|
+
*/
|
|
67
|
+
const REGISTRATION_KEY = Symbol.for('NcAppSettingsDialog:registration')
|
|
68
|
+
|
|
69
|
+
/** Renders a section's icon vnodes as they were registered. */
|
|
70
|
+
const CnVNodes = {
|
|
71
|
+
name: 'CnVNodes',
|
|
72
|
+
props: { vnodes: { type: [Array, Object], default: null } },
|
|
73
|
+
render() {
|
|
74
|
+
return this.vnodes
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* CnUserSettingsDialog - the user settings dialog in the board look (board
|
|
80
|
+
* PqPersoonlijk): eyebrow, title and subtitle, a left navigation listing the
|
|
81
|
+
* sections with their icons, the sections themselves, and a footer with a
|
|
82
|
+
* note that changes save straight away and a Close button.
|
|
83
|
+
*
|
|
84
|
+
* The sections stay the app's: any `NcAppSettingsSection` in the default slot
|
|
85
|
+
* registers itself with this dialog exactly as it does with
|
|
86
|
+
* `NcAppSettingsDialog`, with its name, order and `#icon` slot. CnAppRoot
|
|
87
|
+
* renders this dialog instead of `NcAppSettingsDialog` under the board look.
|
|
88
|
+
*
|
|
89
|
+
* @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look
|
|
90
|
+
*
|
|
91
|
+
* @event update:open Emitted with `false` when the dialog closes.
|
|
92
|
+
*/
|
|
93
|
+
export default {
|
|
94
|
+
name: 'CnUserSettingsDialog',
|
|
95
|
+
|
|
96
|
+
components: { CnDialog, CnVNodes, NcButton },
|
|
97
|
+
|
|
98
|
+
provide() {
|
|
99
|
+
return {
|
|
100
|
+
[REGISTRATION_KEY]: {
|
|
101
|
+
registerSection: this.registerSection,
|
|
102
|
+
unregisterSection: this.unregisterSection,
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
props: {
|
|
108
|
+
/** Whether the dialog is open. */
|
|
109
|
+
open: { type: Boolean, default: false },
|
|
110
|
+
/** Dialog title; also its accessible name. */
|
|
111
|
+
title: { type: String, required: true },
|
|
112
|
+
/** Context line above the title. */
|
|
113
|
+
eyebrow: { type: String, default: () => t('nextcloud-vue', 'Your account') },
|
|
114
|
+
/** Sentence under the title. */
|
|
115
|
+
subtitle: { type: String, default: () => t('nextcloud-vue', 'Only for you. Colleagues keep their own settings.') },
|
|
116
|
+
/** The app's name, after the footer note. */
|
|
117
|
+
appName: { type: String, default: '' },
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
emits: ['update:open'],
|
|
121
|
+
|
|
122
|
+
data() {
|
|
123
|
+
return {
|
|
124
|
+
sections: [],
|
|
125
|
+
selected: '',
|
|
126
|
+
picked: false,
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
computed: {
|
|
131
|
+
/** @return {object} The board's eyebrow: muted, in sentence case. */
|
|
132
|
+
eyebrowStyle() {
|
|
133
|
+
return { '--cn-dialog-eyebrow-transform': 'none', '--cn-dialog-eyebrow-color': 'var(--color-text-maxcontrast)' }
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/** @return {string} The navigation's accessible name. */
|
|
137
|
+
navLabel() {
|
|
138
|
+
return t('nextcloud-vue', 'Sections of the settings')
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/** @return {string} The footer note, with the app's name when known. */
|
|
142
|
+
footerNote() {
|
|
143
|
+
const note = t('nextcloud-vue', 'Changes are saved straight away')
|
|
144
|
+
return this.appName ? `${note} · ${this.appName}` : note
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/** @return {string} The Close button label. */
|
|
148
|
+
closeLabel() {
|
|
149
|
+
return t('nextcloud-vue', 'Close')
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
methods: {
|
|
154
|
+
/**
|
|
155
|
+
* Add a section to the navigation, kept in `order`; a section
|
|
156
|
+
* without one goes last. Until the reader picks one, the first
|
|
157
|
+
* section is current.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} id The section id.
|
|
160
|
+
* @param {string} name The section name.
|
|
161
|
+
* @param {number} [order] The section's place.
|
|
162
|
+
* @param {Array} [icon] The section's icon vnodes.
|
|
163
|
+
*/
|
|
164
|
+
registerSection(id, name, order, icon) {
|
|
165
|
+
const rest = this.sections.filter((section) => section.id !== id)
|
|
166
|
+
const place = typeof order === 'number' ? order : Math.max(0, ...rest.map((section) => section.order)) + 1
|
|
167
|
+
this.sections = [...rest, { id, name, order: place, icon: icon ? markRaw(icon) : null }].sort((a, b) => a.order - b.order)
|
|
168
|
+
if (!this.picked) {
|
|
169
|
+
this.selected = this.sections[0].id
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Remove a section from the navigation.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} id The section id.
|
|
177
|
+
*/
|
|
178
|
+
unregisterSection(id) {
|
|
179
|
+
this.sections = this.sections.filter((section) => section.id !== id)
|
|
180
|
+
if (this.selected === id) {
|
|
181
|
+
this.selected = this.sections.length > 0 ? this.sections[0].id : ''
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Mark a section current and scroll it into view.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} id The section id.
|
|
189
|
+
*/
|
|
190
|
+
selectSection(id) {
|
|
191
|
+
this.picked = true
|
|
192
|
+
this.selected = id
|
|
193
|
+
const target = typeof document !== 'undefined' ? document.getElementById(`settings-section_${id}`) : null
|
|
194
|
+
if (target && typeof target.scrollIntoView === 'function') {
|
|
195
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Follow NcDialog's own open state.
|
|
201
|
+
*
|
|
202
|
+
* @param {boolean} value Whether NcDialog is open.
|
|
203
|
+
*/
|
|
204
|
+
onUpdateOpen(value) {
|
|
205
|
+
if (!value) {
|
|
206
|
+
this.close()
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/** Close the dialog. */
|
|
211
|
+
close() {
|
|
212
|
+
/**
|
|
213
|
+
* @event update:open Emitted with `false` when the dialog closes (Close button, close icon, Escape).
|
|
214
|
+
*/
|
|
215
|
+
this.$emit('update:open', false)
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
</script>
|
|
220
|
+
|
|
221
|
+
<style scoped>
|
|
222
|
+
.cn-user-settings-dialog__body {
|
|
223
|
+
display: flex;
|
|
224
|
+
flex-wrap: wrap;
|
|
225
|
+
gap: 0 24px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.cn-user-settings-dialog__nav {
|
|
229
|
+
flex: 0 0 200px;
|
|
230
|
+
width: 200px;
|
|
231
|
+
padding-top: 18px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.cn-user-settings-dialog__nav ul {
|
|
235
|
+
display: flex;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
gap: 2px;
|
|
238
|
+
margin: 0;
|
|
239
|
+
padding: 0;
|
|
240
|
+
list-style: none;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.cn-user-settings-dialog__nav-link {
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
gap: 10px;
|
|
247
|
+
min-height: 40px;
|
|
248
|
+
padding: 0 12px;
|
|
249
|
+
border-radius: var(--border-radius-element, 8px);
|
|
250
|
+
font-size: 15px;
|
|
251
|
+
color: var(--color-main-text);
|
|
252
|
+
text-decoration: none;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.cn-user-settings-dialog__nav-link:hover {
|
|
256
|
+
background: var(--color-background-hover);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.cn-user-settings-dialog__nav-link:focus-visible {
|
|
260
|
+
outline: 2px solid var(--color-primary-element);
|
|
261
|
+
outline-offset: 2px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.cn-user-settings-dialog__nav-link--active,
|
|
265
|
+
.cn-user-settings-dialog__nav-link--active:hover {
|
|
266
|
+
background: var(--color-primary-element-light);
|
|
267
|
+
color: var(--color-primary-element-light-text);
|
|
268
|
+
font-weight: 600;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.cn-user-settings-dialog__nav-icon {
|
|
272
|
+
display: inline-flex;
|
|
273
|
+
flex: none;
|
|
274
|
+
width: 18px;
|
|
275
|
+
height: 18px;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.cn-user-settings-dialog__sections {
|
|
281
|
+
display: flex;
|
|
282
|
+
flex: 1 1 380px;
|
|
283
|
+
flex-direction: column;
|
|
284
|
+
min-width: 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.cn-user-settings-dialog__sections :deep(.app-settings-section) {
|
|
288
|
+
margin: 0;
|
|
289
|
+
padding: 18px 0;
|
|
290
|
+
border-top: 1px solid var(--color-border);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.cn-user-settings-dialog__sections :deep(.app-settings-section__name) {
|
|
294
|
+
margin: 0 0 12px;
|
|
295
|
+
font-size: 17px;
|
|
296
|
+
font-weight: 700;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.cn-user-settings-dialog__note {
|
|
300
|
+
flex: 1 1 auto;
|
|
301
|
+
margin: 0;
|
|
302
|
+
font-size: 14px;
|
|
303
|
+
color: var(--color-text-maxcontrast);
|
|
304
|
+
}
|
|
305
|
+
</style>
|
|
@@ -45,10 +45,11 @@
|
|
|
45
45
|
<span class="cn-version-info__value">{{ appVersion }}</span>
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
|
-
<!-- Configured
|
|
49
|
-
|
|
48
|
+
<!-- Configured version: when provided, and always in the board look,
|
|
49
|
+
which draws the row ("Unknown" until the app reports it). -->
|
|
50
|
+
<div v-if="configuredVersion || isBoard" class="cn-version-info__item" data-testid="cn-version-info-configured">
|
|
50
51
|
<span class="cn-version-info__label">{{ fieldLabels.configuredVersion }}:</span>
|
|
51
|
-
<span class="cn-version-info__value">{{ configuredVersion }}</span>
|
|
52
|
+
<span class="cn-version-info__value">{{ configuredVersion || t('nextcloud-vue', 'Unknown') }}</span>
|
|
52
53
|
</div>
|
|
53
54
|
|
|
54
55
|
<!-- Additional items -->
|
|
@@ -177,3 +177,11 @@ Request a feature / Report a bug / Documentation render on every widget; the sha
|
|
|
177
177
|
### A text link in the header (`headerLink`)
|
|
178
178
|
|
|
179
179
|
`headerLink` (`{ label, route?, params?, query?, href? }`) draws one text link in the header before the overflow menu, such as "All deadlines".
|
|
180
|
+
|
|
181
|
+
Subtitle: `subtitle` is a muted line under the title, the counterpart of `eyebrow` above it:
|
|
182
|
+
|
|
183
|
+
```vue
|
|
184
|
+
<CnWidgetWrapper title="Op tijd beslist per zaaktype" subtitle="Dit kwartaal, beslist binnen de termijn">
|
|
185
|
+
<p>…</p>
|
|
186
|
+
</CnWidgetWrapper>
|
|
187
|
+
```
|