@conduction/nextcloud-vue 2.140.0 → 2.141.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/CnAssignDialog/CnAssignDialog.vue.js +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +11 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- 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 +12 -0
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +57 -40
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
- 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 +5 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
- 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/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +2 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +2 -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 +652 -391
- package/dist/esm/utils/groupSublines.js +120 -0
- package/dist/esm/utils/groupSublines.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +974 -638
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +652 -391
- package/l10n/en.json +2 -1
- package/l10n/nl.json +2 -1
- package/package.json +1 -1
- package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +11 -3
- package/src/components/CnChartWidget/CnChartWidget.md +1 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +49 -4
- package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +6 -3
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
- package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +33 -9
- package/src/css/dialog.css +13 -1
- package/src/css/look-board-detail.css +12 -9
- package/src/css/look-board-index.css +90 -7
- package/src/css/look-board.css +92 -0
- package/src/css/table.css +12 -0
- package/src/utils/groupSublines.js +125 -0
package/l10n/en.json
CHANGED
|
@@ -2381,7 +2381,8 @@
|
|
|
2381
2381
|
"me": "me",
|
|
2382
2382
|
"Open profile": "Open profile",
|
|
2383
2383
|
"Link a contact from your address book.": "Link a contact from your address book.",
|
|
2384
|
-
"The value could not be shown.": "The value could not be shown."
|
|
2384
|
+
"The value could not be shown.": "The value could not be shown.",
|
|
2385
|
+
"No value": "No value"
|
|
2385
2386
|
},
|
|
2386
2387
|
"plurals": {
|
|
2387
2388
|
"{count} member": [
|
package/l10n/nl.json
CHANGED
|
@@ -2634,7 +2634,8 @@
|
|
|
2634
2634
|
"The value could not be shown.": "De waarde kon niet worden getoond.",
|
|
2635
2635
|
"Save as draft": "Opslaan als concept",
|
|
2636
2636
|
"Draft saved at {time}": "Concept opgeslagen om {time}",
|
|
2637
|
-
"The draft could not be saved": "Het concept kon niet worden opgeslagen"
|
|
2637
|
+
"The draft could not be saved": "Het concept kon niet worden opgeslagen",
|
|
2638
|
+
"No value": "Geen waarde"
|
|
2638
2639
|
},
|
|
2639
2640
|
"plurals": {
|
|
2640
2641
|
"{count} member": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.141.0",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -89,6 +89,7 @@ import { generateUrl } from '@nextcloud/router'
|
|
|
89
89
|
import { NcButton, NcCheckboxRadioSwitch, NcTextArea } from '@nextcloud/vue'
|
|
90
90
|
import { fetchEndpointSource, interpolateUrlTokens } from '../../composables/useEndpointSource.js'
|
|
91
91
|
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
92
|
+
import { groupSublines } from '../../utils/groupSublines.js'
|
|
92
93
|
import { readPath } from '../../utils/readPath.js'
|
|
93
94
|
import { searchNextcloudGroups, searchNextcloudUsers } from '../../utils/userAutocomplete.js'
|
|
94
95
|
import { CnDialog } from '../CnDialog/index.js'
|
|
@@ -134,7 +135,10 @@ export function withTypedAssignee(found, typed, label) {
|
|
|
134
135
|
* `eyebrow` a template filled from the record.
|
|
135
136
|
* - `source`: an endpointSource to read the record from, else `object`.
|
|
136
137
|
* - `assignee`: `{ kinds?, label?, placeholder?, hint?, currentPath?,
|
|
137
|
-
* currentHint?, currentAs?, allowTyped?, field?, kindField? }`.
|
|
138
|
+
* currentHint?, currentAs?, allowTyped?, field?, kindField?, describe? }`.
|
|
139
|
+
* `describe` (`{ register, schema, groupField?, textField? }`) names the
|
|
140
|
+
* records that hold a team's description; each team option then reads
|
|
141
|
+
* "{count} members · {description}", the count where the viewer may read it. `kinds` is `["group"]`
|
|
138
142
|
* (default), `["user"]` or both; `currentPath` reads who has it now from the
|
|
139
143
|
* record and `currentHint` ("Now with {current}.", the placeholder named
|
|
140
144
|
* by `currentAs`) says so, in place of `hint`; `allowTyped`
|
|
@@ -398,8 +402,12 @@ export default {
|
|
|
398
402
|
if (seq !== this.searchSeq) {
|
|
399
403
|
return
|
|
400
404
|
}
|
|
405
|
+
const lines = await groupSublines(groups.map((g) => g.id), typed, this.assigneeConfig.describe || null)
|
|
406
|
+
if (seq !== this.searchSeq) {
|
|
407
|
+
return
|
|
408
|
+
}
|
|
401
409
|
const found = [
|
|
402
|
-
...groups.map((g) => ({ id: g.id, label: g.label, secondary: g.subline || '', icon: 'AccountGroupOutline', kind: 'group' })),
|
|
410
|
+
...groups.map((g) => ({ id: g.id, label: g.label, secondary: lines[g.id] || g.subline || '', icon: 'AccountGroupOutline', kind: 'group' })),
|
|
403
411
|
...users.map((u) => ({ id: u.id, label: u.label, secondary: u.subline || '', avatar: true, kind: 'user' })),
|
|
404
412
|
]
|
|
405
413
|
this.options = this.assigneeConfig.allowTyped ? withTypedAssignee(found, typed, t('nextcloud-vue', 'Use "{name}"', { name: typed })) : found
|
|
@@ -1737,8 +1737,9 @@ export default {
|
|
|
1737
1737
|
|
|
1738
1738
|
/**
|
|
1739
1739
|
* The swatch colour — read from the sibling row field named by
|
|
1740
|
-
* `format.colorField` (default `'color'`)
|
|
1741
|
-
*
|
|
1740
|
+
* `format.colorField` (default `'color'`), through `format.colorMap`
|
|
1741
|
+
* when given (a stored name such as `blue` to a CSS colour). Returns
|
|
1742
|
+
* null when no usable colour is present (the dot is then omitted).
|
|
1742
1743
|
*
|
|
1743
1744
|
* @return {string|null}
|
|
1744
1745
|
*/
|
|
@@ -1748,7 +1749,14 @@ export default {
|
|
|
1748
1749
|
}
|
|
1749
1750
|
const field = (this.format && this.format.colorField) || 'color'
|
|
1750
1751
|
const c = this.row && this.row[field]
|
|
1751
|
-
|
|
1752
|
+
if (typeof c !== 'string' || c.trim() === '') {
|
|
1753
|
+
return null
|
|
1754
|
+
}
|
|
1755
|
+
const map = this.format && this.format.colorMap
|
|
1756
|
+
if (map && typeof map === 'object') {
|
|
1757
|
+
return typeof map[c] === 'string' && map[c] !== '' ? map[c] : null
|
|
1758
|
+
}
|
|
1759
|
+
return c
|
|
1752
1760
|
},
|
|
1753
1761
|
|
|
1754
1762
|
formattedValue() {
|
|
@@ -135,3 +135,4 @@ The `dataSource` OBJECT-form `aggregate` (Wave 3, #91) — `{ groupBy, metric?:
|
|
|
135
135
|
| Prop | Type | Default | Description |
|
|
136
136
|
|------|------|---------|-------------|
|
|
137
137
|
| `widgetId` | String | `''` | Matches `cn:widget:refresh` event-bus events; on a matching id the chart re-queries its `dataSource`. Also exposes a ref-callable `refresh()`. |
|
|
138
|
+
| `barValueAtEnd` | Boolean | `false` | A `bar-list` without a track: each bar as long as its share and its value right after the bar's end (DqProcesanalyse "Doorlooptijd per status"). False keeps the track and the value column. |
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
share of `barMax` (default the largest value), the value at the
|
|
66
66
|
end. Rows navigate when the source declares a drilldown. -->
|
|
67
67
|
<div v-else-if="type === 'bar-list'" class="cn-chart-widget__html" data-testid="cn-chart-widget-bar-list">
|
|
68
|
-
<ul class="cn-chart-widget__bar-list">
|
|
68
|
+
<ul class="cn-chart-widget__bar-list" :class="{ 'cn-chart-widget__bar-list--value-at-end': barValueAtEnd }">
|
|
69
69
|
<li v-for="row in barListRows" :key="row.index" class="cn-chart-widget__bar-row">
|
|
70
70
|
<component
|
|
71
71
|
:is="row.drill ? 'button' : 'div'"
|
|
@@ -75,10 +75,16 @@
|
|
|
75
75
|
data-testid="cn-chart-widget-bar-row"
|
|
76
76
|
@click="row.drill ? onDataPointSelection(null, null, { dataPointIndex: row.index }) : null">
|
|
77
77
|
<span class="cn-chart-widget__bar-label">{{ row.label }}</span>
|
|
78
|
-
<span class="cn-chart-widget__bar-track"
|
|
79
|
-
<span class="cn-chart-widget__bar-fill" :style="{
|
|
78
|
+
<span v-if="barValueAtEnd" class="cn-chart-widget__bar-track">
|
|
79
|
+
<span class="cn-chart-widget__bar-fill" aria-hidden="true" :style="{ flexBasis: `${row.share}%` }" />
|
|
80
|
+
<span class="cn-chart-widget__bar-value">{{ row.text }}</span>
|
|
80
81
|
</span>
|
|
81
|
-
<
|
|
82
|
+
<template v-else>
|
|
83
|
+
<span class="cn-chart-widget__bar-track" aria-hidden="true">
|
|
84
|
+
<span class="cn-chart-widget__bar-fill" :style="{ width: `${row.share}%` }" />
|
|
85
|
+
</span>
|
|
86
|
+
<span class="cn-chart-widget__bar-value">{{ row.text }}</span>
|
|
87
|
+
</template>
|
|
82
88
|
</component>
|
|
83
89
|
</li>
|
|
84
90
|
</ul>
|
|
@@ -468,6 +474,18 @@ export default {
|
|
|
468
474
|
default: null,
|
|
469
475
|
},
|
|
470
476
|
|
|
477
|
+
/**
|
|
478
|
+
* A `bar-list` without a track: each bar as long as its share and its
|
|
479
|
+
* value right after the bar's end (DqProcesanalyse "Doorlooptijd per
|
|
480
|
+
* status"). False, the default, keeps the track and the value column.
|
|
481
|
+
*
|
|
482
|
+
* @spec openspec/changes/chart-bar-list-value-at-end/specs/chart-bar-list-value-at-end/spec.md#requirement-a-bar-list-can-put-each-value-at-its-bars-end
|
|
483
|
+
*/
|
|
484
|
+
barValueAtEnd: {
|
|
485
|
+
type: Boolean,
|
|
486
|
+
default: false,
|
|
487
|
+
},
|
|
488
|
+
|
|
471
489
|
/**
|
|
472
490
|
* A `matrix`'s row-label column width in px. Null, the default, sizes
|
|
473
491
|
* the column to its longest label (at least 120px); a width wraps a
|
|
@@ -3006,6 +3024,33 @@ export default {
|
|
|
3006
3024
|
white-space: nowrap;
|
|
3007
3025
|
}
|
|
3008
3026
|
|
|
3027
|
+
/* `barValueAtEnd`: no track; the bar takes its share of the row and gives way
|
|
3028
|
+
to the value right after its end, so the longest bar still fits. */
|
|
3029
|
+
.cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-line {
|
|
3030
|
+
grid-template-columns: minmax(80px, 130px) minmax(0, 1fr);
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
.cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-track {
|
|
3034
|
+
align-items: center;
|
|
3035
|
+
background: none;
|
|
3036
|
+
display: flex;
|
|
3037
|
+
gap: 6px;
|
|
3038
|
+
height: auto;
|
|
3039
|
+
overflow: visible;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
.cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-fill {
|
|
3043
|
+
border-radius: 4px;
|
|
3044
|
+
flex: 0 1 auto;
|
|
3045
|
+
height: 18px;
|
|
3046
|
+
min-width: 2px;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
.cn-chart-widget__bar-list--value-at-end .cn-chart-widget__bar-value {
|
|
3050
|
+
flex: none;
|
|
3051
|
+
text-align: start;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3009
3054
|
.cn-chart-widget__footnote {
|
|
3010
3055
|
color: var(--color-text-maxcontrast);
|
|
3011
3056
|
margin: 0;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
'cn-table-container--board': isBoardLook,
|
|
7
7
|
'cn-table-container--title-plain': isBoardLook && rowTitle === 'plain',
|
|
8
8
|
'cn-table-container--title-text': isBoardLook && rowTitle === 'text',
|
|
9
|
+
'cn-table-container--title-strong': isBoardLook && rowTitle === 'strong',
|
|
9
10
|
'cn-table-container--scrollable': scrollable,
|
|
10
11
|
'cn-table-container--borderless': borderless,
|
|
11
12
|
'cn-table-container--fill': fillHeight,
|
|
@@ -856,8 +857,10 @@ export default {
|
|
|
856
857
|
* the 15px bold title without an underline and a 13px muted secondary
|
|
857
858
|
* line, as the PqTickets and OcPublicaties boards draw it; `text` is an
|
|
858
859
|
* ordinary cell, for a log table whose first column is a date
|
|
859
|
-
* (DqZaakContact)
|
|
860
|
-
*
|
|
860
|
+
* (DqZaakContact); `strong` is the cell's own size at weight 600 in the
|
|
861
|
+
* main ink without an underline, the name column of a card's list
|
|
862
|
+
* (DqZaaktypeStatussen). The row stays clickable either way. The
|
|
863
|
+
* Nextcloud look ignores it.
|
|
861
864
|
*
|
|
862
865
|
* @spec openspec/changes/screens-table-rows-parity/specs/index-list-board-look/spec.md#requirement-a-row-title-can-be-plain-text
|
|
863
866
|
* @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-a-tables-title-column-can-read-as-plain-text
|
|
@@ -865,7 +868,7 @@ export default {
|
|
|
865
868
|
rowTitle: {
|
|
866
869
|
type: String,
|
|
867
870
|
default: 'link',
|
|
868
|
-
validator: (value) => ['link', 'plain', 'text'].includes(value),
|
|
871
|
+
validator: (value) => ['link', 'plain', 'text', 'strong'].includes(value),
|
|
869
872
|
},
|
|
870
873
|
|
|
871
874
|
/**
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
</p>
|
|
104
104
|
</div>
|
|
105
105
|
|
|
106
|
-
<div v-if="hasActiveFilters" class="cn-index-sidebar__section">
|
|
106
|
+
<div v-if="hasActiveFilters && !isBoardLook" class="cn-index-sidebar__section">
|
|
107
107
|
<NcButton variant="tertiary" @click="onClearAll">
|
|
108
108
|
<template #icon>
|
|
109
109
|
<FilterRemoveOutline :size="20" />
|
|
@@ -113,7 +113,20 @@
|
|
|
113
113
|
</div>
|
|
114
114
|
|
|
115
115
|
<div v-if="schemaFilters.length > 0" class="cn-index-sidebar__section">
|
|
116
|
-
|
|
116
|
+
<!-- The board look: "Alles wissen" beside the heading (DqZakenSorteren). -->
|
|
117
|
+
<div v-if="isBoardLook" class="cn-index-sidebar__filters-head" data-testid="cn-index-sidebar-filters-head">
|
|
118
|
+
<h3>{{ filtersLabel }}</h3>
|
|
119
|
+
<button
|
|
120
|
+
v-if="hasActiveFilters"
|
|
121
|
+
type="button"
|
|
122
|
+
class="cn-index-sidebar__filters-clear"
|
|
123
|
+
@click="onClearAll">
|
|
124
|
+
{{ clearAllLabel }}
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
<h3 v-else>
|
|
128
|
+
{{ filtersLabel }}
|
|
129
|
+
</h3>
|
|
117
130
|
<div
|
|
118
131
|
v-for="filter in schemaFilters"
|
|
119
132
|
:key="filter.key"
|
|
@@ -155,6 +168,27 @@
|
|
|
155
168
|
:label="filter.label"
|
|
156
169
|
:placeholder="filter.label"
|
|
157
170
|
@update:modelValue="onTextChange(filter.key, $event)" />
|
|
171
|
+
<!-- The board look: a short field as checkbox rows with counts. -->
|
|
172
|
+
<fieldset
|
|
173
|
+
v-else-if="checklistOptions(filter)"
|
|
174
|
+
class="cn-index-sidebar__checklist"
|
|
175
|
+
:data-testid="`cn-index-sidebar-checklist-${filter.key}`">
|
|
176
|
+
<legend class="hidden-visually">
|
|
177
|
+
{{ filter.label }}
|
|
178
|
+
</legend>
|
|
179
|
+
<label
|
|
180
|
+
v-for="option in checklistOptions(filter)"
|
|
181
|
+
:key="`${filter.key}-${option.id}`"
|
|
182
|
+
class="cn-index-sidebar__checklist-row"
|
|
183
|
+
data-testid="cn-index-sidebar-checklist-row">
|
|
184
|
+
<input
|
|
185
|
+
type="checkbox"
|
|
186
|
+
:checked="isChecked(filter, option.id)"
|
|
187
|
+
@change="onChecklistToggle(filter, option.id, $event.target.checked)">
|
|
188
|
+
<span class="cn-index-sidebar__checklist-label">{{ option.label }}</span>
|
|
189
|
+
<span v-if="option.count !== null" class="cn-index-sidebar__checklist-count">{{ option.count }}</span>
|
|
190
|
+
</label>
|
|
191
|
+
</fieldset>
|
|
158
192
|
<NcSelect
|
|
159
193
|
v-else
|
|
160
194
|
class="cn-index-sidebar__select"
|
|
@@ -369,7 +403,7 @@ import CnSavedViewsPanel from '../CnSavedViewsPanel/CnSavedViewsPanel.vue'
|
|
|
369
403
|
import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
|
|
370
404
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
371
405
|
import { METADATA_COLUMNS } from '../../constants/metadata.js'
|
|
372
|
-
import { facetOptions } from '../../utils/facets.js'
|
|
406
|
+
import { facetOptions, MISSING_VALUE } from '../../utils/facets.js'
|
|
373
407
|
import { labelLang } from '../../utils/manifestTranslate.js'
|
|
374
408
|
import { columnsFromSchema, filtersFromSchema } from '../../utils/schema.js'
|
|
375
409
|
/**
|
|
@@ -389,6 +423,9 @@ import { labelSidePanelClose } from '../../utils/sidePanelCloseLabel.js'
|
|
|
389
423
|
import { reverseSortKeys, sameSortKeys } from '../../utils/sortOrder.js'
|
|
390
424
|
import { CnDateRangePicker, DEFAULT_DATE_RANGE_PRESETS } from '../CnDateRangePicker/index.js'
|
|
391
425
|
import { CnIcon } from '../CnIcon/index.js'
|
|
426
|
+
/** The most values a filter shows as checkbox rows in the board look; a longer field keeps its picker. */
|
|
427
|
+
const CHECKLIST_MAX = 12
|
|
428
|
+
|
|
392
429
|
export default {
|
|
393
430
|
name: 'CnIndexSidebar',
|
|
394
431
|
|
|
@@ -1411,6 +1448,67 @@ export default {
|
|
|
1411
1448
|
return values.map((v) => options.find((o) => o.id === v) || { id: v, label: String(v) })
|
|
1412
1449
|
},
|
|
1413
1450
|
|
|
1451
|
+
/**
|
|
1452
|
+
* A field's values as checkbox rows, in the board look and for a short
|
|
1453
|
+
* list only: label and count apart, from the facet when there is one,
|
|
1454
|
+
* else the field's own options. Null where the picker stays (the
|
|
1455
|
+
* Nextcloud look, a single choice, a reference, more than
|
|
1456
|
+
* CHECKLIST_MAX values).
|
|
1457
|
+
*
|
|
1458
|
+
* @spec openspec/changes/board-sidebar-filters-as-checkbox-lists/specs/index-sidebar/spec.md#requirement-the-board-look-draws-a-short-filter-as-a-checkbox-list-with-counts
|
|
1459
|
+
* @param {object} filter The filter definition.
|
|
1460
|
+
* @return {Array<{id: (string|number|boolean), label: string, count: (number|null)}>|null} The rows, or null.
|
|
1461
|
+
*/
|
|
1462
|
+
checklistOptions(filter) {
|
|
1463
|
+
if (!this.isBoardLook || filter.multiple === false || filter.type === 'reference') {
|
|
1464
|
+
return null
|
|
1465
|
+
}
|
|
1466
|
+
const facet = this.facetData[filter.key]
|
|
1467
|
+
let rows
|
|
1468
|
+
if (facet && Array.isArray(facet.values) && facet.values.length > 0) {
|
|
1469
|
+
rows = facet.values.map((bucket) => ({
|
|
1470
|
+
id: bucket.value,
|
|
1471
|
+
label: String(bucket.label !== undefined && bucket.label !== null ? bucket.label : bucket.value),
|
|
1472
|
+
count: typeof bucket.count === 'number' ? bucket.count : null,
|
|
1473
|
+
}))
|
|
1474
|
+
if (typeof facet.missing === 'number' && facet.missing > 0) {
|
|
1475
|
+
rows.push({ id: MISSING_VALUE, label: t('nextcloud-vue', 'No value'), count: facet.missing })
|
|
1476
|
+
}
|
|
1477
|
+
} else {
|
|
1478
|
+
rows = (filter.options || []).map((option) => ({ id: option.id, label: String(option.label), count: null }))
|
|
1479
|
+
}
|
|
1480
|
+
return rows.length > 0 && rows.length <= CHECKLIST_MAX ? rows : null
|
|
1481
|
+
},
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* Whether a value of a field is among its active filter values.
|
|
1485
|
+
*
|
|
1486
|
+
* @param {object} filter The filter definition.
|
|
1487
|
+
* @param {string|number|boolean} id The value.
|
|
1488
|
+
* @return {boolean}
|
|
1489
|
+
*/
|
|
1490
|
+
isChecked(filter, id) {
|
|
1491
|
+
const value = this.activeFilters[filter.key]
|
|
1492
|
+
const values = Array.isArray(value) ? value : (value === undefined || value === null || value === '' ? [] : [value])
|
|
1493
|
+
return values.map(String).includes(String(id))
|
|
1494
|
+
},
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* A checkbox row was toggled: the field's values with it added or taken out.
|
|
1498
|
+
*
|
|
1499
|
+
* @spec openspec/changes/board-sidebar-filters-as-checkbox-lists/specs/index-sidebar/spec.md#requirement-the-board-look-draws-a-short-filter-as-a-checkbox-list-with-counts
|
|
1500
|
+
* @param {object} filter The filter definition.
|
|
1501
|
+
* @param {string|number|boolean} id The value.
|
|
1502
|
+
* @param {boolean} checked Whether it is now ticked.
|
|
1503
|
+
* @return {void}
|
|
1504
|
+
*/
|
|
1505
|
+
onChecklistToggle(filter, id, checked) {
|
|
1506
|
+
const value = this.activeFilters[filter.key]
|
|
1507
|
+
const current = Array.isArray(value) ? [...value] : (value === undefined || value === null || value === '' ? [] : [value])
|
|
1508
|
+
const rest = current.filter((v) => String(v) !== String(id))
|
|
1509
|
+
this.$emit('filter-change', { key: filter.key, values: checked ? [...rest, id] : rest })
|
|
1510
|
+
},
|
|
1511
|
+
|
|
1414
1512
|
/**
|
|
1415
1513
|
* Handle filter select change
|
|
1416
1514
|
*
|
|
@@ -226,6 +226,25 @@ function controlIcon(path) {
|
|
|
226
226
|
* @slot popup Per-marker popup body override. Scoped props: `{ feature, properties }`. When omitted, the widget renders the value of `markers.popupField` (or no popup at all when unset).
|
|
227
227
|
* @slot fallback Replaces the default "map unavailable" message when Leaflet fails to load (test environments, CSP-blocked builds, offline dev). No scoped props.
|
|
228
228
|
*/
|
|
229
|
+
/**
|
|
230
|
+
* The count line a header spec gives (`{ one, other }` or one string, with
|
|
231
|
+
* `{total}`), once the marker source answered with markers.
|
|
232
|
+
*
|
|
233
|
+
* @param {object|string|undefined} spec The declared line.
|
|
234
|
+
* @param {object} vm The map: `markersLoaded`, `markersFailed`, `markerCount`, `cnTranslate`.
|
|
235
|
+
* @return {string} The line, or ''.
|
|
236
|
+
*/
|
|
237
|
+
function countLine(spec, vm) {
|
|
238
|
+
if (!spec || !vm.markersLoaded || vm.markersFailed || vm.markerCount === 0) {
|
|
239
|
+
return ''
|
|
240
|
+
}
|
|
241
|
+
const text = typeof spec === 'string' ? spec : (vm.markerCount === 1 ? spec.one : spec.other)
|
|
242
|
+
if (typeof text !== 'string' || text === '') {
|
|
243
|
+
return ''
|
|
244
|
+
}
|
|
245
|
+
return vm.cnTranslate(text).replace(/\{total\}/g, String(vm.markerCount))
|
|
246
|
+
}
|
|
247
|
+
|
|
229
248
|
export default {
|
|
230
249
|
name: 'CnMapWidget',
|
|
231
250
|
|
|
@@ -577,15 +596,19 @@ export default {
|
|
|
577
596
|
* @spec openspec/changes/map-widget-header-count/specs/map-widget-header-count/spec.md#requirement-a-map-card-can-say-how-many-places-it-shows
|
|
578
597
|
*/
|
|
579
598
|
headerSubtitleText() {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
599
|
+
return countLine(this.content?.headerSubtitle, this)
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* The same count as a note at the right of the card's title row
|
|
604
|
+
* (`content.headerNote`, the same shape as `headerSubtitle`), as the
|
|
605
|
+
* case boards draw "Locaties ... 1 locatie".
|
|
606
|
+
*
|
|
607
|
+
* @return {string}
|
|
608
|
+
* @spec openspec/changes/map-count-note/specs/map-widget/spec.md
|
|
609
|
+
*/
|
|
610
|
+
headerNoteText() {
|
|
611
|
+
return countLine(this.content?.headerNote, this)
|
|
589
612
|
},
|
|
590
613
|
|
|
591
614
|
markerStatusText() {
|
|
@@ -672,6 +695,21 @@ export default {
|
|
|
672
695
|
}
|
|
673
696
|
},
|
|
674
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Report the count note to the card around the map.
|
|
700
|
+
*
|
|
701
|
+
* @param {string} note The note, or ''.
|
|
702
|
+
*/
|
|
703
|
+
headerNoteText(note) {
|
|
704
|
+
if (this.content?.headerNote) {
|
|
705
|
+
/**
|
|
706
|
+
* @event header The card header's note at the right of the title.
|
|
707
|
+
* @type {{note: string}}
|
|
708
|
+
*/
|
|
709
|
+
this.$emit('header', { note })
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
|
|
675
713
|
// Keyed, never `deep: true` — see `layersKey`.
|
|
676
714
|
layersKey() {
|
|
677
715
|
if (this.map) {
|
|
@@ -669,14 +669,14 @@ export default {
|
|
|
669
669
|
},
|
|
670
670
|
|
|
671
671
|
/**
|
|
672
|
-
* How the table draws its title column (`content.rowTitle`: link, plain
|
|
673
|
-
* or
|
|
672
|
+
* How the table draws its title column (`content.rowTitle`: link, plain,
|
|
673
|
+
* text or strong), link when unset or unknown.
|
|
674
674
|
*
|
|
675
675
|
* @return {string}
|
|
676
676
|
* @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-a-tables-title-column-can-read-as-plain-text
|
|
677
677
|
*/
|
|
678
678
|
rowTitle() {
|
|
679
|
-
return ['plain', 'text'].includes(this.content.rowTitle) ? this.content.rowTitle : 'link'
|
|
679
|
+
return ['plain', 'text', 'strong'].includes(this.content.rowTitle) ? this.content.rowTitle : 'link'
|
|
680
680
|
},
|
|
681
681
|
|
|
682
682
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<template>
|
|
7
7
|
<div
|
|
8
8
|
class="cn-record-list"
|
|
9
|
-
:class="[`cn-record-list--${presentation}`, { 'cn-record-list--compact': content.density === 'compact', 'cn-record-list--tiles': presentation === 'rows' && content.tiles === true, 'cn-record-list--rule-above': content.ruleAbove === true, 'cn-record-list--data-table': presentation === 'table' && content.tableLook === 'data' }]"
|
|
9
|
+
:class="[`cn-record-list--${presentation}`, { 'cn-record-list--compact': content.density === 'compact', 'cn-record-list--stacked': content.density === 'stacked', 'cn-record-list--tiles': presentation === 'rows' && content.tiles === true, 'cn-record-list--rule-above': content.ruleAbove === true, 'cn-record-list--data-table': presentation === 'table' && content.tableLook === 'data' }]"
|
|
10
10
|
data-testid="cn-record-list">
|
|
11
11
|
<p v-if="intro && !absent" class="cn-record-list__intro" data-testid="cn-record-list-intro">
|
|
12
12
|
{{ intro }}
|
|
@@ -713,6 +713,8 @@ export default {
|
|
|
713
713
|
* without a value.
|
|
714
714
|
* `density: "compact"` draws the rows 10px above and below, at least
|
|
715
715
|
* 56px tall, with the title at 500 (DqProcesanalyse).
|
|
716
|
+
* `density: "stacked"` stands the rows 10px apart with no rule and no
|
|
717
|
+
* row padding (DqDagAfsluiten "Morgen eerst").
|
|
716
718
|
* In `rows`, `text` is a line under the meta line ("12 dagen stil"),
|
|
717
719
|
* and `chevron: false` leaves the chevron off a linked row
|
|
718
720
|
* (DqDashboard).
|
|
@@ -3103,6 +3105,23 @@ export default {
|
|
|
3103
3105
|
font-weight: 500;
|
|
3104
3106
|
}
|
|
3105
3107
|
|
|
3108
|
+
/* `density: "stacked"`: the rows stand 10px apart with no rule and no row
|
|
3109
|
+
padding, a short side list (DqDagAfsluiten "Morgen eerst"). */
|
|
3110
|
+
.cn-record-list--stacked .cn-record-list__list {
|
|
3111
|
+
display: flex;
|
|
3112
|
+
flex-direction: column;
|
|
3113
|
+
gap: 10px;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
.cn-record-list--stacked .cn-record-list__row {
|
|
3117
|
+
min-height: 0;
|
|
3118
|
+
padding: 0;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
.cn-record-list--stacked .cn-record-list__item + .cn-record-list__item {
|
|
3122
|
+
border-top: 0;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3106
3125
|
.cn-record-list__meta {
|
|
3107
3126
|
color: var(--color-text-maxcontrast);
|
|
3108
3127
|
font-size: 13px;
|
|
@@ -37,15 +37,20 @@
|
|
|
37
37
|
data-testid="cn-record-list-due">{{ item.due.text }}</span>
|
|
38
38
|
</div>
|
|
39
39
|
<div class="cn-record-notes__fields">
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
<!-- The label stands above the field, as the leaf's own beside it
|
|
41
|
+
(humaniq "Uren"), so both fields share one line. -->
|
|
42
|
+
<div class="cn-record-notes__field">
|
|
43
|
+
<label :for="fieldId(item.key)" class="cn-record-notes__field-label">{{ fieldLabel }}</label>
|
|
44
|
+
<NcTextField
|
|
45
|
+
:id="fieldId(item.key)"
|
|
46
|
+
:modelValue="drafts[item.key] || ''"
|
|
47
|
+
:label="fieldLabel"
|
|
48
|
+
labelOutside
|
|
49
|
+
:placeholder="placeholder"
|
|
50
|
+
:disabled="busy"
|
|
51
|
+
:data-testid="`cn-record-list-note-${item.key}`"
|
|
52
|
+
@update:modelValue="setDraft(item.key, $event)" />
|
|
53
|
+
</div>
|
|
49
54
|
<div v-if="leaf && leafProvider" class="cn-record-notes__leaf" :data-testid="`cn-record-list-leaf-${item.key}`">
|
|
50
55
|
<CnLeafMountHost
|
|
51
56
|
v-if="leafIsMount"
|
|
@@ -301,6 +306,16 @@ export default {
|
|
|
301
306
|
this.drafts = { ...this.drafts, [key]: String(value ?? '') }
|
|
302
307
|
},
|
|
303
308
|
|
|
309
|
+
/**
|
|
310
|
+
* The id of a row's note field, for its label.
|
|
311
|
+
*
|
|
312
|
+
* @param {string} key The row key.
|
|
313
|
+
* @return {string} The id.
|
|
314
|
+
*/
|
|
315
|
+
fieldId(key) {
|
|
316
|
+
return `cn-record-notes-${this.$.uid}-${String(key).replace(/[^A-Za-z0-9_-]/g, '-')}`
|
|
317
|
+
},
|
|
318
|
+
|
|
304
319
|
/**
|
|
305
320
|
* @param {string} key The row key.
|
|
306
321
|
* @return {boolean} Whether the row has a note typed.
|
|
@@ -472,7 +487,16 @@ export default {
|
|
|
472
487
|
}
|
|
473
488
|
|
|
474
489
|
.cn-record-notes__field {
|
|
490
|
+
display: flex;
|
|
475
491
|
flex: 1 1 260px;
|
|
492
|
+
flex-direction: column;
|
|
493
|
+
gap: 6px;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.cn-record-notes__field-label {
|
|
497
|
+
color: var(--color-main-text);
|
|
498
|
+
font-size: 14px;
|
|
499
|
+
font-weight: 600;
|
|
476
500
|
}
|
|
477
501
|
|
|
478
502
|
.cn-record-notes__leaf {
|
package/src/css/dialog.css
CHANGED
|
@@ -238,8 +238,11 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
|
|
|
238
238
|
padding-inline-end: 24px;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/* The body starts 22px under the header, as most dialog boards draw it
|
|
242
|
+
(header padding-bottom 4px + form padding-top 18px; DqBerichtVersturen,
|
|
243
|
+
DqOverdragen, DqNieuweZaak, DqBesluit): the 18px gap plus 4px. */
|
|
241
244
|
.cn-dialog-board .dialog__content > .cn-dialog-header {
|
|
242
|
-
margin-bottom:
|
|
245
|
+
margin-bottom: 4px;
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
/* Footer: 16px 24px, hairline on top, right aligned, 40px buttons, 10px gap. */
|
|
@@ -344,6 +347,15 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
|
|
|
344
347
|
margin-block-start: 26px;
|
|
345
348
|
}
|
|
346
349
|
|
|
350
|
+
/* A select in a form dialog sits under its label and over its help line
|
|
351
|
+
as a text field does (DqNieuweZaak "Zaaktype", "Intakekanaal"): the
|
|
352
|
+
form's label stands outside the select, so NcSelect's own 6px/4px margins
|
|
353
|
+
only pushed the control 6px below the text fields beside it and its help
|
|
354
|
+
line 4px further down. */
|
|
355
|
+
.cn-dialog-board .dialog__content .cn-form-dialog__select-wrapper > .v-select.select:not(:has(.input-field__label)) {
|
|
356
|
+
margin-block: 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
347
359
|
/* A multiple select is the board's 40px field too (DqFilterDelen "Delen
|
|
348
360
|
met"). The edge goes on the toggle, which holds the chips and the search,
|
|
349
361
|
so the chosen people and groups sit inside the field; the search inside it
|
|
@@ -721,6 +721,13 @@
|
|
|
721
721
|
padding-top: calc(14px - var(--cn-board-first-line-leading));
|
|
722
722
|
}
|
|
723
723
|
|
|
724
|
+
/* A table (an object list) starts 12px below the title's box: its head has
|
|
725
|
+
a fill and no leading, so the glyph-to-glyph gap above left it 2px short
|
|
726
|
+
(DqZaaktypeStatussen "Statussen", "Resultaten"). */
|
|
727
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper__content:has(> .cn-object-list-widget):not(.cn-detail-page__side *) {
|
|
728
|
+
padding-top: 12px;
|
|
729
|
+
}
|
|
730
|
+
|
|
724
731
|
/* Running text (DqZaak "Het verzoek") sits 12px below the title's box and
|
|
725
732
|
keeps its own half-leading, at 1.6 as drawn.
|
|
726
733
|
@spec openspec/changes/board-card-title-gap-variants/specs/board-card-title-gap-variants/spec.md#requirement-a-board-card-starts-its-body-as-drawn-for-its-kind-of-body */
|
|
@@ -1527,12 +1534,12 @@
|
|
|
1527
1534
|
}
|
|
1528
1535
|
|
|
1529
1536
|
/* ---- A card's Add ------------------------------------------------------ */
|
|
1530
|
-
/* The board draws a list card's Add as the neutral secondary
|
|
1531
|
-
|
|
1532
|
-
"
|
|
1537
|
+
/* The board draws a list card's Add as the neutral secondary right-aligned
|
|
1538
|
+
in the title row, at the title row's 34px (DqZaaktypeStatussen "Status
|
|
1539
|
+
toevoegen", DqZaaktype "Taak toevoegen", DqCatalogus "Objecttype
|
|
1540
|
+
toevoegen": 13 of the 15 boards that draw one). CnDetailWidgetHost renders
|
|
1533
1541
|
that button in the board look, so the list's footer Add bar, which ADR-062
|
|
1534
|
-
draws in the Nextcloud look, is hidden in the card.
|
|
1535
|
-
card title row's 34px buttons.
|
|
1542
|
+
draws in the Nextcloud look, is hidden in the card.
|
|
1536
1543
|
@spec openspec/changes/board-object-list-add-button/specs/board-object-list-add-button/spec.md#requirement-the-board-look-draws-a-cards-add-as-a-title-row-button */
|
|
1537
1544
|
.cn-look-board .cn-detail-page__catalog-card:not(.cn-look-nextcloud *) .cn-object-list-widget__add {
|
|
1538
1545
|
display: none;
|
|
@@ -1544,10 +1551,6 @@
|
|
|
1544
1551
|
display: none;
|
|
1545
1552
|
}
|
|
1546
1553
|
|
|
1547
|
-
.cn-look-board .cn-widget-wrapper__actions .button-vue.cn-detail-page__catalog-add:has(> .button-vue__wrapper > .button-vue__text:not(:empty)):where(:not(.cn-look-nextcloud *)) {
|
|
1548
|
-
--button-size: var(--cn-board-control-height);
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
1554
|
/* A map in a board card has the board's control corners (DqZaak, DqZaakMeer
|
|
1552
1555
|
"Locaties"); overflow keeps the tiles inside them. */
|
|
1553
1556
|
.cn-look-board .cn-map-widget:not(.cn-map-widget--fullscreen) {
|