@conduction/nextcloud-vue 2.137.0 → 2.138.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/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +6 -4
- 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/CnDetailCard/CnDetailCard.vue.js +1 -1
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue2.js +6 -0
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue3.js +24 -7
- package/dist/esm/components/CnDetailCard/CnDetailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +2 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +43 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +34 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +23 -15
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnMailTab/dashboardRegistration.js +4 -0
- package/dist/esm/components/CnMailTab/dashboardRegistration.js.map +1 -1
- package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue.js +1 -1
- package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue2.js.map +1 -1
- package/dist/esm/components/CnNotificationMatrix/CnNotificationMatrix.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +2 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +6 -0
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +3 -3
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +16 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +11 -5
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue.js +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js +1 -0
- package/dist/esm/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue3.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +36 -9
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/l10n/en.json.js +10 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +10 -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 +431 -360
- package/dist/esm/utils/resolveFilterTokens.js +9 -0
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +1 -1
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +418 -221
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +431 -360
- package/l10n/en.json +10 -1
- package/l10n/nl.json +10 -1
- package/package.json +1 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +6 -4
- package/src/components/CnDetailCard/CnDetailCard.md +1 -0
- package/src/components/CnDetailCard/CnDetailCard.vue +25 -1
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +43 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +34 -1
- package/src/components/CnMailTab/dashboardRegistration.js +4 -0
- package/src/components/CnNotificationMatrix/CnNotificationMatrix.vue +4 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +3 -0
- package/src/components/CnOrderedListField/CnOrderedListField.vue +11 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +22 -3
- package/src/components/CnSavedViewMenuSettings/CnSavedViewMenuSettings.vue +1 -0
- package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +3 -0
- package/src/composables/indexSources.js +36 -9
- package/src/css/dialog.css +8 -0
- package/src/css/index-page.css +5 -0
- package/src/css/look-board-detail.css +12 -0
- package/src/css/look-board.css +26 -0
- package/src/utils/resolveFilterTokens.js +9 -0
- package/src/utils/sentinelTokens.js +1 -1
package/l10n/en.json
CHANGED
|
@@ -2377,7 +2377,8 @@
|
|
|
2377
2377
|
"Talk to {agent}": "Talk to {agent}",
|
|
2378
2378
|
"Search a person or group": "Search a person or group",
|
|
2379
2379
|
"Now: by {order}.": "Now: by {order}.",
|
|
2380
|
-
"You save the order along with a view.": "You save the order along with a view."
|
|
2380
|
+
"You save the order along with a view.": "You save the order along with a view.",
|
|
2381
|
+
"me": "me"
|
|
2381
2382
|
},
|
|
2382
2383
|
"plurals": {
|
|
2383
2384
|
"{count} member": [
|
|
@@ -2578,6 +2579,14 @@
|
|
|
2578
2579
|
"within %n day": [
|
|
2579
2580
|
"within %n day",
|
|
2580
2581
|
"within %n days"
|
|
2582
|
+
],
|
|
2583
|
+
"%n other": [
|
|
2584
|
+
"%n other",
|
|
2585
|
+
"%n others"
|
|
2586
|
+
],
|
|
2587
|
+
"{names} has no location, so it is not on the map.": [
|
|
2588
|
+
"{names} has no location, so it is not on the map.",
|
|
2589
|
+
"{names} have no location, so they are not on the map."
|
|
2581
2590
|
]
|
|
2582
2591
|
}
|
|
2583
2592
|
}
|
package/l10n/nl.json
CHANGED
|
@@ -2627,7 +2627,8 @@
|
|
|
2627
2627
|
"Talk to {agent}": "Praten met {agent}",
|
|
2628
2628
|
"Search a person or group": "Zoek een persoon of groep",
|
|
2629
2629
|
"Now: by {order}.": "Nu: op {order}.",
|
|
2630
|
-
"You save the order along with a view.": "De sortering bewaart u mee in een weergave."
|
|
2630
|
+
"You save the order along with a view.": "De sortering bewaart u mee in een weergave.",
|
|
2631
|
+
"me": "ik"
|
|
2631
2632
|
},
|
|
2632
2633
|
"plurals": {
|
|
2633
2634
|
"{count} member": [
|
|
@@ -2845,6 +2846,14 @@
|
|
|
2845
2846
|
"within %n day": [
|
|
2846
2847
|
"binnen %n dag",
|
|
2847
2848
|
"binnen %n dagen"
|
|
2849
|
+
],
|
|
2850
|
+
"%n other": [
|
|
2851
|
+
"%n andere",
|
|
2852
|
+
"%n andere"
|
|
2853
|
+
],
|
|
2854
|
+
"{names} has no location, so it is not on the map.": [
|
|
2855
|
+
"{names} heeft geen locatie, dus staat niet op de kaart.",
|
|
2856
|
+
"{names} hebben geen locatie, dus staan ze niet op de kaart."
|
|
2848
2857
|
]
|
|
2849
2858
|
}
|
|
2850
2859
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.138.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": {
|
|
@@ -639,7 +639,7 @@ export default {
|
|
|
639
639
|
* type-aware rendering, so a manifest column can opt into currency or a
|
|
640
640
|
* colour swatch without registering a function.
|
|
641
641
|
*
|
|
642
|
-
* @type {{style?: 'currency'|'number'|'percent'|'duration'|'swatch', currency?: string, decimals?: number, unit?: 'milliseconds'|'seconds'|'minutes'|'hours', prefix?: string, suffix?: string, colorField?: string}}
|
|
642
|
+
* @type {{style?: 'currency'|'number'|'decimal'|'percent'|'duration'|'swatch', currency?: string, decimals?: number, unit?: 'milliseconds'|'seconds'|'minutes'|'hours', prefix?: string, suffix?: string, colorField?: string}}
|
|
643
643
|
*/
|
|
644
644
|
format: {
|
|
645
645
|
type: Object,
|
|
@@ -1709,9 +1709,9 @@ export default {
|
|
|
1709
1709
|
return typeof s === 'string' ? s : null
|
|
1710
1710
|
},
|
|
1711
1711
|
|
|
1712
|
-
/** True when a built-in NUMERIC format (currency/number/percent/duration) applies. */
|
|
1712
|
+
/** True when a built-in NUMERIC format (currency/number/decimal/percent/duration) applies. */
|
|
1713
1713
|
hasBuiltinFormat() {
|
|
1714
|
-
return ['currency', 'number', 'percent', 'duration'].includes(this.formatStyle)
|
|
1714
|
+
return ['currency', 'number', 'decimal', 'percent', 'duration'].includes(this.formatStyle)
|
|
1715
1715
|
},
|
|
1716
1716
|
|
|
1717
1717
|
/** True when this cell renders as a colour swatch (`format.style:"swatch"`). */
|
|
@@ -1916,9 +1916,11 @@ export default {
|
|
|
1916
1916
|
if (!Number.isFinite(num)) {
|
|
1917
1917
|
return formatValue(this.value, this.property, { truncate: this.truncate })
|
|
1918
1918
|
}
|
|
1919
|
+
// `decimal` defaults to one fraction digit, as formatMetricValue does.
|
|
1920
|
+
const defaultDecimals = { currency: 2, decimal: 1 }
|
|
1919
1921
|
const decimals = Number.isFinite(fmt.decimals)
|
|
1920
1922
|
? fmt.decimals
|
|
1921
|
-
: (fmt.style
|
|
1923
|
+
: (defaultDecimals[fmt.style] ?? 0)
|
|
1922
1924
|
let body
|
|
1923
1925
|
if (fmt.style === 'currency') {
|
|
1924
1926
|
// `format.currencyField` reads the code from the row's own field.
|
|
@@ -84,6 +84,7 @@ With footer slot:
|
|
|
84
84
|
| Prop | Type | Default | Description |
|
|
85
85
|
|------|------|---------|-------------|
|
|
86
86
|
| `icon` | Object\|Function | `null` | Optional MDI icon component rendered in the card header |
|
|
87
|
+
| `subtitle` | String | `''` | A muted line under the title, as given (translate before passing). No line when empty. |
|
|
87
88
|
|
|
88
89
|
| Slot | Description |
|
|
89
90
|
|------|-------------|
|
|
@@ -22,7 +22,13 @@
|
|
|
22
22
|
:size="20"
|
|
23
23
|
class="cn-detail-card__icon" />
|
|
24
24
|
</slot>
|
|
25
|
-
<
|
|
25
|
+
<div v-if="subtitle" class="cn-detail-card__heading">
|
|
26
|
+
<h3 class="cn-detail-card__title">
|
|
27
|
+
{{ title }}
|
|
28
|
+
</h3>
|
|
29
|
+
<span class="cn-detail-card__subtitle" data-testid="cn-detail-card-subtitle">{{ subtitle }}</span>
|
|
30
|
+
</div>
|
|
31
|
+
<h3 v-else class="cn-detail-card__title">
|
|
26
32
|
{{ title }}
|
|
27
33
|
</h3>
|
|
28
34
|
</div>
|
|
@@ -125,6 +131,12 @@ export default {
|
|
|
125
131
|
default: '',
|
|
126
132
|
},
|
|
127
133
|
|
|
134
|
+
/** A muted line under the title, as given (translate before passing). */
|
|
135
|
+
subtitle: {
|
|
136
|
+
type: String,
|
|
137
|
+
default: '',
|
|
138
|
+
},
|
|
139
|
+
|
|
128
140
|
/** Optional MDI icon component for the header */
|
|
129
141
|
icon: {
|
|
130
142
|
type: [Object, Function],
|
|
@@ -247,6 +259,18 @@ export default {
|
|
|
247
259
|
flex-shrink: 0;
|
|
248
260
|
}
|
|
249
261
|
|
|
262
|
+
.cn-detail-card__heading {
|
|
263
|
+
display: flex;
|
|
264
|
+
flex-direction: column;
|
|
265
|
+
min-width: 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.cn-detail-card__subtitle {
|
|
269
|
+
font-size: 13px;
|
|
270
|
+
line-height: 1.3;
|
|
271
|
+
color: var(--color-text-maxcontrast);
|
|
272
|
+
}
|
|
273
|
+
|
|
250
274
|
.cn-detail-card__title {
|
|
251
275
|
font-weight: 600;
|
|
252
276
|
font-size: 14px;
|
|
@@ -1444,6 +1444,7 @@ export default {
|
|
|
1444
1444
|
objectId: this.objectId,
|
|
1445
1445
|
object: this.object,
|
|
1446
1446
|
config: unwrapAppConfig(this.cnAppConfig),
|
|
1447
|
+
context: (this.cnDetailVisibilityRows && this.cnDetailVisibilityRows.value) || null,
|
|
1447
1448
|
})
|
|
1448
1449
|
},
|
|
1449
1450
|
|
|
@@ -1510,6 +1511,7 @@ export default {
|
|
|
1510
1511
|
// prints its title as given.
|
|
1511
1512
|
title: this.translateLabel(this.widget?.title || ''),
|
|
1512
1513
|
...this.leafTitleLabel,
|
|
1514
|
+
...(this.widgetSubtitle ? { subtitle: this.translateLabel(this.widgetSubtitle) } : {}),
|
|
1513
1515
|
// A provider opting into `bareWidget` is being rendered where the
|
|
1514
1516
|
// tab strip already supplies the card and the title, so tell it to
|
|
1515
1517
|
// drop its own. Without this the panel gets a titled card inside
|
|
@@ -1128,6 +1128,21 @@ export default {
|
|
|
1128
1128
|
default: '',
|
|
1129
1129
|
},
|
|
1130
1130
|
|
|
1131
|
+
/**
|
|
1132
|
+
* The second line as a list of fields joined with " · ", a field
|
|
1133
|
+
* without a value left out: the `nameSecondary` tokens without their
|
|
1134
|
+
* braces (`["type", "size", "modified|date"]`, a row-data field such as
|
|
1135
|
+
* `"senderName"`). Or a list of variants `{ when, fields }`: the first
|
|
1136
|
+
* whose `when` is `true`, or that has no `when`, is used (a case type
|
|
1137
|
+
* flag read through `@context`). Takes precedence over `nameSecondary`.
|
|
1138
|
+
*
|
|
1139
|
+
* @spec openspec/changes/files-second-line-fields/specs/files-second-line-fields/spec.md#requirement-a-files-second-line-is-a-list-of-fields
|
|
1140
|
+
*/
|
|
1141
|
+
secondLine: {
|
|
1142
|
+
type: Array,
|
|
1143
|
+
default: null,
|
|
1144
|
+
},
|
|
1145
|
+
|
|
1131
1146
|
/** The line `dropHint` shows. */
|
|
1132
1147
|
dropHintLabel: {
|
|
1133
1148
|
type: String,
|
|
@@ -1526,6 +1541,26 @@ export default {
|
|
|
1526
1541
|
*
|
|
1527
1542
|
* @return {Array<object>} The file nodes.
|
|
1528
1543
|
*/
|
|
1544
|
+
/**
|
|
1545
|
+
* The fields `secondLine` names for this browser: the list itself, or
|
|
1546
|
+
* the fields of its first variant whose `when` is `true` or absent.
|
|
1547
|
+
* Null without `secondLine` or a matching variant.
|
|
1548
|
+
*
|
|
1549
|
+
* @return {Array<string>|null}
|
|
1550
|
+
* @spec openspec/changes/files-second-line-fields/specs/files-second-line-fields/spec.md#requirement-a-files-second-line-is-a-list-of-fields
|
|
1551
|
+
*/
|
|
1552
|
+
secondLineFields() {
|
|
1553
|
+
const list = Array.isArray(this.secondLine) ? this.secondLine : []
|
|
1554
|
+
if (list.length === 0) {
|
|
1555
|
+
return null
|
|
1556
|
+
}
|
|
1557
|
+
if (list.every((item) => typeof item === 'string')) {
|
|
1558
|
+
return list
|
|
1559
|
+
}
|
|
1560
|
+
const variant = list.find((item) => item && typeof item === 'object' && Array.isArray(item.fields) && (!('when' in item) || item.when === true))
|
|
1561
|
+
return variant ? variant.fields.filter((field) => typeof field === 'string') : null
|
|
1562
|
+
},
|
|
1563
|
+
|
|
1529
1564
|
/**
|
|
1530
1565
|
* The bulk bar's count text.
|
|
1531
1566
|
*
|
|
@@ -2079,7 +2114,8 @@ export default {
|
|
|
2079
2114
|
* @return {string}
|
|
2080
2115
|
*/
|
|
2081
2116
|
nameSecondaryOf(node) {
|
|
2082
|
-
|
|
2117
|
+
const fields = this.secondLineFields
|
|
2118
|
+
if ((!this.nameSecondary && fields === null) || this.isFolder(node)) {
|
|
2083
2119
|
return ''
|
|
2084
2120
|
}
|
|
2085
2121
|
const byId = this.resolvedRowData && typeof this.resolvedRowData === 'object' ? this.resolvedRowData : {}
|
|
@@ -2093,6 +2129,12 @@ export default {
|
|
|
2093
2129
|
type: this.typeLabelOf(node),
|
|
2094
2130
|
...(owner ? { owner } : {}),
|
|
2095
2131
|
}
|
|
2132
|
+
if (fields !== null) {
|
|
2133
|
+
return fields
|
|
2134
|
+
.map((field) => fillTemplate(`{${field}}`, object))
|
|
2135
|
+
.filter((text) => text !== '')
|
|
2136
|
+
.join(' · ')
|
|
2137
|
+
}
|
|
2096
2138
|
return fillTemplate(this.cnTranslate(this.nameSecondary), object)
|
|
2097
2139
|
},
|
|
2098
2140
|
|
|
@@ -1034,6 +1034,7 @@
|
|
|
1034
1034
|
</ol>
|
|
1035
1035
|
<div v-if="mapList.missing.length > 0" class="cn-index-page__map-missing" data-testid="cn-index-map-missing">
|
|
1036
1036
|
<strong>{{ mapMissingTitle }}</strong>
|
|
1037
|
+
<span v-if="mapMissingNote" class="cn-index-page__map-missing-note" data-testid="cn-index-map-missing-note">{{ mapMissingNote }}</span>
|
|
1037
1038
|
<NcButton variant="tertiary-no-background" data-testid="cn-index-map-missing-table" @click="onViewModeChange('table')">
|
|
1038
1039
|
{{ mapMissingLink }}
|
|
1039
1040
|
</NcButton>
|
|
@@ -1459,6 +1460,7 @@ import { countLine } from '../../utils/countLine.js'
|
|
|
1459
1460
|
import { reportBindingProblems } from '../../utils/diagnostics.js'
|
|
1460
1461
|
import { fetchGroupedCounts } from '../../utils/fetchAggregate.js'
|
|
1461
1462
|
import { fetchFilterCounts, fetchListTotal } from '../../utils/fetchFilterCounts.js'
|
|
1463
|
+
import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
1462
1464
|
import { listText, rangeChipText, rangeEnds, relativeRangeText } from '../../utils/filterRangeLabel.js'
|
|
1463
1465
|
import { unwrapAppConfig } from '../../utils/formatMetric.js'
|
|
1464
1466
|
import { groupDisplayName, loadGroupDisplayName } from '../../utils/groupAutocomplete.js'
|
|
@@ -7217,6 +7219,31 @@ export default {
|
|
|
7217
7219
|
return n('nextcloud-vue', '%n without an address', '%n without an address', this.mapList.missing.length)
|
|
7218
7220
|
},
|
|
7219
7221
|
|
|
7222
|
+
/**
|
|
7223
|
+
* The sentence on that card naming the rows without a place by the
|
|
7224
|
+
* list's title template, three at most and the rest counted: "A, B and
|
|
7225
|
+
* C have no location, so they are not on the map." Empty without a
|
|
7226
|
+
* title template.
|
|
7227
|
+
*
|
|
7228
|
+
* @spec openspec/changes/map-missing-card-names-the-cases/specs/map-missing-card-names-the-cases/spec.md#requirement-the-missing-address-card-names-the-cases-it-counts
|
|
7229
|
+
* @return {string} The sentence.
|
|
7230
|
+
*/
|
|
7231
|
+
mapMissingNote() {
|
|
7232
|
+
const list = this.mapConfig && this.mapConfig.list
|
|
7233
|
+
const template = list && typeof list.title === 'string' ? this.cnTranslate(list.title) : ''
|
|
7234
|
+
const missing = this.mapList.missing
|
|
7235
|
+
if (template === '' || missing.length === 0) {
|
|
7236
|
+
return ''
|
|
7237
|
+
}
|
|
7238
|
+
const shown = missing.slice(0, 3).map((row) => fillTemplate(template, row, undefined, this.cnFormatters ? { formatters: this.cnFormatters } : null)).filter((title) => title !== '')
|
|
7239
|
+
if (shown.length === 0) {
|
|
7240
|
+
return ''
|
|
7241
|
+
}
|
|
7242
|
+
const rest = missing.length - shown.length
|
|
7243
|
+
const names = listText(rest > 0 ? [...shown, n('nextcloud-vue', '%n other', '%n others', rest)] : shown, getCanonicalLocale() || undefined)
|
|
7244
|
+
return n('nextcloud-vue', '{names} has no location, so it is not on the map.', '{names} have no location, so they are not on the map.', missing.length, { names })
|
|
7245
|
+
},
|
|
7246
|
+
|
|
7220
7247
|
/** @return {string} The link from that card to the table. */
|
|
7221
7248
|
mapMissingLink() {
|
|
7222
7249
|
return t('nextcloud-vue', 'Show them in the table')
|
|
@@ -11519,10 +11546,16 @@ export default {
|
|
|
11519
11546
|
const bucket = facet && Array.isArray(facet.values)
|
|
11520
11547
|
? facet.values.find((fv) => fv && typeof fv === 'object' && String(fv.value) === id)
|
|
11521
11548
|
: null
|
|
11522
|
-
|
|
11549
|
+
// A facet that only echoes the value names nothing; a group or an
|
|
11550
|
+
// option below may.
|
|
11551
|
+
if (bucket && typeof bucket.label === 'string' && bucket.label !== '' && bucket.label !== id) {
|
|
11523
11552
|
return this.cnTranslate(bucket.label)
|
|
11524
11553
|
}
|
|
11525
11554
|
const prop = this.effectiveSchema?.properties?.[key]
|
|
11555
|
+
// The reader on a user filter is "me" (DqFilterInMenu "Behandelaar: ik").
|
|
11556
|
+
if (prop && prop.referenceType === 'nextcloud-user' && id === (getCurrentUser()?.uid ?? '')) {
|
|
11557
|
+
return t('nextcloud-vue', 'me')
|
|
11558
|
+
}
|
|
11526
11559
|
if (prop && prop.referenceType === 'nextcloud-group') {
|
|
11527
11560
|
const name = groupDisplayName(id)
|
|
11528
11561
|
if (name === null) {
|
|
@@ -25,4 +25,8 @@ registerDashboardWidget('mail', {
|
|
|
25
25
|
displayName: 'Mail',
|
|
26
26
|
icon: 'EmailOutline',
|
|
27
27
|
surfaces: ['detail-page'],
|
|
28
|
+
// Draws its own title row; on a card surface it stands in an untitled card.
|
|
29
|
+
// @spec openspec/changes/mail-widget-card/specs/mail-widget-card/spec.md#requirement-the-mail-widget-stands-in-a-card-on-a-card-surface
|
|
30
|
+
ownsTitle: true,
|
|
31
|
+
chrome: 'card',
|
|
28
32
|
})
|
|
@@ -1093,12 +1093,16 @@ export default {
|
|
|
1093
1093
|
white-space: nowrap;
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
|
+
/* A theme that colours every th (thematiq's table header token) leaves
|
|
1097
|
+
the board grid's head white. */
|
|
1096
1098
|
.cn-notification-matrix__table {
|
|
1099
|
+
--nldesign-component-table-header-background-color: transparent;
|
|
1097
1100
|
font-size: 14px;
|
|
1098
1101
|
}
|
|
1099
1102
|
|
|
1100
1103
|
.cn-notification-matrix__table thead th {
|
|
1101
1104
|
padding: 8px 10px 8px 0;
|
|
1105
|
+
line-height: 1.2;
|
|
1102
1106
|
border-block-end: 1px solid var(--color-border);
|
|
1103
1107
|
color: var(--color-text-maxcontrast);
|
|
1104
1108
|
font-size: 13px;
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
:reviewSummary="reviewSummary"
|
|
25
25
|
:footnote="footnote"
|
|
26
26
|
:nameSecondary="nameSecondary"
|
|
27
|
+
:secondLine="secondLine"
|
|
27
28
|
:linkedItems="linkedItems"
|
|
28
29
|
:columns="columns"
|
|
29
30
|
:rowData="rowDataSource"
|
|
@@ -450,6 +451,8 @@ export default {
|
|
|
450
451
|
footnote: { type: String, default: '' },
|
|
451
452
|
/** Forwarded to CnFilesBrowser: the second line under each file's name. */
|
|
452
453
|
nameSecondary: { type: String, default: '' },
|
|
454
|
+
/** Forwarded to CnFilesBrowser: the second line as a list of fields, or variants. */
|
|
455
|
+
secondLine: { type: Array, default: null },
|
|
453
456
|
/** A dotted path to the list of records in the `rowDataUrl` answer; empty reads the answer itself. */
|
|
454
457
|
rowDataPath: { type: String, default: '' },
|
|
455
458
|
/** The record field that holds the Nextcloud file id. */
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
7
|
<div class="cn-ordered-list-field" data-testid="cn-ordered-list-field">
|
|
8
|
-
<p
|
|
8
|
+
<p
|
|
9
|
+
v-if="label"
|
|
10
|
+
:id="labelId"
|
|
11
|
+
class="cn-ordered-list-field__label"
|
|
12
|
+
:class="{ 'hidden-visually': hideLabel }">
|
|
9
13
|
{{ label }}
|
|
10
14
|
</p>
|
|
11
15
|
<ol
|
|
@@ -192,6 +196,12 @@ export default {
|
|
|
192
196
|
default: '',
|
|
193
197
|
},
|
|
194
198
|
|
|
199
|
+
/** Keep the label for assistive technology only, when a heading above already says it. */
|
|
200
|
+
hideLabel: {
|
|
201
|
+
type: Boolean,
|
|
202
|
+
default: false,
|
|
203
|
+
},
|
|
204
|
+
|
|
195
205
|
/** The picker's label. */
|
|
196
206
|
addLabel: {
|
|
197
207
|
type: String,
|
|
@@ -20,9 +20,11 @@
|
|
|
20
20
|
<div v-else-if="loading && rows.length === 0" class="cn-record-list__loading">
|
|
21
21
|
<NcLoadingIcon :size="20" :name="loadingLabel" />
|
|
22
22
|
</div>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
<template v-else-if="rows.length === 0">
|
|
24
|
+
<p v-if="emptyLabel !== ''" class="cn-record-list__empty" data-testid="cn-record-list-empty">
|
|
25
|
+
{{ emptyLabel }}
|
|
26
|
+
</p>
|
|
27
|
+
</template>
|
|
26
28
|
<!-- notice: the rows as sentences in one toned box ("2 waarschuwingen: ..."). -->
|
|
27
29
|
<div
|
|
28
30
|
v-else-if="presentation === 'notice'"
|
|
@@ -251,6 +253,7 @@
|
|
|
251
253
|
<component
|
|
252
254
|
:is="item.action.tag"
|
|
253
255
|
class="cn-record-list__action"
|
|
256
|
+
:class="{ 'cn-record-list__action--outlined': rowActionOutlined }"
|
|
254
257
|
v-bind="item.action.attrs"
|
|
255
258
|
data-testid="cn-record-list-action"
|
|
256
259
|
v-on="item.action.tag === 'button' ? { click: () => runRowAction(item.row) } : {}">
|
|
@@ -1469,6 +1472,18 @@ export default {
|
|
|
1469
1472
|
* @return {string}
|
|
1470
1473
|
* @spec openspec/changes/record-list-table-row-actions/specs/record-list-table/spec.md#requirement-a-table-row-can-carry-a-second-line-and-an-actions-menu
|
|
1471
1474
|
*/
|
|
1475
|
+
/**
|
|
1476
|
+
* Whether the row action is drawn as an outlined button sized to its
|
|
1477
|
+
* label (`rowAction.variant: "outlined"`, DqMijnTeam "Oppakken") instead
|
|
1478
|
+
* of the 150px pill (DcMijnActies "Stem uitbrengen").
|
|
1479
|
+
*
|
|
1480
|
+
* @return {boolean} True for the outlined variant.
|
|
1481
|
+
* @spec openspec/changes/board-row-action-outlined/specs/board-row-action-outlined/spec.md#requirement-a-dashboard-rows-action-is-an-outlined-button
|
|
1482
|
+
*/
|
|
1483
|
+
rowActionOutlined() {
|
|
1484
|
+
return this.content?.rowAction?.variant === 'outlined'
|
|
1485
|
+
},
|
|
1486
|
+
|
|
1472
1487
|
rowActionsHeader() {
|
|
1473
1488
|
const own = this.content?.rowActionsHeader
|
|
1474
1489
|
return typeof own === 'string' && own !== '' ? this.translate(own) : t('nextcloud-vue', 'Actions')
|
|
@@ -1511,6 +1526,10 @@ export default {
|
|
|
1511
1526
|
if (this.conditionalEmptyText !== '') {
|
|
1512
1527
|
return this.conditionalEmptyText
|
|
1513
1528
|
}
|
|
1529
|
+
// `emptyText: ""` says nothing on an empty list.
|
|
1530
|
+
if (this.content?.emptyText === '') {
|
|
1531
|
+
return ''
|
|
1532
|
+
}
|
|
1514
1533
|
return this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'Nothing here yet')
|
|
1515
1534
|
},
|
|
1516
1535
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
:modelValue="values"
|
|
13
13
|
:options="options"
|
|
14
14
|
:label="t('nextcloud-vue', 'Views in my menu')"
|
|
15
|
+
:hideLabel="true"
|
|
15
16
|
:addLabel="t('nextcloud-vue', 'Add view')"
|
|
16
17
|
:addPlaceholder="t('nextcloud-vue', 'Pick a saved view')"
|
|
17
18
|
:addHint="t('nextcloud-vue', 'You see your own views, the views shared with you and the default views. A new view goes to the bottom.')"
|
|
@@ -400,7 +400,10 @@ export default {
|
|
|
400
400
|
background: var(--color-main-background);
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
/* A section's heading and description start at the text's edge, as the
|
|
404
|
+
board draws them (NcAppSettingsSection indents them 12px). */
|
|
403
405
|
.cn-user-settings-dialog__sections :deep(.app-settings-section) {
|
|
406
|
+
--app-settings-section-text-offset: 0;
|
|
404
407
|
margin: 0;
|
|
405
408
|
padding: 0;
|
|
406
409
|
border: 0;
|
|
@@ -1088,7 +1088,7 @@ function fillRowTemplate(template, row) {
|
|
|
1088
1088
|
* and its count (DqZoekresultaten).
|
|
1089
1089
|
*
|
|
1090
1090
|
* `sourceConfig.kinds` lists what is searched:
|
|
1091
|
-
* `{ id, label, register, schema, titleField?, subtitleField?, statusField?, statusResolve?, linkedField?, linkedTemplate?, href?, limit? }`
|
|
1091
|
+
* `{ id, label, register, schema, titleField?, subtitleField?, subtitleTemplate?, statusField?, statusResolve?, linkedField?, linkedTemplate?, href?, limit? }`
|
|
1092
1092
|
* for objects (OpenRegister's `_search`, so the reader sees what they may
|
|
1093
1093
|
* read), or `{ id, label, files: true, limit? }` for the reader's files
|
|
1094
1094
|
* (Nextcloud's unified search). The term is `sourceConfig.term`, else the
|
|
@@ -1102,6 +1102,8 @@ function fillRowTemplate(template, row) {
|
|
|
1102
1102
|
* coloured by `sourceConfig.statusColorMap`. `linkedTemplate` (a field
|
|
1103
1103
|
* template, "{case.title}") or `linkedField` (a path) fills the "Linked"
|
|
1104
1104
|
* column, drawn when a kind declares one; `sourceConfig.linkedLabel` names it.
|
|
1105
|
+
* `subtitleTemplate` ("Case {identifier}", translated) fills the line under a
|
|
1106
|
+
* result, else `subtitleField`.
|
|
1105
1107
|
* The page's title and count line can name the term as `{term}`, and the
|
|
1106
1108
|
* count is the active kind's total. Kind, link and status are filterable.
|
|
1107
1109
|
*
|
|
@@ -1199,6 +1201,13 @@ function searchSource(config = {}, context = {}) {
|
|
|
1199
1201
|
return ''
|
|
1200
1202
|
}
|
|
1201
1203
|
|
|
1204
|
+
const subtitleOf = (kind, object) => {
|
|
1205
|
+
if (kind.subtitleTemplate) {
|
|
1206
|
+
return fillTemplate(translate(kind.subtitleTemplate), object)
|
|
1207
|
+
}
|
|
1208
|
+
return kind.subtitleField ? String(readPath(object, kind.subtitleField) ?? '') : ''
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1202
1211
|
const searchObjects = async (axios, kind, term) => {
|
|
1203
1212
|
const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: kind.register, schema: kind.schema })
|
|
1204
1213
|
const res = await axios.get(url, { params: { _search: term, _limit: kind.limit || 20 } })
|
|
@@ -1219,7 +1228,7 @@ function searchSource(config = {}, context = {}) {
|
|
|
1219
1228
|
kindLabel: kind.label,
|
|
1220
1229
|
kindText: kindTextOf(kind, object),
|
|
1221
1230
|
title: String(readPath(object, kind.titleField || 'title') ?? object.name ?? id ?? ''),
|
|
1222
|
-
subtitle: kind
|
|
1231
|
+
subtitle: subtitleOf(kind, object),
|
|
1223
1232
|
status,
|
|
1224
1233
|
linked: linkedOf(kind, object),
|
|
1225
1234
|
href: kind.href ? generateUrl(fillRowTemplate(kind.href, { ...object, id })) : '',
|
|
@@ -1406,14 +1415,24 @@ function searchSource(config = {}, context = {}) {
|
|
|
1406
1415
|
* the pager asks for.
|
|
1407
1416
|
*
|
|
1408
1417
|
* @param {Array<object>} members The members, by name.
|
|
1409
|
-
* @param {object} config `search`, `sortKey`, `sortOrder`, `limit`, `offset`.
|
|
1418
|
+
* @param {object} config `search`, `nameLike`, `rolesLike`, `emailLike`, `today`, `sortKey`, `sortOrder`, `limit`, `offset`.
|
|
1410
1419
|
* @return {{rows: Array<object>, total: number}} The page and how many match.
|
|
1411
1420
|
*/
|
|
1412
1421
|
function membersPage(members, config = {}) {
|
|
1422
|
+
const has = (member, fields, text) => fields.some((field) => String(member[field] || '').toLowerCase().includes(text))
|
|
1413
1423
|
const term = String(config.search || '').trim().toLowerCase()
|
|
1414
|
-
let rows = term === ''
|
|
1415
|
-
|
|
1416
|
-
|
|
1424
|
+
let rows = term === '' ? members.slice() : members.filter((member) => has(member, ['displayname', 'function', 'roles', 'email'], term))
|
|
1425
|
+
// The table heads' filters: text contains, and the Today values picked.
|
|
1426
|
+
for (const [param, fields] of [['nameLike', ['displayname', 'function']], ['rolesLike', ['roles']], ['emailLike', ['email']]]) {
|
|
1427
|
+
const text = String(config[param] || '').trim().toLowerCase()
|
|
1428
|
+
if (text !== '') {
|
|
1429
|
+
rows = rows.filter((member) => has(member, fields, text))
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
const today = (Array.isArray(config.today) ? config.today : (config.today ? [config.today] : [])).map(String)
|
|
1433
|
+
if (today.length > 0) {
|
|
1434
|
+
rows = rows.filter((member) => today.includes(member.today))
|
|
1435
|
+
}
|
|
1417
1436
|
if (config.sortKey) {
|
|
1418
1437
|
const sign = config.sortOrder === 'desc' ? -1 : 1
|
|
1419
1438
|
rows = rows.sort((a, b) => sign * String(a[config.sortKey] ?? '').localeCompare(String(b[config.sortKey] ?? '')))
|
|
@@ -1475,10 +1494,11 @@ function usersSource() {
|
|
|
1475
1494
|
*/
|
|
1476
1495
|
load: async (config) => {
|
|
1477
1496
|
const group = String((config && (config.group || config.defaultGroup)) || '')
|
|
1478
|
-
const { search, sortKey, sortOrder, limit, offset, ...request } = config || {}
|
|
1497
|
+
const { search, sortKey, sortOrder, limit, offset, nameLike, rolesLike, emailLike, today, ...request } = config || {}
|
|
1479
1498
|
const key = JSON.stringify({ ...request, group })
|
|
1480
|
-
|
|
1481
|
-
|
|
1499
|
+
const narrows = [offset, search, sortKey, nameLike, rolesLike, emailLike, today].some((value) => value !== undefined)
|
|
1500
|
+
// A new page, term, order or head filter of the members already read: no request.
|
|
1501
|
+
if (key === membersKey && narrows) {
|
|
1482
1502
|
const page = membersPage(readMembers, config)
|
|
1483
1503
|
state.rows = page.rows
|
|
1484
1504
|
state.total = page.total
|
|
@@ -1574,8 +1594,15 @@ function usersSource() {
|
|
|
1574
1594
|
|
|
1575
1595
|
searchFields: {
|
|
1576
1596
|
group: { param: 'group', single: true },
|
|
1597
|
+
'displayname[like]': { param: 'nameLike', single: true },
|
|
1598
|
+
'roles[like]': { param: 'rolesLike', single: true },
|
|
1599
|
+
'email[like]': { param: 'emailLike', single: true },
|
|
1600
|
+
today: { param: 'today' },
|
|
1577
1601
|
},
|
|
1578
1602
|
|
|
1603
|
+
/** A filter button on every head (DqAfdelingen); the members read are narrowed here. */
|
|
1604
|
+
headerFilters: true,
|
|
1605
|
+
|
|
1579
1606
|
showAdd: false,
|
|
1580
1607
|
}
|
|
1581
1608
|
}
|
package/src/css/dialog.css
CHANGED
|
@@ -212,6 +212,14 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
|
|
|
212
212
|
padding: 22px 24px 20px;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/* NcDialog's scoped `.dialog__content[data-v-…]` sets padding-inline-end
|
|
216
|
+
12px with the same specificity as the rule above, so wherever its chunk
|
|
217
|
+
loads later (an admin page's setup wizard) the body ran 12px wider than
|
|
218
|
+
the board (DqInstellen 684 for 672). This one outranks it. */
|
|
219
|
+
.cn-dialog-board .dialog .dialog__content {
|
|
220
|
+
padding-inline-end: 24px;
|
|
221
|
+
}
|
|
222
|
+
|
|
215
223
|
.cn-dialog-board .dialog__content > .cn-dialog-header {
|
|
216
224
|
margin-bottom: -2px;
|
|
217
225
|
}
|
package/src/css/index-page.css
CHANGED
|
@@ -221,6 +221,11 @@
|
|
|
221
221
|
font-size: 14px;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
.cn-index-page__map-missing-note {
|
|
225
|
+
color: var(--color-main-text);
|
|
226
|
+
line-height: 1.45;
|
|
227
|
+
}
|
|
228
|
+
|
|
224
229
|
/* The selected folder's heading above the list. */
|
|
225
230
|
/* A sort option whose endpoint failed: the note above the fallen-back list. */
|
|
226
231
|
.cn-index-page__sort-source-note {
|
|
@@ -1270,6 +1270,18 @@
|
|
|
1270
1270
|
line-height: normal;
|
|
1271
1271
|
}
|
|
1272
1272
|
|
|
1273
|
+
/* A long task name wraps under itself, as the board draws it, rather than
|
|
1274
|
+
widening the table past the card. */
|
|
1275
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper__content > .cn-tasks-widget .cn-tasks-widget__table :is(.cn-tasks-widget__name, .cn-tasks-widget__meta) {
|
|
1276
|
+
white-space: normal;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/* A row's button and its task menu stand on one line. */
|
|
1280
|
+
.cn-look-board .cn-detail-page .cn-widget-wrapper__content > .cn-tasks-widget .cn-tasks-widget__table .cn-tasks-widget__cell-actions > * {
|
|
1281
|
+
display: inline-flex;
|
|
1282
|
+
vertical-align: middle;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1273
1285
|
/* ---- An object list table edge to edge (`content.tableBleed`) ------------ */
|
|
1274
1286
|
/* The case-tab boards draw a list's table edge to edge in its card
|
|
1275
1287
|
(DqZaakContact "Contactmomenten op deze zaak", DqZaakGerelateerd): it runs
|
package/src/css/look-board.css
CHANGED
|
@@ -2335,6 +2335,32 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .v-popper__popper.action-
|
|
|
2335
2335
|
font-weight: 600;
|
|
2336
2336
|
}
|
|
2337
2337
|
|
|
2338
|
+
/* A row whose one action is outlined (`rowAction.variant: "outlined"`,
|
|
2339
|
+
DqMijnTeam "Oppakken"): its title link reads as the row's title in the main
|
|
2340
|
+
ink (the theme's link rule reads --nldesign-color-on-surface), and the
|
|
2341
|
+
action is an outlined button sized to its label. Without the variant the
|
|
2342
|
+
action keeps its 150px pill (DcMijnActies).
|
|
2343
|
+
@spec openspec/changes/board-row-action-outlined/specs/board-row-action-outlined/spec.md#requirement-a-dashboard-rows-action-is-an-outlined-button */
|
|
2344
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--action:has(.cn-record-list__action--outlined) .cn-record-list__title--link {
|
|
2345
|
+
--nldesign-color-on-surface: var(--color-main-text);
|
|
2346
|
+
color: var(--color-main-text);
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__action--outlined {
|
|
2350
|
+
--nldesign-color-on-surface: var(--color-main-text);
|
|
2351
|
+
flex: none;
|
|
2352
|
+
padding: 0 12px;
|
|
2353
|
+
border: 1px solid var(--cn-board-control-border);
|
|
2354
|
+
border-radius: 8px;
|
|
2355
|
+
background: var(--color-main-background);
|
|
2356
|
+
color: var(--color-main-text);
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__action--outlined:hover,
|
|
2360
|
+
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__action--outlined:focus-visible {
|
|
2361
|
+
background: var(--color-background-hover);
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2338
2364
|
.cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__meta {
|
|
2339
2365
|
color: var(--color-text-maxcontrast);
|
|
2340
2366
|
font-size: 13px;
|