@conduction/nextcloud-vue 2.82.0 → 2.83.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 +2 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +24 -0
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +9 -4
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +9 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +96 -12
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +95 -50
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js +2 -2
- package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js.map +1 -1
- package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js +2 -2
- package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +16 -2
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +3 -3
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +43 -0
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +130 -53
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +70 -5
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +61 -35
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +3 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +55 -3
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +88 -71
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +18 -4
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -3
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +549 -338
- package/dist/esm/utils/fetchAggregate.js +25 -2
- package/dist/esm/utils/fetchAggregate.js.map +1 -1
- package/dist/esm/utils/switchValues.js +62 -0
- package/dist/esm/utils/switchValues.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 +36086 -35536
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +549 -338
- package/l10n/nl.json +3 -3
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +31 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +125 -15
- package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +54 -3
- package/src/components/CnReportsPage/CnReportsPage.vue +84 -4
- package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +110 -18
- package/src/components/CnStatWidget/CnStatWidget.vue +3 -1
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +92 -5
- package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +18 -4
- package/src/css/index-page.css +11 -0
- package/src/css/index.css +1 -0
- package/src/css/look-board-reports.css +117 -0
- package/src/css/look-board.css +32 -1
- package/src/schemas/app-manifest-v2.schema.json +2 -2
- package/src/utils/fetchAggregate.js +24 -1
- package/src/utils/switchValues.js +59 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/nl.json
CHANGED
|
@@ -355,7 +355,7 @@
|
|
|
355
355
|
"Copy items": "Items kopiëren",
|
|
356
356
|
"Copy of {name}": "Kopie van {name}",
|
|
357
357
|
"Copy property": "Eigenschap kopiëren",
|
|
358
|
-
"Copy selected": "
|
|
358
|
+
"Copy selected": "Selectie kopiëren",
|
|
359
359
|
"Copy {key}": "Kopieer {key}",
|
|
360
360
|
"Create": "Aanmaken",
|
|
361
361
|
"Create (C)": "Aanmaken (C)",
|
|
@@ -386,7 +386,7 @@
|
|
|
386
386
|
"Delete note": "Notitie verwijderen",
|
|
387
387
|
"Delete objects": "Objecten verwijderen",
|
|
388
388
|
"Delete property": "Eigenschap verwijderen",
|
|
389
|
-
"Delete selected": "
|
|
389
|
+
"Delete selected": "Selectie verwijderen",
|
|
390
390
|
"Delete {key}": "Verwijder {key}",
|
|
391
391
|
"Deprecated": "Verouderd",
|
|
392
392
|
"Description": "Beschrijving",
|
|
@@ -1148,7 +1148,7 @@
|
|
|
1148
1148
|
"Could not load entries.": "Kon registraties niet laden.",
|
|
1149
1149
|
"Could not create client.": "Kon klant niet aanmaken.",
|
|
1150
1150
|
"{count} selected": "{count} geselecteerd",
|
|
1151
|
-
"Clear selection": "Selectie
|
|
1151
|
+
"Clear selection": "Selectie opheffen",
|
|
1152
1152
|
"Overdue by 1 day": "1 dag over tijd",
|
|
1153
1153
|
"Overdue by {days} days": "{days} dagen over tijd",
|
|
1154
1154
|
"Overdue": "Over tijd",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.83.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": {
|
|
@@ -56,6 +56,11 @@
|
|
|
56
56
|
</div>
|
|
57
57
|
</div>
|
|
58
58
|
|
|
59
|
+
<!-- `versionCardPosition: "end"`: the app's sections come first, and the
|
|
60
|
+
version card and the credential broker follow them (board DcBeheer
|
|
61
|
+
starts with the app's own card). The same slot, rendered once. -->
|
|
62
|
+
<slot v-if="versionCardAtEnd" />
|
|
63
|
+
|
|
59
64
|
<!-- Version information card (version, up-to-date check, re-import, support footer) -->
|
|
60
65
|
<CnVersionInfoCard
|
|
61
66
|
v-if="showVersionCard"
|
|
@@ -151,8 +156,8 @@
|
|
|
151
156
|
:appCredentials="appCredentials"
|
|
152
157
|
data-testid="cn-admin-organisation-credentials" />
|
|
153
158
|
|
|
154
|
-
<!-- @slot default The app's own settings sections, rendered below the version card
|
|
155
|
-
<slot />
|
|
159
|
+
<!-- @slot default The app's own settings sections, rendered below the version card, or above it with `versionCardPosition: "end"`. -->
|
|
160
|
+
<slot v-if="!versionCardAtEnd" />
|
|
156
161
|
|
|
157
162
|
<!-- First-time setup wizard, opened from the admin page (ADR-042). -->
|
|
158
163
|
<CnSetupWizard
|
|
@@ -358,6 +363,20 @@ export default {
|
|
|
358
363
|
default: true,
|
|
359
364
|
},
|
|
360
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Where the version card (and the organisation credential broker) sits:
|
|
368
|
+
* `start`, above the app's sections (the default), or `end`, after them,
|
|
369
|
+
* for a page whose board starts with the app's own card. The keyboard
|
|
370
|
+
* order follows, because the slot moves in the markup.
|
|
371
|
+
*
|
|
372
|
+
* @spec openspec/changes/screens-admin-settings-version-card-end/specs/settings-board-look/spec.md#requirement-the-version-card-can-follow-the-apps-sections
|
|
373
|
+
*/
|
|
374
|
+
versionCardPosition: {
|
|
375
|
+
type: String,
|
|
376
|
+
default: 'start',
|
|
377
|
+
validator: (value) => ['start', 'end'].includes(value),
|
|
378
|
+
},
|
|
379
|
+
|
|
361
380
|
/** Version card heading. */
|
|
362
381
|
versionTitle: {
|
|
363
382
|
type: String,
|
|
@@ -482,6 +501,16 @@ export default {
|
|
|
482
501
|
},
|
|
483
502
|
|
|
484
503
|
computed: {
|
|
504
|
+
/**
|
|
505
|
+
* Whether the app's sections come before the version card.
|
|
506
|
+
*
|
|
507
|
+
* @return {boolean}
|
|
508
|
+
* @spec openspec/changes/screens-admin-settings-version-card-end/specs/settings-board-look/spec.md#requirement-the-version-card-can-follow-the-apps-sections
|
|
509
|
+
*/
|
|
510
|
+
versionCardAtEnd() {
|
|
511
|
+
return this.versionCardPosition === 'end'
|
|
512
|
+
},
|
|
513
|
+
|
|
485
514
|
/**
|
|
486
515
|
* The crumbs that have a label.
|
|
487
516
|
*
|
|
@@ -4578,6 +4578,15 @@ export default {
|
|
|
4578
4578
|
// because the dispatcher only forwards `content` — honour it with a
|
|
4579
4579
|
// one-time deprecation warning so those cards render while authors
|
|
4580
4580
|
// migrate the key under `content`.
|
|
4581
|
+
// Grid layout (`layout: "grid"`): stat tiles in columns.
|
|
4582
|
+
const stats = content.stats !== undefined ? content.stats : props.stats
|
|
4583
|
+
if (Array.isArray(stats)) {
|
|
4584
|
+
out.stats = stats
|
|
4585
|
+
}
|
|
4586
|
+
const columns = content.columns !== undefined ? content.columns : props.columns
|
|
4587
|
+
if (columns !== undefined) {
|
|
4588
|
+
out.columns = Number(columns)
|
|
4589
|
+
}
|
|
4581
4590
|
const contentEntries = content.entries !== undefined ? content.entries : props.entries
|
|
4582
4591
|
if (contentEntries !== undefined) {
|
|
4583
4592
|
out.entries = contentEntries
|
|
@@ -47,8 +47,19 @@
|
|
|
47
47
|
actions when it names none): a header action, a built-in by
|
|
48
48
|
name, or a link (an entry with `href`, the documentation). -->
|
|
49
49
|
<template v-for="entry in headerMenuEntries(button)" :key="entry.key">
|
|
50
|
+
<!-- A group heading and a separator (`{ heading }` and
|
|
51
|
+
`separator` in items), as the board's "Ga naar" and
|
|
52
|
+
"Selectie" groups. -->
|
|
53
|
+
<NcActionCaption
|
|
54
|
+
v-if="entry.kind === 'heading'"
|
|
55
|
+
class="cn-index-header-menu__heading"
|
|
56
|
+
:name="entry.label"
|
|
57
|
+
:data-testid="`cn-index-header-menu-${entry.key}`" />
|
|
58
|
+
<NcActionSeparator
|
|
59
|
+
v-else-if="entry.kind === 'separator'"
|
|
60
|
+
:data-testid="`cn-index-header-menu-${entry.key}`" />
|
|
50
61
|
<NcActionLink
|
|
51
|
-
v-if="entry.href"
|
|
62
|
+
v-else-if="entry.href"
|
|
52
63
|
:href="entry.href"
|
|
53
64
|
target="_blank"
|
|
54
65
|
rel="noopener noreferrer"
|
|
@@ -58,6 +69,17 @@
|
|
|
58
69
|
</template>
|
|
59
70
|
{{ entry.label }}
|
|
60
71
|
</NcActionLink>
|
|
72
|
+
<NcActionLink
|
|
73
|
+
v-else-if="entry.link"
|
|
74
|
+
:href="entry.link.href"
|
|
75
|
+
:closeAfterClick="true"
|
|
76
|
+
:data-testid="`cn-index-header-menu-${entry.key}`"
|
|
77
|
+
@click="onHeaderMenuLinkClick(entry, $event)">
|
|
78
|
+
<template v-if="entry.icon" #icon>
|
|
79
|
+
<CnIcon :name="entry.icon" :size="20" />
|
|
80
|
+
</template>
|
|
81
|
+
{{ entry.label }}
|
|
82
|
+
</NcActionLink>
|
|
61
83
|
<NcActionButton
|
|
62
84
|
v-else
|
|
63
85
|
:disabled="entry.disabled"
|
|
@@ -65,8 +87,12 @@
|
|
|
65
87
|
:closeAfterClick="true"
|
|
66
88
|
:data-testid="`cn-index-header-menu-${entry.key}`"
|
|
67
89
|
@click="entry.run()">
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
<!-- A built-in ships its own icon (`iconComponent`), so it
|
|
91
|
+
never falls back to the "?" when the app has not
|
|
92
|
+
registered that icon name. -->
|
|
93
|
+
<template v-if="entry.iconComponent || entry.icon" #icon>
|
|
94
|
+
<component :is="entry.iconComponent" v-if="entry.iconComponent" :size="20" />
|
|
95
|
+
<CnIcon v-else :name="entry.icon" :size="20" />
|
|
70
96
|
</template>
|
|
71
97
|
{{ entry.label }}
|
|
72
98
|
</NcActionButton>
|
|
@@ -1028,7 +1054,7 @@
|
|
|
1028
1054
|
<script>
|
|
1029
1055
|
import { getCurrentUser } from '@nextcloud/auth'
|
|
1030
1056
|
import { translate as t } from '@nextcloud/l10n'
|
|
1031
|
-
import { NcActionButton, NcActionCaption, NcActionCheckbox, NcActionLink, NcActions, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'
|
|
1057
|
+
import { NcActionButton, NcActionCaption, NcActionCheckbox, NcActionLink, NcActions, NcActionSeparator, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'
|
|
1032
1058
|
import { getCurrentInstance, inject, markRaw, ref } from 'vue'
|
|
1033
1059
|
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
|
|
1034
1060
|
import Close from 'vue-material-design-icons/Close.vue'
|
|
@@ -1038,6 +1064,8 @@ import DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'
|
|
|
1038
1064
|
import Export from 'vue-material-design-icons/Export.vue'
|
|
1039
1065
|
import Eye from 'vue-material-design-icons/Eye.vue'
|
|
1040
1066
|
import FilterOutline from 'vue-material-design-icons/FilterOutline.vue'
|
|
1067
|
+
import ImportIcon from 'vue-material-design-icons/Import.vue'
|
|
1068
|
+
import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
|
|
1041
1069
|
import Tune from 'vue-material-design-icons/Tune.vue'
|
|
1042
1070
|
import ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue'
|
|
1043
1071
|
import CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'
|
|
@@ -1053,13 +1081,13 @@ import { createRefLabelResolver } from '../../composables/useRefLabels.js'
|
|
|
1053
1081
|
import { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'
|
|
1054
1082
|
import { METADATA_COLUMNS } from '../../constants/metadata.js'
|
|
1055
1083
|
import { useObjectStore } from '../../store/useObjectStore.js'
|
|
1056
|
-
import { routeHref } from '../../utils/actionLink.js'
|
|
1084
|
+
import { followItemActionLink, resolveItemActionLink, routeHref } from '../../utils/actionLink.js'
|
|
1057
1085
|
import { buildOnSuccessRoute, resolveRegisteredHandler } from '../../utils/actionsDispatcher.js'
|
|
1058
1086
|
import { reportBindingProblems } from '../../utils/diagnostics.js'
|
|
1059
1087
|
import { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'
|
|
1060
1088
|
import { buildExportUrl } from '../../utils/indexExportHelpers.js'
|
|
1061
1089
|
import { lensUnavailableText } from '../../utils/lensAvailability.js'
|
|
1062
|
-
import { openRowTarget } from '../../utils/linkNavigation.js'
|
|
1090
|
+
import { isModifiedClick, openRowTarget } from '../../utils/linkNavigation.js'
|
|
1063
1091
|
import { resolveClaimedTeams, resolveClaimTokens, splitViewsIntoTabs, viewAsTab, viewIdOf } from '../../utils/listLenses.js'
|
|
1064
1092
|
import { LIST_SHORTCUTS, listPaletteCommands, shortcutFor } from '../../utils/listShortcuts.js'
|
|
1065
1093
|
import { multiKeySort } from '../../utils/multiKeySort.js'
|
|
@@ -1352,6 +1380,7 @@ export default {
|
|
|
1352
1380
|
NcActionLink,
|
|
1353
1381
|
NcActionButton,
|
|
1354
1382
|
NcActionCaption,
|
|
1383
|
+
NcActionSeparator,
|
|
1355
1384
|
NcActionCheckbox,
|
|
1356
1385
|
NcButton,
|
|
1357
1386
|
AlertCircleOutline,
|
|
@@ -1631,9 +1660,11 @@ export default {
|
|
|
1631
1660
|
* actions, or what its `items` name (`header-actions`, a header action
|
|
1632
1661
|
* id, `import`, `export`, `refresh`, `copy-selected`, `delete-selected`,
|
|
1633
1662
|
* `documentation`), so the entries the bar's menu loses have a place.
|
|
1663
|
+
* `items` can group them: `{ heading }` draws a group heading (an i18n
|
|
1664
|
+
* key) and `separator` a rule, each only when an entry follows.
|
|
1634
1665
|
*
|
|
1635
1666
|
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-an-index-page-can-take-the-board-header
|
|
1636
|
-
* @type {Array<{label?: string, action: string, variant?: ('primary'|'secondary'), icon?: string, format?: ('csv'|'excel'), id?: string, items?: string
|
|
1667
|
+
* @type {Array<{label?: string, action: string, variant?: ('primary'|'secondary'), icon?: string, format?: ('csv'|'excel'), id?: string, items?: Array<(string|{heading: string})>}>}
|
|
1637
1668
|
*/
|
|
1638
1669
|
headerButtons: {
|
|
1639
1670
|
type: Array,
|
|
@@ -5560,7 +5591,10 @@ export default {
|
|
|
5560
5591
|
variant: button.variant === 'primary' ? 'primary' : 'secondary',
|
|
5561
5592
|
icon,
|
|
5562
5593
|
format: button.format === 'excel' ? 'excel' : 'csv',
|
|
5563
|
-
items: Array.isArray(button.items)
|
|
5594
|
+
items: Array.isArray(button.items)
|
|
5595
|
+
? button.items.filter((item) => (typeof item === 'string' && item !== '')
|
|
5596
|
+
|| (item && typeof item === 'object' && typeof item.heading === 'string' && item.heading !== ''))
|
|
5597
|
+
: null,
|
|
5564
5598
|
}
|
|
5565
5599
|
})
|
|
5566
5600
|
.filter((button) => button.label !== '')
|
|
@@ -6855,11 +6889,14 @@ export default {
|
|
|
6855
6889
|
* `import`, `export`, `refresh`, `copy-selected`, `delete-selected`
|
|
6856
6890
|
* (disabled until a row is selected) and `documentation` (a link to
|
|
6857
6891
|
* `documentationUrl`, left out when the page has none). An unknown name
|
|
6858
|
-
* is left out.
|
|
6892
|
+
* is left out. `{ heading }` and `separator` group the entries: an
|
|
6893
|
+
* entry of `kind` `heading` or `separator`, drawn only when an entry
|
|
6894
|
+
* follows it.
|
|
6859
6895
|
*
|
|
6860
6896
|
* @spec openspec/changes/screens-index-header-actions-menu/specs/index-header/spec.md#requirement-the-header-actions-menu-lists-what-its-items-name
|
|
6861
|
-
* @
|
|
6862
|
-
* @
|
|
6897
|
+
* @spec openspec/changes/screens-index-header-menu-headings/specs/index-header/spec.md#requirement-the-header-actions-menu-can-draw-group-headings-and-separators
|
|
6898
|
+
* @param {{items?: Array<(string|{heading: string})>|null, format?: string}} button The resolved header button.
|
|
6899
|
+
* @return {Array<{key: string, label: string, kind?: ('heading'|'separator'), icon?: string, href?: string, disabled?: boolean, title?: string, iconComponent?: object, run?: Function}>}
|
|
6863
6900
|
*/
|
|
6864
6901
|
headerMenuEntries(button) {
|
|
6865
6902
|
const fromAction = (entry) => ({
|
|
@@ -6867,14 +6904,18 @@ export default {
|
|
|
6867
6904
|
label: entry.label ? this.cnTranslate(entry.label) : '',
|
|
6868
6905
|
icon: entry.icon && typeof entry.icon === 'string' && !entry.icon.startsWith('icon-') ? entry.icon : '',
|
|
6869
6906
|
href: typeof entry.href === 'string' && entry.href !== '' ? entry.href : '',
|
|
6907
|
+
// A `handler: "navigate"` entry whose route resolves carries `to`
|
|
6908
|
+
// (resolveHeaderHandler drops its handler): the menu draws it as
|
|
6909
|
+
// an in-app link, as the actions bar does, so it navigates.
|
|
6910
|
+
link: !entry.href && entry.to && !entry.disabled ? resolveItemActionLink({ to: entry.to }, null, this.$router) : null,
|
|
6870
6911
|
disabled: Boolean(entry.disabled),
|
|
6871
6912
|
run: () => this.onHeaderAction({ action: entry.id, id: entry.id }),
|
|
6872
6913
|
})
|
|
6873
6914
|
const names = button && Array.isArray(button.items) ? button.items : ['header-actions']
|
|
6874
6915
|
const noSelection = this.internalSelectedIds.length < 1
|
|
6875
6916
|
const builtIns = {
|
|
6876
|
-
import: () => ({ key: 'import', label: t('nextcloud-vue', 'Import'), icon: 'Import', run: () => { this.showImportDialog = true } }),
|
|
6877
|
-
export: () => ({ key: 'export', label: t('nextcloud-vue', 'Export'), icon: 'Export', run: () => this.onHeaderButton({ action: 'export', format: button.format }) }),
|
|
6917
|
+
import: () => ({ key: 'import', label: t('nextcloud-vue', 'Import'), icon: 'Import', iconComponent: ImportIcon, run: () => { this.showImportDialog = true } }),
|
|
6918
|
+
export: () => ({ key: 'export', label: t('nextcloud-vue', 'Export'), icon: 'Export', iconComponent: Export, run: () => this.onHeaderButton({ action: 'export', format: button.format }) }),
|
|
6878
6919
|
refresh: () => ({ key: 'refresh', label: t('nextcloud-vue', 'Refresh'), icon: 'Refresh', run: () => this.onRefreshEvent() }),
|
|
6879
6920
|
'copy-selected': () => ({
|
|
6880
6921
|
key: 'copy-selected',
|
|
@@ -6889,6 +6930,7 @@ export default {
|
|
|
6889
6930
|
key: 'delete-selected',
|
|
6890
6931
|
label: t('nextcloud-vue', 'Delete selected'),
|
|
6891
6932
|
icon: 'TrashCanOutline',
|
|
6933
|
+
iconComponent: TrashCanOutline,
|
|
6892
6934
|
disabled: noSelection,
|
|
6893
6935
|
title: noSelection ? t('nextcloud-vue', 'Select 1 or more items to delete') : '',
|
|
6894
6936
|
run: () => { this.showMassDeleteDialog = true },
|
|
@@ -6906,8 +6948,17 @@ export default {
|
|
|
6906
6948
|
entries.push(entry)
|
|
6907
6949
|
}
|
|
6908
6950
|
}
|
|
6951
|
+
// A group heading (`{ heading }`) or a `separator` is kept as a mark
|
|
6952
|
+
// and only drawn when an entry follows it (see groupMenuEntries).
|
|
6953
|
+
let mark = 0
|
|
6909
6954
|
for (const name of names) {
|
|
6910
|
-
if (name === '
|
|
6955
|
+
if (name && typeof name === 'object') {
|
|
6956
|
+
mark += 1
|
|
6957
|
+
entries.push({ key: `__heading-${mark}`, kind: 'heading', label: this.cnTranslate(name.heading) })
|
|
6958
|
+
} else if (name === 'separator') {
|
|
6959
|
+
mark += 1
|
|
6960
|
+
entries.push({ key: `__separator-${mark}`, kind: 'separator', label: '' })
|
|
6961
|
+
} else if (name === 'header-actions') {
|
|
6911
6962
|
this.mergedHeaderActions.forEach((entry) => push(fromAction(entry)))
|
|
6912
6963
|
} else if (Object.hasOwn(builtIns, name)) {
|
|
6913
6964
|
push(builtIns[name]())
|
|
@@ -6918,7 +6969,66 @@ export default {
|
|
|
6918
6969
|
}
|
|
6919
6970
|
}
|
|
6920
6971
|
}
|
|
6921
|
-
return entries
|
|
6972
|
+
return this.groupMenuEntries(entries)
|
|
6973
|
+
},
|
|
6974
|
+
|
|
6975
|
+
/**
|
|
6976
|
+
* Tidy the marks of the header's Actions menu: a heading or separator
|
|
6977
|
+
* with no entry after it (before the next mark or the end) is dropped,
|
|
6978
|
+
* a separator never opens the menu, two separators in a row are one,
|
|
6979
|
+
* and a heading followed by a separator before any entry is dropped. So a group whose entries are all left out (no documentation
|
|
6980
|
+
* URL, an unknown id) leaves no empty heading or double rule behind,
|
|
6981
|
+
* and a menu of marks only has no entries and is not drawn.
|
|
6982
|
+
*
|
|
6983
|
+
* @spec openspec/changes/screens-index-header-menu-headings/specs/index-header/spec.md#requirement-the-header-actions-menu-can-draw-group-headings-and-separators
|
|
6984
|
+
* @param {Array<{key: string, kind?: string}>} entries The entries with their marks, in order.
|
|
6985
|
+
* @return {Array<{key: string, kind?: string}>} The entries to draw.
|
|
6986
|
+
*/
|
|
6987
|
+
groupMenuEntries(entries) {
|
|
6988
|
+
const out = []
|
|
6989
|
+
let separator = null
|
|
6990
|
+
let heading = null
|
|
6991
|
+
for (const entry of entries) {
|
|
6992
|
+
if (entry.kind === 'separator') {
|
|
6993
|
+
// A rule ends the group before it: a heading not yet followed
|
|
6994
|
+
// by an entry is dropped with it.
|
|
6995
|
+
separator = entry
|
|
6996
|
+
heading = null
|
|
6997
|
+
continue
|
|
6998
|
+
}
|
|
6999
|
+
if (entry.kind === 'heading') {
|
|
7000
|
+
heading = entry
|
|
7001
|
+
continue
|
|
7002
|
+
}
|
|
7003
|
+
if (separator && out.length > 0) {
|
|
7004
|
+
out.push(separator)
|
|
7005
|
+
}
|
|
7006
|
+
if (heading) {
|
|
7007
|
+
out.push(heading)
|
|
7008
|
+
}
|
|
7009
|
+
separator = null
|
|
7010
|
+
heading = null
|
|
7011
|
+
out.push(entry)
|
|
7012
|
+
}
|
|
7013
|
+
return out
|
|
7014
|
+
},
|
|
7015
|
+
|
|
7016
|
+
/**
|
|
7017
|
+
* An in-app link of the header's Actions menu was clicked: a plain click
|
|
7018
|
+
* goes through the router (a modified click is left to the browser),
|
|
7019
|
+
* and the entry is reported as a header action, as the actions bar does.
|
|
7020
|
+
*
|
|
7021
|
+
* @spec openspec/changes/screens-index-header-actions-menu/specs/index-header/spec.md#requirement-the-header-actions-menu-lists-what-its-items-name
|
|
7022
|
+
* @param {{key: string, link: object}} entry The menu entry.
|
|
7023
|
+
* @param {MouseEvent} event The click.
|
|
7024
|
+
* @return {void}
|
|
7025
|
+
*/
|
|
7026
|
+
onHeaderMenuLinkClick(entry, event) {
|
|
7027
|
+
if (isModifiedClick(event)) {
|
|
7028
|
+
return
|
|
7029
|
+
}
|
|
7030
|
+
followItemActionLink(event, entry.link, this.$router)
|
|
7031
|
+
this.$emit('header-action', { action: entry.key, id: entry.key })
|
|
6922
7032
|
},
|
|
6923
7033
|
|
|
6924
7034
|
/**
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
-->
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
|
-
<fieldset class="cn-radio-cards"
|
|
7
|
+
<fieldset class="cn-radio-cards"
|
|
8
|
+
:class="{ 'cn-radio-cards--list': isList }"
|
|
9
|
+
data-testid="cn-radio-cards"
|
|
10
|
+
:aria-busy="busy ? 'true' : 'false'">
|
|
8
11
|
<legend v-if="label" class="cn-radio-cards__legend">
|
|
9
12
|
{{ label }}
|
|
10
13
|
</legend>
|
|
@@ -52,7 +55,7 @@ let uid = 0
|
|
|
52
55
|
* underneath, so the choice is perceivable without colour and works from the
|
|
53
56
|
* keyboard.
|
|
54
57
|
*
|
|
55
|
-
* @spec openspec/changes/abstract-radio-cards/specs/radio-cards/spec.md
|
|
58
|
+
* @spec openspec/changes/abstract-radio-cards/specs/abstract-radio-cards/spec.md
|
|
56
59
|
*/
|
|
57
60
|
export default {
|
|
58
61
|
name: 'CnRadioCardsWidget',
|
|
@@ -72,6 +75,7 @@ export default {
|
|
|
72
75
|
* - `options` (required) — `[{ value, label, description? }]`.
|
|
73
76
|
* - `label` — a legend over the cards (visually shown).
|
|
74
77
|
* - `columns` — 1, 2 or 3 (default 2).
|
|
78
|
+
* - `variant` — `cards` (the default) or `list`: a compact list of radio buttons, one per line, with the description under the label and no card around each (board DcBeheer). A list is one column.
|
|
75
79
|
* - `readOnly`.
|
|
76
80
|
*
|
|
77
81
|
* @type {object}
|
|
@@ -133,7 +137,20 @@ export default {
|
|
|
133
137
|
return this.tr(this.content.label || '')
|
|
134
138
|
},
|
|
135
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Whether the options draw as a compact list (`variant: "list"`).
|
|
142
|
+
*
|
|
143
|
+
* @return {boolean}
|
|
144
|
+
* @spec openspec/changes/screens-radio-cards-list/specs/radio-cards/spec.md#requirement-radio-cards-can-draw-as-a-compact-list
|
|
145
|
+
*/
|
|
146
|
+
isList() {
|
|
147
|
+
return this.content.variant === 'list'
|
|
148
|
+
},
|
|
149
|
+
|
|
136
150
|
columns() {
|
|
151
|
+
if (this.isList) {
|
|
152
|
+
return 1
|
|
153
|
+
}
|
|
137
154
|
return [1, 3].includes(this.content.columns) ? this.content.columns : 2
|
|
138
155
|
},
|
|
139
156
|
|
|
@@ -185,7 +202,7 @@ export default {
|
|
|
185
202
|
*
|
|
186
203
|
* @param {object} option The chosen option.
|
|
187
204
|
* @return {Promise<void>}
|
|
188
|
-
* @spec openspec/changes/abstract-radio-cards/specs/radio-cards/spec.md#requirement-choosing-a-card-saves-that-one-field
|
|
205
|
+
* @spec openspec/changes/abstract-radio-cards/specs/abstract-radio-cards/spec.md#requirement-choosing-a-card-saves-that-one-field
|
|
189
206
|
*/
|
|
190
207
|
async choose(option) {
|
|
191
208
|
if ((!this.objectUrl && !this.hasSave) || !this.content.field) {
|
|
@@ -310,6 +327,40 @@ export default {
|
|
|
310
327
|
color: var(--color-text-maxcontrast);
|
|
311
328
|
}
|
|
312
329
|
|
|
330
|
+
/* `variant: "list"`: one radio button per line, the description under the
|
|
331
|
+
label, no card around each (board DcBeheer, "Soort organisatie"). */
|
|
332
|
+
.cn-radio-cards--list .cn-radio-cards__grid {
|
|
333
|
+
gap: 8px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.cn-radio-cards--list .cn-radio-cards__card,
|
|
337
|
+
.cn-radio-cards--list .cn-radio-cards__card--selected {
|
|
338
|
+
padding: 0;
|
|
339
|
+
border: 0;
|
|
340
|
+
border-radius: 0;
|
|
341
|
+
background: transparent;
|
|
342
|
+
box-shadow: none;
|
|
343
|
+
font-size: 15px;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.cn-radio-cards--list .cn-radio-cards__dot {
|
|
347
|
+
width: 18px;
|
|
348
|
+
height: 18px;
|
|
349
|
+
margin-top: 2px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.cn-radio-cards--list .cn-radio-cards__card--selected .cn-radio-cards__dot {
|
|
353
|
+
border-width: 5px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.cn-radio-cards--list .cn-radio-cards__title {
|
|
357
|
+
font-weight: 400;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.cn-radio-cards--list .cn-radio-cards__description {
|
|
361
|
+
font-size: 13px;
|
|
362
|
+
}
|
|
363
|
+
|
|
313
364
|
@media (max-width: 640px) {
|
|
314
365
|
.cn-radio-cards__grid--2,
|
|
315
366
|
.cn-radio-cards__grid--3 {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
Spec: hydra openspec/architecture/adr-112-reports-are-one-page.md (ConductionNL/hydra#640).
|
|
33
33
|
-->
|
|
34
34
|
<template>
|
|
35
|
-
<div class="cn-reports-page">
|
|
35
|
+
<div class="cn-reports-page" :class="{ 'cn-reports-page--board': isBoard }">
|
|
36
36
|
<div class="cn-reports-page__header">
|
|
37
37
|
<h2 class="cn-reports-page__title">
|
|
38
38
|
{{ resolvedTitle }}
|
|
@@ -42,7 +42,44 @@
|
|
|
42
42
|
</p>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
<!-- The board look (board DcRapportages): one section per category,
|
|
46
|
+
headed by its label, instead of the category filter; each card
|
|
47
|
+
draws its icon in a tile. -->
|
|
48
|
+
<template v-if="isBoard">
|
|
49
|
+
<p v-if="resolvedCards.length === 0"
|
|
50
|
+
class="cn-reports-page__empty"
|
|
51
|
+
data-testid="cn-reports-empty">
|
|
52
|
+
{{ resolvedEmptyLabel }}
|
|
53
|
+
</p>
|
|
54
|
+
<section v-for="section in boardSections"
|
|
55
|
+
:key="section.key"
|
|
56
|
+
class="cn-reports-page__section"
|
|
57
|
+
data-testid="cn-reports-section">
|
|
58
|
+
<h3 v-if="section.label" class="cn-reports-page__section-title">
|
|
59
|
+
{{ section.label }}
|
|
60
|
+
</h3>
|
|
61
|
+
<ul class="cn-reports-page__grid" data-testid="cn-reports-grid">
|
|
62
|
+
<li v-for="card in section.cards" :key="card.id" class="cn-reports-page__cell">
|
|
63
|
+
<a class="cn-reports-page__card"
|
|
64
|
+
data-testid="cn-report-card"
|
|
65
|
+
:href="hrefFor(card)"
|
|
66
|
+
@click="open(card, $event)">
|
|
67
|
+
<span v-if="card.icon" class="cn-reports-page__card-icon" aria-hidden="true">
|
|
68
|
+
<CnIcon :name="card.icon" :size="22" />
|
|
69
|
+
</span>
|
|
70
|
+
<span class="cn-reports-page__card-text">
|
|
71
|
+
<span class="cn-reports-page__card-title">{{ card.label }}</span>
|
|
72
|
+
<span v-if="card.description" class="cn-reports-page__card-description">
|
|
73
|
+
{{ card.description }}
|
|
74
|
+
</span>
|
|
75
|
+
</span>
|
|
76
|
+
</a>
|
|
77
|
+
</li>
|
|
78
|
+
</ul>
|
|
79
|
+
</section>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<div v-if="!isBoard && categoryOptions.length > 1" class="cn-reports-page__filter">
|
|
46
83
|
<label class="cn-reports-page__filter-label" :for="filterId">
|
|
47
84
|
{{ resolvedCategoryLabel }}
|
|
48
85
|
</label>
|
|
@@ -58,13 +95,13 @@
|
|
|
58
95
|
</select>
|
|
59
96
|
</div>
|
|
60
97
|
|
|
61
|
-
<p v-if="visibleCards.length === 0"
|
|
98
|
+
<p v-if="!isBoard && visibleCards.length === 0"
|
|
62
99
|
class="cn-reports-page__empty"
|
|
63
100
|
data-testid="cn-reports-empty">
|
|
64
101
|
{{ resolvedEmptyLabel }}
|
|
65
102
|
</p>
|
|
66
103
|
|
|
67
|
-
<ul v-else class="cn-reports-page__grid" data-testid="cn-reports-grid">
|
|
104
|
+
<ul v-else-if="!isBoard" class="cn-reports-page__grid" data-testid="cn-reports-grid">
|
|
68
105
|
<li v-for="card in visibleCards" :key="card.id" class="cn-reports-page__cell">
|
|
69
106
|
<a class="cn-reports-page__card"
|
|
70
107
|
data-testid="cn-report-card"
|
|
@@ -98,6 +135,8 @@
|
|
|
98
135
|
*/
|
|
99
136
|
import { translate as t } from '@nextcloud/l10n'
|
|
100
137
|
import { defineAsyncComponent } from 'vue'
|
|
138
|
+
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
139
|
+
import { useLook } from '../../composables/useLook.js'
|
|
101
140
|
import { followLinkClick } from '../../utils/linkNavigation.js'
|
|
102
141
|
|
|
103
142
|
export default {
|
|
@@ -107,6 +146,7 @@ export default {
|
|
|
107
146
|
// Loaded only for a page that declares widgets: a cards-only reports
|
|
108
147
|
// page does not pull the dashboard in.
|
|
109
148
|
CnDashboardPage: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),
|
|
149
|
+
CnIcon,
|
|
110
150
|
},
|
|
111
151
|
|
|
112
152
|
inject: {
|
|
@@ -228,6 +268,17 @@ export default {
|
|
|
228
268
|
default: null,
|
|
229
269
|
},
|
|
230
270
|
|
|
271
|
+
/**
|
|
272
|
+
* The look to draw in: `board` or `nextcloud`. Absent: the look
|
|
273
|
+
* CnAppRoot provides (`cnLook`), else `nextcloud`.
|
|
274
|
+
*
|
|
275
|
+
* @type {string|null}
|
|
276
|
+
*/
|
|
277
|
+
look: {
|
|
278
|
+
type: String,
|
|
279
|
+
default: null,
|
|
280
|
+
},
|
|
281
|
+
|
|
231
282
|
/**
|
|
232
283
|
* Translate function. Falls back to injected `cnTranslate`.
|
|
233
284
|
*
|
|
@@ -239,6 +290,11 @@ export default {
|
|
|
239
290
|
},
|
|
240
291
|
},
|
|
241
292
|
|
|
293
|
+
setup(props) {
|
|
294
|
+
const { isBoard } = useLook(props)
|
|
295
|
+
return { isBoard }
|
|
296
|
+
},
|
|
297
|
+
|
|
242
298
|
data() {
|
|
243
299
|
return {
|
|
244
300
|
activeCategory: 'all',
|
|
@@ -385,6 +441,30 @@ export default {
|
|
|
385
441
|
return options
|
|
386
442
|
},
|
|
387
443
|
|
|
444
|
+
/**
|
|
445
|
+
* The board look's sections: the cards without a category first, under
|
|
446
|
+
* no heading, then one section per declared category that holds a
|
|
447
|
+
* card, in the declared order, headed by its label.
|
|
448
|
+
*
|
|
449
|
+
* @spec openspec/changes/screens-reports-page-board-look/specs/reports-page/spec.md#requirement-the-reports-page-takes-the-board-look
|
|
450
|
+
* @return {Array<{key: string, label: string, cards: Array<object>}>} The sections.
|
|
451
|
+
*/
|
|
452
|
+
boardSections() {
|
|
453
|
+
const declared = this.config.categories || {}
|
|
454
|
+
const sections = []
|
|
455
|
+
const loose = this.resolvedCards.filter((card) => !card.category || !Object.hasOwn(declared, card.category))
|
|
456
|
+
if (loose.length > 0) {
|
|
457
|
+
sections.push({ key: '__none', label: '', cards: loose })
|
|
458
|
+
}
|
|
459
|
+
for (const key of Object.keys(declared)) {
|
|
460
|
+
const cards = this.resolvedCards.filter((card) => card.category === key)
|
|
461
|
+
if (cards.length > 0) {
|
|
462
|
+
sections.push({ key, label: this.tr(declared[key]), cards })
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return sections
|
|
466
|
+
},
|
|
467
|
+
|
|
388
468
|
/**
|
|
389
469
|
* The cards the active filter admits.
|
|
390
470
|
*
|