@conduction/nextcloud-vue 2.65.0 → 2.67.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 +19 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -3
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +2 -2
- 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/CnDashboardPage/CnDashboardPage.vue2.js +99 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +438 -383
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +4 -4
- 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 +78 -21
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +129 -96
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +63 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +2 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +31 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +335 -313
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +5 -11
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +44 -1
- package/dist/esm/composables/useDependencyCheck.js.map +1 -1
- package/dist/esm/composables/useListView.js +43 -1
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/composables/useSetupStatus.js +26 -3
- package/dist/esm/composables/useSetupStatus.js.map +1 -1
- package/dist/esm/l10n/en.json.js +7 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +7 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +400 -0
- package/dist/esm/mixins/pageViews.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +38 -2
- package/dist/esm/store/plugins/liveUpdates.js +19 -9
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/useObjectStore.js +20 -9
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +11 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +27 -3
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/schema.js +40 -2
- package/dist/esm/utils/schema.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 +2377 -1247
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +38 -2
- package/l10n/en.json +7 -1
- package/l10n/nl.json +7 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +2 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +25 -1
- package/src/components/CnAppNav/CnAppNav.vue +2 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +165 -8
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +4 -4
- package/src/components/CnDetailPage/CnDetailPage.vue +94 -5
- package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +67 -8
- package/src/components/CnIndexPage/CnIndexPage.md +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +47 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
- package/src/components/CnSetupWizard/CnSetupWizard.vue +5 -11
- package/src/composables/useDependencyCheck.js +43 -0
- package/src/composables/useListView.js +43 -1
- package/src/composables/useSetupStatus.js +26 -3
- package/src/css/actions-bar.css +20 -2
- package/src/css/detail-page.css +5 -0
- package/src/mixins/pageViews.js +396 -0
- package/src/schemas/app-manifest-v2.schema.json +31 -3
- package/src/schemas/app-manifest.schema.json +1 -1
- package/src/store/plugins/liveUpdates.js +19 -9
- package/src/store/useObjectStore.js +19 -8
- package/src/utils/actionsDispatcher.js +11 -2
- package/src/utils/columnFilters.js +29 -2
- package/src/utils/schema.js +40 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/nextcloud-vue.cjs.js.map
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35372297 bytes
|
|
4
|
+
sha256: 7065df25e721d338e4612aac2ee75c58c87fb5e17477c811e7bcf66cee41783d
|
package/dist/nextcloud-vue.css
CHANGED
|
@@ -1940,6 +1940,23 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
1940
1940
|
color: var(--color-text-maxcontrast);
|
|
1941
1941
|
}
|
|
1942
1942
|
|
|
1943
|
+
/* Beside the inline search (`showCountWithSearch`): the search field shrinks
|
|
1944
|
+
first and the counter stays on one line. */
|
|
1945
|
+
.cn-actions-bar__count--beside-search {
|
|
1946
|
+
flex-shrink: 0;
|
|
1947
|
+
white-space: nowrap;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/* Taken out of the flex row so it adds no gap, but kept in the accessibility
|
|
1951
|
+
tree so the live region exists before a total appears. */
|
|
1952
|
+
.cn-actions-bar__count--empty {
|
|
1953
|
+
position: absolute;
|
|
1954
|
+
width: 1px;
|
|
1955
|
+
height: 1px;
|
|
1956
|
+
overflow: hidden;
|
|
1957
|
+
clip-path: inset(50%);
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1943
1960
|
/* Inline search field */
|
|
1944
1961
|
.cn-actions-bar__search {
|
|
1945
1962
|
position: relative;
|
|
@@ -2199,8 +2216,9 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
2199
2216
|
display: contents;
|
|
2200
2217
|
}
|
|
2201
2218
|
|
|
2202
|
-
/* #after-search filter buttons
|
|
2203
|
-
search field (DOM order — search first via its
|
|
2219
|
+
/* #after-search filter buttons, the beside-search counter and the view
|
|
2220
|
+
toggle ride beside the search field (DOM order — search first via its
|
|
2221
|
+
own order). */
|
|
2204
2222
|
.cn-actions-bar__info > * {
|
|
2205
2223
|
order: 1;
|
|
2206
2224
|
}
|
|
@@ -3279,6 +3297,11 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
3279
3297
|
font-size: 0.9em;
|
|
3280
3298
|
white-space: nowrap;
|
|
3281
3299
|
}
|
|
3300
|
+
|
|
3301
|
+
/* A page view the switch shows with nothing to draw (view-switch-containers). */
|
|
3302
|
+
.cn-detail-page .cn-page-view-region--empty {
|
|
3303
|
+
padding: 40px 20px;
|
|
3304
|
+
}
|
|
3282
3305
|
/* CnReferencePreview — a reference that shows what it points at. */
|
|
3283
3306
|
|
|
3284
3307
|
.cn-reference-preview {
|
|
@@ -30567,6 +30590,19 @@ to {
|
|
|
30567
30590
|
.cn-dashboard-page__empty[data-v-824052fe] {
|
|
30568
30591
|
padding: 60px 20px;
|
|
30569
30592
|
}
|
|
30593
|
+
|
|
30594
|
+
/* The page's view switch on a page without a header row: at the end of its
|
|
30595
|
+
own row, above the grids, where the header would have put it. */
|
|
30596
|
+
.cn-dashboard-page__view-switch[data-v-824052fe] {
|
|
30597
|
+
display: flex;
|
|
30598
|
+
justify-content: flex-end;
|
|
30599
|
+
margin-bottom: 12px;
|
|
30600
|
+
}
|
|
30601
|
+
|
|
30602
|
+
/* A view the switch shows with nothing to draw. */
|
|
30603
|
+
.cn-page-view-region--empty[data-v-824052fe] {
|
|
30604
|
+
padding: 40px 20px;
|
|
30605
|
+
}
|
|
30570
30606
|
.cn-dashboard-page__page-filters[data-v-824052fe] {
|
|
30571
30607
|
display: flex;
|
|
30572
30608
|
flex-wrap: wrap;
|
package/l10n/en.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"translations": {
|
|
3
|
+
"Advanced": "Advanced",
|
|
3
4
|
"The item could not be created.": "The item could not be created.",
|
|
4
5
|
"Start over": "Start over",
|
|
5
6
|
"Continue where you left off": "Continue where you left off",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"This integration is not available.": "This integration is not available.",
|
|
50
51
|
"Integrations": "Integrations",
|
|
51
52
|
"View": "View",
|
|
53
|
+
"Views": "Views",
|
|
52
54
|
"current step": "current step",
|
|
53
55
|
"{app} not available": "{app} not available",
|
|
54
56
|
"{app} not configured": "{app} not configured",
|
|
@@ -408,6 +410,7 @@
|
|
|
408
410
|
"Any": "Any",
|
|
409
411
|
"To": "To",
|
|
410
412
|
"Equals": "Equals",
|
|
413
|
+
"Contains": "Contains",
|
|
411
414
|
"Clear": "Clear",
|
|
412
415
|
"Apply": "Apply",
|
|
413
416
|
"This column cannot list its objects.": "This column cannot list its objects.",
|
|
@@ -1088,6 +1091,7 @@
|
|
|
1088
1091
|
"The actions of an attention card are declared in the manifest.": "The actions of an attention card are declared in the manifest.",
|
|
1089
1092
|
"An attention card needs a title": "An attention card needs a title",
|
|
1090
1093
|
"Good morning, {name}": "Good morning, {name}",
|
|
1094
|
+
"This view has no widgets yet.": "This view has no widgets yet.",
|
|
1091
1095
|
"Good morning": "Good morning",
|
|
1092
1096
|
"Good afternoon, {name}": "Good afternoon, {name}",
|
|
1093
1097
|
"Good afternoon": "Good afternoon",
|
|
@@ -1097,7 +1101,9 @@
|
|
|
1097
1101
|
"Show the date of today above the heading": "Show the date of today above the heading",
|
|
1098
1102
|
"Plain header without a coloured background": "Plain header without a coloured background",
|
|
1099
1103
|
"Loading…": "Loading…",
|
|
1100
|
-
"A register and schema are required": "A register and schema are required"
|
|
1104
|
+
"A register and schema are required": "A register and schema are required",
|
|
1105
|
+
"Change the search or try again.": "Change the search or try again.",
|
|
1106
|
+
"Try again later.": "Try again later."
|
|
1101
1107
|
},
|
|
1102
1108
|
"plurals": {
|
|
1103
1109
|
"%n year": ["%n year", "%n years"],
|
package/l10n/nl.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"translations": {
|
|
3
|
+
"Advanced": "Geavanceerd",
|
|
3
4
|
"The item could not be created.": "Het item kon niet worden aangemaakt.",
|
|
4
5
|
"Start over": "Opnieuw beginnen",
|
|
5
6
|
"Continue where you left off": "Ga verder waar je was",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"This integration is not available.": "Deze integratie is niet beschikbaar.",
|
|
50
51
|
"Integrations": "Integraties",
|
|
51
52
|
"View": "Weergave",
|
|
53
|
+
"Views": "Weergaven",
|
|
52
54
|
"current step": "huidige stap",
|
|
53
55
|
"{app} not available": "{app} niet beschikbaar",
|
|
54
56
|
"{app} not configured": "{app} niet geconfigureerd",
|
|
@@ -408,6 +410,7 @@
|
|
|
408
410
|
"Any": "Alle",
|
|
409
411
|
"To": "Tot",
|
|
410
412
|
"Equals": "Is gelijk aan",
|
|
413
|
+
"Contains": "Bevat",
|
|
411
414
|
"Clear": "Wissen",
|
|
412
415
|
"Apply": "Toepassen",
|
|
413
416
|
"This column cannot list its objects.": "Deze kolom kan zijn objecten niet tonen.",
|
|
@@ -1132,6 +1135,7 @@
|
|
|
1132
1135
|
"The actions of an attention card are declared in the manifest.": "De acties van een aandachtskaart staan in het manifest.",
|
|
1133
1136
|
"An attention card needs a title": "Een aandachtskaart heeft een titel nodig",
|
|
1134
1137
|
"Good morning, {name}": "Goedemorgen, {name}",
|
|
1138
|
+
"This view has no widgets yet.": "Deze weergave heeft nog geen widgets.",
|
|
1135
1139
|
"Good morning": "Goedemorgen",
|
|
1136
1140
|
"Good afternoon, {name}": "Goedemiddag, {name}",
|
|
1137
1141
|
"Good afternoon": "Goedemiddag",
|
|
@@ -1141,7 +1145,9 @@
|
|
|
1141
1145
|
"Show the date of today above the heading": "Toon de datum van vandaag boven de kop",
|
|
1142
1146
|
"Plain header without a coloured background": "Eenvoudige kop zonder gekleurde achtergrond",
|
|
1143
1147
|
"Loading…": "Laden…",
|
|
1144
|
-
"A register and schema are required": "Een register en schema zijn verplicht"
|
|
1148
|
+
"A register and schema are required": "Een register en schema zijn verplicht",
|
|
1149
|
+
"Change the search or try again.": "Pas de zoekopdracht aan of probeer het opnieuw.",
|
|
1150
|
+
"Try again later.": "Probeer het later opnieuw."
|
|
1145
1151
|
},
|
|
1146
1152
|
"plurals": {
|
|
1147
1153
|
"%n year": ["%n jaar", "%n jaar"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.67.0",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -152,9 +152,10 @@ Controlling the inline action button count — `inlineActionCount` sets how many
|
|
|
152
152
|
| `showMap` | `Boolean` | `false` | Whether to render the third "Map" view-toggle segment. Off by default so existing two-segment consumers are unchanged |
|
|
153
153
|
| `mapLabel` | `String` | `''` | Label for the map view-toggle option (defaults to "Map"). Only shown when `showMap` |
|
|
154
154
|
| `mapIcon` | `String` | `''` | MDI icon name for the map option (defaults to the built-in map-marker icon). Resolved via `CnIcon` |
|
|
155
|
-
| `showSearch` | `Boolean` | `false` | Whether to show the inline search field on the left of the bar |
|
|
155
|
+
| `showSearch` | `Boolean` | `false` | Whether to show the inline search field on the left of the bar. The field takes the place of the "Showing X of Y" counter unless `showCountWithSearch` is set |
|
|
156
156
|
| `searchValue` | `String` | `''` | Current value of the inline search field (controlled) |
|
|
157
157
|
| `searchPlaceholder` | `String` | `''` | Placeholder / accessible label for the inline search field |
|
|
158
|
+
| `showCountWithSearch` | `Boolean` | `false` | Keep the "Showing X of Y" counter visible beside the inline search field, after the search and any `#after-search` controls. No effect without `showSearch` |
|
|
158
159
|
| `headerActions` | `Array` | `[]` | Manifest-declared page-level actions rendered inside the overflow dropdown between the built-in Refresh and the `#action-items` slot. Each entry is `{ id, label, icon?, disabled? }` — the bar emits `@header-action({ action: id, id })` on click and the parent (e.g. `CnIndexPage`) dispatches the resolved handler. The `icon` field accepts EITHER an MDI Vue component name (e.g. `'History'`) — rendered via `CnIcon` — OR a Nextcloud core CSS icon class (e.g. `'icon-history'`) — rendered as a `<span>` carrying that class. An entry with `href` (URL) or `to` (vue-router location) renders as a real link, `linkTarget` as its `target`; it still emits `@header-action`, so the parent must not navigate for it again. |
|
|
159
160
|
|
|
160
161
|
## Manifest header actions example
|
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
:aria-label="searchPlaceholder || t('nextcloud-vue', 'Search')"
|
|
13
13
|
@input="onSearchInput">
|
|
14
14
|
</div>
|
|
15
|
-
<span v-else-if="
|
|
15
|
+
<span v-else-if="hasTotal" class="cn-actions-bar__count">
|
|
16
16
|
{{ countText }}
|
|
17
17
|
</span>
|
|
18
18
|
<!-- @slot after-search Refinement controls rendered beside the search field on the LEFT side of the bar (e.g. a filter menu button). Convention: the left side groups the VISUAL controls — search, filters, view toggle — while the right cluster holds the ACT controls (add, overflow); the standalone sort select is a display control too but keeps its legacy right-side placement. -->
|
|
19
19
|
<slot name="after-search" />
|
|
20
|
+
<!-- Counter beside the search (`showCountWithSearch`): after the search and its #after-search controls. The live region stays mounted while there is no total, so results coming back after none are announced too. -->
|
|
21
|
+
<span v-if="showSearch && showCountWithSearch"
|
|
22
|
+
class="cn-actions-bar__count cn-actions-bar__count--beside-search"
|
|
23
|
+
:class="{ 'cn-actions-bar__count--empty': !hasTotal }"
|
|
24
|
+
aria-live="polite">{{ hasTotal ? countText : '' }}</span>
|
|
20
25
|
|
|
21
26
|
<!-- View mode toggle (Cards / Table / List) — segmented control with
|
|
22
27
|
a sliding thumb that animates between the N segments. Lives in
|
|
@@ -644,6 +649,16 @@ export default {
|
|
|
644
649
|
default: '',
|
|
645
650
|
},
|
|
646
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Keep the "Showing X of Y" counter visible beside the inline search field,
|
|
654
|
+
* after any `#after-search` controls. By default the search field takes the
|
|
655
|
+
* counter's place. No effect without `showSearch`.
|
|
656
|
+
*/
|
|
657
|
+
showCountWithSearch: {
|
|
658
|
+
type: Boolean,
|
|
659
|
+
default: false,
|
|
660
|
+
},
|
|
661
|
+
|
|
647
662
|
/** Whether the refresh action is currently in progress */
|
|
648
663
|
refreshing: {
|
|
649
664
|
type: Boolean,
|
|
@@ -811,6 +826,15 @@ export default {
|
|
|
811
826
|
}))
|
|
812
827
|
},
|
|
813
828
|
|
|
829
|
+
/**
|
|
830
|
+
* Whether there is a non-empty total for the "Showing X of Y" counter.
|
|
831
|
+
*
|
|
832
|
+
* @return {boolean}
|
|
833
|
+
*/
|
|
834
|
+
hasTotal() {
|
|
835
|
+
return Boolean(this.pagination && this.pagination.total > 0)
|
|
836
|
+
},
|
|
837
|
+
|
|
814
838
|
countText() {
|
|
815
839
|
if (!this.pagination) {
|
|
816
840
|
return ''
|
|
@@ -1113,7 +1113,7 @@ export default {
|
|
|
1113
1113
|
|
|
1114
1114
|
/**
|
|
1115
1115
|
* Label for the foldout's gear button. Manifest override:
|
|
1116
|
-
* `nav.settingsLabel`; defaults to "
|
|
1116
|
+
* `nav.settingsLabel`; defaults to "Advanced".
|
|
1117
1117
|
*
|
|
1118
1118
|
* @return {string}
|
|
1119
1119
|
*/
|
|
@@ -1122,7 +1122,7 @@ export default {
|
|
|
1122
1122
|
if (typeof custom === 'string' && custom.length > 0) {
|
|
1123
1123
|
return this.effectiveTranslate(custom)
|
|
1124
1124
|
}
|
|
1125
|
-
return t('nextcloud-vue', '
|
|
1125
|
+
return t('nextcloud-vue', 'Advanced')
|
|
1126
1126
|
},
|
|
1127
1127
|
|
|
1128
1128
|
/**
|
|
@@ -32,6 +32,18 @@
|
|
|
32
32
|
</p>
|
|
33
33
|
</div>
|
|
34
34
|
<div class="cn-dashboard-page__header-actions">
|
|
35
|
+
<!-- The page's view switch (`views`), unless a greeting header
|
|
36
|
+
widget on the page draws it. Its options control the view
|
|
37
|
+
region below the grid. -->
|
|
38
|
+
<CnSegmentedControl
|
|
39
|
+
v-if="showsPageViewSwitch"
|
|
40
|
+
class="cn-page-view-switch"
|
|
41
|
+
:options="viewSwitchOptions"
|
|
42
|
+
:modelValue="activeViewId"
|
|
43
|
+
:ariaLabel="viewSwitchLabel"
|
|
44
|
+
:controls="viewRegionId"
|
|
45
|
+
data-testid="cn-dashboard-page-view-switch"
|
|
46
|
+
@update:modelValue="selectView" />
|
|
35
47
|
<!-- Declarative header actions (#91 Wave 3): a manifest
|
|
36
48
|
`headerActions[]` renders as buttons (open-form / api-call /
|
|
37
49
|
toggle / navigate / refresh) with visibleWhen gating,
|
|
@@ -218,7 +230,7 @@
|
|
|
218
230
|
sections (before-grid / after-grid / end): a page can legitimately
|
|
219
231
|
have no grid widgets yet still show bodyWidget content, so
|
|
220
232
|
"No widgets configured" would be a false negative. -->
|
|
221
|
-
<div v-else-if="!hasWidgets && !hasBodyWidgets" class="cn-dashboard-page__empty">
|
|
233
|
+
<div v-else-if="!hasWidgets && !hasBodyWidgets && !hasViews" class="cn-dashboard-page__empty">
|
|
222
234
|
<!-- @slot empty Replaces the default empty state shown when the dashboard has no widgets. Defaults to an `NcEmptyContent` block. -->
|
|
223
235
|
<slot name="empty">
|
|
224
236
|
<NcEmptyContent :description="emptyLabel">
|
|
@@ -253,15 +265,34 @@
|
|
|
253
265
|
to the layout/widgets arrays — in-place pushes (Add widget) keep
|
|
254
266
|
the same array identity and can't re-render it. One remount per
|
|
255
267
|
edit flip re-subscribes against the now-reactive graph. -->
|
|
268
|
+
<!-- The view switch for a page without a header row, when no greeting
|
|
269
|
+
header widget draws it. -->
|
|
270
|
+
<div
|
|
271
|
+
v-if="gridSectionsShown && !showHeader && showsPageViewSwitch"
|
|
272
|
+
class="cn-dashboard-page__view-switch">
|
|
273
|
+
<CnSegmentedControl
|
|
274
|
+
class="cn-page-view-switch"
|
|
275
|
+
:options="viewSwitchOptions"
|
|
276
|
+
:modelValue="activeViewId"
|
|
277
|
+
:ariaLabel="viewSwitchLabel"
|
|
278
|
+
:controls="viewRegionId"
|
|
279
|
+
data-testid="cn-dashboard-page-view-switch"
|
|
280
|
+
@update:modelValue="selectView" />
|
|
281
|
+
</div>
|
|
282
|
+
<!-- One grid per section: the page's own layout, then the chosen
|
|
283
|
+
view (`views`) in a region the view switch controls. Both are the
|
|
284
|
+
same grid with the same widget rendering below. A view grid is
|
|
285
|
+
keyed on its view, so switching mounts the other view's grid. -->
|
|
256
286
|
<CnDashboardGrid
|
|
257
|
-
v-
|
|
258
|
-
:key="`cn-dashboard-grid-${gridEditable ? 'editing' : 'live'}`"
|
|
259
|
-
|
|
260
|
-
:
|
|
287
|
+
v-for="section in gridSections"
|
|
288
|
+
:key="`cn-dashboard-grid-${section.key}-${gridEditable ? 'editing' : 'live'}`"
|
|
289
|
+
v-bind="section.attrs"
|
|
290
|
+
:layout="section.layout"
|
|
291
|
+
:editable="section.editable"
|
|
261
292
|
:columns="columns"
|
|
262
293
|
:cellHeight="cellHeight"
|
|
263
294
|
:margin="gridMargin"
|
|
264
|
-
@layoutChange="onLayoutChange">
|
|
295
|
+
@layoutChange="section.view ? onViewLayoutChange($event) : onLayoutChange($event)">
|
|
265
296
|
<template #widget="{ item }">
|
|
266
297
|
<!-- In-app edit overlay (ADR-041): a single launchpad-style
|
|
267
298
|
configure cog opens the per-widget style/config editor,
|
|
@@ -655,6 +686,22 @@
|
|
|
655
686
|
</template>
|
|
656
687
|
</CnDashboardGrid>
|
|
657
688
|
|
|
689
|
+
<!-- The chosen view has nothing to draw: say so in the region, never
|
|
690
|
+
leave a blank area under the switch. -->
|
|
691
|
+
<div
|
|
692
|
+
v-if="gridSectionsShown && viewIsEmpty"
|
|
693
|
+
:id="viewRegionId"
|
|
694
|
+
role="region"
|
|
695
|
+
:aria-label="activeView ? activeView.label : null"
|
|
696
|
+
class="cn-page-view-region cn-page-view-region--empty"
|
|
697
|
+
data-testid="cn-dashboard-page-view-empty">
|
|
698
|
+
<NcEmptyContent :description="viewEmptyText">
|
|
699
|
+
<template #icon>
|
|
700
|
+
<ViewDashboardOutline :size="48" />
|
|
701
|
+
</template>
|
|
702
|
+
</NcEmptyContent>
|
|
703
|
+
</div>
|
|
704
|
+
|
|
658
705
|
<!-- Declarative in-body sections, `placement: "after-grid"` — host-app
|
|
659
706
|
section components rendered BELOW the widget grid. -->
|
|
660
707
|
<CnBodySections
|
|
@@ -714,6 +761,7 @@ import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
|
|
|
714
761
|
import CnChartWidget from '../CnChartWidget/CnChartWidget.vue'
|
|
715
762
|
import CnDashboardGrid from '../CnDashboardGrid/CnDashboardGrid.vue'
|
|
716
763
|
import CnDateRangePicker, { DEFAULT_DATE_RANGE_PRESETS, resolvePresetWindow } from '../CnDateRangePicker/CnDateRangePicker.vue'
|
|
764
|
+
import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
|
|
717
765
|
import CnStatsBlockWidget from '../CnStatsBlockWidget/CnStatsBlockWidget.vue'
|
|
718
766
|
import CnTileWidget from '../CnTileWidget/CnTileWidget.vue'
|
|
719
767
|
import CnWidgetRefItem from '../CnWidgetRefItem/CnWidgetRefItem.vue'
|
|
@@ -721,6 +769,7 @@ import CnWidgetRenderer from '../CnWidgetRenderer/CnWidgetRenderer.vue'
|
|
|
721
769
|
import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
|
|
722
770
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
723
771
|
import { readUserPreference, writeUserPreference } from '../../composables/useUserPreferences.js'
|
|
772
|
+
import { pageViews } from '../../mixins/pageViews.js'
|
|
724
773
|
import { dashboardLayoutKey, mergeUserLayout } from '../../store/plugins/dashboardLayouts.js'
|
|
725
774
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
726
775
|
import { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'
|
|
@@ -875,8 +924,11 @@ export default {
|
|
|
875
924
|
CnBuildiqEditButton,
|
|
876
925
|
CnWidgetStyleEditorModal,
|
|
877
926
|
CnLeafMountHost,
|
|
927
|
+
CnSegmentedControl,
|
|
878
928
|
},
|
|
879
929
|
|
|
930
|
+
mixins: [pageViews],
|
|
931
|
+
|
|
880
932
|
inject: {
|
|
881
933
|
/**
|
|
882
934
|
* Buildiq in-app edit state (ADR-041), provided by CnAppRoot as a ref.
|
|
@@ -1879,6 +1931,89 @@ export default {
|
|
|
1879
1931
|
return this.layout.length > 0 || this.widgetRefItems.length > 0
|
|
1880
1932
|
},
|
|
1881
1933
|
|
|
1934
|
+
/**
|
|
1935
|
+
* Whether the grid sections render at all: not while loading, not
|
|
1936
|
+
* while the widget predicates settle, not on the empty page and not
|
|
1937
|
+
* on a widget-ref content page.
|
|
1938
|
+
*
|
|
1939
|
+
* @return {boolean}
|
|
1940
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
|
|
1941
|
+
*/
|
|
1942
|
+
gridSectionsShown() {
|
|
1943
|
+
if (this.loading || (!this.widgetConditionsSettled && !this.gridEditable)) {
|
|
1944
|
+
return false
|
|
1945
|
+
}
|
|
1946
|
+
if (!this.hasWidgets && !this.hasBodyWidgets && !this.hasViews) {
|
|
1947
|
+
return false
|
|
1948
|
+
}
|
|
1949
|
+
return this.widgetRefItems.length === 0
|
|
1950
|
+
},
|
|
1951
|
+
|
|
1952
|
+
/**
|
|
1953
|
+
* The chosen view's layout as the grid draws it: in live mode the
|
|
1954
|
+
* widgets that would draw nothing leave and the rest close up, as
|
|
1955
|
+
* for the page's own layout.
|
|
1956
|
+
*
|
|
1957
|
+
* @return {Array<object>}
|
|
1958
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
|
|
1959
|
+
*/
|
|
1960
|
+
viewDisplayLayout() {
|
|
1961
|
+
const items = this.activeView ? this.activeView.layout : []
|
|
1962
|
+
if (this.gridEditable) {
|
|
1963
|
+
return items
|
|
1964
|
+
}
|
|
1965
|
+
const visible = items.filter((item) => !this.isCollapsedWidget(item))
|
|
1966
|
+
return visible.length === items.length ? items : this.compactDisplayLayout(visible)
|
|
1967
|
+
},
|
|
1968
|
+
|
|
1969
|
+
/**
|
|
1970
|
+
* Whether the chosen view has nothing to draw.
|
|
1971
|
+
*
|
|
1972
|
+
* @return {boolean}
|
|
1973
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
|
|
1974
|
+
*/
|
|
1975
|
+
viewIsEmpty() {
|
|
1976
|
+
return this.hasViews && this.viewDisplayLayout.length === 0
|
|
1977
|
+
},
|
|
1978
|
+
|
|
1979
|
+
/**
|
|
1980
|
+
* The grids this page draws. Without views: the page's own layout,
|
|
1981
|
+
* as it always was. With views: the page's own layout when it has
|
|
1982
|
+
* items, then the chosen view's layout in the region the switch
|
|
1983
|
+
* controls (left out when the view is empty; the empty state takes
|
|
1984
|
+
* the region then).
|
|
1985
|
+
*
|
|
1986
|
+
* @return {Array<{key: string, view: boolean, layout: Array<object>, editable: boolean, attrs: object}>}
|
|
1987
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
|
|
1988
|
+
*/
|
|
1989
|
+
gridSections() {
|
|
1990
|
+
if (!this.gridSectionsShown) {
|
|
1991
|
+
return []
|
|
1992
|
+
}
|
|
1993
|
+
const page = { key: 'page', view: false, layout: this.displayLayout, editable: this.gridEditable, attrs: {} }
|
|
1994
|
+
if (!this.hasViews) {
|
|
1995
|
+
return [page]
|
|
1996
|
+
}
|
|
1997
|
+
const sections = this.displayLayout.length > 0 ? [page] : []
|
|
1998
|
+
if (!this.viewIsEmpty) {
|
|
1999
|
+
sections.push({
|
|
2000
|
+
key: `view-${this.activeViewId}`,
|
|
2001
|
+
view: true,
|
|
2002
|
+
layout: this.viewDisplayLayout,
|
|
2003
|
+
// A user arrangement covers the page's own layout only.
|
|
2004
|
+
editable: this.gridEditable && !this.userLayout,
|
|
2005
|
+
attrs: {
|
|
2006
|
+
id: this.viewRegionId,
|
|
2007
|
+
role: 'region',
|
|
2008
|
+
'aria-label': this.activeView.label,
|
|
2009
|
+
class: 'cn-page-view-region',
|
|
2010
|
+
'data-testid': 'cn-dashboard-page-view-region',
|
|
2011
|
+
},
|
|
2012
|
+
})
|
|
2013
|
+
}
|
|
2014
|
+
return sections
|
|
2015
|
+
},
|
|
2016
|
+
|
|
1882
2017
|
/**
|
|
1883
2018
|
* The widget settings object handed to CnWidgetStyleEditorModal,
|
|
1884
2019
|
* built from the widget definition currently being configured
|
|
@@ -2009,6 +2144,14 @@ export default {
|
|
|
2009
2144
|
this.evaluateWidgetConditions()
|
|
2010
2145
|
},
|
|
2011
2146
|
},
|
|
2147
|
+
|
|
2148
|
+
/** A view's widgets carry predicates too. */
|
|
2149
|
+
views: {
|
|
2150
|
+
deep: true,
|
|
2151
|
+
handler() {
|
|
2152
|
+
this.evaluateWidgetConditions()
|
|
2153
|
+
},
|
|
2154
|
+
},
|
|
2012
2155
|
},
|
|
2013
2156
|
|
|
2014
2157
|
created() {
|
|
@@ -2873,7 +3016,8 @@ export default {
|
|
|
2873
3016
|
* @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check
|
|
2874
3017
|
*/
|
|
2875
3018
|
async evaluateWidgetConditions() {
|
|
2876
|
-
const
|
|
3019
|
+
const defs = [...(this.widgets || []), ...this.normalizedViews.flatMap((view) => view.widgets)]
|
|
3020
|
+
const conditional = defs.filter((def) => def && def.id && def.type
|
|
2877
3021
|
&& this.widgetDisplayConfig(def).visibleWhen)
|
|
2878
3022
|
// Skip when the CONDITIONAL SET is unchanged: the deep `widgets`
|
|
2879
3023
|
// watch fires on any def edit, but only an added/removed/edited
|
|
@@ -3007,7 +3151,7 @@ export default {
|
|
|
3007
3151
|
*/
|
|
3008
3152
|
getWidgetDef(widgetId) {
|
|
3009
3153
|
const list = Array.isArray(this.widgets) ? this.widgets : []
|
|
3010
|
-
return list.find((w) => w && w.id === widgetId) ||
|
|
3154
|
+
return list.find((w) => w && w.id === widgetId) || this.findViewWidget(widgetId)
|
|
3011
3155
|
},
|
|
3012
3156
|
|
|
3013
3157
|
/**
|
|
@@ -4021,6 +4165,19 @@ export default {
|
|
|
4021
4165
|
padding: 60px 20px;
|
|
4022
4166
|
}
|
|
4023
4167
|
|
|
4168
|
+
/* The page's view switch on a page without a header row: at the end of its
|
|
4169
|
+
own row, above the grids, where the header would have put it. */
|
|
4170
|
+
.cn-dashboard-page__view-switch {
|
|
4171
|
+
display: flex;
|
|
4172
|
+
justify-content: flex-end;
|
|
4173
|
+
margin-bottom: 12px;
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
/* A view the switch shows with nothing to draw. */
|
|
4177
|
+
.cn-page-view-region--empty {
|
|
4178
|
+
padding: 40px 20px;
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4024
4181
|
.cn-dashboard-page__page-filters {
|
|
4025
4182
|
display: flex;
|
|
4026
4183
|
flex-wrap: wrap;
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
</label>
|
|
92
92
|
</div>
|
|
93
93
|
|
|
94
|
-
<!-- string:
|
|
94
|
+
<!-- string: contains (OpenRegister `[like]`, case-insensitive) -->
|
|
95
95
|
<label v-else class="cn-column-filter__field">
|
|
96
|
-
<span class="cn-column-filter__label">{{
|
|
96
|
+
<span class="cn-column-filter__label">{{ containsLabel }}</span>
|
|
97
97
|
<input
|
|
98
98
|
v-model="draft.value"
|
|
99
99
|
class="cn-column-filter__input"
|
|
@@ -131,7 +131,7 @@ let popoverSeq = 0
|
|
|
131
131
|
*
|
|
132
132
|
* Renders the control that fits the column: a checkbox list for an enum, yes,
|
|
133
133
|
* no or any for a boolean, from and to for a number or a date, a searchable
|
|
134
|
-
* list of referenced objects for a reference, and
|
|
134
|
+
* list of referenced objects for a reference, and a contains box for text.
|
|
135
135
|
* Real inputs throughout, so every control works from the keyboard. Escape
|
|
136
136
|
* closes it and the host returns focus to the filter button.
|
|
137
137
|
*
|
|
@@ -265,8 +265,14 @@ export default {
|
|
|
265
265
|
return t('nextcloud-vue', 'To')
|
|
266
266
|
},
|
|
267
267
|
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Label of the text box: the text filter matches on contains.
|
|
270
|
+
*
|
|
271
|
+
* @return {string}
|
|
272
|
+
* @spec openspec/changes/header-filter-contains/specs/cn-data-table/spec.md#requirement-a-text-header-filter-matches-on-contains
|
|
273
|
+
*/
|
|
274
|
+
containsLabel() {
|
|
275
|
+
return t('nextcloud-vue', 'Contains')
|
|
270
276
|
},
|
|
271
277
|
|
|
272
278
|
clearLabel() {
|
|
@@ -186,7 +186,7 @@ export default {
|
|
|
186
186
|
| `includeColumns` | Array | `null` | Column keys to include (whitelist) in schema mode |
|
|
187
187
|
| `sortKey` | String | `null` | Currently active sort column key (controlled) |
|
|
188
188
|
| `sortOrder` | String | `'asc'` | Current sort direction: `'asc'` or `'desc'` |
|
|
189
|
-
| `sortKeys` | Array | `[]` | Ordered multi-column ("shift+click") sort key list, `[{ key, order }, …]` (0–3 entries); takes precedence over `sortKey`/`sortOrder` when non-empty. Priority badges show only when two or more rendered, sortable columns are sorted; a key whose column is not rendered or not sortable (such as a `_uuid` tie-break) still sorts, but is not counted or numbered |
|
|
189
|
+
| `sortKeys` | Array | `[]` | Ordered multi-column ("shift+click") sort key list, `[{ key, order }, …]` (0–3 entries); takes precedence over `sortKey`/`sortOrder` when non-empty. Priority badges show only when two or more rendered, sortable columns are sorted; a key whose column is not rendered or not sortable (such as a `_uuid` tie-break) still sorts, but is not counted or numbered, and `aria-sort` goes to the first sort key whose column is rendered and sortable |
|
|
190
190
|
| `selectedIds` | Array | `[]` | Array of selected row IDs (controlled) |
|
|
191
191
|
| `rowClass` | Function | `null` | `(row) => string` — CSS class(es) applied to each `<tr>` |
|
|
192
192
|
| `rowClickToView` | Boolean | `false` | Emit `row-click` on a row-body click even while `selectable` (selection then via the checkbox column only) |
|
|
@@ -371,7 +371,7 @@ import '../../css/table.css'
|
|
|
371
371
|
*
|
|
372
372
|
* Sorting: a plain click on a sortable header is single-sort (cycle asc → desc → cleared), unchanged from before.
|
|
373
373
|
* Shift+click (or Shift+Enter on a focused header) appends the column as a secondary/tertiary sort key, capped at 3, with numbered priority badges (1, 2, 3) once two or more rendered, sortable columns are sorted.
|
|
374
|
-
* A sort key whose column is not rendered, or not sortable, still sorts, but is not counted or numbered.
|
|
374
|
+
* A sort key whose column is not rendered, or not sortable, still sorts, but is not counted or numbered, and `aria-sort` goes to the first sort key whose column is rendered and sortable.
|
|
375
375
|
* Pass `sortKeys: [{key, order}, ...]` for multi-sort (falls back to the legacy `sortKey`/`sortOrder` props when empty).
|
|
376
376
|
* The `sort` event payload is extended, not replaced: `{key, order}` still mirrors the primary key exactly as before; a new `keys` field carries the full ordered list.
|
|
377
377
|
* See `src/utils/multiColumnSort.js` for the state machine.
|
|
@@ -1722,8 +1722,8 @@ export default {
|
|
|
1722
1722
|
},
|
|
1723
1723
|
|
|
1724
1724
|
/**
|
|
1725
|
-
* `aria-sort` value for a column header: `'ascending'`/`'descending'` for the
|
|
1726
|
-
* `null` omits the attribute entirely (unsorted, not sortable, or not the
|
|
1725
|
+
* `aria-sort` value for a column header: `'ascending'`/`'descending'` for the first rendered sort key only, per WCAG guidance that `aria-sort` describes single-column sort state.
|
|
1726
|
+
* `null` omits the attribute entirely (unsorted, not sortable, or not the first rendered sort key).
|
|
1727
1727
|
*
|
|
1728
1728
|
* @param {object} col Column definition.
|
|
1729
1729
|
* @return {string|null}
|
|
@@ -1732,7 +1732,7 @@ export default {
|
|
|
1732
1732
|
if (!col.sortable) {
|
|
1733
1733
|
return null
|
|
1734
1734
|
}
|
|
1735
|
-
const primary = this.
|
|
1735
|
+
const primary = this.renderedSortKeys[0]
|
|
1736
1736
|
if (!primary || primary.key !== col.key) {
|
|
1737
1737
|
return null
|
|
1738
1738
|
}
|