@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
|
@@ -389,6 +389,18 @@
|
|
|
389
389
|
</button>
|
|
390
390
|
</slot>
|
|
391
391
|
</div>
|
|
392
|
+
<!-- `pageSize`: the board's pager under the rows, "4 of 7" and the
|
|
393
|
+
page buttons (DqCatalogus). -->
|
|
394
|
+
<CnPagination
|
|
395
|
+
v-if="pageSize > 0 && totalRowCount > pageSize"
|
|
396
|
+
variant="board"
|
|
397
|
+
class="cn-data-table__pager"
|
|
398
|
+
:currentPage="currentPage"
|
|
399
|
+
:totalPages="pageCount"
|
|
400
|
+
:totalItems="totalRowCount"
|
|
401
|
+
:currentPageSize="pageSize"
|
|
402
|
+
data-testid="cn-data-table-pager"
|
|
403
|
+
@pageChanged="currentPage = $event" />
|
|
392
404
|
</div>
|
|
393
405
|
</template>
|
|
394
406
|
|
|
@@ -400,6 +412,7 @@ import { NcCheckboxRadioSwitch, NcLoadingIcon } from '@nextcloud/vue'
|
|
|
400
412
|
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
|
|
401
413
|
import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'
|
|
402
414
|
import FilterOutline from 'vue-material-design-icons/FilterOutline.vue'
|
|
415
|
+
import CnPagination from '../CnPagination/CnPagination.vue'
|
|
403
416
|
import CnColumnFilterPopover from './CnColumnFilterPopover.vue'
|
|
404
417
|
import { useClickDragGuard } from '../../composables/useClickDragGuard.js'
|
|
405
418
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
@@ -491,6 +504,7 @@ export default {
|
|
|
491
504
|
CnColumnFilterPopover,
|
|
492
505
|
CnIcon,
|
|
493
506
|
CnLockIndicator,
|
|
507
|
+
CnPagination,
|
|
494
508
|
CnUnreadMarker,
|
|
495
509
|
ChevronDown,
|
|
496
510
|
ChevronUp,
|
|
@@ -858,6 +872,19 @@ export default {
|
|
|
858
872
|
default: 0,
|
|
859
873
|
},
|
|
860
874
|
|
|
875
|
+
/**
|
|
876
|
+
* Rows per page. Above 0 the table shows one page of its rows and the
|
|
877
|
+
* board's pager under them ("4 of 7", page buttons, Next) once there is
|
|
878
|
+
* more than one page. Pages the rows the table holds; 0 (the default)
|
|
879
|
+
* shows them all, or the first `limit`.
|
|
880
|
+
*
|
|
881
|
+
* @spec openspec/changes/object-table-pager/specs/object-table/spec.md#requirement-an-object-table-can-page-its-rows
|
|
882
|
+
*/
|
|
883
|
+
pageSize: {
|
|
884
|
+
type: Number,
|
|
885
|
+
default: 0,
|
|
886
|
+
},
|
|
887
|
+
|
|
861
888
|
/**
|
|
862
889
|
* vue-router route object for the "View all" footer link. The footer only
|
|
863
890
|
* shows when set AND the rows are a `limit`-ed subset. Folded in from CnTableWidget.
|
|
@@ -1010,6 +1037,8 @@ export default {
|
|
|
1010
1037
|
aggregateRequestId: 0,
|
|
1011
1038
|
/** Rows fetched in self-fetch mode (register + schemaId). */
|
|
1012
1039
|
fetchedRows: [],
|
|
1040
|
+
/** The page shown under `pageSize`. */
|
|
1041
|
+
currentPage: 1,
|
|
1013
1042
|
/** Personal-lens reports (`@self.lenses`) of the latest self-fetch response. */
|
|
1014
1043
|
fetchedLenses: {},
|
|
1015
1044
|
/** True while a self-fetch request is in flight. */
|
|
@@ -1125,9 +1154,23 @@ export default {
|
|
|
1125
1154
|
* @return {Array<object>}
|
|
1126
1155
|
*/
|
|
1127
1156
|
effectiveRows() {
|
|
1157
|
+
if (this.pageSize > 0) {
|
|
1158
|
+
const page = Math.min(this.currentPage, this.pageCount)
|
|
1159
|
+
return this.sourceRows.slice((page - 1) * this.pageSize, page * this.pageSize)
|
|
1160
|
+
}
|
|
1128
1161
|
return this.limit > 0 ? this.sourceRows.slice(0, this.limit) : this.sourceRows
|
|
1129
1162
|
},
|
|
1130
1163
|
|
|
1164
|
+
/**
|
|
1165
|
+
* The number of pages under `pageSize` (at least 1).
|
|
1166
|
+
*
|
|
1167
|
+
* @spec openspec/changes/object-table-pager/specs/object-table/spec.md#requirement-an-object-table-can-page-its-rows
|
|
1168
|
+
* @return {number}
|
|
1169
|
+
*/
|
|
1170
|
+
pageCount() {
|
|
1171
|
+
return this.pageSize > 0 ? Math.max(1, Math.ceil(this.sourceRows.length / this.pageSize)) : 1
|
|
1172
|
+
},
|
|
1173
|
+
|
|
1131
1174
|
/**
|
|
1132
1175
|
* Total row count before the `limit` cap (drives the count badge + the
|
|
1133
1176
|
* "View all" footer condition).
|
|
@@ -1339,6 +1382,12 @@ export default {
|
|
|
1339
1382
|
},
|
|
1340
1383
|
},
|
|
1341
1384
|
|
|
1385
|
+
pageCount(count) {
|
|
1386
|
+
if (this.currentPage > count) {
|
|
1387
|
+
this.currentPage = count
|
|
1388
|
+
}
|
|
1389
|
+
},
|
|
1390
|
+
|
|
1342
1391
|
effectiveColumns: {
|
|
1343
1392
|
handler() {
|
|
1344
1393
|
this.loadAggregates()
|
|
@@ -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
|
|
|
@@ -426,6 +426,7 @@ Four levels of action and the case surfaces around them. All opt in. See [the re
|
|
|
426
426
|
| `statusPill` | Object \| null | `null` | A second pill, same shape. |
|
|
427
427
|
| `headerFields` | Array | `[]` | Fields shown as chips under the title (manifest `config.headerFields`). Each entry is a property key, or `{ key, format, labelField, colorField, warnWhenPast }`; `format` is `text` (default), `mono`, `badge`, `user` or `date`. A `$ref` value shows `labelField` of the referenced object and falls back to the raw id in mono; a badge maps `colorField` (`success`, `warning`, `error`, `info`, `neutral`) to a variant; `warnWhenPast` gives a past date the error variant. An empty value shows no chip, a page without the key shows no row. Each chip reads "Title: value" to a screen reader, and the row prints as plain text. |
|
|
428
428
|
| `sideColumn` | Array | `[]` | Widget definitions or widget ids, rendered as a column of cards beside the body. |
|
|
429
|
+
| `sideColumnFrom` | String | `''` | A field path, read like a `visibleWhen` field (e.g. `@context.caseType.sideCards`), whose list of ids picks the `sideColumn` entries this record shows and their order. Any other value keeps `sideColumn` as declared. |
|
|
429
430
|
| `isAdmin` | Boolean \| null | `null` | Shows `adminOnly` header actions. `null` reads it from Nextcloud. |
|
|
430
431
|
|
|
431
432
|
## The record as a place
|
|
@@ -1267,7 +1267,7 @@ import { isActivityWidget, resolveHeaderMeta } from '../../utils/headerMeta.js'
|
|
|
1267
1267
|
import { patchStoredSelf } from '../../utils/patchStoredSelf.js'
|
|
1268
1268
|
import { setReadState } from '../../utils/recordInteractions.js'
|
|
1269
1269
|
import { slotRenders } from '../../utils/slotContent.js'
|
|
1270
|
-
import { evaluateVisibleWhen, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'
|
|
1270
|
+
import { evaluateVisibleWhen, isLocallyDecidableVisibleWhen, readVisibleWhenPath } from '../../utils/visibleWhen.js'
|
|
1271
1271
|
import {
|
|
1272
1272
|
isCardWidgetDef,
|
|
1273
1273
|
isContentOnlyWidgetDef,
|
|
@@ -2582,6 +2582,22 @@ export default {
|
|
|
2582
2582
|
default: () => [],
|
|
2583
2583
|
},
|
|
2584
2584
|
|
|
2585
|
+
/**
|
|
2586
|
+
* A field path whose value picks the side column and its order for
|
|
2587
|
+
* this record (manifest `config.sideColumnFrom`), read like a
|
|
2588
|
+
* `visibleWhen` field: `@context.caseType.sideCards` reads the list a
|
|
2589
|
+
* case type keeps. A non-empty list of ids keeps those `sideColumn`
|
|
2590
|
+
* entries, in that order, and leaves the rest out; each still follows
|
|
2591
|
+
* its own `visibleWhen`. Any other value keeps `sideColumn` as
|
|
2592
|
+
* declared, so a record whose list is empty changes nothing.
|
|
2593
|
+
*
|
|
2594
|
+
* @spec openspec/changes/detail-side-column-from/specs/detail-page/spec.md#requirement-a-record-can-pick-the-side-column-and-its-order
|
|
2595
|
+
*/
|
|
2596
|
+
sideColumnFrom: {
|
|
2597
|
+
type: String,
|
|
2598
|
+
default: '',
|
|
2599
|
+
},
|
|
2600
|
+
|
|
2585
2601
|
/**
|
|
2586
2602
|
* Draw the header as a card (manifest `config.headerCard: true`): the
|
|
2587
2603
|
* pills, the title and the actions on the page's surface colour inside
|
|
@@ -3492,7 +3508,7 @@ export default {
|
|
|
3492
3508
|
* @spec openspec/changes/detail-side-visible-when/specs/detail-page/spec.md#requirement-a-side-column-widget-shows-only-while-its-visiblewhen-holds
|
|
3493
3509
|
*/
|
|
3494
3510
|
sideColumnWidgets() {
|
|
3495
|
-
const resolved = this.
|
|
3511
|
+
const resolved = this.pickedSideWidgets.filter((widget) => this.sideWidgetVisible(widget))
|
|
3496
3512
|
if (!this.isBoard) {
|
|
3497
3513
|
return resolved
|
|
3498
3514
|
}
|
|
@@ -3504,6 +3520,28 @@ export default {
|
|
|
3504
3520
|
.map(({ widget }) => widget)
|
|
3505
3521
|
},
|
|
3506
3522
|
|
|
3523
|
+
/**
|
|
3524
|
+
* The side column's definitions as the record picks them
|
|
3525
|
+
* (`sideColumnFrom`): the ids it lists, in its order, or every
|
|
3526
|
+
* declared entry when it lists none. Edit mode shows them all.
|
|
3527
|
+
*
|
|
3528
|
+
* @return {Array<object>}
|
|
3529
|
+
* @spec openspec/changes/detail-side-column-from/specs/detail-page/spec.md#requirement-a-record-can-pick-the-side-column-and-its-order
|
|
3530
|
+
*/
|
|
3531
|
+
pickedSideWidgets() {
|
|
3532
|
+
const declared = this.declaredSideWidgets
|
|
3533
|
+
if (!this.sideColumnFrom || this.editingBody || !this.widgetVisibilityData) {
|
|
3534
|
+
return declared
|
|
3535
|
+
}
|
|
3536
|
+
const ids = readVisibleWhenPath(this.widgetVisibilityData, this.sideColumnFrom)
|
|
3537
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
3538
|
+
return declared
|
|
3539
|
+
}
|
|
3540
|
+
return ids
|
|
3541
|
+
.map((id) => declared.find((widget) => widget.id === id) || null)
|
|
3542
|
+
.filter(Boolean)
|
|
3543
|
+
},
|
|
3544
|
+
|
|
3507
3545
|
/**
|
|
3508
3546
|
* The side column's entries resolved to definitions, before any
|
|
3509
3547
|
* `visibleWhen` is read: a string names a widget declared in
|
|
@@ -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)
|
|
@@ -389,6 +389,7 @@ export default {
|
|
|
389
389
|
| `customComponents` | Object | `null` | Custom-component / handler registry. When set, takes precedence over the injected `cnCustomComponents` from CnAppRoot. Used to resolve `actions[].handler` registry names declared in the manifest (manifest-actions-dispatch). Named handlers resolve out of the v2 `registry` (a `kind: "handler"` entry) FIRST and fall back to this map. |
|
|
390
390
|
| `showViewToggle` | Boolean | `true` | Whether to show the Cards/Table view toggle |
|
|
391
391
|
| `inlineSearch` | Boolean | `false` | Show an inline search field in the actions bar (in addition to / instead of the sidebar search). Fed from the manifest as `pages[].config.inlineSearch`. The field takes the place of the "Showing X of Y" counter unless `showCountWithSearch` is set. |
|
|
392
|
+
| `search` | Boolean | `true` | `false` draws no inline search in the actions bar, also in the board look, which otherwise draws one on every index page (manifest: `config.search`). Wins over `inlineSearch`. |
|
|
392
393
|
| `showCountWithSearch` | Boolean | `false` | Keep the "Showing X of Y" counter visible beside the inline search field, after the search and any `#after-search` controls (only relevant with `inlineSearch`, which otherwise takes the counter's place). Fed from the manifest as `pages[].config.showCountWithSearch`. |
|
|
393
394
|
| `searchInFiles` | Boolean | `false` | "Also search inside files" switch beside the search box (manifest `config.searchInFiles`). On, a search with a term sends `_content_search=true`; rows found through a file show "Found in {file}" (`@self.matchedFile`). State kept in the route as `contentSearch=1`. Emits `content-search`. |
|
|
394
395
|
| `searchPlaceholder` | String | `''` | Placeholder for the inline search field (manifest `config.searchPlaceholder`). |
|
|
@@ -878,8 +879,9 @@ The list view (`view-mode="list"`) and standalone sort dropdown add these props:
|
|
|
878
879
|
| `listConfig` | Object | `{}` | Field mapping for the default list rows (`CnObjectRow`). |
|
|
879
880
|
| `listComponent` | String | `''` | Custom row component, resolved against the v2 `registry` (any kind carrying a `component`) and then the legacy `customComponents` map. |
|
|
880
881
|
| `showSortSelect` | Boolean | `false` | Show a standalone sort dropdown in the actions bar. |
|
|
881
|
-
| `sortSelectOptions` | Array | `[]` | Options `{ value, label }` for the sort dropdown. |
|
|
882
|
-
| `sortSelectValue` | String | `''` | Selected sort dropdown value
|
|
882
|
+
| `sortSelectOptions` | Array | `[]` | Options `{ value, label, order? }` for the sort dropdown. On a self-fetch page a choice orders the list as a header click does (`_order`, kept in the address): by the option's `order`, else its `value` read as an order (`"-createdAt"`, `"priority:desc,title"`). |
|
|
883
|
+
| `sortSelectValue` | String | `''` | Selected sort dropdown value. Empty lets a self-fetch page show the option matching its current order, so a header click that matches an option shows it. |
|
|
884
|
+
| `defaultOrder` | String, Object or Array | `''` | The order a self-fetch page opens on when neither the address nor `sortKeys`/`sortKey` gives one (manifest `config.defaultOrder`): `"-createdAt"`, `"priority:desc,title"`, an `_order` map `{ "priority": "desc" }` or a `[{ key, order }]` list. |
|
|
883
885
|
|
|
884
886
|
The `#list-item`, `#row-icon`, `#row-badges`, and `#row-actions` slots override the list rows (see [CnObjectList](./cn-object-list.md)). Emits `@sort-change` with the chosen sort value.
|
|
885
887
|
|
|
@@ -1094,6 +1096,16 @@ Sources: `register` (fetch the folder list from an OpenRegister `register`/`sche
|
|
|
1094
1096
|
}
|
|
1095
1097
|
```
|
|
1096
1098
|
|
|
1099
|
+
`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):
|
|
1100
|
+
|
|
1101
|
+
```json
|
|
1102
|
+
"folderSidebar": {
|
|
1103
|
+
"source": "register", "register": "dossiq", "schema": "caseType",
|
|
1104
|
+
"idField": "@self.uuid", "nameField": "title",
|
|
1105
|
+
"filterField": "caseType", "pane": false
|
|
1106
|
+
}
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1097
1109
|
### A column set per folder
|
|
1098
1110
|
|
|
1099
1111
|
A folder is also a scope. Give it `columns`, `defaultSort` or `searchFields`
|