@conduction/nextcloud-vue 2.96.0 → 2.97.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/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +383 -359
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +17 -3
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +61 -43
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +159 -18
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +9 -7
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +48 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +46 -12
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +144 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +488 -399
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +301 -18
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +29 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +2 -2
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +13 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +5 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +182 -3
- package/dist/esm/composables/indexSources.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/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +301 -177
- package/dist/esm/utils/queryPresets.js +13 -2
- package/dist/esm/utils/queryPresets.js.map +1 -1
- package/dist/esm/utils/scopeListLayout.js +37 -1
- package/dist/esm/utils/scopeListLayout.js.map +1 -1
- 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 +3009 -1907
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +301 -177
- package/l10n/en.json +7 -1
- package/l10n/nl.json +7 -1
- package/package.json +1 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +57 -1
- package/src/components/CnConversationThread/CnConversationThread.md +12 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +30 -4
- package/src/components/CnConversationThread/CnConversationWidget.vue +167 -24
- package/src/components/CnFactsWidget/CnFactsWidget.vue +114 -3
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +508 -236
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
- package/src/components/CnIndexPage/CnIndexPage.vue +324 -24
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +13 -4
- package/src/components/CnIndexPage/useNamedSource.js +1 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +17 -0
- package/src/composables/indexSources.js +182 -3
- package/src/schemas/app-manifest-v2.schema.json +11 -4
- package/src/utils/queryPresets.js +13 -2
- package/src/utils/scopeListLayout.js +37 -1
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -144,7 +144,8 @@ export function attributePropertiesFor(columns) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
|
-
* The value one column shows for one node.
|
|
147
|
+
* The value one column shows for one node. A `row` column's key may be a
|
|
148
|
+
* dotted path into the host's record.
|
|
148
149
|
*
|
|
149
150
|
* @param {object} column A resolved column.
|
|
150
151
|
* @param {object} node The node.
|
|
@@ -169,7 +170,12 @@ export function fileColumnValue(column, node, rowData = null) {
|
|
|
169
170
|
return undefined
|
|
170
171
|
}
|
|
171
172
|
const entry = byId[node.fileid] ?? byId[String(node.fileid)]
|
|
172
|
-
|
|
173
|
+
if (!entry) {
|
|
174
|
+
return undefined
|
|
175
|
+
}
|
|
176
|
+
// A key the record holds as written wins; otherwise a dotted key reads
|
|
177
|
+
// a nested field (`scanVerdict.state`, `integrity.value`).
|
|
178
|
+
return Object.hasOwn(entry, column.key) ? entry[column.key] : atPath(entry, column.key)
|
|
173
179
|
}
|
|
174
180
|
return atPath(node, column.nodeKey || column.key)
|
|
175
181
|
}
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
region (WCAG 2.4.6 / 1.3.1). -->
|
|
14
14
|
<slot
|
|
15
15
|
name="header"
|
|
16
|
-
:title="title"
|
|
16
|
+
:title="presented('title')"
|
|
17
17
|
:description="headerDescription"
|
|
18
18
|
:icon="resolvedIcon"
|
|
19
19
|
:showTitle="showTitle">
|
|
20
20
|
<CnPageHeader
|
|
21
|
-
:title="title"
|
|
21
|
+
:title="presented('title')"
|
|
22
22
|
:description="headerDescription"
|
|
23
23
|
:icon="showTitleIcon && !isBoardLook ? resolvedIcon : ''"
|
|
24
|
-
:breadcrumb="breadcrumb"
|
|
24
|
+
:breadcrumb="presented('breadcrumb')"
|
|
25
25
|
:meta="headerMetaText"
|
|
26
26
|
:visuallyHidden="!showTitle">
|
|
27
27
|
<!-- The board's header buttons (`headerButtons`), beside the
|
|
@@ -125,11 +125,11 @@
|
|
|
125
125
|
<!-- Page tabs a named source declares (`pageTabs()`): sibling lists
|
|
126
126
|
of the same thing, such as a flow list and its runs this week.
|
|
127
127
|
Links, not tabs: each one is its own page. -->
|
|
128
|
-
<nav v-if="
|
|
128
|
+
<nav v-if="effectivePageTabs.length > 0"
|
|
129
129
|
class="cn-index-page__page-tabs"
|
|
130
130
|
:aria-label="title || t('nextcloud-vue', 'Pages')"
|
|
131
131
|
data-testid="cn-index-page-tabs">
|
|
132
|
-
<a v-for="tab in
|
|
132
|
+
<a v-for="tab in effectivePageTabs"
|
|
133
133
|
:key="tab.id || tab.to"
|
|
134
134
|
class="cn-index-page__page-tab"
|
|
135
135
|
:class="{ 'cn-index-page__page-tab--active': isActivePageTab(tab) }"
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
:sortValue="sortSelectValue"
|
|
194
194
|
:showSearch="inlineSearch || isBoardLook"
|
|
195
195
|
:searchValue="effectiveSearchValue"
|
|
196
|
-
:searchPlaceholder="searchPlaceholder ? cnTranslate(searchPlaceholder) : searchPlaceholder"
|
|
196
|
+
:searchPlaceholder="presented('searchPlaceholder') ? cnTranslate(presented('searchPlaceholder')) : presented('searchPlaceholder')"
|
|
197
197
|
:showCountWithSearch="showCountWithSearch"
|
|
198
198
|
:refreshing="effectiveRefreshing"
|
|
199
199
|
:refreshDisabled="refreshDisabled"
|
|
@@ -1075,6 +1075,23 @@
|
|
|
1075
1075
|
class="cn-index-page__pagination"
|
|
1076
1076
|
@pageChanged="onPageEvent"
|
|
1077
1077
|
@pageSizeChanged="onPageSizeEvent" />
|
|
1078
|
+
|
|
1079
|
+
<!-- A notice under the list (`notice`): DqPrivacy's "Een
|
|
1080
|
+
AVG-verzoek is een zaak met ..." band. -->
|
|
1081
|
+
<NcNoteCard
|
|
1082
|
+
v-if="notice && notice.text"
|
|
1083
|
+
class="cn-index-page__notice"
|
|
1084
|
+
:type="noticeType"
|
|
1085
|
+
data-testid="cn-index-page-notice">
|
|
1086
|
+
{{ cnTranslate(notice.text) }}
|
|
1087
|
+
</NcNoteCard>
|
|
1088
|
+
|
|
1089
|
+
<!-- Cards after the list (`afterList`), stacked in order (PqSocial). -->
|
|
1090
|
+
<CnIndexPageWidgets
|
|
1091
|
+
v-if="presentedAfterList.length > 0"
|
|
1092
|
+
class="cn-index-page__after-list"
|
|
1093
|
+
variant="after-list"
|
|
1094
|
+
:widgets="presentedAfterList" />
|
|
1078
1095
|
</div>
|
|
1079
1096
|
|
|
1080
1097
|
<!-- The open record, beside the list on a wide screen and instead
|
|
@@ -1158,7 +1175,7 @@
|
|
|
1158
1175
|
<script>
|
|
1159
1176
|
import { getCurrentUser } from '@nextcloud/auth'
|
|
1160
1177
|
import { translatePlural as n, translate as t } from '@nextcloud/l10n'
|
|
1161
|
-
import { NcActionButton, NcActionCaption, NcActionCheckbox, NcActionLink, NcActions, NcActionSeparator, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'
|
|
1178
|
+
import { NcActionButton, NcActionCaption, NcActionCheckbox, NcActionLink, NcActions, NcActionSeparator, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
|
|
1162
1179
|
import { getCurrentInstance, inject, markRaw, ref } from 'vue'
|
|
1163
1180
|
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
|
|
1164
1181
|
import Close from 'vue-material-design-icons/Close.vue'
|
|
@@ -1200,7 +1217,7 @@ import {
|
|
|
1200
1217
|
} from '../../utils/boardReferenceColumns.js'
|
|
1201
1218
|
import { reportBindingProblems } from '../../utils/diagnostics.js'
|
|
1202
1219
|
import { fetchGroupedCounts } from '../../utils/fetchAggregate.js'
|
|
1203
|
-
import { fetchFilterCounts } from '../../utils/fetchFilterCounts.js'
|
|
1220
|
+
import { fetchFilterCounts, fetchListTotal } from '../../utils/fetchFilterCounts.js'
|
|
1204
1221
|
import { buildExportUrl } from '../../utils/indexExportHelpers.js'
|
|
1205
1222
|
import { lensUnavailableText } from '../../utils/lensAvailability.js'
|
|
1206
1223
|
import { isModifiedClick, openRowTarget } from '../../utils/linkNavigation.js'
|
|
@@ -1219,7 +1236,7 @@ import { isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, pr
|
|
|
1219
1236
|
import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
|
|
1220
1237
|
import { buildRouteQueryFromViewState, buildViewCreatePayload, extractViewState, extractViewStateFromRouteQuery, normalizeSharedWith, savedViewScope, viewMatchesScope } from '../../utils/savedViewHelpers.js'
|
|
1221
1238
|
import { columnsFromSchema, fieldsFromSchema } from '../../utils/schema.js'
|
|
1222
|
-
import { resolveScopeLayout } from '../../utils/scopeListLayout.js'
|
|
1239
|
+
import { resolveScopeLayout, resolveScopePresentation } from '../../utils/scopeListLayout.js'
|
|
1223
1240
|
import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js'
|
|
1224
1241
|
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
1225
1242
|
import { CnActionsBar } from '../CnActionsBar/index.js'
|
|
@@ -1490,6 +1507,7 @@ export default {
|
|
|
1490
1507
|
|
|
1491
1508
|
components: {
|
|
1492
1509
|
CnIndexPageWidgets,
|
|
1510
|
+
NcNoteCard,
|
|
1493
1511
|
CnBuildiqEditButton,
|
|
1494
1512
|
CnFollowToggle,
|
|
1495
1513
|
NcLoadingIcon,
|
|
@@ -1658,6 +1676,35 @@ export default {
|
|
|
1658
1676
|
default: '',
|
|
1659
1677
|
},
|
|
1660
1678
|
|
|
1679
|
+
/**
|
|
1680
|
+
* Tabs to sibling pages above the toolbar (manifest `config.pageTabs`),
|
|
1681
|
+
* each `{ id?, label, route?, params?, query?, href?, countFrom?: {
|
|
1682
|
+
* register, schema, filter? } }`: DqPrivacy's "AVG-verzoeken |
|
|
1683
|
+
* Datalekken". The current page's tab is marked; `countFrom` adds the
|
|
1684
|
+
* count of that list. Wins over a named source's own page tabs.
|
|
1685
|
+
*
|
|
1686
|
+
* @type {Array<object>|null}
|
|
1687
|
+
* @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
|
|
1688
|
+
*/
|
|
1689
|
+
pageTabs: {
|
|
1690
|
+
type: Array,
|
|
1691
|
+
default: null,
|
|
1692
|
+
},
|
|
1693
|
+
|
|
1694
|
+
/**
|
|
1695
|
+
* A notice under the list (manifest `config.notice`): `{ text,
|
|
1696
|
+
* variant?: 'info' | 'success' | 'warning' }`, a band that explains
|
|
1697
|
+
* the list, such as who decides what. The text goes through the host
|
|
1698
|
+
* translate.
|
|
1699
|
+
*
|
|
1700
|
+
* @type {{text: string, variant?: string}|null}
|
|
1701
|
+
* @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-show-a-notice-under-its-list
|
|
1702
|
+
*/
|
|
1703
|
+
notice: {
|
|
1704
|
+
type: Object,
|
|
1705
|
+
default: null,
|
|
1706
|
+
},
|
|
1707
|
+
|
|
1661
1708
|
/** Optional description shown below the title */
|
|
1662
1709
|
description: {
|
|
1663
1710
|
type: String,
|
|
@@ -3045,6 +3092,33 @@ export default {
|
|
|
3045
3092
|
default: () => [],
|
|
3046
3093
|
},
|
|
3047
3094
|
|
|
3095
|
+
/**
|
|
3096
|
+
* Cards after the list (manifest `config.afterList`): dashboard widget
|
|
3097
|
+
* definitions `{ id, type, title?, content, headerLink? }` stacked
|
|
3098
|
+
* under the list in the listed order, the counterpart of `statRow`
|
|
3099
|
+
* above it. A query preset or a scope can set its own.
|
|
3100
|
+
*
|
|
3101
|
+
* @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-an-index-page-can-draw-widgets-after-its-list
|
|
3102
|
+
* @type {Array<object>}
|
|
3103
|
+
*/
|
|
3104
|
+
afterList: {
|
|
3105
|
+
type: Array,
|
|
3106
|
+
default: () => [],
|
|
3107
|
+
},
|
|
3108
|
+
|
|
3109
|
+
/**
|
|
3110
|
+
* The route query pairs that chose this page's query preset, set by
|
|
3111
|
+
* CnPageRenderer. They scope the list, so they show no removable
|
|
3112
|
+
* active-filter chip.
|
|
3113
|
+
*
|
|
3114
|
+
* @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-the-query-that-chose-a-preset-is-not-a-removable-filter
|
|
3115
|
+
* @type {object|null}
|
|
3116
|
+
*/
|
|
3117
|
+
fixedFilters: {
|
|
3118
|
+
type: Object,
|
|
3119
|
+
default: null,
|
|
3120
|
+
},
|
|
3121
|
+
|
|
3048
3122
|
/**
|
|
3049
3123
|
* Where a row carries the actions the server says this caller may run
|
|
3050
3124
|
* on it: a dotted path, read off the rows the list already fetched, so
|
|
@@ -3645,6 +3719,8 @@ export default {
|
|
|
3645
3719
|
tabCounts: null,
|
|
3646
3720
|
// The counts of the `filterStrip` pills, by pill key ('all', 'v0', ...).
|
|
3647
3721
|
filterStripCounts: null,
|
|
3722
|
+
/** The `countFrom` counts of the manifest page tabs, by index. */
|
|
3723
|
+
pageTabCounts: {},
|
|
3648
3724
|
/** The tabs `quickFiltersFrom` produced, or null before they load. */
|
|
3649
3725
|
generatedQuickFilters: null,
|
|
3650
3726
|
/** Count per saved-view id, for the views control; null = none. */
|
|
@@ -4194,7 +4270,8 @@ export default {
|
|
|
4194
4270
|
}
|
|
4195
4271
|
// The index boards draw all four segments, so the board look does
|
|
4196
4272
|
// too unless the manifest names its own list.
|
|
4197
|
-
const
|
|
4273
|
+
const viewSwitch = this.presented('viewSwitch')
|
|
4274
|
+
const listed = Array.isArray(viewSwitch) ? viewSwitch : ['table', 'cards', 'board', 'map']
|
|
4198
4275
|
const offered = [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])]
|
|
4199
4276
|
return listed.filter((mode) => ['table', 'cards', 'board', 'map'].includes(mode) && !offered.includes(mode))
|
|
4200
4277
|
},
|
|
@@ -4665,6 +4742,58 @@ export default {
|
|
|
4665
4742
|
})
|
|
4666
4743
|
},
|
|
4667
4744
|
|
|
4745
|
+
/**
|
|
4746
|
+
* This page's own value of each presentation key, which a query preset
|
|
4747
|
+
* may have set: what `presented()` falls back to without a scope.
|
|
4748
|
+
*
|
|
4749
|
+
* @return {object}
|
|
4750
|
+
* @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
|
|
4751
|
+
*/
|
|
4752
|
+
ownPresentation() {
|
|
4753
|
+
return {
|
|
4754
|
+
title: this.title,
|
|
4755
|
+
quickFilters: this.quickFilters,
|
|
4756
|
+
cardFields: this.cardFields,
|
|
4757
|
+
countText: this.countText,
|
|
4758
|
+
searchPlaceholder: this.searchPlaceholder,
|
|
4759
|
+
footerNote: this.footerNote,
|
|
4760
|
+
viewSwitch: this.viewSwitch,
|
|
4761
|
+
addLabel: this.addLabel,
|
|
4762
|
+
headerButtons: this.headerButtons,
|
|
4763
|
+
breadcrumb: this.breadcrumb,
|
|
4764
|
+
headerMeta: this.headerMeta,
|
|
4765
|
+
afterList: this.afterList,
|
|
4766
|
+
}
|
|
4767
|
+
},
|
|
4768
|
+
|
|
4769
|
+
/**
|
|
4770
|
+
* The presentation the active scope declares (`title`, `countText`,
|
|
4771
|
+
* `quickFilters`, `addLabel`, `afterList` and the other query preset
|
|
4772
|
+
* keys), from its folder entry or its row's `x-index`. Empty without a
|
|
4773
|
+
* scope, so the page's own values stand.
|
|
4774
|
+
*
|
|
4775
|
+
* @return {object}
|
|
4776
|
+
* @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
|
|
4777
|
+
*/
|
|
4778
|
+
activeScopePresentation() {
|
|
4779
|
+
if (!this.folderSidebar) {
|
|
4780
|
+
return {}
|
|
4781
|
+
}
|
|
4782
|
+
return resolveScopePresentation({ scope: this.activeScope, row: this.activeScopeRow })
|
|
4783
|
+
},
|
|
4784
|
+
|
|
4785
|
+
/**
|
|
4786
|
+
* The widgets drawn after the list: the active scope's `afterList`,
|
|
4787
|
+
* else the page's.
|
|
4788
|
+
*
|
|
4789
|
+
* @return {Array<object>}
|
|
4790
|
+
* @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-an-index-page-can-draw-widgets-after-its-list
|
|
4791
|
+
*/
|
|
4792
|
+
presentedAfterList() {
|
|
4793
|
+
const list = this.presented('afterList')
|
|
4794
|
+
return Array.isArray(list) ? list : []
|
|
4795
|
+
},
|
|
4796
|
+
|
|
4668
4797
|
/**
|
|
4669
4798
|
* The fields the search box searches over while a scope declaring
|
|
4670
4799
|
* `searchFields` is active. Read by `useSelfFetchList`'s fixed-filter
|
|
@@ -5124,8 +5253,9 @@ export default {
|
|
|
5124
5253
|
* @return {?Array<string>}
|
|
5125
5254
|
*/
|
|
5126
5255
|
resolvedCardFields() {
|
|
5127
|
-
|
|
5128
|
-
|
|
5256
|
+
const cardFields = this.presented('cardFields')
|
|
5257
|
+
if (Array.isArray(cardFields) && cardFields.length > 0) {
|
|
5258
|
+
return cardFields
|
|
5129
5259
|
}
|
|
5130
5260
|
if (!this.isBoardLook) {
|
|
5131
5261
|
return null
|
|
@@ -5164,6 +5294,10 @@ export default {
|
|
|
5164
5294
|
}
|
|
5165
5295
|
})
|
|
5166
5296
|
}
|
|
5297
|
+
// An `actions` column: the page's row actions it names, as buttons.
|
|
5298
|
+
cols = cols.map((col) => (col && typeof col === 'object' && col.widget === 'actions'
|
|
5299
|
+
? { ...col, sortable: false, widgetProps: { ...(col.widgetProps || {}), items: (row) => this.actionColumnItems(col, row), run: this.runColumnAction } }
|
|
5300
|
+
: col))
|
|
5167
5301
|
// The Recent lens owns the order: no column sorts while it is on.
|
|
5168
5302
|
if (this.recentLensActive) {
|
|
5169
5303
|
cols = cols.map((col) => (col && typeof col === 'object' ? { ...col, sortable: false } : col))
|
|
@@ -6040,6 +6174,51 @@ export default {
|
|
|
6040
6174
|
return (source.pageTabs() || []).filter((tab) => tab && tab.to && tab.label)
|
|
6041
6175
|
},
|
|
6042
6176
|
|
|
6177
|
+
/**
|
|
6178
|
+
* The page tabs to draw: the manifest's `pageTabs`, each with its
|
|
6179
|
+
* `countFrom` count once known, else a named source's own.
|
|
6180
|
+
*
|
|
6181
|
+
* @return {Array<object>} The tabs, `{ id, label, to, count? }`.
|
|
6182
|
+
* @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
|
|
6183
|
+
*/
|
|
6184
|
+
/**
|
|
6185
|
+
* The notice band's NcNoteCard type: its `variant`, else info.
|
|
6186
|
+
*
|
|
6187
|
+
* @return {string}
|
|
6188
|
+
* @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-show-a-notice-under-its-list
|
|
6189
|
+
*/
|
|
6190
|
+
noticeType() {
|
|
6191
|
+
const variant = this.notice && this.notice.variant
|
|
6192
|
+
return ['success', 'warning', 'error'].includes(variant) ? variant : 'info'
|
|
6193
|
+
},
|
|
6194
|
+
|
|
6195
|
+
effectivePageTabs() {
|
|
6196
|
+
if (!Array.isArray(this.pageTabs) || this.pageTabs.length === 0) {
|
|
6197
|
+
return this.sourcePageTabs
|
|
6198
|
+
}
|
|
6199
|
+
return this.pageTabs
|
|
6200
|
+
.filter((tab) => tab && typeof tab.label === 'string' && tab.label !== '' && (tab.route || tab.to || tab.href))
|
|
6201
|
+
.map((tab, index) => {
|
|
6202
|
+
const to = tab.route
|
|
6203
|
+
? { name: tab.route, ...(tab.params ? { params: tab.params } : {}), ...(tab.query ? { query: tab.query } : {}) }
|
|
6204
|
+
: (tab.to || tab.href)
|
|
6205
|
+
const count = this.pageTabCounts[index]
|
|
6206
|
+
return { id: tab.id || `page-tab-${index}`, label: this.cnTranslate(tab.label), to, ...(typeof count === 'number' ? { count } : {}) }
|
|
6207
|
+
})
|
|
6208
|
+
},
|
|
6209
|
+
|
|
6210
|
+
/**
|
|
6211
|
+
* What the page tab counts depend on, as one key.
|
|
6212
|
+
*
|
|
6213
|
+
* @return {string}
|
|
6214
|
+
* @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
|
|
6215
|
+
*/
|
|
6216
|
+
pageTabCountKey() {
|
|
6217
|
+
const tabs = Array.isArray(this.pageTabs) ? this.pageTabs : []
|
|
6218
|
+
const sources = tabs.map((tab) => (tab && tab.countFrom && tab.countFrom.register && tab.countFrom.schema ? tab.countFrom : null))
|
|
6219
|
+
return sources.some(Boolean) ? JSON.stringify(sources) : ''
|
|
6220
|
+
},
|
|
6221
|
+
|
|
6043
6222
|
/**
|
|
6044
6223
|
* The numbers on the quick filters: a named source counts its own
|
|
6045
6224
|
* tabs (`tabCounts()`), a self-fetching page asks OpenRegister.
|
|
@@ -6061,11 +6240,15 @@ export default {
|
|
|
6061
6240
|
* @return {string} The note, translated, or ''.
|
|
6062
6241
|
*/
|
|
6063
6242
|
effectiveFooterNote() {
|
|
6064
|
-
|
|
6065
|
-
|
|
6243
|
+
// The scope's note, else the page's (a preset may have set it),
|
|
6244
|
+
// else the named source's.
|
|
6245
|
+
const scoped = this.activeScopePresentation ? this.activeScopePresentation.footerNote : undefined
|
|
6246
|
+
const footerNote = scoped !== undefined ? scoped : this.footerNote
|
|
6247
|
+
if (footerNote) {
|
|
6248
|
+
return this.cnTranslate(footerNote)
|
|
6066
6249
|
}
|
|
6067
6250
|
const source = this.isNamedSource ? this.namedSource : null
|
|
6068
|
-
return (source && source.footerNote) ||
|
|
6251
|
+
return (source && source.footerNote) || footerNote
|
|
6069
6252
|
},
|
|
6070
6253
|
|
|
6071
6254
|
/**
|
|
@@ -6076,7 +6259,7 @@ export default {
|
|
|
6076
6259
|
* @return {Array<object>|null} The tabs, or null when there are none.
|
|
6077
6260
|
*/
|
|
6078
6261
|
effectiveQuickFilters() {
|
|
6079
|
-
const own = withPersonalLenses(this.quickFilters || this.generatedQuickFilters, this.personalLenses)
|
|
6262
|
+
const own = withPersonalLenses(this.presented('quickFilters') || this.generatedQuickFilters, this.personalLenses)
|
|
6080
6263
|
if (own && own.length > 0) {
|
|
6081
6264
|
return own
|
|
6082
6265
|
}
|
|
@@ -6142,7 +6325,8 @@ export default {
|
|
|
6142
6325
|
* @return {Array<{key: string, label: string, action: string, variant: string, icon: string, format: string}>}
|
|
6143
6326
|
*/
|
|
6144
6327
|
resolvedHeaderButtons() {
|
|
6145
|
-
const
|
|
6328
|
+
const headerButtons = this.presented('headerButtons')
|
|
6329
|
+
const declared = Array.isArray(headerButtons) ? headerButtons : []
|
|
6146
6330
|
return declared
|
|
6147
6331
|
.filter((button) => button && typeof button === 'object' && typeof button.action === 'string' && button.action !== '')
|
|
6148
6332
|
.map((button, index) => {
|
|
@@ -6267,6 +6451,7 @@ export default {
|
|
|
6267
6451
|
for (const [key, raw] of Object.entries(this.effectiveActiveFilters || {})) {
|
|
6268
6452
|
const values = (Array.isArray(raw) ? raw : [raw])
|
|
6269
6453
|
.filter((value) => value !== null && value !== undefined && value !== '')
|
|
6454
|
+
.filter((value) => !this.isFixedFilter(key, value))
|
|
6270
6455
|
.map((value) => this.activeFilterValueLabel(key, value))
|
|
6271
6456
|
if (values.length === 0) {
|
|
6272
6457
|
continue
|
|
@@ -6450,11 +6635,12 @@ export default {
|
|
|
6450
6635
|
* @return {string}
|
|
6451
6636
|
*/
|
|
6452
6637
|
headerMetaText() {
|
|
6453
|
-
|
|
6638
|
+
const headerMeta = this.presented('headerMeta')
|
|
6639
|
+
if (!headerMeta) {
|
|
6454
6640
|
return ''
|
|
6455
6641
|
}
|
|
6456
6642
|
const total = this.effectivePagination?.total
|
|
6457
|
-
return this.cnTranslate(
|
|
6643
|
+
return this.cnTranslate(headerMeta)
|
|
6458
6644
|
.replace('{shown}', String(this.effectiveObjects.length))
|
|
6459
6645
|
.replace('{total}', typeof total === 'number' ? String(total) : '')
|
|
6460
6646
|
},
|
|
@@ -6468,11 +6654,12 @@ export default {
|
|
|
6468
6654
|
// The default count line is the library's own string, so it
|
|
6469
6655
|
// reads "14 van 14" in Dutch; a page's own template goes
|
|
6470
6656
|
// through the app's label lookup.
|
|
6471
|
-
|
|
6657
|
+
const countText = this.presented('countText')
|
|
6658
|
+
if (!countText && !this.countSubtitle) {
|
|
6472
6659
|
return t('nextcloud-vue', '{shown} of {total}', { shown, total })
|
|
6473
6660
|
}
|
|
6474
|
-
if (
|
|
6475
|
-
return this.cnTranslate(
|
|
6661
|
+
if (countText) {
|
|
6662
|
+
return this.cnTranslate(countText)
|
|
6476
6663
|
.replace('{shown}', String(shown))
|
|
6477
6664
|
.replace('{total}', String(total))
|
|
6478
6665
|
}
|
|
@@ -6486,8 +6673,10 @@ export default {
|
|
|
6486
6673
|
|
|
6487
6674
|
/** Add button label — derived from schema.title if not explicitly set */
|
|
6488
6675
|
resolvedAddLabel() {
|
|
6489
|
-
|
|
6490
|
-
|
|
6676
|
+
const scoped = this.activeScopePresentation ? this.activeScopePresentation.addLabel : undefined
|
|
6677
|
+
const addLabel = scoped !== undefined ? scoped : this.addLabel
|
|
6678
|
+
if (addLabel) {
|
|
6679
|
+
return this.cnTranslate(addLabel)
|
|
6491
6680
|
}
|
|
6492
6681
|
// A named source names its own create action. Without this the button
|
|
6493
6682
|
// falls back to a schema-derived noun, and a named source has no
|
|
@@ -6712,6 +6901,14 @@ export default {
|
|
|
6712
6901
|
},
|
|
6713
6902
|
},
|
|
6714
6903
|
|
|
6904
|
+
pageTabCountKey: {
|
|
6905
|
+
immediate: true,
|
|
6906
|
+
/** @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs */
|
|
6907
|
+
handler() {
|
|
6908
|
+
this.loadPageTabCounts()
|
|
6909
|
+
},
|
|
6910
|
+
},
|
|
6911
|
+
|
|
6715
6912
|
filterStripCountKey: {
|
|
6716
6913
|
immediate: true,
|
|
6717
6914
|
/** @spec openspec/changes/screens-filter-strip/specs/index-filter-strip/spec.md#requirement-an-index-page-can-draw-a-second-filter-strip */
|
|
@@ -6979,6 +7176,36 @@ export default {
|
|
|
6979
7176
|
},
|
|
6980
7177
|
|
|
6981
7178
|
methods: {
|
|
7179
|
+
/**
|
|
7180
|
+
* A presentation value: the active scope's when it declares the key,
|
|
7181
|
+
* else this page's own prop (which a query preset may have set).
|
|
7182
|
+
*
|
|
7183
|
+
* @param {string} key One of the query preset keys.
|
|
7184
|
+
* @return {unknown} The value.
|
|
7185
|
+
* @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
|
|
7186
|
+
*/
|
|
7187
|
+
presented(key) {
|
|
7188
|
+
const scoped = this.activeScopePresentation
|
|
7189
|
+
return scoped[key] !== undefined ? scoped[key] : this.ownPresentation[key]
|
|
7190
|
+
},
|
|
7191
|
+
|
|
7192
|
+
/**
|
|
7193
|
+
* Whether a filter value is one of the query pairs that chose the
|
|
7194
|
+
* preset, so it shows no removable chip.
|
|
7195
|
+
*
|
|
7196
|
+
* @param {string} key The filter key.
|
|
7197
|
+
* @param {unknown} value One filter value.
|
|
7198
|
+
* @return {boolean}
|
|
7199
|
+
* @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-the-query-that-chose-a-preset-is-not-a-removable-filter
|
|
7200
|
+
*/
|
|
7201
|
+
isFixedFilter(key, value) {
|
|
7202
|
+
const fixed = this.fixedFilters
|
|
7203
|
+
if (!fixed || !Object.hasOwn(fixed, key)) {
|
|
7204
|
+
return false
|
|
7205
|
+
}
|
|
7206
|
+
return String(fixed[key]) === String(value)
|
|
7207
|
+
},
|
|
7208
|
+
|
|
6982
7209
|
/**
|
|
6983
7210
|
* The manifest page that shows one object of a schema, for a linked
|
|
6984
7211
|
* label. Null without a manifest or a matching detail page.
|
|
@@ -7092,6 +7319,32 @@ export default {
|
|
|
7092
7319
|
]
|
|
7093
7320
|
},
|
|
7094
7321
|
|
|
7322
|
+
/**
|
|
7323
|
+
* Count the lists behind the manifest page tabs (`countFrom`), one list
|
|
7324
|
+
* request each. A failed count shows no number.
|
|
7325
|
+
*
|
|
7326
|
+
* @return {Promise<void>}
|
|
7327
|
+
* @spec openspec/changes/index-page-tabs-notice/specs/index-page/spec.md#requirement-an-index-page-can-link-its-sibling-pages-as-tabs
|
|
7328
|
+
*/
|
|
7329
|
+
async loadPageTabCounts() {
|
|
7330
|
+
const key = this.pageTabCountKey
|
|
7331
|
+
if (key === '') {
|
|
7332
|
+
this.pageTabCounts = {}
|
|
7333
|
+
return
|
|
7334
|
+
}
|
|
7335
|
+
const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {}
|
|
7336
|
+
const counts = {}
|
|
7337
|
+
await Promise.all(this.pageTabs.map(async (tab, index) => {
|
|
7338
|
+
const from = tab && tab.countFrom
|
|
7339
|
+
if (from && from.register && from.schema) {
|
|
7340
|
+
counts[index] = await fetchListTotal(from.register, from.schema, from.filter || {}, ctx).catch(() => null)
|
|
7341
|
+
}
|
|
7342
|
+
}))
|
|
7343
|
+
if (key === this.pageTabCountKey) {
|
|
7344
|
+
this.pageTabCounts = counts
|
|
7345
|
+
}
|
|
7346
|
+
},
|
|
7347
|
+
|
|
7095
7348
|
/**
|
|
7096
7349
|
* Read the counts of the `filterStrip` pills in one grouped request,
|
|
7097
7350
|
* narrowed by the page filter and the active quick filter. A failed
|
|
@@ -8525,6 +8778,50 @@ export default {
|
|
|
8525
8778
|
this.$emit('quick-edit-save', { id, patch })
|
|
8526
8779
|
},
|
|
8527
8780
|
|
|
8781
|
+
/**
|
|
8782
|
+
* The buttons an `actions` column shows on a row: the row actions its
|
|
8783
|
+
* `widgetProps.actions` names (by id, else by label), in that order,
|
|
8784
|
+
* that are visible for the row.
|
|
8785
|
+
*
|
|
8786
|
+
* @param {object} col The column.
|
|
8787
|
+
* @param {object} row The row.
|
|
8788
|
+
* @return {Array<{key: string, label: string, variant: string, disabled: boolean}>}
|
|
8789
|
+
* @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons
|
|
8790
|
+
*/
|
|
8791
|
+
actionColumnItems(col, row) {
|
|
8792
|
+
const wanted = Array.isArray(col.widgetProps && col.widgetProps.actions) ? col.widgetProps.actions : []
|
|
8793
|
+
const available = this.rowActionsFor(row)
|
|
8794
|
+
return wanted
|
|
8795
|
+
.map((ref) => available.find((action) => action.id === ref) || available.find((action) => action.label === ref))
|
|
8796
|
+
.filter((action) => action && isRowActionVisible(action, row))
|
|
8797
|
+
.map((action, index) => ({
|
|
8798
|
+
key: action.id || action.label,
|
|
8799
|
+
label: this.cnTranslate(action.label),
|
|
8800
|
+
variant: action.variant || (index === 0 ? 'secondary' : 'tertiary'),
|
|
8801
|
+
disabled: typeof action.disabled === 'function' ? !!action.disabled(row) : action.disabled === true,
|
|
8802
|
+
}))
|
|
8803
|
+
},
|
|
8804
|
+
|
|
8805
|
+
/**
|
|
8806
|
+
* Run one `actions` column button: its handler, then the same `action`
|
|
8807
|
+
* event a row menu entry emits.
|
|
8808
|
+
*
|
|
8809
|
+
* @param {string} key The action's id, else its label.
|
|
8810
|
+
* @param {object} row The row.
|
|
8811
|
+
* @return {void}
|
|
8812
|
+
* @spec openspec/changes/table-actions-column/specs/index-page/spec.md#requirement-a-column-can-show-row-actions-as-buttons
|
|
8813
|
+
*/
|
|
8814
|
+
runColumnAction(key, row) {
|
|
8815
|
+
const action = this.rowActionsFor(row).find((entry) => (entry.id || entry.label) === key)
|
|
8816
|
+
if (!action) {
|
|
8817
|
+
return
|
|
8818
|
+
}
|
|
8819
|
+
if (typeof action.handler === 'function') {
|
|
8820
|
+
action.handler(row)
|
|
8821
|
+
}
|
|
8822
|
+
this.onRowAction(rowActionPayload(action, row))
|
|
8823
|
+
},
|
|
8824
|
+
|
|
8528
8825
|
/**
|
|
8529
8826
|
* Take the server's version of a row after a conflict. Nothing of this
|
|
8530
8827
|
* person's edit is written, and the row in the list is refreshed to
|
|
@@ -9377,6 +9674,9 @@ export default {
|
|
|
9377
9674
|
* @return {boolean} True for the current page.
|
|
9378
9675
|
*/
|
|
9379
9676
|
isActivePageTab(tab) {
|
|
9677
|
+
if (tab.to && typeof tab.to === 'object') {
|
|
9678
|
+
return !!this.$route && tab.to.name !== undefined && this.$route.name === tab.to.name
|
|
9679
|
+
}
|
|
9380
9680
|
const path = this.$route && this.$route.path
|
|
9381
9681
|
return !!path && String(path).replace(/\/+$/, '') === String(tab.to).replace(/\/+$/, '')
|
|
9382
9682
|
},
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
-->
|
|
12
12
|
<template>
|
|
13
13
|
<component
|
|
14
|
-
:is="variant === 'stat-row' ? 'CnKpiGrid' : 'aside'"
|
|
14
|
+
:is="variant === 'stat-row' ? 'CnKpiGrid' : (variant === 'after-list' ? 'section' : 'aside')"
|
|
15
15
|
v-if="entries.length > 0"
|
|
16
16
|
:columns="variant === 'stat-row' ? 'auto' : undefined"
|
|
17
17
|
class="cn-index-page-widgets"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<CnWidgetWrapper
|
|
26
26
|
:widgetId="entry.id"
|
|
27
27
|
:title="entry.title"
|
|
28
|
-
:showTitle="variant
|
|
28
|
+
:showTitle="variant !== 'stat-row' && entry.title !== ''"
|
|
29
29
|
:showActions="false"
|
|
30
30
|
:showRefresh="false"
|
|
31
31
|
:headerLink="entry.headerLink"
|
|
@@ -77,11 +77,11 @@ export default {
|
|
|
77
77
|
default: () => [],
|
|
78
78
|
},
|
|
79
79
|
|
|
80
|
-
/** Where the widgets sit: `stat-row` above the list, `side-panel` beside it. */
|
|
80
|
+
/** Where the widgets sit: `stat-row` above the list, `side-panel` beside it, `after-list` under it. */
|
|
81
81
|
variant: {
|
|
82
82
|
type: String,
|
|
83
83
|
default: 'stat-row',
|
|
84
|
-
validator: (v) => ['stat-row', 'side-panel'].includes(v),
|
|
84
|
+
validator: (v) => ['stat-row', 'side-panel', 'after-list'].includes(v),
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
87
|
|
|
@@ -172,6 +172,15 @@ export default {
|
|
|
172
172
|
min-width: 0;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/* The cards after the list, stacked full width (PqSocial). */
|
|
176
|
+
.cn-index-page-widgets--after-list {
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: var(--cn-board-section-gap, 16px);
|
|
180
|
+
margin-top: var(--cn-board-section-gap, 16px);
|
|
181
|
+
min-width: 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
175
184
|
/* The side panel column (DqTeamwachtrij: 300px, 16px between cards). Its
|
|
176
185
|
place beside the toolbar and the list is set by CnIndexPage
|
|
177
186
|
(`cn-index-page--with-side-panel`, index-page.css). */
|
|
@@ -75,7 +75,7 @@ export function useNamedSource(props, options = {}) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const source = resolveIndexSource(props.entitySource)
|
|
78
|
+
const source = resolveIndexSource(props.entitySource, props.sourceConfig)
|
|
79
79
|
|
|
80
80
|
// An unknown source name resolves to null and was already warned about.
|
|
81
81
|
// The page then behaves as an empty index rather than throwing.
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
:preferenceApp="preferenceApp"
|
|
22
22
|
:preferenceKey="preferenceKey"
|
|
23
23
|
:bulkActions="bulkActions"
|
|
24
|
+
:selectionLabel="selectionLabel"
|
|
25
|
+
:selectionLabelOne="selectionLabelOne"
|
|
26
|
+
:showClearSelection="showClearSelection"
|
|
27
|
+
:sideCards="sideCards"
|
|
24
28
|
:groupBy="groupBy"
|
|
25
29
|
:facets="facets"
|
|
26
30
|
@changed="onBrowserChanged" />
|
|
@@ -394,6 +398,19 @@ export default {
|
|
|
394
398
|
* @type {Array<{id: string, label: string, icon?: string, type?: string, target?: string, props?: object}>}
|
|
395
399
|
*/
|
|
396
400
|
bulkActions: { type: Array, default: () => [] },
|
|
401
|
+
/** Forwarded to CnFilesBrowser: the bulk bar's count, a template with `{count}`. */
|
|
402
|
+
selectionLabel: { type: String, default: '' },
|
|
403
|
+
/** Forwarded to CnFilesBrowser: the bulk bar's count for one file. */
|
|
404
|
+
selectionLabelOne: { type: String, default: '' },
|
|
405
|
+
/** Forwarded to CnFilesBrowser: whether the bulk bar ends in "Clear selection". */
|
|
406
|
+
showClearSelection: { type: Boolean, default: true },
|
|
407
|
+
/**
|
|
408
|
+
* Forwarded to CnFilesBrowser: cards beside the list about the last
|
|
409
|
+
* selected file.
|
|
410
|
+
*
|
|
411
|
+
* @type {Array<{id: string, title: string, fields?: Array<object>, actions?: Array<object>}>}
|
|
412
|
+
*/
|
|
413
|
+
sideCards: { type: Array, default: () => [] },
|
|
397
414
|
/** Forwarded to CnFilesBrowser: the key of a declared column to group the files on. */
|
|
398
415
|
groupBy: { type: String, default: '' },
|
|
399
416
|
/**
|