@conduction/nextcloud-vue 2.110.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/CnAppRoot/CnAppRoot.vue2.js +2 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.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 +52 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +98 -63
- 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 +203 -14
- 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/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.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 +630 -303
- 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/savedViewHelpers.js +40 -3
- package/dist/esm/utils/savedViewHelpers.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 +2760 -1995
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +630 -303
- 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/CnAppRoot/CnAppRoot.vue +2 -1
- 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 +77 -20
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.md +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +211 -16
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +1 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +2 -2
- 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/board.css +5 -2
- package/src/css/dialog.css +53 -3
- package/src/css/index-page.css +26 -1
- package/src/css/look-board-detail.css +55 -22
- package/src/css/look-board-index.css +82 -12
- package/src/css/look-board-reports.css +1 -1
- package/src/css/look-board.css +84 -11
- 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/savedViewHelpers.js +39 -2
- package/src/utils/savedViewMenu.js +12 -5
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -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
|
package/src/css/board.css
CHANGED
|
@@ -55,10 +55,13 @@
|
|
|
55
55
|
margin-inline-start: auto;
|
|
56
56
|
padding: 1px 8px;
|
|
57
57
|
border-radius: 10px;
|
|
58
|
+
--nldesign-color-on-surface: var(--cn-board-text-soft, var(--color-main-text));
|
|
58
59
|
background-color: var(--color-main-background);
|
|
59
|
-
color: var(--color-main-text);
|
|
60
|
+
color: var(--cn-board-text-soft, var(--color-main-text));
|
|
60
61
|
font-size: 12px;
|
|
61
|
-
font-weight:
|
|
62
|
+
font-weight: 700;
|
|
63
|
+
/* 16px tall on its 1px pad (DqWerkbord's column counts). */
|
|
64
|
+
line-height: 14px;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
/* A column at its limit: the count in the error colour; "full" is said in words too. */
|
package/src/css/dialog.css
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
--nldesign-component-dialog-border-radius: var(--cn-board-card-radius, 12px);
|
|
21
21
|
--cn-dialog-width: 640px;
|
|
22
22
|
--cn-dialog-danger: var(--color-error);
|
|
23
|
-
--cn-dialog-eyebrow-color: var(--color-primary-element-text, var(--color-main-text));
|
|
23
|
+
--cn-dialog-eyebrow-color: var(--color-primary-element-light-text, var(--color-main-text));
|
|
24
24
|
--cn-dialog-eyebrow-transform: none;
|
|
25
25
|
--cn-dialog-eyebrow-tracking: 0;
|
|
26
26
|
}
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
.cn-dialog-header__eyebrow {
|
|
89
|
+
--nldesign-color-on-surface: var(--cn-dialog-eyebrow-color, var(--color-main-text));
|
|
89
90
|
margin-bottom: 4px;
|
|
90
91
|
font-size: 13px;
|
|
91
92
|
font-weight: 700;
|
|
@@ -101,13 +102,24 @@
|
|
|
101
102
|
color: var(--color-main-text);
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
/* The subtitle is a <p>, which the nldesign theme sizes and inks with
|
|
106
|
+
`!important` through its paragraph tokens: the board's 14px muted line
|
|
107
|
+
goes to those tokens too (DqOverdragen, DqDocumentGenereren). */
|
|
104
108
|
.cn-dialog-header__subtitle {
|
|
109
|
+
--nldesign-component-paragraph-font-size: 14px;
|
|
110
|
+
--nldesign-component-paragraph-color: var(--color-text-maxcontrast);
|
|
111
|
+
--nldesign-color-on-surface: var(--color-text-maxcontrast);
|
|
105
112
|
margin: 4px 0 0;
|
|
106
113
|
font-size: 14px;
|
|
107
114
|
color: var(--color-text-maxcontrast);
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
.
|
|
117
|
+
/* The close button is a 36px circle. NcButton sizes an icon-only button by
|
|
118
|
+
--button-size with `!important`, so the size goes through that; the radius
|
|
119
|
+
also goes to the nldesign theme's button radius token. */
|
|
120
|
+
.cn-dialog-header .cn-dialog-header__close.button-vue {
|
|
121
|
+
--button-size: 36px;
|
|
122
|
+
--nldesign-component-button-border-radius: 50%;
|
|
111
123
|
flex: 0 0 auto;
|
|
112
124
|
width: 36px;
|
|
113
125
|
min-width: 36px;
|
|
@@ -147,6 +159,28 @@
|
|
|
147
159
|
border-radius: var(--cn-board-control-radius, 8px);
|
|
148
160
|
}
|
|
149
161
|
|
|
162
|
+
/* The footer buttons set 14px text, 16px in at both sides, and 18px when
|
|
163
|
+
they carry an icon (DqOverdragen "Annuleren" and "Overdragen"). The padding
|
|
164
|
+
goes through NcButton's --button-padding. The mask class and the doubled
|
|
165
|
+
class outrank NcButton's text-only rule, `.button-vue[data-v-…]:has(…:empty)`
|
|
166
|
+
(four classes, in a stylesheet a spawned dialog loads later), and the board
|
|
167
|
+
look's general icon-and-label rule.
|
|
168
|
+
@spec openspec/changes/board-dialog-controls/specs/board-dialog-controls/spec.md#requirement-a-board-dialog-draws-its-controls-as-the-boards-do */
|
|
169
|
+
.modal-mask.cn-dialog-board .dialog__actions .button-vue.button-vue {
|
|
170
|
+
--button-padding: 16px;
|
|
171
|
+
font-size: 14px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.modal-mask.cn-dialog-board .dialog__actions .button-vue.button-vue:has(> .button-vue__wrapper > .button-vue__icon:not(:empty)):has(> .button-vue__wrapper > .button-vue__text:not(:empty)) {
|
|
175
|
+
--button-padding: 18px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* NcButton pads a text-only label 4px a side inside the button's own padding;
|
|
179
|
+
the board's 16px is the whole inset. */
|
|
180
|
+
.modal-mask.cn-dialog-board .dialog__actions .button-vue .button-vue__text.button-vue__text {
|
|
181
|
+
padding-inline: 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
150
184
|
/* Cancel is an outlined secondary, never a text link. */
|
|
151
185
|
.cn-dialog-board .dialog__actions .button-vue--vue-secondary,
|
|
152
186
|
.cn-dialog-board .dialog__actions .button-vue--secondary {
|
|
@@ -223,15 +257,31 @@
|
|
|
223
257
|
height: calc(var(--cn-field-height, 40px) + 2 * var(--border-width-input-focused, 2px));
|
|
224
258
|
}
|
|
225
259
|
|
|
260
|
+
/* The board draws the control's edge as a 1px border (DqOverdragen "Waarom de
|
|
261
|
+
zaak verhuist", DqBesluit); NcInputField and NcTextArea draw it as a shadow.
|
|
262
|
+
The colour is the one that meets WCAG's 3:1 for a control's edge, as the
|
|
263
|
+
footer's secondary button. Focus keeps NcInputField's own ring, which
|
|
264
|
+
carries `!important`. */
|
|
226
265
|
.cn-dialog-board .dialog__content .input-field__input,
|
|
227
266
|
.cn-dialog-board .dialog__content .input-field__input:hover:not([disabled]),
|
|
228
267
|
.cn-dialog-board .dialog__content .textarea__input,
|
|
229
268
|
.cn-dialog-board .dialog__content .textarea__input:hover:not([disabled]) {
|
|
269
|
+
border: 1px solid var(--color-border-maxcontrast);
|
|
230
270
|
border-radius: 8px;
|
|
231
|
-
box-shadow:
|
|
271
|
+
box-shadow: none;
|
|
232
272
|
font-size: var(--cn-field-text-size, 15px);
|
|
233
273
|
}
|
|
234
274
|
|
|
275
|
+
/* A plain input an app puts in a board dialog (a date, a number) is the same
|
|
276
|
+
40px control; Nextcloud's core stylesheet makes it 34px. */
|
|
277
|
+
.cn-dialog-board .dialog__content input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=range]):not([type=color]):not([type=submit]):not([type=button]):not([type=reset]):not([type=image]):not(.input-field__input) {
|
|
278
|
+
box-sizing: border-box;
|
|
279
|
+
height: var(--cn-field-height, 40px);
|
|
280
|
+
min-height: var(--cn-field-height, 40px);
|
|
281
|
+
border: 1px solid var(--color-border-maxcontrast);
|
|
282
|
+
border-radius: 8px;
|
|
283
|
+
}
|
|
284
|
+
|
|
235
285
|
.cn-dialog-board .dialog__content .input-field:has(.input-field__label) .input-field__input:not(:focus)::placeholder,
|
|
236
286
|
.cn-dialog-board .dialog__content .textarea__input:not(:focus)::placeholder {
|
|
237
287
|
opacity: 1 !important;
|
package/src/css/index-page.css
CHANGED
|
@@ -102,11 +102,17 @@
|
|
|
102
102
|
overflow-y: auto;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/* The list scrolls inside the side column and the card for the rows without
|
|
106
|
+
a place stays under it, as the board draws it (DqZakenKaart). Scrolling the
|
|
107
|
+
column as a whole pushed that card below a page's worth of rows. */
|
|
105
108
|
.cn-index-page__map-list {
|
|
106
109
|
display: flex;
|
|
110
|
+
flex: 0 1 auto;
|
|
107
111
|
flex-direction: column;
|
|
112
|
+
min-height: 0;
|
|
108
113
|
margin: 0;
|
|
109
114
|
padding: 0;
|
|
115
|
+
overflow-y: auto;
|
|
110
116
|
border: 1px solid var(--color-border);
|
|
111
117
|
border-radius: 10px;
|
|
112
118
|
list-style: none;
|
|
@@ -114,6 +120,7 @@
|
|
|
114
120
|
|
|
115
121
|
.cn-index-page__map-item {
|
|
116
122
|
display: flex;
|
|
123
|
+
flex: none;
|
|
117
124
|
gap: 12px;
|
|
118
125
|
padding: 12px 14px;
|
|
119
126
|
}
|
|
@@ -152,11 +159,22 @@
|
|
|
152
159
|
min-width: 0;
|
|
153
160
|
}
|
|
154
161
|
|
|
155
|
-
|
|
162
|
+
/* The title is a button for the keyboard but reads as the row's title text.
|
|
163
|
+
Nextcloud styles every `button:not(.button-vue, ...)` (specificity 0,2,1)
|
|
164
|
+
as a 34px tinted pill with a 3px margin, which outranked a single class and
|
|
165
|
+
turned each title into a blue block (DqZakenKaart rows 111px against 89).
|
|
166
|
+
Three classes outrank it, and the box properties it sets are reset.
|
|
167
|
+
@spec openspec/changes/map-list-title-button/specs/map-list-title-button/spec.md#requirement-a-map-list-title-reads-as-text-not-as-a-button */
|
|
168
|
+
.cn-index-page__map-list .cn-index-page__map-item .cn-index-page__map-title {
|
|
169
|
+
width: auto;
|
|
170
|
+
height: auto;
|
|
171
|
+
min-height: 0;
|
|
156
172
|
margin: 0;
|
|
157
173
|
padding: 0;
|
|
158
174
|
border: 0;
|
|
175
|
+
border-radius: 0;
|
|
159
176
|
background: none;
|
|
177
|
+
box-shadow: none;
|
|
160
178
|
color: var(--color-main-text);
|
|
161
179
|
font: inherit;
|
|
162
180
|
font-size: 15px;
|
|
@@ -166,6 +184,12 @@
|
|
|
166
184
|
cursor: pointer;
|
|
167
185
|
}
|
|
168
186
|
|
|
187
|
+
.cn-index-page__map-list .cn-index-page__map-item .cn-index-page__map-title:hover,
|
|
188
|
+
.cn-index-page__map-list .cn-index-page__map-item .cn-index-page__map-title:focus {
|
|
189
|
+
background: none;
|
|
190
|
+
color: var(--color-main-text);
|
|
191
|
+
}
|
|
192
|
+
|
|
169
193
|
.cn-index-page__map-sub,
|
|
170
194
|
.cn-index-page__map-due {
|
|
171
195
|
color: var(--color-text-maxcontrast);
|
|
@@ -181,6 +205,7 @@
|
|
|
181
205
|
|
|
182
206
|
.cn-index-page__map-missing {
|
|
183
207
|
display: flex;
|
|
208
|
+
flex: none;
|
|
184
209
|
flex-direction: column;
|
|
185
210
|
align-items: flex-start;
|
|
186
211
|
gap: 8px;
|