@conduction/nextcloud-vue 2.102.0 → 2.104.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 +23 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +94 -72
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +18 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -3
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +73 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +99 -64
- 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 +46 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +37 -2
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -6
- 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/CnDataTable/CnDataTable.vue2.js +39 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +14 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +40 -2
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +113 -20
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +387 -359
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +173 -3
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +107 -87
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +7 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +10 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue.js +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +57 -5
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +37 -4
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +97 -3
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +46 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +9 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js.map +1 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js +165 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js +85 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -0
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +54 -6
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +3 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +8 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +18 -2
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +7 -7
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +4 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +4 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/mixins/pageViews.js +8 -1
- package/dist/esm/mixins/pageViews.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +586 -416
- package/dist/esm/utils/nextcloudDirectory.js +18 -1
- package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
- package/dist/esm/utils/sortOrder.js +61 -0
- package/dist/esm/utils/sortOrder.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/esm/utils/visibleWhen.js +13 -2
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +12270 -10936
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +586 -416
- package/l10n/en.json +4 -0
- package/l10n/nl.json +4 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +1 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +39 -2
- package/src/components/CnAppRoot/CnAppRoot.md +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +21 -4
- package/src/components/CnConversationThread/CnConversationThread.md +17 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +113 -8
- package/src/components/CnConversationThread/CnConversationWidget.vue +60 -1
- package/src/components/CnDashboardPage/CnDashboardPage.md +4 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +29 -6
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +49 -0
- package/src/components/CnDetailPage/CnDetailPage.md +2 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +40 -2
- package/src/components/CnFormDialog/CnFormDialog.md +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +127 -0
- package/src/components/CnIndexPage/CnIndexPage.md +14 -2
- package/src/components/CnIndexPage/CnIndexPage.vue +192 -11
- package/src/components/CnIndexPage/useSelfFetchList.js +6 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +10 -1
- package/src/components/CnObjectRow/CnObjectRow.vue +95 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +134 -2
- package/src/components/CnSearchSelect/CnSearchSelect.vue +283 -0
- package/src/components/CnSearchSelect/index.js +12 -0
- package/src/components/CnSetupWizard/CnSetupWizard.vue +56 -6
- package/src/components/CnTabsWidget/CnTabsWidget.vue +8 -1
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +7 -2
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +18 -2
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +7 -7
- package/src/css/dialog.css +5 -3
- package/src/css/index-page.css +19 -0
- package/src/css/look-board-index.css +48 -0
- package/src/index.js +1 -0
- package/src/mixins/pageViews.js +8 -1
- package/src/schemas/app-manifest-v2.schema.json +75 -6
- package/src/utils/nextcloudDirectory.js +17 -0
- package/src/utils/sortOrder.js +58 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +13 -2
|
@@ -191,8 +191,9 @@
|
|
|
191
191
|
:listIcon="listIcon"
|
|
192
192
|
:showSortSelect="showSortSelect"
|
|
193
193
|
:sortOptions="sortSelectOptions"
|
|
194
|
-
:sortValue="
|
|
195
|
-
:showSearch="inlineSearch || isBoardLook"
|
|
194
|
+
:sortValue="effectiveSortSelectValue"
|
|
195
|
+
:showSearch="search !== false && (inlineSearch || isBoardLook)"
|
|
196
|
+
:searchPlacement="searchPlacement"
|
|
196
197
|
:searchValue="effectiveSearchValue"
|
|
197
198
|
:searchPlaceholder="presented('searchPlaceholder') ? cnTranslate(presented('searchPlaceholder')) : presented('searchPlaceholder')"
|
|
198
199
|
:showCountWithSearch="showCountWithSearch"
|
|
@@ -209,7 +210,7 @@
|
|
|
209
210
|
:bulkActions="mergedBulkActions"
|
|
210
211
|
:documentationUrl="documentationUrl"
|
|
211
212
|
:documentationLabel="documentationLabel || undefined"
|
|
212
|
-
@sortChange="
|
|
213
|
+
@sortChange="onSortSelectChange"
|
|
213
214
|
@add="onAddClick"
|
|
214
215
|
@clearSelection="onSelect([])"
|
|
215
216
|
@removeFilter="onRemoveActiveFilter"
|
|
@@ -660,11 +661,12 @@
|
|
|
660
661
|
</slot>
|
|
661
662
|
|
|
662
663
|
<!-- Body -->
|
|
663
|
-
<div class="cn-index-page__body" :class="{ 'cn-index-page__body--with-folders':
|
|
664
|
+
<div class="cn-index-page__body" :class="{ 'cn-index-page__body--with-folders': showsFolderPane }">
|
|
664
665
|
<!-- Optional folder navigation pane (opt-in via the `folderSidebar`
|
|
665
666
|
config). Selecting a folder filters the list by the config's
|
|
666
|
-
`filterField`; "All" clears it.
|
|
667
|
-
|
|
667
|
+
`filterField`; "All" clears it. `pane: false` keeps the scope
|
|
668
|
+
from the deep link and draws no pane. -->
|
|
669
|
+
<div v-if="showsFolderPane" class="cn-index-page__folder-pane">
|
|
668
670
|
<CnFolderSidebar
|
|
669
671
|
:source="folderSidebarSource"
|
|
670
672
|
:folders="folderSidebarFolders"
|
|
@@ -674,8 +676,8 @@
|
|
|
674
676
|
:partial="folderSidebarPartial"
|
|
675
677
|
:filesPath="folderSidebar.filesPath || '/'"
|
|
676
678
|
:selectedId="selectedFolderId"
|
|
677
|
-
:allLabel="folderSidebar.allLabel
|
|
678
|
-
:title="folderSidebar.title
|
|
679
|
+
:allLabel="folderSidebar.allLabel ? cnTranslate(folderSidebar.allLabel) : undefined"
|
|
680
|
+
:title="folderSidebar.title ? cnTranslate(folderSidebar.title) : ''"
|
|
679
681
|
:idField="folderPassthroughIdField"
|
|
680
682
|
:nameField="folderPassthroughNameField"
|
|
681
683
|
:allowCreate="Boolean(folderSidebar.allowCreate)"
|
|
@@ -700,6 +702,15 @@
|
|
|
700
702
|
<!-- @slot before-collection Content rendered above the collection in EVERY view mode (table, cards, list, map) — e.g. a folder/group strip that must stay visually separate from the objects instead of masquerading as rows or cards. -->
|
|
701
703
|
<slot name="before-collection" />
|
|
702
704
|
|
|
705
|
+
<!-- The selected folder as a heading above the list (a group on
|
|
706
|
+
DqAfdelingen: its name, then its parent and member count). -->
|
|
707
|
+
<div v-if="scopeHeading" class="cn-index-page__scope-heading" data-testid="cn-index-page-scope-heading">
|
|
708
|
+
<h2 class="cn-index-page__scope-title">
|
|
709
|
+
{{ scopeHeading.name }}
|
|
710
|
+
</h2>
|
|
711
|
+
<span v-if="scopeHeading.subtitle" class="cn-index-page__scope-subtitle" data-testid="cn-index-page-scope-subtitle">{{ scopeHeading.subtitle }}</span>
|
|
712
|
+
</div>
|
|
713
|
+
|
|
703
714
|
<!-- Loading state — initial fetch only; a background refresh keeps the table visible -->
|
|
704
715
|
<div v-if="showInitialLoader" class="cn-index-page__loading">
|
|
705
716
|
<!-- name gives NcLoadingIcon a non-empty aria-label (WCAG role-img-alt); empty name ships an unlabeled role="img" -->
|
|
@@ -1299,6 +1310,7 @@ import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
|
|
|
1299
1310
|
import { buildRouteQueryFromViewState, buildViewCreatePayload, extractViewState, extractViewStateFromRouteQuery, normalizeSharedWith, savedViewScope, viewMatchesScope } from '../../utils/savedViewHelpers.js'
|
|
1300
1311
|
import { columnsFromSchema, fieldsFromSchema } from '../../utils/schema.js'
|
|
1301
1312
|
import { resolveScopeLayout, resolveScopePresentation } from '../../utils/scopeListLayout.js'
|
|
1313
|
+
import { orderToSortKeys, sameSortKeys } from '../../utils/sortOrder.js'
|
|
1302
1314
|
import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js'
|
|
1303
1315
|
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
1304
1316
|
import { CnActionsBar } from '../CnActionsBar/index.js'
|
|
@@ -1968,7 +1980,10 @@ export default {
|
|
|
1968
1980
|
* case" (manifest `config.headerButtons`). Each is `{ label?, action,
|
|
1969
1981
|
* variant?, icon?, format?, id? }`: `action` is `add` (the Add flow;
|
|
1970
1982
|
* its label defaults to the Add label), `export` (the export leaf in
|
|
1971
|
-
* `format`, `csv` by default, else the export dialog
|
|
1983
|
+
* `format`, `csv` by default, else the export dialog; with `run` the
|
|
1984
|
+
* `headerActions` entry of that id; on an `entitySource` page without
|
|
1985
|
+
* `run` and without a `@mass-export` listener it is not drawn),
|
|
1986
|
+
* `import`,
|
|
1972
1987
|
* `refresh`, `save-view` (the save-current-view dialog: the board's
|
|
1973
1988
|
* "Zoekopdracht opslaan", label "Save view" by default), or the id of a
|
|
1974
1989
|
* `headerActions` entry; `variant` is
|
|
@@ -2148,6 +2163,20 @@ export default {
|
|
|
2148
2163
|
validator: (v) => ['', 'toolbar'].includes(v),
|
|
2149
2164
|
},
|
|
2150
2165
|
|
|
2166
|
+
/**
|
|
2167
|
+
* Where the board look's search field sits (manifest
|
|
2168
|
+
* `config.searchPlacement`): `''` (the default) opens row 2 with it,
|
|
2169
|
+
* `'toolbar'` puts it on row 1 before the Filter button and the view
|
|
2170
|
+
* switch (DqTeamwachtrij).
|
|
2171
|
+
*
|
|
2172
|
+
* @type {''|'toolbar'}
|
|
2173
|
+
*/
|
|
2174
|
+
searchPlacement: {
|
|
2175
|
+
type: String,
|
|
2176
|
+
default: '',
|
|
2177
|
+
validator: (v) => ['', 'toolbar'].includes(v),
|
|
2178
|
+
},
|
|
2179
|
+
|
|
2151
2180
|
/**
|
|
2152
2181
|
* How the quick filters render: `'chips'` (pill strip, default) or
|
|
2153
2182
|
* `'dropdown'` (a single `NcSelect`). Sourced from the manifest as
|
|
@@ -3094,6 +3123,16 @@ export default {
|
|
|
3094
3123
|
default: false,
|
|
3095
3124
|
},
|
|
3096
3125
|
|
|
3126
|
+
/**
|
|
3127
|
+
* `false` draws no inline search in the actions bar, also in the board
|
|
3128
|
+
* look, which otherwise draws one on every index page. Fed from the
|
|
3129
|
+
* manifest as `pages[].config.search`.
|
|
3130
|
+
*/
|
|
3131
|
+
search: {
|
|
3132
|
+
type: Boolean,
|
|
3133
|
+
default: true,
|
|
3134
|
+
},
|
|
3135
|
+
|
|
3097
3136
|
/**
|
|
3098
3137
|
* Keep the "Showing X of Y" counter visible beside the inline search field,
|
|
3099
3138
|
* after any `#after-search` controls (only relevant with `inlineSearch`,
|
|
@@ -3161,12 +3200,28 @@ export default {
|
|
|
3161
3200
|
default: () => [],
|
|
3162
3201
|
},
|
|
3163
3202
|
|
|
3164
|
-
/**
|
|
3203
|
+
/**
|
|
3204
|
+
* Selected value of the standalone sort dropdown. Empty lets a
|
|
3205
|
+
* self-fetch page show the option matching its current order.
|
|
3206
|
+
*/
|
|
3165
3207
|
sortSelectValue: {
|
|
3166
3208
|
type: String,
|
|
3167
3209
|
default: '',
|
|
3168
3210
|
},
|
|
3169
3211
|
|
|
3212
|
+
/**
|
|
3213
|
+
* The order a self-fetch page opens on when neither the address nor
|
|
3214
|
+
* `sortKeys`/`sortKey` gives one (manifest `config.defaultOrder`):
|
|
3215
|
+
* `"-createdAt"`, `"priority:desc,title"`, an `_order` map or a
|
|
3216
|
+
* `[{ key, order }]` list.
|
|
3217
|
+
*
|
|
3218
|
+
* @type {string|object|Array}
|
|
3219
|
+
*/
|
|
3220
|
+
defaultOrder: {
|
|
3221
|
+
type: [String, Object, Array],
|
|
3222
|
+
default: '',
|
|
3223
|
+
},
|
|
3224
|
+
|
|
3170
3225
|
/** MDI icon name for the list view-toggle option (manifest `config.listIcon`) */
|
|
3171
3226
|
listIcon: {
|
|
3172
3227
|
type: String,
|
|
@@ -3200,6 +3255,9 @@ export default {
|
|
|
3200
3255
|
* defaults to `field`).
|
|
3201
3256
|
* - `folders` — explicit folder list for `source:'custom'`.
|
|
3202
3257
|
* - `allLabel` / `title` / `allowCreate` — passed to CnFolderSidebar.
|
|
3258
|
+
* - `pane` — `false` draws no pane: the scope still comes from the
|
|
3259
|
+
* deep-link filter (`?caseType=<id>`) and still applies its look, as
|
|
3260
|
+
* a list narrowed to one case type does without a folder column.
|
|
3203
3261
|
*
|
|
3204
3262
|
* A folder entry is also a SCOPE: it may carry `columns`, `defaultSort`
|
|
3205
3263
|
* and `searchFields`, and while it is selected the list is shown that
|
|
@@ -4833,6 +4891,17 @@ export default {
|
|
|
4833
4891
|
return { shown, total: Math.max(shown, Number(this.effectivePagination?.total || 0)) }
|
|
4834
4892
|
},
|
|
4835
4893
|
|
|
4894
|
+
/**
|
|
4895
|
+
* Whether the folder pane is drawn: a `folderSidebar` without
|
|
4896
|
+
* `pane: false`.
|
|
4897
|
+
*
|
|
4898
|
+
* @spec openspec/changes/folder-sidebar-pane-off/specs/index-page/spec.md#requirement-a-folder-scope-without-a-pane
|
|
4899
|
+
* @return {boolean}
|
|
4900
|
+
*/
|
|
4901
|
+
showsFolderPane() {
|
|
4902
|
+
return Boolean(this.folderSidebar) && this.folderSidebar.pane !== false
|
|
4903
|
+
},
|
|
4904
|
+
|
|
4836
4905
|
/**
|
|
4837
4906
|
* The CnFolderSidebar source. A `register` source is fetched by this
|
|
4838
4907
|
* component and handed to CnFolderSidebar as a `custom` folder list.
|
|
@@ -4881,6 +4950,60 @@ export default {
|
|
|
4881
4950
|
return match || null
|
|
4882
4951
|
},
|
|
4883
4952
|
|
|
4953
|
+
/**
|
|
4954
|
+
* The heading above the list for the selected folder: on a `groups`
|
|
4955
|
+
* pane (or with `folderSidebar.heading: true`), its name and, under
|
|
4956
|
+
* it, its parent folder and its count. The count reads
|
|
4957
|
+
* `folderSidebar.countText` (`{count}`, through the app's translate),
|
|
4958
|
+
* else "{count} members" on a groups pane.
|
|
4959
|
+
*
|
|
4960
|
+
* @return {{name: string, subtitle: string}|null} The heading, or null on All.
|
|
4961
|
+
*/
|
|
4962
|
+
scopeHeading() {
|
|
4963
|
+
const cfg = this.folderSidebar
|
|
4964
|
+
const scope = this.activeScope
|
|
4965
|
+
if (!cfg || !scope || !(cfg.source === 'groups' || cfg.heading === true)) {
|
|
4966
|
+
return null
|
|
4967
|
+
}
|
|
4968
|
+
const idField = this.folderPassthroughIdField
|
|
4969
|
+
const name = String(this.getByPath(scope, this.folderPassthroughNameField) ?? this.selectedFolderId)
|
|
4970
|
+
const parentId = scope.parentId ?? scope.parent ?? null
|
|
4971
|
+
const parent = parentId === null || parentId === undefined
|
|
4972
|
+
? null
|
|
4973
|
+
: this.folderSidebarFolders.find((f) => f && String(this.getByPath(f, idField)) === String(parentId))
|
|
4974
|
+
const parts = []
|
|
4975
|
+
if (parent) {
|
|
4976
|
+
parts.push(String(this.getByPath(parent, this.folderPassthroughNameField) ?? parentId))
|
|
4977
|
+
}
|
|
4978
|
+
if (typeof scope.count === 'number') {
|
|
4979
|
+
if (cfg.countText) {
|
|
4980
|
+
parts.push(this.cnTranslate(cfg.countText).replace('{count}', String(scope.count)))
|
|
4981
|
+
} else if (cfg.source === 'groups') {
|
|
4982
|
+
parts.push(n('nextcloud-vue', '{count} member', '{count} members', scope.count, { count: scope.count }))
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
return { name, subtitle: parts.join(' · ') }
|
|
4986
|
+
},
|
|
4987
|
+
|
|
4988
|
+
/**
|
|
4989
|
+
* The sort select's shown value: `sortSelectValue` when the host
|
|
4990
|
+
* controls it, else on a self-fetch page the option whose order is the
|
|
4991
|
+
* list's current order (a header click that matches one shows it).
|
|
4992
|
+
*
|
|
4993
|
+
* @return {string} The option value, or '' when none matches.
|
|
4994
|
+
*/
|
|
4995
|
+
effectiveSortSelectValue() {
|
|
4996
|
+
if (this.sortSelectValue) {
|
|
4997
|
+
return this.sortSelectValue
|
|
4998
|
+
}
|
|
4999
|
+
if (!this.isSelfFetchMode || !this.list || !this.list.sortKeys) {
|
|
5000
|
+
return ''
|
|
5001
|
+
}
|
|
5002
|
+
const current = this.list.sortKeys.value || []
|
|
5003
|
+
const match = (this.sortSelectOptions || []).find((o) => o && sameSortKeys(this.sortSelectKeys(o), current))
|
|
5004
|
+
return match ? match.value : ''
|
|
5005
|
+
},
|
|
5006
|
+
|
|
4884
5007
|
/**
|
|
4885
5008
|
* The `x-index` block the active scope's own schema row carries, for a
|
|
4886
5009
|
* folder list derived from a register. Empty for every other source:
|
|
@@ -6654,6 +6777,7 @@ export default {
|
|
|
6654
6777
|
variant: button.variant === 'primary' ? 'primary' : 'secondary',
|
|
6655
6778
|
icon,
|
|
6656
6779
|
format: button.format === 'excel' ? 'excel' : 'csv',
|
|
6780
|
+
run: typeof button.run === 'string' ? button.run : '',
|
|
6657
6781
|
items: Array.isArray(button.items)
|
|
6658
6782
|
? button.items.filter((item) => (typeof item === 'string' && item !== '')
|
|
6659
6783
|
|| (item && typeof item === 'object' && typeof item.heading === 'string' && item.heading !== ''))
|
|
@@ -6661,6 +6785,10 @@ export default {
|
|
|
6661
6785
|
}
|
|
6662
6786
|
})
|
|
6663
6787
|
.filter((button) => button.label !== '')
|
|
6788
|
+
// An Export with nothing to export through is not drawn: a
|
|
6789
|
+
// named or endpoint source with no `run` and no `@mass-export`
|
|
6790
|
+
// listener.
|
|
6791
|
+
.filter((button) => button.action !== 'export' || this.headerExportAvailable(button))
|
|
6664
6792
|
// The Actions menu lists the page's header actions (or the
|
|
6665
6793
|
// entries its `items` name), so a menu with nothing to list
|
|
6666
6794
|
// is not drawn.
|
|
@@ -8449,7 +8577,9 @@ export default {
|
|
|
8449
8577
|
if (button.action === 'add') {
|
|
8450
8578
|
this.onAddClick()
|
|
8451
8579
|
} else if (button.action === 'export') {
|
|
8452
|
-
if (
|
|
8580
|
+
if (button.run) {
|
|
8581
|
+
this.onHeaderAction({ action: button.run, id: button.run })
|
|
8582
|
+
} else if (this.register && this.exportSchemaSlug) {
|
|
8453
8583
|
this.onExportClick(button.format)
|
|
8454
8584
|
} else {
|
|
8455
8585
|
this.showExportDialog = true
|
|
@@ -8465,6 +8595,25 @@ export default {
|
|
|
8465
8595
|
}
|
|
8466
8596
|
},
|
|
8467
8597
|
|
|
8598
|
+
/**
|
|
8599
|
+
* Whether a header Export has something to export through. Any page
|
|
8600
|
+
* listing OpenRegister objects (self-fetch or the consumer's
|
|
8601
|
+
* `objects`) keeps it; a named or endpoint source (`entitySource`)
|
|
8602
|
+
* needs a `run` naming one of the page's header actions, or a
|
|
8603
|
+
* `@mass-export` listener.
|
|
8604
|
+
*
|
|
8605
|
+
* @spec openspec/changes/index-header-export-source/specs/index-page/spec.md#requirement-a-header-export-is-drawn-only-when-it-can-export
|
|
8606
|
+
* @param {{run?: string}} button The resolved header button.
|
|
8607
|
+
* @return {boolean}
|
|
8608
|
+
*/
|
|
8609
|
+
headerExportAvailable(button) {
|
|
8610
|
+
if (!this.isNamedSource || (button && button.run)) {
|
|
8611
|
+
return true
|
|
8612
|
+
}
|
|
8613
|
+
const props = this.$ && this.$.vnode && this.$.vnode.props
|
|
8614
|
+
return Boolean(props && typeof props.onMassExport === 'function')
|
|
8615
|
+
},
|
|
8616
|
+
|
|
8468
8617
|
/**
|
|
8469
8618
|
* Click dispatch from CnActionsBar's `@header-action`. Looks
|
|
8470
8619
|
* up the resolved entry by id, invokes its handler if any,
|
|
@@ -8585,6 +8734,38 @@ export default {
|
|
|
8585
8734
|
this.$emit('quick-filter-change', this.selectedQuickFilterIndices)
|
|
8586
8735
|
},
|
|
8587
8736
|
|
|
8737
|
+
/**
|
|
8738
|
+
* The sort keys a sort-select option stands for: its `order` (any
|
|
8739
|
+
* `orderToSortKeys` form), else its `value` read as an order.
|
|
8740
|
+
*
|
|
8741
|
+
* @param {object} option A `sortSelectOptions` entry.
|
|
8742
|
+
* @return {Array<{key: string, order: string}>} The sort keys.
|
|
8743
|
+
*/
|
|
8744
|
+
sortSelectKeys(option) {
|
|
8745
|
+
if (!option) {
|
|
8746
|
+
return []
|
|
8747
|
+
}
|
|
8748
|
+
return orderToSortKeys(option.order !== undefined ? option.order : option.value)
|
|
8749
|
+
},
|
|
8750
|
+
|
|
8751
|
+
/**
|
|
8752
|
+
* A choice in the sort select. On a self-fetch page it orders the list
|
|
8753
|
+
* as a header click does (`_order`, kept in the address); every page
|
|
8754
|
+
* also emits `sort-change` with the value.
|
|
8755
|
+
*
|
|
8756
|
+
* @param {string} value The chosen option's value.
|
|
8757
|
+
* @return {void}
|
|
8758
|
+
*/
|
|
8759
|
+
onSortSelectChange(value) {
|
|
8760
|
+
const option = (this.sortSelectOptions || []).find((o) => o && o.value === value)
|
|
8761
|
+
const keys = this.sortSelectKeys(option)
|
|
8762
|
+
if (option && keys.length > 0 && this.isSelfFetchMode && typeof this.list.onSort === 'function') {
|
|
8763
|
+
this.list.onSort({ key: keys[0].key, order: keys[0].order, keys })
|
|
8764
|
+
this.persistViewStateToRoute(this.currentViewState())
|
|
8765
|
+
}
|
|
8766
|
+
this.$emit('sort-change', value)
|
|
8767
|
+
},
|
|
8768
|
+
|
|
8588
8769
|
/**
|
|
8589
8770
|
* @param {{key: string, order: string, keys?: Array<{key: string, order: string}>}} payload Sort change from CnDataTable.
|
|
8590
8771
|
* @return {void}
|
|
@@ -7,6 +7,7 @@ import { useObjectStore } from '../../store/index.js'
|
|
|
7
7
|
// without pulling in this composable's index-only sidebar/subscription wiring.
|
|
8
8
|
import { withPersonalLenses } from '../../utils/personalLenses.js'
|
|
9
9
|
import { parseSortKeysFromQuery, resolveFilterMap, resolveQueryFilters } from '../../utils/routeFilters.js'
|
|
10
|
+
import { orderToSortKeys } from '../../utils/sortOrder.js'
|
|
10
11
|
|
|
11
12
|
function resolveInitialQuickFilterIndex(quickFilters) {
|
|
12
13
|
const tabs = Array.isArray(quickFilters) ? quickFilters : null
|
|
@@ -219,9 +220,12 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
|
|
|
219
220
|
// legacy single-key `sortKey`/`sortOrder` props.
|
|
220
221
|
const initialRoute = instance && instance.proxy && instance.proxy.$route
|
|
221
222
|
// The page's own order, which a lens without a `sort` goes back to.
|
|
223
|
+
// `defaultOrder` (manifest `config.defaultOrder`) is the order of last
|
|
224
|
+
// resort, after `sortKeys` and `sortKey`.
|
|
225
|
+
const defaultOrderKeys = orderToSortKeys(props.defaultOrder)
|
|
222
226
|
const pageSortKeys = (Array.isArray(props.sortKeys) && props.sortKeys.length > 0)
|
|
223
227
|
? props.sortKeys
|
|
224
|
-
: (props.sortKey ? [{ key: props.sortKey, order: props.sortOrder || 'asc' }] :
|
|
228
|
+
: (props.sortKey ? [{ key: props.sortKey, order: props.sortOrder || 'asc' }] : defaultOrderKeys)
|
|
225
229
|
// The lens that is active on mount may set its own order.
|
|
226
230
|
const initialTabs = withPersonalLenses(props.quickFilters, props.personalLenses)
|
|
227
231
|
const initialTabSort = (!isMultiQuickFilter && Array.isArray(initialTabs))
|
|
@@ -230,6 +234,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
|
|
|
230
234
|
const initialSortKeys = parseSortKeysFromQuery(initialRoute)
|
|
231
235
|
|| initialTabSort
|
|
232
236
|
|| (Array.isArray(props.sortKeys) && props.sortKeys.length > 0 ? props.sortKeys : undefined)
|
|
237
|
+
|| (!props.sortKey && defaultOrderKeys.length > 0 ? defaultOrderKeys : undefined)
|
|
233
238
|
// Same deep-link restore for facet filters and search, so opening a
|
|
234
239
|
// shared/bookmarked URL reproduces the same view (CnIndexPage persists
|
|
235
240
|
// both back to the route on change).
|
|
@@ -1385,7 +1385,8 @@ export default {
|
|
|
1385
1385
|
* is the budget). Measures the cell's remaining height below the table
|
|
1386
1386
|
* top, reserves room for the "View all" footer, and derives the row
|
|
1387
1387
|
* budget from the first rendered row's height. No-ops (fitRows null =
|
|
1388
|
-
* render all fetched rows) outside a fixed-height cell
|
|
1388
|
+
* render all fetched rows) outside a fixed-height cell and inside a
|
|
1389
|
+
* tab panel, unless the content sets `fit: true`.
|
|
1389
1390
|
*
|
|
1390
1391
|
* @return {void}
|
|
1391
1392
|
*/
|
|
@@ -1407,6 +1408,14 @@ export default {
|
|
|
1407
1408
|
this.fitRows = null
|
|
1408
1409
|
return
|
|
1409
1410
|
}
|
|
1411
|
+
// A list inside a tab panel does not own the cell, as on a
|
|
1412
|
+
// dashboard: it renders every fetched row unless the content asks
|
|
1413
|
+
// for the fit with `fit: true`.
|
|
1414
|
+
const panel = this.$el.closest('[role="tabpanel"]')
|
|
1415
|
+
if (panel && cell.contains(panel) && !(this.content && this.content.fit === true)) {
|
|
1416
|
+
this.fitRows = null
|
|
1417
|
+
return
|
|
1418
|
+
}
|
|
1410
1419
|
const table = this.$el.querySelector('.cn-object-list-widget__table table')
|
|
1411
1420
|
if (!table) {
|
|
1412
1421
|
return
|
|
@@ -15,6 +15,22 @@
|
|
|
15
15
|
@update:modelValue="$emit('select', object)" />
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
+
<!-- `config.leading`: one cell before the title, as a table cell renders
|
|
19
|
+
it (`{ key, widget?, widgetProps?, formatter? }`), e.g. the age
|
|
20
|
+
"6 dagen" in the team queue. -->
|
|
21
|
+
<span
|
|
22
|
+
v-if="leadingCell"
|
|
23
|
+
class="cn-object-row__lead"
|
|
24
|
+
:style="leadingCell.width ? { width: leadingCell.width } : null"
|
|
25
|
+
data-testid="cn-object-row-lead">
|
|
26
|
+
<CnCellRenderer
|
|
27
|
+
:value="cellValue(leadingCell)"
|
|
28
|
+
:formatter="leadingCell.formatter || null"
|
|
29
|
+
:widget="leadingCell.widget || null"
|
|
30
|
+
:widgetProps="leadingCell.widgetProps || undefined"
|
|
31
|
+
:row="object" />
|
|
32
|
+
</span>
|
|
33
|
+
|
|
18
34
|
<!-- Leading icon / image — omitted entirely when nothing is configured
|
|
19
35
|
(no image, no icon name, no slot). -->
|
|
20
36
|
<span v-if="hasLeadingIcon" class="cn-object-row__icon">
|
|
@@ -52,6 +68,22 @@
|
|
|
52
68
|
</slot>
|
|
53
69
|
</span>
|
|
54
70
|
|
|
71
|
+
<!-- `config.trailing`: cells after the badges, each in its own column
|
|
72
|
+
(the case type badge, the due text). -->
|
|
73
|
+
<span
|
|
74
|
+
v-for="(cell, index) in trailingCells"
|
|
75
|
+
:key="cell.key + '-' + index"
|
|
76
|
+
class="cn-object-row__trail"
|
|
77
|
+
:style="cell.width ? { width: cell.width } : null"
|
|
78
|
+
data-testid="cn-object-row-trail">
|
|
79
|
+
<CnCellRenderer
|
|
80
|
+
:value="cellValue(cell)"
|
|
81
|
+
:formatter="cell.formatter || null"
|
|
82
|
+
:widget="cell.widget || null"
|
|
83
|
+
:widgetProps="cell.widgetProps || undefined"
|
|
84
|
+
:row="object" />
|
|
85
|
+
</span>
|
|
86
|
+
|
|
55
87
|
<!-- Trailing actions -->
|
|
56
88
|
<span v-if="$slots.actions"
|
|
57
89
|
class="cn-object-row__actions"
|
|
@@ -67,8 +99,11 @@
|
|
|
67
99
|
<script>
|
|
68
100
|
import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
|
|
69
101
|
import { useClickDragGuard } from '../../composables/useClickDragGuard.js'
|
|
102
|
+
import { joinTemplateParts } from '../../utils/fillTemplate.js'
|
|
103
|
+
import { readPath } from '../../utils/readPath.js'
|
|
70
104
|
import { resolveImageUrl } from '../../utils/resolveImageUrl.js'
|
|
71
105
|
import { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
|
|
106
|
+
import { CnCellRenderer } from '../CnCellRenderer/index.js'
|
|
72
107
|
import { CnIcon } from '../CnIcon/index.js'
|
|
73
108
|
import { CnStatusBadge } from '../CnStatusBadge/index.js'
|
|
74
109
|
|
|
@@ -98,6 +133,7 @@ export default {
|
|
|
98
133
|
|
|
99
134
|
components: {
|
|
100
135
|
NcCheckboxRadioSwitch,
|
|
136
|
+
CnCellRenderer,
|
|
101
137
|
CnIcon,
|
|
102
138
|
CnStatusBadge,
|
|
103
139
|
},
|
|
@@ -119,7 +155,15 @@ export default {
|
|
|
119
155
|
* Explicit field mapping. Every key is optional and overrides the
|
|
120
156
|
* schema-configuration default.
|
|
121
157
|
*
|
|
122
|
-
*
|
|
158
|
+
* `subtitleTemplate` fills `{field}` tokens from the row (empty parts and
|
|
159
|
+
* their separators drop). `leading` (one cell) and `trailing` (a cell or
|
|
160
|
+
* a list) draw cells as a table column does, `{ key, widget?,
|
|
161
|
+
* widgetProps?, formatter? }`: `{ key: "createdAt", widget: "age" }`
|
|
162
|
+
* reads "6 dagen", `{ key: "deadline", widget: "date", widgetProps: {
|
|
163
|
+
* due: {} } }` the due text. A cell's `width` (CSS) lines the column
|
|
164
|
+
* up across rows.
|
|
165
|
+
*
|
|
166
|
+
* @type {{ titleField?: string, subtitleField?: string, subtitleTemplate?: string, imageField?: string, iconField?: string, iconName?: string, badgeField?: string, badgeVariantField?: string, badgeVariant?: string, badgeColorMap?: object, leading?: object, trailing?: (object|Array<object>) }}
|
|
123
167
|
*/
|
|
124
168
|
config: {
|
|
125
169
|
type: Object,
|
|
@@ -168,6 +212,13 @@ export default {
|
|
|
168
212
|
},
|
|
169
213
|
|
|
170
214
|
subtitle() {
|
|
215
|
+
if (typeof this.config.subtitleTemplate === 'string' && this.config.subtitleTemplate !== '') {
|
|
216
|
+
const text = joinTemplateParts(this.config.subtitleTemplate, (key) => {
|
|
217
|
+
const value = readPath(this.object, key)
|
|
218
|
+
return value === null || value === undefined ? '' : String(value)
|
|
219
|
+
})
|
|
220
|
+
return text.trim() || null
|
|
221
|
+
}
|
|
171
222
|
const field = this.config.subtitleField || this.schemaConfig.objectDescriptionField
|
|
172
223
|
if (field && this.object[field] !== null && this.object[field] !== undefined && this.object[field] !== '') {
|
|
173
224
|
return String(this.object[field])
|
|
@@ -215,9 +266,42 @@ export default {
|
|
|
215
266
|
badgeColorMap() {
|
|
216
267
|
return this.config.badgeColorMap || null
|
|
217
268
|
},
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The `config.leading` cell, or null.
|
|
272
|
+
*
|
|
273
|
+
* @spec openspec/changes/index-row-list-cells/specs/object-row/spec.md#requirement-a-list-row-can-carry-leading-and-trailing-cells
|
|
274
|
+
* @return {object|null}
|
|
275
|
+
*/
|
|
276
|
+
leadingCell() {
|
|
277
|
+
const cell = this.config.leading
|
|
278
|
+
return cell && typeof cell === 'object' && typeof cell.key === 'string' && cell.key !== '' ? cell : null
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The `config.trailing` cells (one object or a list), those with a key.
|
|
283
|
+
*
|
|
284
|
+
* @spec openspec/changes/index-row-list-cells/specs/object-row/spec.md#requirement-a-list-row-can-carry-leading-and-trailing-cells
|
|
285
|
+
* @return {Array<object>}
|
|
286
|
+
*/
|
|
287
|
+
trailingCells() {
|
|
288
|
+
const cells = this.config.trailing
|
|
289
|
+
const list = Array.isArray(cells) ? cells : (cells && typeof cells === 'object' ? [cells] : [])
|
|
290
|
+
return list.filter((cell) => cell && typeof cell === 'object' && typeof cell.key === 'string' && cell.key !== '')
|
|
291
|
+
},
|
|
218
292
|
},
|
|
219
293
|
|
|
220
294
|
methods: {
|
|
295
|
+
/**
|
|
296
|
+
* A cell's value: the dot-path `key` read off the row.
|
|
297
|
+
*
|
|
298
|
+
* @param {{key: string}} cell The cell.
|
|
299
|
+
* @return {unknown} The value.
|
|
300
|
+
*/
|
|
301
|
+
cellValue(cell) {
|
|
302
|
+
return readPath(this.object, cell.key)
|
|
303
|
+
},
|
|
304
|
+
|
|
221
305
|
/**
|
|
222
306
|
* Row-body click: emits `select` when `selectable` (ignoring drags),
|
|
223
307
|
* otherwise emits `click` for navigation.
|
|
@@ -297,6 +381,16 @@ export default {
|
|
|
297
381
|
background-color: var(--color-primary-element-light);
|
|
298
382
|
}
|
|
299
383
|
|
|
384
|
+
.cn-object-row__lead,
|
|
385
|
+
.cn-object-row__trail {
|
|
386
|
+
flex: 0 0 auto;
|
|
387
|
+
min-width: 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.cn-object-row__lead {
|
|
391
|
+
font-weight: 600;
|
|
392
|
+
}
|
|
393
|
+
|
|
300
394
|
.cn-object-row__checkbox {
|
|
301
395
|
flex-shrink: 0;
|
|
302
396
|
}
|