@conduction/nextcloud-vue 2.101.0 → 2.103.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 +22 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.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 +49 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.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 +84 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +8 -6
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +26 -2
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.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/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.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/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
- 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 +9 -0
- 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 +5 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +5 -1
- 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 +593 -459
- package/dist/esm/utils/builtInFormatters.js +24 -4
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/rowClassRules.js +43 -0
- package/dist/esm/utils/rowClassRules.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 +32 -4
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +10093 -8858
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +593 -459
- package/l10n/en.json +5 -1
- package/l10n/nl.json +5 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +1 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
- package/src/components/CnChartWidget/CnChartWidget.md +3 -2
- package/src/components/CnChartWidget/CnChartWidget.vue +42 -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 +50 -7
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnFormDialog/CnFormDialog.md +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +127 -0
- package/src/components/CnIndexPage/CnIndexPage.md +15 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +92 -9
- package/src/components/CnIndexPage/useNamedSource.js +26 -2
- package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +10 -1
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
- 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/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +5 -3
- package/src/css/look-board-index.css +10 -0
- package/src/index.js +1 -0
- package/src/mixins/pageViews.js +8 -1
- package/src/schemas/app-manifest-v2.schema.json +107 -8
- package/src/utils/builtInFormatters.js +24 -4
- package/src/utils/rowClassRules.js +39 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +32 -4
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
<Pencil v-if="!isEditing" :size="20" />
|
|
112
112
|
<Check v-else :size="20" />
|
|
113
113
|
</template>
|
|
114
|
-
{{ isEditing ?
|
|
114
|
+
{{ isEditing ? shownDoneLabel : shownEditLabel }}
|
|
115
115
|
</NcButton>
|
|
116
116
|
<!-- With `userLayout`, edit mode offers a way back to the
|
|
117
117
|
manifest: the page's own grid and the chosen view return
|
|
@@ -1105,6 +1105,7 @@ const CHART_PROP_KEYS = [
|
|
|
1105
1105
|
'footnote',
|
|
1106
1106
|
'highlight',
|
|
1107
1107
|
'cellSource',
|
|
1108
|
+
'emptyCellLabel',
|
|
1108
1109
|
]
|
|
1109
1110
|
|
|
1110
1111
|
/**
|
|
@@ -1838,6 +1839,9 @@ export default {
|
|
|
1838
1839
|
* and renders exactly as it did before this existed, which is the
|
|
1839
1840
|
* property that makes shipping it safe on every dashboard at once.
|
|
1840
1841
|
*
|
|
1842
|
+
* Deprecated: layout belongs to Buildiq. Kept working for one release.
|
|
1843
|
+
*
|
|
1844
|
+
* @deprecated
|
|
1841
1845
|
* @type {boolean}
|
|
1842
1846
|
*/
|
|
1843
1847
|
userLayout: {
|
|
@@ -2215,9 +2219,25 @@ export default {
|
|
|
2215
2219
|
* @return {boolean}
|
|
2216
2220
|
*/
|
|
2217
2221
|
gridEditable() {
|
|
2222
|
+
return this.isEditing || this.editingBody
|
|
2223
|
+
},
|
|
2224
|
+
|
|
2225
|
+
/**
|
|
2226
|
+
* Whether Buildiq is editing the page (the injected `cnEditingBody`
|
|
2227
|
+
* ref, or a plain boolean). Buildiq is the layout editor: while it
|
|
2228
|
+
* edits, the grid shows and changes the manifest layout, also on a
|
|
2229
|
+
* page with `userLayout`.
|
|
2230
|
+
*
|
|
2231
|
+
* @return {boolean}
|
|
2232
|
+
*/
|
|
2233
|
+
editingBody() {
|
|
2218
2234
|
const e = this.cnEditingBody
|
|
2219
|
-
|
|
2220
|
-
|
|
2235
|
+
return Boolean(e && typeof e === 'object' && 'value' in e ? e.value : e)
|
|
2236
|
+
},
|
|
2237
|
+
|
|
2238
|
+
/** @return {boolean} Whether the grid shows this user's arrangement. */
|
|
2239
|
+
showsUserLayout() {
|
|
2240
|
+
return this.userLayout && !this.editingBody && Array.isArray(this.userLayoutItems)
|
|
2221
2241
|
},
|
|
2222
2242
|
|
|
2223
2243
|
/**
|
|
@@ -2278,9 +2298,7 @@ export default {
|
|
|
2278
2298
|
* @return {Array<object>} The layout.
|
|
2279
2299
|
*/
|
|
2280
2300
|
renderedLayout() {
|
|
2281
|
-
const base =
|
|
2282
|
-
? this.userLayoutItems
|
|
2283
|
-
: this.layout
|
|
2301
|
+
const base = this.showsUserLayout ? this.userLayoutItems : this.layout
|
|
2284
2302
|
// The KPI row is not part of the grid.
|
|
2285
2303
|
if (Array.isArray(this.kpiRow) && this.kpiRow.length > 0 && Array.isArray(base)) {
|
|
2286
2304
|
return base.filter((item) => !this.kpiRow.includes(item.widgetId))
|
|
@@ -2400,6 +2418,25 @@ export default {
|
|
|
2400
2418
|
return (this.pageFilters || []).filter((pf) => pf && pf.key && !inDescription.includes(pf))
|
|
2401
2419
|
},
|
|
2402
2420
|
|
|
2421
|
+
/**
|
|
2422
|
+
* The edit button's label through the app's translate: a manifest's
|
|
2423
|
+
* `editLabel` ("Edit layout", DqDashboard) reads in the user's language.
|
|
2424
|
+
*
|
|
2425
|
+
* @return {string}
|
|
2426
|
+
*/
|
|
2427
|
+
shownEditLabel() {
|
|
2428
|
+
return this.effectiveTranslate(this.editLabel)
|
|
2429
|
+
},
|
|
2430
|
+
|
|
2431
|
+
/**
|
|
2432
|
+
* The done button's label through the app's translate.
|
|
2433
|
+
*
|
|
2434
|
+
* @return {string}
|
|
2435
|
+
*/
|
|
2436
|
+
shownDoneLabel() {
|
|
2437
|
+
return this.effectiveTranslate(this.doneLabel)
|
|
2438
|
+
},
|
|
2439
|
+
|
|
2403
2440
|
/**
|
|
2404
2441
|
* The preset list passed to the picker. Falls back to the
|
|
2405
2442
|
* `DEFAULT_DATE_RANGE_PRESETS` constant when the consumer
|
|
@@ -3557,7 +3594,8 @@ export default {
|
|
|
3557
3594
|
// write below is for the admin editor, which edits the page for
|
|
3558
3595
|
// everybody; doing it for a user rearranging their own dashboard
|
|
3559
3596
|
// would rewrite the page for everyone who opens it after them.
|
|
3560
|
-
|
|
3597
|
+
// While Buildiq edits, the drag is the admin's and goes below.
|
|
3598
|
+
if (this.showsUserLayout) {
|
|
3561
3599
|
this.userLayoutDirty = true
|
|
3562
3600
|
this.userLayoutItems = this.userLayoutItems.map((item) => {
|
|
3563
3601
|
const u = (Array.isArray(updated) ? updated : []).find((candidate) => String(candidate.id) === String(item.id)
|
|
@@ -3566,6 +3604,11 @@ export default {
|
|
|
3566
3604
|
return (u ? { ...item, ...this.geometryFrom(u) } : item)
|
|
3567
3605
|
})
|
|
3568
3606
|
this.$emit('layout-change', this.userLayoutItems)
|
|
3607
|
+
// Outside an in-page edit session no Done will come to save
|
|
3608
|
+
// the arrangement, so it is saved now.
|
|
3609
|
+
if (!this.isEditing) {
|
|
3610
|
+
this.saveUserLayout()
|
|
3611
|
+
}
|
|
3569
3612
|
|
|
3570
3613
|
return
|
|
3571
3614
|
}
|
|
@@ -215,7 +215,7 @@ export default {
|
|
|
215
215
|
|
|
216
216
|
| Prop | Type | Default | Description |
|
|
217
217
|
|------|------|---------|-------------|
|
|
218
|
-
| `userLayout` | Boolean | `false` | Let the user arrange each of the page's `views` for themselves. The header shows **Arrange view** next to the view switch; **Done** stores the arrangement per view and user (a user preference, mirrored in the browser), **Reset view** returns the chosen view to its manifest layout. The manifest `layout` is never changed. See [the guide](../../../docs/components/cn-detail-page.md#each-view-arranged-per-user-userlayout). |
|
|
218
|
+
| `userLayout` | Boolean | `false` | **Deprecated** (layout belongs to Buildiq; removed after this release). Let the user arrange each of the page's `views` for themselves. The header shows **Arrange view** next to the view switch; **Done** stores the arrangement per view and user (a user preference, mirrored in the browser), **Reset view** returns the chosen view to its manifest layout. The manifest `layout` is never changed. See [the guide](../../../docs/components/cn-detail-page.md#each-view-arranged-per-user-userlayout). |
|
|
219
219
|
|
|
220
220
|
## Slots
|
|
221
221
|
|
|
@@ -100,7 +100,7 @@ export default {
|
|
|
100
100
|
| `lockedFields` | `[]` | Field keys rendered read-only (disabled) and immutable — typically the parent reference seeded via `initialData` so the user can't repoint a child away from the record it was created under. |
|
|
101
101
|
| `excludeFields` | `[]` | Array of field keys to exclude from the auto-generated form. |
|
|
102
102
|
| `includeFields` | `null` | Array of field keys to include (whitelist mode). Null means all fields. |
|
|
103
|
-
| `fieldOverrides` | `{}` | Per-field override objects passed to `fieldsFromSchema`. |
|
|
103
|
+
| `fieldOverrides` | `{}` | Per-field override objects passed to `fieldsFromSchema`. A `widget` that names a registered component (v2 registry, then `customComponents`) mounts it as the field's control, with `value`/`modelValue`, `field`, `error` and `disabled`, writing back on `input` or `update:modelValue`. |
|
|
104
104
|
| `nameField` | `'title'` | Which field is the "name" of the item (used in result messages). |
|
|
105
105
|
| `size` | `'normal'` | NcDialog size — `'small'`, `'normal'`, or `'large'`. |
|
|
106
106
|
| `dynamicLoadingLabel` | `'Loading the fields this choice adds.'` | Text shown while the fields a chosen value brings with it are being fetched. See *Fields the data decides* below. |
|
|
@@ -131,6 +131,22 @@
|
|
|
131
131
|
:error="errors[field.key]"
|
|
132
132
|
:updateField="updateField" />
|
|
133
133
|
|
|
134
|
+
<!-- A `widget` naming a registered component (v2 registry or
|
|
135
|
+
customComponents) mounts that component as the control.
|
|
136
|
+
It gets `value`/`modelValue`, `field`, `error` and
|
|
137
|
+
`disabled`, and writes back with `input` or
|
|
138
|
+
`update:modelValue`. -->
|
|
139
|
+
<component
|
|
140
|
+
:is="resolveFieldComponent(field)"
|
|
141
|
+
v-else-if="resolveFieldComponent(field)"
|
|
142
|
+
:value="formData[field.key]"
|
|
143
|
+
:modelValue="formData[field.key]"
|
|
144
|
+
:field="field"
|
|
145
|
+
:error="errors[field.key] || ''"
|
|
146
|
+
:disabled="!!field.readOnly"
|
|
147
|
+
@input="value => updateField(field.key, value)"
|
|
148
|
+
@update:modelValue="value => updateField(field.key, value)" />
|
|
149
|
+
|
|
134
150
|
<!-- referenceType (AD-18): render the integration's
|
|
135
151
|
single-entity widget instead of a plain input. -->
|
|
136
152
|
<component
|
|
@@ -314,8 +330,26 @@
|
|
|
314
330
|
plain select. NcSelectUsers wraps NcSelect and merges the
|
|
315
331
|
rest of these bindings through, so the async load, search
|
|
316
332
|
and select machinery is unchanged either way. -->
|
|
333
|
+
<!-- `look: "search"` (a `fieldOverrides` key): a search field
|
|
334
|
+
with rows of icon or initials, name, a secondary line from
|
|
335
|
+
`optionSecondary` and a check on the chosen one
|
|
336
|
+
(DqOverdragen "Nieuw team"). -->
|
|
337
|
+
<CnSearchSelect
|
|
338
|
+
v-if="field.look === 'search' && !isUserField(field)"
|
|
339
|
+
:inputId="'cn-form-' + field.key"
|
|
340
|
+
:inputLabel="fieldLabelText(field)"
|
|
341
|
+
:labelOutside="isBoardLook"
|
|
342
|
+
:options="searchSelectOptions(field)"
|
|
343
|
+
:modelValue="getEffectiveSelectedOption(field)"
|
|
344
|
+
:clearable="!field.required"
|
|
345
|
+
:disabled="field.readOnly"
|
|
346
|
+
:loading="isFieldLoading(field)"
|
|
347
|
+
:filterable="!isAsyncEnum(field)"
|
|
348
|
+
@update:modelValue="onSearchSelectChange(field, $event)"
|
|
349
|
+
@search="isAsyncEnum(field) ? onAsyncSearch(field, $event) : undefined" />
|
|
317
350
|
<component
|
|
318
351
|
:is="isUserField(field) ? 'NcSelectUsers' : 'NcSelect'"
|
|
352
|
+
v-else
|
|
319
353
|
v-cn-select-aria="fieldAria(field)"
|
|
320
354
|
:inputId="'cn-form-' + field.key"
|
|
321
355
|
:inputLabel="fieldLabelText(field)"
|
|
@@ -799,6 +833,7 @@ import CnIconBrowser from '../CnIconBrowser/CnIconBrowser.vue'
|
|
|
799
833
|
import CnJsonViewer from '../CnJsonViewer/CnJsonViewer.vue'
|
|
800
834
|
import CnPropertySourceField from '../CnPropertySourceField/CnPropertySourceField.vue'
|
|
801
835
|
import CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'
|
|
836
|
+
import CnSearchSelect from '../CnSearchSelect/CnSearchSelect.vue'
|
|
802
837
|
import CnSubObjectsField from '../CnSubObjectsField/CnSubObjectsField.vue'
|
|
803
838
|
import { describeRowProblem, useChildRecords } from '../../composables/useChildRecords.js'
|
|
804
839
|
import { clearAnonymousDrafts, draftIndicatorText, draftKey, formDraftMixin, hasDraftChanges, readDraft, resolveDraftUserId } from '../../composables/useFormDraft.js'
|
|
@@ -821,6 +856,7 @@ import {
|
|
|
821
856
|
splitDynamicFormData,
|
|
822
857
|
} from '../../utils/dynamicProperties.js'
|
|
823
858
|
import { shouldShow } from '../../utils/fieldCondition.js'
|
|
859
|
+
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
824
860
|
import { computeAssignments } from '../../utils/formAssign.js'
|
|
825
861
|
import { resolveNextcloudGroup, searchNextcloudGroups } from '../../utils/groupAutocomplete.js'
|
|
826
862
|
import { durationSeconds } from '../../utils/isoDuration.js'
|
|
@@ -844,6 +880,36 @@ import { resolveNextcloudUser, searchNextcloudUsers } from '../../utils/userAuto
|
|
|
844
880
|
*/
|
|
845
881
|
const WIDE_WIDGETS = ['textarea', 'json', 'code']
|
|
846
882
|
|
|
883
|
+
// The widget names the form draws itself; a registered component cannot take one over.
|
|
884
|
+
const BUILT_IN_WIDGETS = new Set([
|
|
885
|
+
'text',
|
|
886
|
+
'email',
|
|
887
|
+
'url',
|
|
888
|
+
'number',
|
|
889
|
+
'textarea',
|
|
890
|
+
'switch',
|
|
891
|
+
'select',
|
|
892
|
+
'user',
|
|
893
|
+
'group',
|
|
894
|
+
'multiselect',
|
|
895
|
+
'user-multiselect',
|
|
896
|
+
'group-multiselect',
|
|
897
|
+
'tags',
|
|
898
|
+
'checkbox',
|
|
899
|
+
'date',
|
|
900
|
+
'datetime',
|
|
901
|
+
'json',
|
|
902
|
+
'code',
|
|
903
|
+
'file',
|
|
904
|
+
'duration',
|
|
905
|
+
'child-records',
|
|
906
|
+
'sub-objects',
|
|
907
|
+
'property-source',
|
|
908
|
+
'icon',
|
|
909
|
+
'language',
|
|
910
|
+
'timezone',
|
|
911
|
+
])
|
|
912
|
+
|
|
847
913
|
/** Largest file carried inline in the saved payload; bigger ones upload after save. */
|
|
848
914
|
const INLINE_FILE_CAP = 1024 * 1024
|
|
849
915
|
|
|
@@ -1033,6 +1099,7 @@ export default {
|
|
|
1033
1099
|
CnFileField,
|
|
1034
1100
|
CnDurationField,
|
|
1035
1101
|
CnPropertySourceField,
|
|
1102
|
+
CnSearchSelect,
|
|
1036
1103
|
CnReplaceValuesDialog,
|
|
1037
1104
|
CnSubObjectsField,
|
|
1038
1105
|
CnChildRecordsField,
|
|
@@ -2029,6 +2096,28 @@ export default {
|
|
|
2029
2096
|
},
|
|
2030
2097
|
|
|
2031
2098
|
methods: {
|
|
2099
|
+
/**
|
|
2100
|
+
* Resolve the registered component a field's `widget` names: the v2
|
|
2101
|
+
* registry (any entry with a `component`) first, then the legacy
|
|
2102
|
+
* customComponents map. Built-in widget names never resolve here.
|
|
2103
|
+
*
|
|
2104
|
+
* @param {object} field A resolved field descriptor.
|
|
2105
|
+
* @return {object|null} Vue component, or null.
|
|
2106
|
+
*/
|
|
2107
|
+
resolveFieldComponent(field) {
|
|
2108
|
+
const name = field && field.widget
|
|
2109
|
+
if (typeof name !== 'string' || name === '' || BUILT_IN_WIDGETS.has(name)) {
|
|
2110
|
+
return null
|
|
2111
|
+
}
|
|
2112
|
+
const entry = this.cnRegistry && this.cnRegistry[name]
|
|
2113
|
+
if (entry && entry.component) {
|
|
2114
|
+
return entry.component
|
|
2115
|
+
}
|
|
2116
|
+
const legacy = this.cnCustomComponents && this.cnCustomComponents[name]
|
|
2117
|
+
// A bare function in the legacy map is a handler, not a component.
|
|
2118
|
+
return legacy && typeof legacy === 'object' ? legacy : null
|
|
2119
|
+
},
|
|
2120
|
+
|
|
2032
2121
|
/**
|
|
2033
2122
|
* Resolve a field's reference integration widget, if any.
|
|
2034
2123
|
* Returns the integration's single-entity widget component
|
|
@@ -4337,6 +4426,44 @@ export default {
|
|
|
4337
4426
|
* @param {object} field The field definition
|
|
4338
4427
|
* @return {Array}
|
|
4339
4428
|
*/
|
|
4429
|
+
/**
|
|
4430
|
+
* The options of a `look: "search"` field as CnSearchSelect rows: each
|
|
4431
|
+
* option's own `secondary` / `icon` / `avatar`, else the field's
|
|
4432
|
+
* `optionSecondary` template (filled from the option), `optionIcon` and
|
|
4433
|
+
* `optionAvatar`.
|
|
4434
|
+
*
|
|
4435
|
+
* @spec openspec/changes/search-select-look/specs/search-select/spec.md#requirement-a-single-picker-can-take-the-search-look
|
|
4436
|
+
* @param {object} field The field definition.
|
|
4437
|
+
* @return {Array<object>}
|
|
4438
|
+
*/
|
|
4439
|
+
searchSelectOptions(field) {
|
|
4440
|
+
const template = typeof field.optionSecondary === 'string' ? field.optionSecondary : ''
|
|
4441
|
+
return this.getEffectiveOptions(field).map((option) => ({
|
|
4442
|
+
...option,
|
|
4443
|
+
secondary: option.secondary || (template ? fillTemplate(template, option).trim() : ''),
|
|
4444
|
+
icon: option.icon || field.optionIcon || '',
|
|
4445
|
+
avatar: option.avatar ?? (field.optionAvatar === true),
|
|
4446
|
+
}))
|
|
4447
|
+
},
|
|
4448
|
+
|
|
4449
|
+
/**
|
|
4450
|
+
* Pick the option a CnSearchSelect field emitted by its id, and hand
|
|
4451
|
+
* it to the select change handler.
|
|
4452
|
+
*
|
|
4453
|
+
* @spec openspec/changes/search-select-look/specs/search-select/spec.md#requirement-a-single-picker-can-take-the-search-look
|
|
4454
|
+
* @param {object} field The field definition.
|
|
4455
|
+
* @param {string|number|null} id The chosen option's id.
|
|
4456
|
+
*/
|
|
4457
|
+
onSearchSelectChange(field, id) {
|
|
4458
|
+
if (id === null || id === undefined) {
|
|
4459
|
+
this.onEffectiveSelectChange(field, null)
|
|
4460
|
+
return
|
|
4461
|
+
}
|
|
4462
|
+
const option = this.searchSelectOptions(field).find((o) => String(o.id ?? o.value) === String(id)) || { id, label: String(id) }
|
|
4463
|
+
const { secondary, icon, avatar, ...plain } = option
|
|
4464
|
+
this.onEffectiveSelectChange(field, plain)
|
|
4465
|
+
},
|
|
4466
|
+
|
|
4340
4467
|
getEffectiveOptions(field) {
|
|
4341
4468
|
if (this.isAsyncEnum(field)) {
|
|
4342
4469
|
// TODO: restore to `this.asyncState[field.key]?.options || []` once on Vue 3 (buble doesn't support optional chaining)
|
|
@@ -362,7 +362,7 @@ export default {
|
|
|
362
362
|
| `lensReasonTexts` | Object \| null | `null` | App wording for an unavailable lens, keyed `<lens>.<reason>` or `<reason>`. |
|
|
363
363
|
| `loadingText` | String | `'Loading…'` | Accessible label for the loading spinner (NcLoadingIcon aria-label) |
|
|
364
364
|
| `rowIcon` | String \| Function | `null` | Optional leading icon for every table row — a static MDI name or `(row) => iconName`. Forwarded to CnDataTable; fed from the manifest as `pages[].config.rowIcon`. Unset = no icon column. |
|
|
365
|
-
| `rowClass` | Function | `null` | Callback returning CSS class(es) for a row |
|
|
365
|
+
| `rowClass` | Function \| Array | `null` | Callback returning CSS class(es) for a row, or (manifest `config.rowClass`) rules `[{ when: { field, op?, value }, class }]` compiled into one: each adds its class to a row whose field compares to the value (`visibleWhen` operators; two dates compare as moments, `@today` included). |
|
|
366
366
|
| `rowIcon` | String \| Function | `null` | Optional leading row icon forwarded to CnDataTable — a static MDI icon name or `(row) => iconName`. Fed from the manifest as `pages[].config.rowIcon`; unset = no icon column |
|
|
367
367
|
| `inlineActionCount` | Number | `2` | How many row actions to show inline (rest go in overflow menu) |
|
|
368
368
|
| `showMassImport` | Boolean | `true` | Whether to show the mass Import action |
|
|
@@ -414,6 +414,7 @@ export default {
|
|
|
414
414
|
| `quickFilterMode` | String | `'chips'` | How the quick filters render: `'chips'` (pill strip) or `'dropdown'` (a single `NcSelect`). Fed from `pages[].config.quickFilterMode`. |
|
|
415
415
|
| `quickFilterMultiple` | Boolean | `false` | Allow several quick filters active at once; selected tabs' filters are OR-ed into the fetch (same field → `field[]=` array). Fed from `pages[].config.quickFilterMultiple`. |
|
|
416
416
|
| `quickFilterMaxVisible` | Number | `0` | Chips mode only — how many quick-filter pills render inline before the rest move behind one more chip — a `⋯` pill that opens a panel of the hidden lenses. `0` renders every tab, which wraps the actions bar onto a second line once a page declares more than a handful. The visible set is the first *n* entries of `quickFilters`. Fed from `pages[].config.quickFilterMaxVisible`. |
|
|
417
|
+
| `quickFilterPlacement` | String | `''` | Where the quick-filter chips sit. `'toolbar'` keeps them on one row with the Filter button and the view switch, in whatever width that row has beside a side panel (DqTeamwachtrij): the chips take the room the buttons leave, and those that do not fit move behind the overflow chip. Fed from `pages[].config.quickFilterPlacement`. |
|
|
417
418
|
| `cardComponent` | String | `''` | Optional name of a consumer-provided card component (registered in the v2 `registry` — any kind carrying a `component` — or in the legacy `customComponents` map on `CnAppRoot`) to render in place of the default `CnObjectCard` when the page is in card-grid view mode. Resolution priority: `#card` scoped slot → `cardComponent` registry entry → default `CnObjectCard`. Unknown names log `console.warn` once and fall back to the default. |
|
|
418
419
|
| `customComponents` | Object | `null` | Optional explicit `customComponents` registry. Overrides the registry injected from `CnAppRoot` via `cnCustomComponents`. Mostly used by unit tests; production consumers register components on `CnAppRoot` instead. |
|
|
419
420
|
| `subscribe` | Boolean | `true` | Self-fetch mode only. When `register` + `schema` are set (and no `objects` prop), the page subscribes to the collection's `or-collection-{register}-{schema}` live-update scope on mount and refetches on an update event; released on unmount. Set `false` (manifest: `config.subscribe: false`) for static / read-once views. No-op in consumer-managed mode or on stores without live-updates support. |
|
|
@@ -1093,6 +1094,16 @@ Sources: `register` (fetch the folder list from an OpenRegister `register`/`sche
|
|
|
1093
1094
|
}
|
|
1094
1095
|
```
|
|
1095
1096
|
|
|
1097
|
+
`pane: false` draws no pane. The folder still comes from the deep-link filter (`/cases?caseType=<uuid>`) and its scope look (title, count line, columns, quick filters, `afterList` from the folder or its row's `x-index`) still applies, so a list narrowed to one case type looks like that case type's list without a folder column (boards DqWooVerzoeken, DqKlachten):
|
|
1098
|
+
|
|
1099
|
+
```json
|
|
1100
|
+
"folderSidebar": {
|
|
1101
|
+
"source": "register", "register": "dossiq", "schema": "caseType",
|
|
1102
|
+
"idField": "@self.uuid", "nameField": "title",
|
|
1103
|
+
"filterField": "caseType", "pane": false
|
|
1104
|
+
}
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1096
1107
|
### A column set per folder
|
|
1097
1108
|
|
|
1098
1109
|
A folder is also a scope. Give it `columns`, `defaultSort` or `searchFields`
|
|
@@ -1136,3 +1147,6 @@ columns are right.
|
|
|
1136
1147
|
## Board look
|
|
1137
1148
|
|
|
1138
1149
|
With `look: "board"` (the page's `look` prop, `config.look`, or the app's) the index page takes the DqZaken setup: a header with the count line from `countText` (`{shown}`, `{total}`), the buttons Download, Actions, the buildiq square and the primary one in that order, a two-row toolbar (saved-view chips, Filter with a count and the view switch; then search and active filter chips), a bulk band between toolbar and table (`bulkHint`), the table as a white card with one menu button per row, and a footer inside the card (`footerNote`). `cardFields` names the facts of each card in the cards view. Without the look nothing changes.
|
|
1150
|
+
|
|
1151
|
+
`bulkBandAlways` (`config.bulkBandAlways`) draws the board bulk band before a selection too, its buttons
|
|
1152
|
+
disabled until rows are selected (DqZaken, DqWerkbord).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cn-index-page"
|
|
3
|
-
:class="[lookClass, { 'cn-index-page--with-side-panel': hasSidePanel }]"
|
|
3
|
+
:class="[lookClass, { 'cn-index-page--with-side-panel': hasSidePanel, 'cn-index-page--quick-filters-toolbar': quickFilterPlacement === 'toolbar' }]"
|
|
4
4
|
data-testid="cn-index-page"
|
|
5
5
|
@keydown="onListKeydown">
|
|
6
6
|
<!-- Header — overridable via #header slot. CnPageHeader ALWAYS renders:
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
:showBuildiqButton="!buildiqInHeader"
|
|
164
164
|
:bulkNoun="boardBulkNoun"
|
|
165
165
|
:bulkHint="bulkHint ? cnTranslate(bulkHint) : bulkHint"
|
|
166
|
+
:bulkBandAlways="bulkBandAlways"
|
|
166
167
|
:pagination="effectivePagination"
|
|
167
168
|
:objectCount="effectiveObjects.length"
|
|
168
169
|
:selectable="selectable"
|
|
@@ -369,6 +370,7 @@
|
|
|
369
370
|
:tabs="tabStripEntries"
|
|
370
371
|
:mode="quickFilterMode"
|
|
371
372
|
:maxVisible="quickFilterMaxVisible"
|
|
373
|
+
:fit="quickFilterPlacement === 'toolbar'"
|
|
372
374
|
:multiple="quickFilterMultiple"
|
|
373
375
|
:activeIndex="activeQuickFilterIndex"
|
|
374
376
|
:selectedIndices="selectedQuickFilterIndices"
|
|
@@ -658,11 +660,12 @@
|
|
|
658
660
|
</slot>
|
|
659
661
|
|
|
660
662
|
<!-- Body -->
|
|
661
|
-
<div class="cn-index-page__body" :class="{ 'cn-index-page__body--with-folders':
|
|
663
|
+
<div class="cn-index-page__body" :class="{ 'cn-index-page__body--with-folders': showsFolderPane }">
|
|
662
664
|
<!-- Optional folder navigation pane (opt-in via the `folderSidebar`
|
|
663
665
|
config). Selecting a folder filters the list by the config's
|
|
664
|
-
`filterField`; "All" clears it.
|
|
665
|
-
|
|
666
|
+
`filterField`; "All" clears it. `pane: false` keeps the scope
|
|
667
|
+
from the deep link and draws no pane. -->
|
|
668
|
+
<div v-if="showsFolderPane" class="cn-index-page__folder-pane">
|
|
666
669
|
<CnFolderSidebar
|
|
667
670
|
:source="folderSidebarSource"
|
|
668
671
|
:folders="folderSidebarFolders"
|
|
@@ -755,7 +758,7 @@
|
|
|
755
758
|
:excludeColumns="excludeColumns"
|
|
756
759
|
:includeColumns="includeColumns"
|
|
757
760
|
:columnOverrides="columnOverrides"
|
|
758
|
-
:rowClass="
|
|
761
|
+
:rowClass="compiledRowClass"
|
|
759
762
|
:rowTitle="rowTitle"
|
|
760
763
|
:showUnread="showUnreadRows"
|
|
761
764
|
:filterable="tableHeaderFilters"
|
|
@@ -1292,6 +1295,7 @@ import { resolveFilterMap } from '../../utils/routeFilters.js'
|
|
|
1292
1295
|
import { availableRowActions, DEFAULT_ROW_ACTION_FIELD, refusalReasonFor, undeclaredRowActions, withoutViewWhenRowOpensDetail } from '../../utils/rowActionAvailability.js'
|
|
1293
1296
|
import { isRowActionVisible, rowActionPayload } from '../../utils/rowActionItem.js'
|
|
1294
1297
|
import { isNewTabClick, isNewTabHandled, isRowMiddleClick, markNewTabHandled, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
|
|
1298
|
+
import { compileRowClass } from '../../utils/rowClassRules.js'
|
|
1295
1299
|
import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
|
|
1296
1300
|
import { buildRouteQueryFromViewState, buildViewCreatePayload, extractViewState, extractViewStateFromRouteQuery, normalizeSharedWith, savedViewScope, viewMatchesScope } from '../../utils/savedViewHelpers.js'
|
|
1297
1301
|
import { columnsFromSchema, fieldsFromSchema } from '../../utils/schema.js'
|
|
@@ -1938,6 +1942,17 @@ export default {
|
|
|
1938
1942
|
default: '',
|
|
1939
1943
|
},
|
|
1940
1944
|
|
|
1945
|
+
/**
|
|
1946
|
+
* Board look: draw the bulk band before anything is selected too, its
|
|
1947
|
+
* buttons disabled until there is a selection (DqZaken, DqWerkbord).
|
|
1948
|
+
* Manifest key `config.bulkBandAlways`. Only when the band has an
|
|
1949
|
+
* action to offer.
|
|
1950
|
+
*/
|
|
1951
|
+
bulkBandAlways: {
|
|
1952
|
+
type: Boolean,
|
|
1953
|
+
default: false,
|
|
1954
|
+
},
|
|
1955
|
+
|
|
1941
1956
|
/**
|
|
1942
1957
|
* Board look: the 13px hint after the bulk band's buttons ("See what
|
|
1943
1958
|
* changes first, then run it"). Manifest key `config.bulkHint`.
|
|
@@ -2119,6 +2134,21 @@ export default {
|
|
|
2119
2134
|
default: null,
|
|
2120
2135
|
},
|
|
2121
2136
|
|
|
2137
|
+
/**
|
|
2138
|
+
* Where the quick-filter chips sit (manifest `config.quickFilterPlacement`).
|
|
2139
|
+
* `''` (the default) as before. `'toolbar'` keeps them on one row with
|
|
2140
|
+
* the Filter button and the view switch, in whatever width that row has
|
|
2141
|
+
* beside a side panel (DqTeamwachtrij): the chips take the room the
|
|
2142
|
+
* buttons leave, and those that do not fit move into the overflow chip.
|
|
2143
|
+
*
|
|
2144
|
+
* @type {''|'toolbar'}
|
|
2145
|
+
*/
|
|
2146
|
+
quickFilterPlacement: {
|
|
2147
|
+
type: String,
|
|
2148
|
+
default: '',
|
|
2149
|
+
validator: (v) => ['', 'toolbar'].includes(v),
|
|
2150
|
+
},
|
|
2151
|
+
|
|
2122
2152
|
/**
|
|
2123
2153
|
* How the quick filters render: `'chips'` (pill strip, default) or
|
|
2124
2154
|
* `'dropdown'` (a single `NcSelect`). Sourced from the manifest as
|
|
@@ -2695,9 +2725,18 @@ export default {
|
|
|
2695
2725
|
default: 'Loading…',
|
|
2696
2726
|
},
|
|
2697
2727
|
|
|
2698
|
-
/**
|
|
2728
|
+
/**
|
|
2729
|
+
* CSS class(es) for a row: a function `(row) => string`, or (manifest
|
|
2730
|
+
* `config.rowClass`) rules `[{ when: { field, op?, value }, class }]`,
|
|
2731
|
+
* each adding its class to a row whose field compares to the value
|
|
2732
|
+
* (the `visibleWhen` operators, `@today` included). DqTaken marks an
|
|
2733
|
+
* overdue task: `{ when: { field: "dueAt", op: "lt", value: "@today" },
|
|
2734
|
+
* class: "cn-row--overdue" }`.
|
|
2735
|
+
*
|
|
2736
|
+
* @type {((row: object) => string)|Array<{when: {field: string, op?: string, value: unknown}, class: string}>|null}
|
|
2737
|
+
*/
|
|
2699
2738
|
rowClass: {
|
|
2700
|
-
type: Function,
|
|
2739
|
+
type: [Function, Array],
|
|
2701
2740
|
default: null,
|
|
2702
2741
|
},
|
|
2703
2742
|
|
|
@@ -3162,6 +3201,9 @@ export default {
|
|
|
3162
3201
|
* defaults to `field`).
|
|
3163
3202
|
* - `folders` — explicit folder list for `source:'custom'`.
|
|
3164
3203
|
* - `allLabel` / `title` / `allowCreate` — passed to CnFolderSidebar.
|
|
3204
|
+
* - `pane` — `false` draws no pane: the scope still comes from the
|
|
3205
|
+
* deep-link filter (`?caseType=<id>`) and still applies its look, as
|
|
3206
|
+
* a list narrowed to one case type does without a folder column.
|
|
3165
3207
|
*
|
|
3166
3208
|
* A folder entry is also a SCOPE: it may carry `columns`, `defaultSort`
|
|
3167
3209
|
* and `searchFields`, and while it is selected the list is shown that
|
|
@@ -3779,6 +3821,10 @@ export default {
|
|
|
3779
3821
|
//
|
|
3780
3822
|
// Seeded from the route query so a shared link lands filtered, the
|
|
3781
3823
|
// same way `_order` and a deep-link filter already do for self-fetch.
|
|
3824
|
+
// The page a paging named source shows: page 1 at the source config's
|
|
3825
|
+
// `limit` (20 by default), moved by the pager.
|
|
3826
|
+
const namedPageSize = Number(props.sourceConfig && props.sourceConfig.limit) > 0 ? Number(props.sourceConfig.limit) : 20
|
|
3827
|
+
const namedPaging = ref({ page: 1, limit: namedPageSize })
|
|
3782
3828
|
const namedActiveFilters = ref(namedFiltersFromRoute(getCurrentInstance(), props))
|
|
3783
3829
|
|
|
3784
3830
|
const {
|
|
@@ -3787,7 +3833,7 @@ export default {
|
|
|
3787
3833
|
namedRows,
|
|
3788
3834
|
namedLoading,
|
|
3789
3835
|
namedQuickFilters,
|
|
3790
|
-
} = useNamedSource(props, { activeQuickFilterIndex, activeFilters: namedActiveFilters })
|
|
3836
|
+
} = useNamedSource(props, { activeQuickFilterIndex, activeFilters: namedActiveFilters, paging: namedPaging })
|
|
3791
3837
|
|
|
3792
3838
|
return {
|
|
3793
3839
|
isBoard: isBoardLook,
|
|
@@ -3799,6 +3845,7 @@ export default {
|
|
|
3799
3845
|
namedLoading,
|
|
3800
3846
|
namedQuickFilters,
|
|
3801
3847
|
namedActiveFilters,
|
|
3848
|
+
namedPaging,
|
|
3802
3849
|
contextMenuOpen,
|
|
3803
3850
|
contextMenuRow,
|
|
3804
3851
|
openContextMenu,
|
|
@@ -4761,6 +4808,9 @@ export default {
|
|
|
4761
4808
|
const loaded = this.effectivePagination || {}
|
|
4762
4809
|
return Number(loaded.page || 1) < Number(loaded.pages || 1)
|
|
4763
4810
|
}
|
|
4811
|
+
if (this.isNamedSource && this.namedSource && this.namedSource.pages === true) {
|
|
4812
|
+
return Number(this.effectivePagination?.pages || 1) > 1
|
|
4813
|
+
}
|
|
4764
4814
|
return Number(this.pagination?.pages || 1) > 1
|
|
4765
4815
|
},
|
|
4766
4816
|
|
|
@@ -4787,6 +4837,17 @@ export default {
|
|
|
4787
4837
|
return { shown, total: Math.max(shown, Number(this.effectivePagination?.total || 0)) }
|
|
4788
4838
|
},
|
|
4789
4839
|
|
|
4840
|
+
/**
|
|
4841
|
+
* Whether the folder pane is drawn: a `folderSidebar` without
|
|
4842
|
+
* `pane: false`.
|
|
4843
|
+
*
|
|
4844
|
+
* @spec openspec/changes/folder-sidebar-pane-off/specs/index-page/spec.md#requirement-a-folder-scope-without-a-pane
|
|
4845
|
+
* @return {boolean}
|
|
4846
|
+
*/
|
|
4847
|
+
showsFolderPane() {
|
|
4848
|
+
return Boolean(this.folderSidebar) && this.folderSidebar.pane !== false
|
|
4849
|
+
},
|
|
4850
|
+
|
|
4790
4851
|
/**
|
|
4791
4852
|
* The CnFolderSidebar source. A `register` source is fetched by this
|
|
4792
4853
|
* component and handed to CnFolderSidebar as a `custom` folder list.
|
|
@@ -5800,7 +5861,13 @@ export default {
|
|
|
5800
5861
|
// A named source that reports its own total (the search source)
|
|
5801
5862
|
// gives the count line its number.
|
|
5802
5863
|
if (this.isNamedSource && this.namedSource && typeof this.namedSource.total === 'function') {
|
|
5803
|
-
|
|
5864
|
+
const total = this.namedSource.total()
|
|
5865
|
+
// A source with `pages: true` gets the pager the self-fetched lists draw.
|
|
5866
|
+
if (this.namedSource.pages === true) {
|
|
5867
|
+
const { page, limit } = this.namedPaging
|
|
5868
|
+
return { page, limit, total, pages: Math.max(1, Math.ceil(total / limit)) }
|
|
5869
|
+
}
|
|
5870
|
+
return { ...(this.pagination || {}), total }
|
|
5804
5871
|
}
|
|
5805
5872
|
return this.isSelfFetchMode ? this.list.pagination.value : this.pagination
|
|
5806
5873
|
},
|
|
@@ -6053,6 +6120,16 @@ export default {
|
|
|
6053
6120
|
return (this.effectiveSchema && this.effectiveSchema.slug) || ''
|
|
6054
6121
|
},
|
|
6055
6122
|
|
|
6123
|
+
/**
|
|
6124
|
+
* The row-class function the table takes: a function as given, or the
|
|
6125
|
+
* manifest's rules compiled.
|
|
6126
|
+
*
|
|
6127
|
+
* @return {?(row: object) => string}
|
|
6128
|
+
*/
|
|
6129
|
+
compiledRowClass() {
|
|
6130
|
+
return compileRowClass(this.rowClass)
|
|
6131
|
+
},
|
|
6132
|
+
|
|
6056
6133
|
/** @return {number} How many leading columns the person pinned (0 while a saved view's columns are applied). */
|
|
6057
6134
|
pinnedColumnCount() {
|
|
6058
6135
|
if (!this.personalColumns || Array.isArray(this.appliedViewColumns) || !this.personalLayout) {
|
|
@@ -8777,6 +8854,9 @@ export default {
|
|
|
8777
8854
|
if (this.isSelfFetchMode && typeof this.list.onPageChange === 'function') {
|
|
8778
8855
|
this.list.onPageChange(page)
|
|
8779
8856
|
}
|
|
8857
|
+
if (this.isNamedSource && this.namedSource && this.namedSource.pages === true) {
|
|
8858
|
+
this.namedPaging = { ...this.namedPaging, page }
|
|
8859
|
+
}
|
|
8780
8860
|
this.$emit('page-changed', page)
|
|
8781
8861
|
},
|
|
8782
8862
|
|
|
@@ -8792,6 +8872,9 @@ export default {
|
|
|
8792
8872
|
if (this.isSelfFetchMode && typeof this.list.onPageSizeChange === 'function') {
|
|
8793
8873
|
this.list.onPageSizeChange(size)
|
|
8794
8874
|
}
|
|
8875
|
+
if (this.isNamedSource && this.namedSource && this.namedSource.pages === true) {
|
|
8876
|
+
this.namedPaging = { page: 1, limit: size }
|
|
8877
|
+
}
|
|
8795
8878
|
this.$emit('page-size-changed', size)
|
|
8796
8879
|
},
|
|
8797
8880
|
|
|
@@ -105,6 +105,9 @@ export function useNamedSource(props, options = {}) {
|
|
|
105
105
|
const tabs = manifestTabs || sourceTabs
|
|
106
106
|
const activeIndex = options.activeQuickFilterIndex || null
|
|
107
107
|
const activeFilters = options.activeFilters || null
|
|
108
|
+
// A source that pages (`pages: true`) gets the page as `limit` and
|
|
109
|
+
// `offset`, from the pager the page draws for it.
|
|
110
|
+
const paging = source.pages === true ? (options.paging || null) : null
|
|
108
111
|
|
|
109
112
|
// Seed the default tab before the watcher below registers: the mount load
|
|
110
113
|
// already carries this tab's filter, so the seed must not fire a second
|
|
@@ -165,6 +168,7 @@ export function useNamedSource(props, options = {}) {
|
|
|
165
168
|
activeFilters ? activeFilters.value : null,
|
|
166
169
|
props.entitySource,
|
|
167
170
|
),
|
|
171
|
+
...(paging ? { limit: paging.value.limit, offset: (paging.value.page - 1) * paging.value.limit } : {}),
|
|
168
172
|
},
|
|
169
173
|
tokenCtx(),
|
|
170
174
|
)
|
|
@@ -182,15 +186,35 @@ export function useNamedSource(props, options = {}) {
|
|
|
182
186
|
|
|
183
187
|
// Registered AFTER the seed above, so only a person switching tabs
|
|
184
188
|
// reloads. Without tabs there is nothing to watch.
|
|
189
|
+
/**
|
|
190
|
+
* Reload for a new query: on a paging source back to page 1, which
|
|
191
|
+
* reloads through the paging watcher below.
|
|
192
|
+
*
|
|
193
|
+
* @return {void}
|
|
194
|
+
*/
|
|
195
|
+
function reloadFromFirstPage() {
|
|
196
|
+
if (paging && paging.value.page !== 1) {
|
|
197
|
+
paging.value = { ...paging.value, page: 1 }
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
loadActive()
|
|
201
|
+
}
|
|
202
|
+
|
|
185
203
|
if (tabs && activeIndex) {
|
|
186
|
-
watch(activeIndex,
|
|
204
|
+
watch(activeIndex, reloadFromFirstPage)
|
|
187
205
|
}
|
|
188
206
|
|
|
189
207
|
// A sidebar choice is a new QUERY, not a new view of the page already
|
|
190
208
|
// fetched, so it reloads. `deep` because the map is rewritten key by key
|
|
191
209
|
// and a shallow watch misses a second value added to the same field.
|
|
192
210
|
if (activeFilters) {
|
|
193
|
-
watch(activeFilters,
|
|
211
|
+
watch(activeFilters, reloadFromFirstPage, { deep: true })
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// A new page, or a new page size, is a new request. A new tab or filter
|
|
215
|
+
// first returns to page 1, which reloads through here.
|
|
216
|
+
if (paging) {
|
|
217
|
+
watch(paging, loadActive, { deep: true })
|
|
194
218
|
}
|
|
195
219
|
|
|
196
220
|
// No manual invalidation tick here on purpose. The adapters read from a
|