@conduction/nextcloud-vue 2.111.0 → 2.112.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/CnActionDialog/CnActionDialog.vue.js +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +3 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +4 -2
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +3 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +54 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +16 -2
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +10 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +2 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +22 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +17 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +32 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +31 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +48 -11
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +160 -97
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +2 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +166 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +19 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +21 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +36 -2
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +44 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +22 -2
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +36 -16
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -1
- package/dist/esm/composables/useRefLabels.js +26 -6
- package/dist/esm/composables/useRefLabels.js.map +1 -1
- package/dist/esm/integrations/registry.js +4 -0
- package/dist/esm/integrations/registry.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +533 -273
- package/dist/esm/store/plugins/liveUpdates.js +3 -1
- package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
- package/dist/esm/store/plugins/logs.js +2 -1
- package/dist/esm/store/plugins/logs.js.map +1 -1
- package/dist/esm/utils/detailActionModel.js +28 -1
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +13 -5
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/headerFieldChips.js +21 -2
- package/dist/esm/utils/headerFieldChips.js.map +1 -1
- package/dist/esm/utils/mapList.js +26 -11
- package/dist/esm/utils/mapList.js.map +1 -1
- package/dist/esm/utils/savedViewMenu.js +12 -5
- package/dist/esm/utils/savedViewMenu.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2562 -1886
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +533 -273
- package/l10n/en.json +3 -1
- package/l10n/nl.json +3 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
- package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -2
- package/src/components/CnBoardView/CnBoardView.vue +58 -5
- package/src/components/CnCardGrid/CnCardGrid.md +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +11 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +27 -3
- package/src/components/CnChartWidget/CnChartWidget.vue +6 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +42 -9
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +174 -4
- package/src/components/CnNcDashboardList/CnNcDashboardList.vue +21 -1
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +51 -5
- package/src/components/CnPartyPicker/CnPartyPicker.vue +62 -16
- package/src/components/CnSearchSelect/CnSearchSelect.vue +8 -0
- package/src/composables/useRefLabels.js +25 -5
- package/src/css/dialog.css +53 -3
- package/src/css/index-page.css +26 -1
- package/src/css/look-board-detail.css +36 -15
- package/src/css/look-board-index.css +69 -3
- package/src/css/look-board.css +30 -2
- package/src/integrations/registry.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +11 -5
- package/src/store/plugins/liveUpdates.js +3 -1
- package/src/store/plugins/logs.js +2 -1
- package/src/utils/detailActionModel.js +27 -0
- package/src/utils/fillTemplate.js +13 -5
- package/src/utils/headerFieldChips.js +21 -2
- package/src/utils/mapList.js +26 -11
- package/src/utils/savedViewMenu.js +12 -5
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -947,7 +947,11 @@
|
|
|
947
947
|
<CnStatusBadge v-for="(pill, index) in item.pills"
|
|
948
948
|
:key="index"
|
|
949
949
|
:label="pill.label"
|
|
950
|
-
:variant="pill.variant"
|
|
950
|
+
:variant="pill.variant">
|
|
951
|
+
<template v-if="pill.icon" #icon>
|
|
952
|
+
<CnIcon :name="pill.icon" :size="14" />
|
|
953
|
+
</template>
|
|
954
|
+
</CnStatusBadge>
|
|
951
955
|
<span v-if="item.due" class="cn-index-page__map-due">{{ item.due }}</span>
|
|
952
956
|
</span>
|
|
953
957
|
</div>
|
|
@@ -995,6 +999,7 @@
|
|
|
995
999
|
:swimlaneField="board.swimlaneField || ''"
|
|
996
1000
|
:dueRule="board.dueRule || null"
|
|
997
1001
|
:cardRoles="board.card || null"
|
|
1002
|
+
:pillLooks="refLooks"
|
|
998
1003
|
:colorField="board.colorField || ''"
|
|
999
1004
|
:sumField="board.sumField || ''"
|
|
1000
1005
|
:sumFormat="boardSumFormat"
|
|
@@ -1047,7 +1052,7 @@
|
|
|
1047
1052
|
v-else-if="currentViewMode === 'list'"
|
|
1048
1053
|
:objects="displayObjects"
|
|
1049
1054
|
:schema="effectiveSchema"
|
|
1050
|
-
:config="
|
|
1055
|
+
:config="renderedListConfig"
|
|
1051
1056
|
:selectable="selectable"
|
|
1052
1057
|
:selectedIds="internalSelectedIds"
|
|
1053
1058
|
:rowKey="rowKey"
|
|
@@ -1107,6 +1112,7 @@
|
|
|
1107
1112
|
:clickToView="rowClickOpens"
|
|
1108
1113
|
:cardFields="resolvedCardFields"
|
|
1109
1114
|
:cardLayout="cardLayout"
|
|
1115
|
+
:pillLooks="refLooks"
|
|
1110
1116
|
:cardFieldColumns="cardFieldColumns"
|
|
1111
1117
|
:accentOf="cardAccent"
|
|
1112
1118
|
:selectedIds="internalSelectedIds"
|
|
@@ -1763,6 +1769,8 @@ export default {
|
|
|
1763
1769
|
|
|
1764
1770
|
inject: {
|
|
1765
1771
|
cnCustomComponents: { default: () => ({}) },
|
|
1772
|
+
/** The app's named formatters, provided by CnAppRoot, for the map list's pills and template tokens. */
|
|
1773
|
+
cnFormatters: { default: () => ({}) },
|
|
1766
1774
|
/** The permissions the user holds, provided by CnAppRoot; for gated page tabs. */
|
|
1767
1775
|
cnPermissions: { default: () => [] },
|
|
1768
1776
|
/** The app config an ancestor provides, for `@config.<key>` in quick-filter labels. */
|
|
@@ -4121,6 +4129,8 @@ export default {
|
|
|
4121
4129
|
boardPrevPageSize: this.viewMode === 'board' ? ((this.pagination && this.pagination.limit) || 20) : null,
|
|
4122
4130
|
/** Resolved labels of reference columns: `{ [columnKey]: { [id]: label|null } }`. */
|
|
4123
4131
|
refLabels: {},
|
|
4132
|
+
/** Colour and icon names of the records badge columns point at: `{ [columnKey]: { [id]: { color, icon } } }`. */
|
|
4133
|
+
refLooks: {},
|
|
4124
4134
|
/** Count per tab-strip index, for the entries that asked for one; null = none. */
|
|
4125
4135
|
tabCounts: null,
|
|
4126
4136
|
// The counts of the `filterStrip` pills, by pill key ('all', 'v0', ...).
|
|
@@ -6035,6 +6045,12 @@ export default {
|
|
|
6035
6045
|
*/
|
|
6036
6046
|
renderedColumns() {
|
|
6037
6047
|
let cols = this.tableColumns
|
|
6048
|
+
if (this.refLookSpecs.length > 0) {
|
|
6049
|
+
const lookKeys = new Set(this.refLookSpecs.map((spec) => spec.key))
|
|
6050
|
+
cols = cols.map((col) => (col && typeof col === 'object' && lookKeys.has(col.key)
|
|
6051
|
+
? { ...col, widgetProps: { ...(col.widgetProps || {}), looks: this.refLooks[col.key] || {} } }
|
|
6052
|
+
: col))
|
|
6053
|
+
}
|
|
6038
6054
|
if (this.refLabelSpecs.length > 0) {
|
|
6039
6055
|
const byKey = new Map(this.refLabelSpecs.map((s) => [s.key, s]))
|
|
6040
6056
|
cols = cols.map((col) => {
|
|
@@ -6105,6 +6121,108 @@ export default {
|
|
|
6105
6121
|
*
|
|
6106
6122
|
* @return {Object<string, string[]>} Distinct ids per column key.
|
|
6107
6123
|
*/
|
|
6124
|
+
/**
|
|
6125
|
+
* The pills that take their tone and icon from the record they point at
|
|
6126
|
+
* (`resolve: { colorField?, iconField?, register?, schema? }`): badge
|
|
6127
|
+
* columns and the list view's leading and trailing cells
|
|
6128
|
+
* (`widgetProps.resolve`), the board card's pill (`board.card.resolve`)
|
|
6129
|
+
* and the record card's pills (`cardLayout` headPill, footerPill, chips).
|
|
6130
|
+
* Register and schema default to the field's reference in the schema.
|
|
6131
|
+
*
|
|
6132
|
+
* @return {Array<{key: string, register: string, schema: string, colorField: string, iconField: string}>} The specs.
|
|
6133
|
+
* @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
|
|
6134
|
+
*/
|
|
6135
|
+
refLookSpecs() {
|
|
6136
|
+
const list = this.listConfig || {}
|
|
6137
|
+
const listCells = [list.leading, ...(Array.isArray(list.trailing) ? list.trailing : [list.trailing])]
|
|
6138
|
+
const badgeSources = [...this.tableColumns, ...listCells]
|
|
6139
|
+
.filter((c) => c && typeof c === 'object' && c.widget === 'badge' && c.widgetProps)
|
|
6140
|
+
.map((c) => ({ key: c.key, resolve: c.widgetProps.resolve }))
|
|
6141
|
+
// The board card's pill (`board.card.pill` with `board.card.resolve`)
|
|
6142
|
+
// and the record card's pills (`cardLayout` headPill, footerPill,
|
|
6143
|
+
// chips, each with its own `resolve`).
|
|
6144
|
+
const card = this.board && this.board.card && typeof this.board.card === 'object' ? this.board.card : {}
|
|
6145
|
+
const layout = this.cardLayout && typeof this.cardLayout === 'object' ? this.cardLayout : {}
|
|
6146
|
+
const layoutPills = [layout.headPill, layout.footerPill, ...(Array.isArray(layout.chips) ? layout.chips : [])]
|
|
6147
|
+
.filter((def) => def && typeof def === 'object')
|
|
6148
|
+
.map((def) => ({ key: def.field, resolve: def.resolve }))
|
|
6149
|
+
// The map list's pills (`mapConfig.list.pills`, each with its own `resolve`).
|
|
6150
|
+
const mapListPills = (this.mapListEnabled && Array.isArray(this.mapConfig.list.pills) ? this.mapConfig.list.pills : [])
|
|
6151
|
+
.filter((def) => def && typeof def === 'object')
|
|
6152
|
+
.map((def) => ({ key: def.path, resolve: def.resolve }))
|
|
6153
|
+
const seen = new Set()
|
|
6154
|
+
const cols = [...badgeSources, { key: card.pill, resolve: card.resolve }, ...layoutPills, ...mapListPills].filter((c) => {
|
|
6155
|
+
const wanted = typeof c.key === 'string' && c.key !== '' && c.resolve && typeof c.resolve === 'object'
|
|
6156
|
+
&& (c.resolve.colorField || c.resolve.iconField) && !seen.has(c.key)
|
|
6157
|
+
if (wanted) {
|
|
6158
|
+
seen.add(c.key)
|
|
6159
|
+
}
|
|
6160
|
+
return wanted
|
|
6161
|
+
})
|
|
6162
|
+
if (cols.length === 0) {
|
|
6163
|
+
return []
|
|
6164
|
+
}
|
|
6165
|
+
const fields = this.effectiveSchema ? fieldsFromSchema(this.effectiveSchema, { includeReadOnly: true }) : []
|
|
6166
|
+
const specs = []
|
|
6167
|
+
for (const col of cols) {
|
|
6168
|
+
const resolve = col.resolve
|
|
6169
|
+
const field = fields.find((f) => f.key === col.key)
|
|
6170
|
+
const ref = (field && field.reference) || {}
|
|
6171
|
+
const register = resolve.register || ref.register || (typeof this.register === 'string' ? this.register : '')
|
|
6172
|
+
const schema = resolve.schema || (ref.schema !== undefined && ref.schema !== null ? String(ref.schema) : '')
|
|
6173
|
+
if (!register || !schema) {
|
|
6174
|
+
continue
|
|
6175
|
+
}
|
|
6176
|
+
specs.push({ key: col.key, register, schema, colorField: resolve.colorField || '', iconField: resolve.iconField || '' })
|
|
6177
|
+
}
|
|
6178
|
+
return specs
|
|
6179
|
+
},
|
|
6180
|
+
|
|
6181
|
+
/**
|
|
6182
|
+
* `listConfig` with the referenced records' looks on its badge cells,
|
|
6183
|
+
* as `renderedColumns` does for the table.
|
|
6184
|
+
*
|
|
6185
|
+
* @return {object} The list view's config.
|
|
6186
|
+
* @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
|
|
6187
|
+
*/
|
|
6188
|
+
renderedListConfig() {
|
|
6189
|
+
const list = this.listConfig
|
|
6190
|
+
if (!list || this.refLookSpecs.length === 0) {
|
|
6191
|
+
return list
|
|
6192
|
+
}
|
|
6193
|
+
const lookKeys = new Set(this.refLookSpecs.map((spec) => spec.key))
|
|
6194
|
+
const withLooks = (cell) => (cell && typeof cell === 'object' && lookKeys.has(cell.key)
|
|
6195
|
+
? { ...cell, widgetProps: { ...(cell.widgetProps || {}), looks: this.refLooks[cell.key] || {} } }
|
|
6196
|
+
: cell)
|
|
6197
|
+
return {
|
|
6198
|
+
...list,
|
|
6199
|
+
...(list.leading ? { leading: withLooks(list.leading) } : {}),
|
|
6200
|
+
...(list.trailing ? { trailing: Array.isArray(list.trailing) ? list.trailing.map(withLooks) : withLooks(list.trailing) } : {}),
|
|
6201
|
+
}
|
|
6202
|
+
},
|
|
6203
|
+
|
|
6204
|
+
/**
|
|
6205
|
+
* The ids each `refLookSpecs` column shows on screen.
|
|
6206
|
+
*
|
|
6207
|
+
* @return {{[key: string]: Array<string>}} Sorted ids by column key.
|
|
6208
|
+
*/
|
|
6209
|
+
refLookIds() {
|
|
6210
|
+
const out = {}
|
|
6211
|
+
for (const spec of this.refLookSpecs) {
|
|
6212
|
+
const ids = new Set()
|
|
6213
|
+
for (const row of this.displayObjects) {
|
|
6214
|
+
const v = row ? row[spec.key] : undefined
|
|
6215
|
+
;(Array.isArray(v) ? v : [v]).forEach((id) => {
|
|
6216
|
+
if (id !== null && id !== undefined && id !== '' && typeof id !== 'object') {
|
|
6217
|
+
ids.add(String(id))
|
|
6218
|
+
}
|
|
6219
|
+
})
|
|
6220
|
+
}
|
|
6221
|
+
out[spec.key] = [...ids].sort()
|
|
6222
|
+
}
|
|
6223
|
+
return out
|
|
6224
|
+
},
|
|
6225
|
+
|
|
6108
6226
|
refLabelIds() {
|
|
6109
6227
|
const out = {}
|
|
6110
6228
|
const facets = this.storeFacets || this.resolvedSidebar.facets || {}
|
|
@@ -6372,13 +6490,15 @@ export default {
|
|
|
6372
6490
|
const translated = {
|
|
6373
6491
|
...list,
|
|
6374
6492
|
title: this.cnTranslate(list.title),
|
|
6375
|
-
sub: list.sub ? this.cnTranslate(list.sub) : '',
|
|
6493
|
+
sub: Array.isArray(list.sub) ? list.sub.map((s) => this.cnTranslate(s)) : (list.sub ? this.cnTranslate(list.sub) : ''),
|
|
6376
6494
|
due: list.due ? this.cnTranslate(list.due) : '',
|
|
6377
6495
|
}
|
|
6378
6496
|
return mapListItems(this.displayObjects, translated, {
|
|
6379
6497
|
rowKey: this.rowKey,
|
|
6380
6498
|
located: (row) => Boolean(this.resolveRowGeometry(row)),
|
|
6381
6499
|
translate: (text) => this.cnTranslate(text),
|
|
6500
|
+
formatters: this.cnFormatters,
|
|
6501
|
+
looks: this.refLooks,
|
|
6382
6502
|
})
|
|
6383
6503
|
},
|
|
6384
6504
|
|
|
@@ -8002,6 +8122,15 @@ export default {
|
|
|
8002
8122
|
},
|
|
8003
8123
|
},
|
|
8004
8124
|
|
|
8125
|
+
// Read the colour and icon of the records badge columns point at.
|
|
8126
|
+
refLookIds: {
|
|
8127
|
+
immediate: true,
|
|
8128
|
+
deep: true,
|
|
8129
|
+
handler() {
|
|
8130
|
+
this.loadRefLooks()
|
|
8131
|
+
},
|
|
8132
|
+
},
|
|
8133
|
+
|
|
8005
8134
|
// The Active line names a referenced value, not its id.
|
|
8006
8135
|
activeFilterRefIds: {
|
|
8007
8136
|
deep: true,
|
|
@@ -8453,6 +8582,45 @@ export default {
|
|
|
8453
8582
|
}))
|
|
8454
8583
|
},
|
|
8455
8584
|
|
|
8585
|
+
/**
|
|
8586
|
+
* Read the colour and icon names `refLookSpecs` columns need, for the
|
|
8587
|
+
* rows on screen, with the same resolver as the labels. A failure
|
|
8588
|
+
* leaves the badge as declared.
|
|
8589
|
+
*
|
|
8590
|
+
* @return {Promise<void>}
|
|
8591
|
+
* @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
|
|
8592
|
+
*/
|
|
8593
|
+
async loadRefLooks() {
|
|
8594
|
+
if (this.refLookSpecs.length === 0) {
|
|
8595
|
+
return
|
|
8596
|
+
}
|
|
8597
|
+
if (!this._refLabelResolver) {
|
|
8598
|
+
this._refLabelResolver = createRefLabelResolver(() => {
|
|
8599
|
+
try {
|
|
8600
|
+
return (this.list && this.list.objectStore) || useObjectStore()
|
|
8601
|
+
} catch {
|
|
8602
|
+
return null
|
|
8603
|
+
}
|
|
8604
|
+
})
|
|
8605
|
+
}
|
|
8606
|
+
const read = (spec, ids, name) => (name
|
|
8607
|
+
? this._refLabelResolver.resolve(spec.register, spec.schema, ids, name, { strict: true }).catch(() => ({}))
|
|
8608
|
+
: Promise.resolve({}))
|
|
8609
|
+
await Promise.all(this.refLookSpecs.map(async (spec) => {
|
|
8610
|
+
const known = this.refLooks[spec.key] || {}
|
|
8611
|
+
const ids = (this.refLookIds[spec.key] || []).filter((id) => !Object.hasOwn(known, id))
|
|
8612
|
+
if (ids.length === 0) {
|
|
8613
|
+
return
|
|
8614
|
+
}
|
|
8615
|
+
const [colors, icons] = await Promise.all([read(spec, ids, spec.colorField), read(spec, ids, spec.iconField)])
|
|
8616
|
+
const found = {}
|
|
8617
|
+
ids.forEach((id) => {
|
|
8618
|
+
found[id] = { color: (colors && colors[id]) || null, icon: (icons && icons[id]) || null }
|
|
8619
|
+
})
|
|
8620
|
+
this.refLooks = { ...this.refLooks, [spec.key]: { ...(this.refLooks[spec.key] || {}), ...found } }
|
|
8621
|
+
}))
|
|
8622
|
+
},
|
|
8623
|
+
|
|
8456
8624
|
/**
|
|
8457
8625
|
* Whether a saved view shows its record count (`viewCounts`).
|
|
8458
8626
|
*
|
|
@@ -11614,7 +11782,7 @@ export default {
|
|
|
11614
11782
|
return
|
|
11615
11783
|
}
|
|
11616
11784
|
const badge = this.savedViewMenuBadge(view)
|
|
11617
|
-
menu.toggle({ id: String(view.id || view.slug), page: this.savedViewPage, name: view.name, ...(badge ? { badge } : {}) })
|
|
11785
|
+
menu.toggle({ id: String(view.id || view.slug), page: this.savedViewPage, name: view.name, ...(typeof view.icon === 'string' && view.icon !== '' ? { icon: view.icon } : {}), ...(badge ? { badge } : {}) })
|
|
11618
11786
|
},
|
|
11619
11787
|
|
|
11620
11788
|
/**
|
|
@@ -12149,7 +12317,9 @@ export default {
|
|
|
12149
12317
|
if (resultData && resultData.success && this._refLabelResolver) {
|
|
12150
12318
|
this._refLabelResolver.invalidate()
|
|
12151
12319
|
this.refLabels = {}
|
|
12320
|
+
this.refLooks = {}
|
|
12152
12321
|
this.loadRefLabels()
|
|
12322
|
+
this.loadRefLooks()
|
|
12153
12323
|
}
|
|
12154
12324
|
},
|
|
12155
12325
|
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import { translate as t } from '@nextcloud/l10n'
|
|
33
33
|
import { generateUrl } from '@nextcloud/router'
|
|
34
34
|
import { fetchEndpointSource } from '../../composables/useEndpointSource.js'
|
|
35
|
+
import { resolveDue } from '../../utils/dueText.js'
|
|
35
36
|
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
36
37
|
import { openRowTarget } from '../../utils/linkNavigation.js'
|
|
37
38
|
import { readPath } from '../../utils/readPath.js'
|
|
@@ -70,6 +71,9 @@ function appUrl(url) {
|
|
|
70
71
|
* - `itemsPath`: the rows' path in the response, when not `results`.
|
|
71
72
|
* - `limit`: the most rows shown (default 7).
|
|
72
73
|
* - `title`, `subtitle`: templates filled from a row (default `{title}`, none).
|
|
74
|
+
* - `due`: `{ field, ...dueText config }`: the trailing line says how a date
|
|
75
|
+
* stands ("3 days overdue", "Due today", the date or the days left), in
|
|
76
|
+
* place of `subtitle` (see `resolveDue`).
|
|
73
77
|
* - `href`: a template for a row's page (`/apps/dossiq/cases/{id}`).
|
|
74
78
|
* - `viewAll`: `{ href, label? }`.
|
|
75
79
|
* - `emptyText`: text for an empty list.
|
|
@@ -125,7 +129,7 @@ export default {
|
|
|
125
129
|
return {
|
|
126
130
|
id: String(id),
|
|
127
131
|
mainText: this.fill(this.config.title || '{title}', item) || String(id),
|
|
128
|
-
subText: this.
|
|
132
|
+
subText: this.subtitleOf(item),
|
|
129
133
|
href: this.config.href ? appUrl(fillTemplate(this.config.href, { ...item, id })) : '',
|
|
130
134
|
}
|
|
131
135
|
})
|
|
@@ -155,6 +159,22 @@ export default {
|
|
|
155
159
|
return typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''
|
|
156
160
|
},
|
|
157
161
|
|
|
162
|
+
/**
|
|
163
|
+
* A row's trailing line: how its due date stands, or the subtitle template.
|
|
164
|
+
*
|
|
165
|
+
* @spec openspec/changes/nc-dashboard-due/specs/nc-dashboard-widgets/spec.md#requirement-a-dashboard-row-says-how-its-date-stands
|
|
166
|
+
* @param {object} item The row.
|
|
167
|
+
* @return {string}
|
|
168
|
+
*/
|
|
169
|
+
subtitleOf(item) {
|
|
170
|
+
const due = this.config.due
|
|
171
|
+
if (due && typeof due === 'object' && due.field) {
|
|
172
|
+
const resolved = resolveDue(readPath(item, due.field), due, (text) => this.cnTranslate(text))
|
|
173
|
+
return resolved ? resolved.text : ''
|
|
174
|
+
}
|
|
175
|
+
return this.fill(this.config.subtitle, item)
|
|
176
|
+
},
|
|
177
|
+
|
|
158
178
|
/**
|
|
159
179
|
* A template filled from a row, translated first.
|
|
160
180
|
*
|
|
@@ -138,6 +138,7 @@ export default {
|
|
|
138
138
|
|---|---|---|---|
|
|
139
139
|
| `maxMetadata` | `Number` | `4` | Maximum number of schema properties to show in the metadata section. Properties are sorted by their `order` value before slicing |
|
|
140
140
|
| `accent` | `Object` | `null` | Status accent `{ variant, icon?, label? }`: a colored start border and, with `icon`, a colored icon before the title. `variant` is `success`, `warning`, `error`, `info` or `primary`; `label` is the icon's tooltip and accessible name |
|
|
141
|
+
| `pillLooks` | `Object` | `{}` | The referenced records' colour and icon names, by field and id (`{ [field]: { [id]: { color, icon } } }`, read by CnIndexPage). A record layout pill with `resolve: { colorField?, iconField? }` takes its record's tone and icon from here. |
|
|
141
142
|
|
|
142
143
|
## Slots
|
|
143
144
|
|
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
class="cn-object-card__record-pill"
|
|
25
25
|
:label="recordHeadPill.label"
|
|
26
26
|
:variant="recordHeadPill.variant"
|
|
27
|
-
data-testid="cn-object-card-head-pill"
|
|
27
|
+
data-testid="cn-object-card-head-pill">
|
|
28
|
+
<template v-if="recordHeadPill.icon" #icon>
|
|
29
|
+
<CnIcon :name="recordHeadPill.icon" :size="14" />
|
|
30
|
+
</template>
|
|
31
|
+
</CnStatusBadge>
|
|
28
32
|
<span v-if="recordNumber" class="cn-object-card__record-number" data-testid="cn-object-card-number">{{ recordNumber }}</span>
|
|
29
33
|
<div
|
|
30
34
|
v-if="$slots.actions"
|
|
@@ -63,7 +67,11 @@
|
|
|
63
67
|
:key="chip.field"
|
|
64
68
|
class="cn-object-card__record-pill"
|
|
65
69
|
:label="chip.label"
|
|
66
|
-
:variant="chip.variant"
|
|
70
|
+
:variant="chip.variant">
|
|
71
|
+
<template v-if="chip.icon" #icon>
|
|
72
|
+
<CnIcon :name="chip.icon" :size="14" />
|
|
73
|
+
</template>
|
|
74
|
+
</CnStatusBadge>
|
|
67
75
|
</div>
|
|
68
76
|
<div v-if="$slots.badges" class="cn-object-card__badges">
|
|
69
77
|
<slot name="badges" :object="object" />
|
|
@@ -74,7 +82,11 @@
|
|
|
74
82
|
class="cn-object-card__record-pill"
|
|
75
83
|
:label="recordFootPill.label"
|
|
76
84
|
:variant="recordFootPill.variant"
|
|
77
|
-
data-testid="cn-object-card-foot-pill"
|
|
85
|
+
data-testid="cn-object-card-foot-pill">
|
|
86
|
+
<template v-if="recordFootPill.icon" #icon>
|
|
87
|
+
<CnIcon :name="recordFootPill.icon" :size="14" />
|
|
88
|
+
</template>
|
|
89
|
+
</CnStatusBadge>
|
|
78
90
|
<span
|
|
79
91
|
v-if="recordDate"
|
|
80
92
|
class="cn-object-card__record-date"
|
|
@@ -207,6 +219,7 @@ import { normalizeLook } from '../../composables/useLook.js'
|
|
|
207
219
|
import { isBadgeVariant } from '../../utils/badgeVariants.js'
|
|
208
220
|
import { formatBoardDate } from '../../utils/boardDate.js'
|
|
209
221
|
import { resolveDateVariant } from '../../utils/dateVariant.js'
|
|
222
|
+
import { pillLook } from '../../utils/detailActionModel.js'
|
|
210
223
|
import { resolveDue } from '../../utils/dueText.js'
|
|
211
224
|
import { readPath } from '../../utils/readPath.js'
|
|
212
225
|
import { resolveImageUrl } from '../../utils/resolveImageUrl.js'
|
|
@@ -375,6 +388,20 @@ export default {
|
|
|
375
388
|
default: null,
|
|
376
389
|
},
|
|
377
390
|
|
|
391
|
+
/**
|
|
392
|
+
* The referenced records' colour and icon names, by field and id
|
|
393
|
+
* (`{ [field]: { [id]: { color, icon } } }`, CnIndexPage reads them).
|
|
394
|
+
* A record layout pill with `resolve: { colorField?, iconField? }` takes
|
|
395
|
+
* its record's tone and icon from here.
|
|
396
|
+
*
|
|
397
|
+
* @type {object}
|
|
398
|
+
* @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-card-pill-takes-its-tone-and-icon-from-the-referenced-record
|
|
399
|
+
*/
|
|
400
|
+
pillLooks: {
|
|
401
|
+
type: Object,
|
|
402
|
+
default: () => ({}),
|
|
403
|
+
},
|
|
404
|
+
|
|
378
405
|
/**
|
|
379
406
|
* The property keys the facts list shows, in order (manifest key
|
|
380
407
|
* `config.cardFields`). Without it the card shows the first
|
|
@@ -588,7 +615,7 @@ export default {
|
|
|
588
615
|
const labels = def.labels && typeof def.labels === 'object' ? def.labels : {}
|
|
589
616
|
const shown = typeof labels[String(raw)] === 'string' ? this.cnTranslate(labels[String(raw)]) : label
|
|
590
617
|
const variant = mapped || def.variant
|
|
591
|
-
return { field: def.field, label: shown, variant: isBadgeVariant(variant) ? variant : 'default' }
|
|
618
|
+
return this.withLook({ field: def.field, label: shown, variant: isBadgeVariant(variant) ? variant : 'default' }, def)
|
|
592
619
|
})
|
|
593
620
|
.filter(Boolean)
|
|
594
621
|
},
|
|
@@ -838,7 +865,26 @@ export default {
|
|
|
838
865
|
const colorMap = def.colorMap && typeof def.colorMap === 'object' ? def.colorMap : {}
|
|
839
866
|
const key = Object.keys(colorMap).find((k) => k.toLowerCase() === value.toLowerCase())
|
|
840
867
|
const variant = key !== undefined ? colorMap[key] : def.variant
|
|
841
|
-
return { label, variant: isBadgeVariant(variant) ? variant : 'default' }
|
|
868
|
+
return this.withLook({ label, variant: isBadgeVariant(variant) ? variant : 'default' }, def)
|
|
869
|
+
},
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Lay the referenced record's tone and icon over a layout pill that
|
|
873
|
+
* declares `resolve` (see `pillLooks`).
|
|
874
|
+
*
|
|
875
|
+
* @param {{label: string, variant: string}} pill The pill as declared.
|
|
876
|
+
* @param {object} def The pill's definition.
|
|
877
|
+
* @return {{label: string, variant: string, icon?: string}} The pill.
|
|
878
|
+
* @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-card-pill-takes-its-tone-and-icon-from-the-referenced-record
|
|
879
|
+
*/
|
|
880
|
+
withLook(pill, def) {
|
|
881
|
+
if (!def || !def.resolve || !this.pillLooks || typeof this.pillLooks !== 'object') {
|
|
882
|
+
return pill
|
|
883
|
+
}
|
|
884
|
+
const looks = this.pillLooks[def.field]
|
|
885
|
+
const raw = readPath(this.object, def.field)
|
|
886
|
+
const look = pillLook(looks && raw !== null && raw !== undefined ? looks[String(raw)] : null)
|
|
887
|
+
return { ...pill, ...(look.variant ? { variant: look.variant } : {}), ...(look.icon ? { icon: look.icon } : {}) }
|
|
842
888
|
},
|
|
843
889
|
|
|
844
890
|
/**
|
|
@@ -3,24 +3,39 @@
|
|
|
3
3
|
- SPDX-License-Identifier: EUPL-1.2
|
|
4
4
|
-->
|
|
5
5
|
<template>
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
<div class="cn-party-picker">
|
|
7
|
+
<CnSearchSelect
|
|
8
|
+
:modelValue="selectedOption"
|
|
9
|
+
:options="options"
|
|
10
|
+
:inputLabel="inputLabel"
|
|
11
|
+
:labelOutside="labelOutside"
|
|
12
|
+
:placeholder="placeholder ? cnTranslate(placeholder) : ''"
|
|
13
|
+
:loading="searching"
|
|
14
|
+
:filterable="false"
|
|
15
|
+
:clearable="clearable"
|
|
16
|
+
:disabled="disabled"
|
|
17
|
+
data-testid="cn-party-picker"
|
|
18
|
+
@search="onSearch"
|
|
19
|
+
@update:modelValue="onPick" />
|
|
20
|
+
<p v-if="refusal"
|
|
21
|
+
class="cn-party-picker__refusal"
|
|
22
|
+
role="alert"
|
|
23
|
+
data-testid="cn-party-picker-refusal">
|
|
24
|
+
{{ t('nextcloud-vue', 'This search could not be read.') }} {{ refusal }}
|
|
25
|
+
</p>
|
|
26
|
+
<p v-for="name in unavailable"
|
|
27
|
+
:key="name"
|
|
28
|
+
class="cn-party-picker__unavailable"
|
|
29
|
+
role="status"
|
|
30
|
+
data-testid="cn-party-picker-unavailable">
|
|
31
|
+
{{ t('nextcloud-vue', '{source} could not be searched, so this list may be incomplete.', { source: name }) }}
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
20
34
|
</template>
|
|
21
35
|
|
|
22
36
|
<script>
|
|
23
37
|
import axios from '@nextcloud/axios'
|
|
38
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
24
39
|
import { generateUrl } from '@nextcloud/router'
|
|
25
40
|
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
26
41
|
import { CnSearchSelect } from '../CnSearchSelect/index.js'
|
|
@@ -142,6 +157,8 @@ export default {
|
|
|
142
157
|
known: null,
|
|
143
158
|
timer: null,
|
|
144
159
|
searchSeq: 0,
|
|
160
|
+
refusal: '',
|
|
161
|
+
unavailable: [],
|
|
145
162
|
}
|
|
146
163
|
},
|
|
147
164
|
|
|
@@ -185,6 +202,8 @@ export default {
|
|
|
185
202
|
},
|
|
186
203
|
|
|
187
204
|
methods: {
|
|
205
|
+
t,
|
|
206
|
+
|
|
188
207
|
/**
|
|
189
208
|
* A source's row as a picker option.
|
|
190
209
|
*
|
|
@@ -244,7 +263,9 @@ export default {
|
|
|
244
263
|
},
|
|
245
264
|
|
|
246
265
|
/**
|
|
247
|
-
* Search every source at once
|
|
266
|
+
* Search every source at once. A term a source refused (400) is said
|
|
267
|
+
* under the field in its words; a source that could not be searched at
|
|
268
|
+
* all is named, so an empty list never hides a missing source.
|
|
248
269
|
*
|
|
249
270
|
* @spec openspec/changes/party-picker/specs/party-picker/spec.md#requirement-one-search-field-over-several-kinds-of-party
|
|
250
271
|
* @param {string} term The search term.
|
|
@@ -253,6 +274,8 @@ export default {
|
|
|
253
274
|
async search(term) {
|
|
254
275
|
const seq = ++this.searchSeq
|
|
255
276
|
this.searching = true
|
|
277
|
+
let refusal = ''
|
|
278
|
+
const unavailable = []
|
|
256
279
|
const sets = await Promise.all((this.sources || []).map(async (source) => {
|
|
257
280
|
try {
|
|
258
281
|
if (source.type === 'contacts') {
|
|
@@ -264,7 +287,16 @@ export default {
|
|
|
264
287
|
}
|
|
265
288
|
const { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${source.register}/${source.schema}`), { params: { _search: term, _limit: source.limit || 8 } })
|
|
266
289
|
return (data && Array.isArray(data.results) ? data.results : []).map((row) => this.optionOf(source, row))
|
|
267
|
-
} catch {
|
|
290
|
+
} catch (failure) {
|
|
291
|
+
// OpenRegister refuses a term it cannot parse with a 400 rather
|
|
292
|
+
// than answering nothing, so that refusal is said, not hidden.
|
|
293
|
+
const response = failure && failure.response
|
|
294
|
+
if (response && response.status === 400) {
|
|
295
|
+
const body = response.data
|
|
296
|
+
refusal = String((body && (body.message || body.error)) || (typeof body === 'string' ? body : '') || refusal)
|
|
297
|
+
} else {
|
|
298
|
+
unavailable.push(source.label ? this.cnTranslate(source.label) : String(source.id || ''))
|
|
299
|
+
}
|
|
268
300
|
return []
|
|
269
301
|
}
|
|
270
302
|
}))
|
|
@@ -272,6 +304,8 @@ export default {
|
|
|
272
304
|
return
|
|
273
305
|
}
|
|
274
306
|
this.options = sets.flat().filter(Boolean)
|
|
307
|
+
this.refusal = refusal
|
|
308
|
+
this.unavailable = unavailable.filter(Boolean)
|
|
275
309
|
this.searching = false
|
|
276
310
|
},
|
|
277
311
|
|
|
@@ -315,3 +349,15 @@ export default {
|
|
|
315
349
|
},
|
|
316
350
|
}
|
|
317
351
|
</script>
|
|
352
|
+
|
|
353
|
+
<style scoped>
|
|
354
|
+
.cn-party-picker__refusal {
|
|
355
|
+
margin: 4px 0 0;
|
|
356
|
+
color: var(--color-error-text, var(--color-error));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.cn-party-picker__unavailable {
|
|
360
|
+
margin: 4px 0 0;
|
|
361
|
+
color: var(--color-text-maxcontrast);
|
|
362
|
+
}
|
|
363
|
+
</style>
|
|
@@ -214,6 +214,13 @@ export default {
|
|
|
214
214
|
position: relative;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/* The field takes the row: NcSelect is an inline block of at least 260px,
|
|
218
|
+
which left the magnifier, placed at the row's end, outside the field
|
|
219
|
+
(DqOverdragen "Nieuw team"). */
|
|
220
|
+
.cn-search-select :deep(.v-select) {
|
|
221
|
+
width: 100%;
|
|
222
|
+
}
|
|
223
|
+
|
|
217
224
|
/* The magnifier sits where the chevron was: in the field, at its end. */
|
|
218
225
|
.cn-search-select__magnifier {
|
|
219
226
|
position: absolute;
|
|
@@ -222,6 +229,7 @@ export default {
|
|
|
222
229
|
height: var(--default-clickable-area, 34px);
|
|
223
230
|
display: flex;
|
|
224
231
|
align-items: center;
|
|
232
|
+
z-index: 1;
|
|
225
233
|
color: var(--color-text-maxcontrast);
|
|
226
234
|
pointer-events: none;
|
|
227
235
|
}
|
|
@@ -65,6 +65,22 @@ export function pickRefLabel(obj, labelField) {
|
|
|
65
65
|
return null
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Read one field off a referenced object, with no fallback: a colour or an
|
|
70
|
+
* icon name must not fall back to the title.
|
|
71
|
+
*
|
|
72
|
+
* @param {object|null} obj The referenced object.
|
|
73
|
+
* @param {string} field The (possibly nested) property.
|
|
74
|
+
* @return {string|null} The value as text, or null when it is empty.
|
|
75
|
+
*/
|
|
76
|
+
export function pickRefField(obj, field) {
|
|
77
|
+
if (!obj || typeof obj !== 'object' || !field) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
const text = asText(readPath(obj, field))
|
|
81
|
+
return text !== '' ? text : null
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
/**
|
|
69
85
|
* Create a resolver bound to an object store. Labels are cached per register,
|
|
70
86
|
* schema and label field for the lifetime of the resolver.
|
|
@@ -91,9 +107,13 @@ export function createRefLabelResolver(getStore) {
|
|
|
91
107
|
* @param {string} schema Schema slug of the referenced objects.
|
|
92
108
|
* @param {string[]} ids Ids to resolve (duplicates are dropped).
|
|
93
109
|
* @param {string} labelField Property holding the label (dotted paths allowed).
|
|
110
|
+
* @param {{strict?: boolean}} [options] `strict`: read exactly that field, with no
|
|
111
|
+
* fallback to the title or name (a colour or an icon name).
|
|
94
112
|
* @return {Promise<Object<string, string|null>>} Label by id.
|
|
95
113
|
*/
|
|
96
|
-
async resolve(register, schema, ids, labelField) {
|
|
114
|
+
async resolve(register, schema, ids, labelField, options = {}) {
|
|
115
|
+
const strict = options.strict === true
|
|
116
|
+
const pick = strict ? pickRefField : pickRefLabel
|
|
97
117
|
const distinct = [...new Set((ids || []).filter((id) => id !== null && id !== undefined && id !== '').map(String))]
|
|
98
118
|
const out = {}
|
|
99
119
|
if (distinct.length === 0) {
|
|
@@ -115,14 +135,14 @@ export function createRefLabelResolver(getStore) {
|
|
|
115
135
|
})
|
|
116
136
|
return out
|
|
117
137
|
}
|
|
118
|
-
const hit = bucket(`${type}::${labelField || ''}`)
|
|
138
|
+
const hit = bucket(`${type}::${labelField || ''}::${strict ? 'field' : 'label'}`)
|
|
119
139
|
const missing = []
|
|
120
140
|
for (const id of distinct) {
|
|
121
141
|
const stored = store.objects && store.objects[type] && store.objects[type][id]
|
|
122
142
|
if (hit.has(id)) {
|
|
123
143
|
continue
|
|
124
144
|
}
|
|
125
|
-
const label = stored ?
|
|
145
|
+
const label = stored ? pick(stored, labelField) : null
|
|
126
146
|
if (label !== null) {
|
|
127
147
|
hit.set(id, label)
|
|
128
148
|
} else {
|
|
@@ -136,7 +156,7 @@ export function createRefLabelResolver(getStore) {
|
|
|
136
156
|
rows = await store.fetchCollectionForOptions(type, {
|
|
137
157
|
_ids: missing.join(','),
|
|
138
158
|
_limit: missing.length,
|
|
139
|
-
_fields: [...new Set([top, 'title', 'name'])].join(','),
|
|
159
|
+
_fields: (strict ? [top] : [...new Set([top, 'title', 'name'])]).join(','),
|
|
140
160
|
})
|
|
141
161
|
} catch {
|
|
142
162
|
rows = []
|
|
@@ -148,7 +168,7 @@ export function createRefLabelResolver(getStore) {
|
|
|
148
168
|
byId.set(String(id), row)
|
|
149
169
|
}
|
|
150
170
|
})
|
|
151
|
-
missing.forEach((id) => hit.set(id,
|
|
171
|
+
missing.forEach((id) => hit.set(id, pick(byId.get(id), labelField)))
|
|
152
172
|
}
|
|
153
173
|
distinct.forEach((id) => {
|
|
154
174
|
out[id] = hit.has(id) ? hit.get(id) : null
|