@conduction/nextcloud-vue 2.101.0 → 2.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +22 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +8 -6
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +29 -4
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +9 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +73 -0
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +99 -64
- package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +46 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +37 -2
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +49 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +113 -20
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +387 -359
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +84 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +8 -6
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +26 -2
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue.js +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js +25 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMenuWidget/CnMenuWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +10 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue.js +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +106 -4
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +70 -25
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +97 -3
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +46 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +9 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js.map +1 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js +165 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js +85 -0
- package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -0
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +54 -6
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +3 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +8 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +2 -23
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +5 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +5 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/mixins/pageViews.js +8 -1
- package/dist/esm/mixins/pageViews.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +593 -459
- package/dist/esm/utils/builtInFormatters.js +24 -4
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/rowClassRules.js +43 -0
- package/dist/esm/utils/rowClassRules.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +32 -4
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +10093 -8858
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +593 -459
- package/l10n/en.json +5 -1
- package/l10n/nl.json +5 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +1 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +31 -4
- package/src/components/CnChartWidget/CnChartWidget.md +3 -2
- package/src/components/CnChartWidget/CnChartWidget.vue +42 -4
- package/src/components/CnConversationThread/CnConversationThread.md +17 -0
- package/src/components/CnConversationThread/CnConversationThread.vue +113 -8
- package/src/components/CnConversationThread/CnConversationWidget.vue +60 -1
- package/src/components/CnDashboardPage/CnDashboardPage.md +4 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +50 -7
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnFormDialog/CnFormDialog.md +1 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +127 -0
- package/src/components/CnIndexPage/CnIndexPage.md +15 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +92 -9
- package/src/components/CnIndexPage/useNamedSource.js +26 -2
- package/src/components/CnMenuWidget/CnMenuWidget.vue +25 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +10 -1
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +1 -0
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +159 -5
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +134 -2
- package/src/components/CnSearchSelect/CnSearchSelect.vue +283 -0
- package/src/components/CnSearchSelect/index.js +12 -0
- package/src/components/CnSetupWizard/CnSetupWizard.vue +56 -6
- package/src/components/CnTabsWidget/CnTabsWidget.vue +8 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +2 -23
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +5 -3
- package/src/css/look-board-index.css +10 -0
- package/src/index.js +1 -0
- package/src/mixins/pageViews.js +8 -1
- package/src/schemas/app-manifest-v2.schema.json +107 -8
- package/src/utils/builtInFormatters.js +24 -4
- package/src/utils/rowClassRules.js +39 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +32 -4
package/l10n/en.json
CHANGED
|
@@ -1720,7 +1720,11 @@
|
|
|
1720
1720
|
"Made {date}": "Made {date}",
|
|
1721
1721
|
"expires {date}": "expires {date}",
|
|
1722
1722
|
"Loading …": "Loading …",
|
|
1723
|
-
"Reorder {item}: drag, or press the up and down arrow keys": "Reorder {item}: drag, or press the up and down arrow keys"
|
|
1723
|
+
"Reorder {item}: drag, or press the up and down arrow keys": "Reorder {item}: drag, or press the up and down arrow keys",
|
|
1724
|
+
"No contacts linked": "No contacts linked",
|
|
1725
|
+
"No relations yet": "No relations yet",
|
|
1726
|
+
"Period": "Period",
|
|
1727
|
+
"On": "On"
|
|
1724
1728
|
},
|
|
1725
1729
|
"plurals": {
|
|
1726
1730
|
"{count} selected": [
|
package/l10n/nl.json
CHANGED
|
@@ -1933,7 +1933,11 @@
|
|
|
1933
1933
|
"Expires today": "Verloopt vandaag",
|
|
1934
1934
|
"Made {date}": "Gemaakt {date}",
|
|
1935
1935
|
"expires {date}": "verloopt {date}",
|
|
1936
|
-
"Reorder {item}: drag, or press the up and down arrow keys": "{item} verplaatsen: slepen, of de pijltoetsen omhoog en omlaag"
|
|
1936
|
+
"Reorder {item}: drag, or press the up and down arrow keys": "{item} verplaatsen: slepen, of de pijltoetsen omhoog en omlaag",
|
|
1937
|
+
"No contacts linked": "Geen contacten gekoppeld",
|
|
1938
|
+
"No relations yet": "Nog geen relaties",
|
|
1939
|
+
"Period": "Periode",
|
|
1940
|
+
"On": "Aan"
|
|
1937
1941
|
},
|
|
1938
1942
|
"plurals": {
|
|
1939
1943
|
"{count} selected": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.103.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": {
|
|
@@ -217,6 +217,7 @@ Controlling the inline action button count — `inlineActionCount` sets how many
|
|
|
217
217
|
| `showBuildiqButton` | Boolean | `true` | Whether the buildiq square renders in the bar (the index page takes it into its header under the board look) |
|
|
218
218
|
| `bulkNoun` | String | `''` | The plural in the bulk band lead, "With the selected <noun>" (default "items") |
|
|
219
219
|
| `bulkHint` | String | `''` | The 13px hint after the bulk band's buttons |
|
|
220
|
+
| `bulkBandAlways` | Boolean | `false` | Board look: draw the bulk band before anything is selected too, its buttons disabled until there is a selection (DqZaken, DqWerkbord). Only when the band has an action to offer. |
|
|
220
221
|
|
|
221
222
|
|
|
222
223
|
`showBuildiqButton` is documented in the prop table of the component reference (board look, screens-chrome-parity).
|
|
@@ -507,12 +507,15 @@
|
|
|
507
507
|
|
|
508
508
|
<!--
|
|
509
509
|
Board look: the bulk band is its own row between the toolbar and the
|
|
510
|
-
table card, outside the toolbar element,
|
|
511
|
-
|
|
510
|
+
table card, outside the toolbar element, while rows are selected; with
|
|
511
|
+
`bulkBandAlways` also before, its buttons disabled until there is a
|
|
512
|
+
selection (DqZaken, DqWerkbord). The count stays announced by the live
|
|
513
|
+
region above.
|
|
512
514
|
-->
|
|
513
515
|
<div
|
|
514
|
-
v-if="isBoardLayout && selectable && selectedIds.length > 0"
|
|
516
|
+
v-if="isBoardLayout && selectable && (selectedIds.length > 0 || (bulkBandAlways && bulkBandHasActions))"
|
|
515
517
|
class="cn-actions-bar__band"
|
|
518
|
+
:class="{ 'cn-actions-bar__band--idle': selectedIds.length === 0 }"
|
|
516
519
|
role="region"
|
|
517
520
|
:aria-label="t('nextcloud-vue', 'Actions for the selection')"
|
|
518
521
|
data-testid="cn-bulk-band">
|
|
@@ -520,12 +523,14 @@
|
|
|
520
523
|
<NcButton
|
|
521
524
|
v-if="showMassCopy"
|
|
522
525
|
variant="secondary"
|
|
526
|
+
:disabled="selectedIds.length === 0"
|
|
523
527
|
@click="$emit('show-copy')">
|
|
524
528
|
{{ t('nextcloud-vue', 'Copy selected') }}
|
|
525
529
|
</NcButton>
|
|
526
530
|
<NcButton
|
|
527
531
|
v-if="showMassDelete"
|
|
528
532
|
variant="secondary"
|
|
533
|
+
:disabled="selectedIds.length === 0"
|
|
529
534
|
@click="$emit('show-delete')">
|
|
530
535
|
{{ t('nextcloud-vue', 'Delete selected') }}
|
|
531
536
|
</NcButton>
|
|
@@ -533,7 +538,7 @@
|
|
|
533
538
|
v-for="entry in bulkActions"
|
|
534
539
|
:key="entry.id"
|
|
535
540
|
variant="secondary"
|
|
536
|
-
:disabled="entry.disabled === true"
|
|
541
|
+
:disabled="entry.disabled === true || selectedIds.length === 0"
|
|
537
542
|
:data-testid="`cn-bulk-action-${entry.id}`"
|
|
538
543
|
:lang="bulkLang(entry)"
|
|
539
544
|
@click="$emit('bulk-action', { id: entry.id, action: entry.id, selectedIds, count: selectedIds.length })">
|
|
@@ -719,6 +724,16 @@ export default {
|
|
|
719
724
|
default: '',
|
|
720
725
|
},
|
|
721
726
|
|
|
727
|
+
/**
|
|
728
|
+
* Board look: draw the bulk band also before anything is selected, its
|
|
729
|
+
* buttons disabled until there is a selection, as DqZaken and DqWerkbord
|
|
730
|
+
* draw it. Only when the band has an action to offer.
|
|
731
|
+
*/
|
|
732
|
+
bulkBandAlways: {
|
|
733
|
+
type: Boolean,
|
|
734
|
+
default: false,
|
|
735
|
+
},
|
|
736
|
+
|
|
722
737
|
/** The 13px hint after the board bulk band's buttons (`config.bulkHint`). */
|
|
723
738
|
bulkHint: {
|
|
724
739
|
type: String,
|
|
@@ -1117,6 +1132,18 @@ export default {
|
|
|
1117
1132
|
return t('nextcloud-vue', '{count} active', { count: this.filterCount })
|
|
1118
1133
|
},
|
|
1119
1134
|
|
|
1135
|
+
/**
|
|
1136
|
+
* Whether the bulk band has an action to offer: copy, delete, a
|
|
1137
|
+
* declared bulk action or the host's own buttons.
|
|
1138
|
+
*
|
|
1139
|
+
* @return {boolean}
|
|
1140
|
+
*/
|
|
1141
|
+
bulkBandHasActions() {
|
|
1142
|
+
return this.showMassCopy || this.showMassDelete
|
|
1143
|
+
|| (Array.isArray(this.bulkActions) && this.bulkActions.length > 0)
|
|
1144
|
+
|| Boolean(this.$slots['selection-actions'])
|
|
1145
|
+
},
|
|
1146
|
+
|
|
1120
1147
|
/**
|
|
1121
1148
|
* The bold lead of the board bulk band.
|
|
1122
1149
|
*
|
|
@@ -18,7 +18,7 @@ When `CnDashboardPage` resolves a widget definition with `type: "chart"`, it mou
|
|
|
18
18
|
}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Supported `props` keys forwarded by the dispatcher: `chartKind` (→ `type`), `series`, `categories`, `labels`, `options`, `colors`, `toolbar`, `legend`, `height`, `width`, `unavailableLabel`, plus the display passthrough `horizontal`, `legendPosition`, `valueAxisBaseline`, `valueFormat`, `colorMap`, `emptyLabel`, `views`, `endpointSource`, `barMax`, `footnote`, `highlight`, `cellSource`. The `dataSource` field resolves declaratively: `aggregate: 'count'` / raw `graphql` via GraphQL, `bucket` / `groupBy` / the OBJECT-form `aggregate` (Wave 3: group-by + topN/Other + labelResolve) via OpenRegister's REST aggregation facets, with an optional sibling `drilldown: { route, filterParam }` that navigates on segment/bar click with the raw category key in the query.
|
|
21
|
+
Supported `props` keys forwarded by the dispatcher: `chartKind` (→ `type`), `series`, `categories`, `labels`, `options`, `colors`, `toolbar`, `legend`, `height`, `width`, `unavailableLabel`, plus the display passthrough `horizontal`, `legendPosition`, `valueAxisBaseline`, `valueFormat`, `colorMap`, `emptyLabel`, `views`, `endpointSource`, `barMax`, `footnote`, `highlight`, `cellSource`, `emptyCellLabel`. The `dataSource` field resolves declaratively: `aggregate: 'count'` / raw `graphql` via GraphQL, `bucket` / `groupBy` / the OBJECT-form `aggregate` (Wave 3: group-by + topN/Other + labelResolve) via OpenRegister's REST aggregation facets, with an optional sibling `drilldown: { route, filterParam }` that navigates on segment/bar click with the raw category key in the query.
|
|
22
22
|
|
|
23
23
|
Area chart — time series data:
|
|
24
24
|
|
|
@@ -107,7 +107,8 @@ Fallback slot — shown when ApexCharts is not available:
|
|
|
107
107
|
| `horizontal` | Boolean | `false` | Render `type: "bar"` charts horizontally (row bars); an explicit `options.plotOptions.bar.horizontal` still wins |
|
|
108
108
|
| `legendPosition` | String | `''` | Legend placement override (`top`/`bottom`/`left`/`right`); empty keeps the automatic placement |
|
|
109
109
|
| `barMax` | Number | `null` | `bar-list`: the value a full track stands for (100 for percentages); null takes the largest value |
|
|
110
|
-
| `footnote` | String | `''` |
|
|
110
|
+
| `footnote` | String | `''` | A line under the chart, through the host's translate: under the rows of a `bar-list` or `matrix`, under the plot of a `line`, `area` or `bar` chart |
|
|
111
|
+
| `emptyCellLabel` | String | `''` | `matrix`: the text of a cell without a value ("geen"), through the host's translate. Empty leaves the cell blank |
|
|
111
112
|
| `highlight` | Object | `null` | `matrix`: the cell to outline, `{ row, column, columnKeys? }`, tokens such as `@object.behaviour` resolved |
|
|
112
113
|
| `cellSource` | Object | `null` | `matrix`: read the cells from records that each hold one cell (`{ register, schema, filter?, rowField, columnField, valueField, … }`), or with `cellsPath` from the cells inside one record, see [Bar list and matrix](#bar-list-and-matrix) |
|
|
113
114
|
| `valueAxisBaseline` | String | `'auto'` | How the value axis picks its baseline, so a small difference cannot read as a dramatic one. `'auto'` anchors magnitude marks (bar/area) at zero and stops line charts zooming past a quarter of the data's magnitude; `'zero'` always anchors at zero; `'fit'` restores plain ApexCharts autoscaling — use it for series that live far from zero (a percentage hovering 95–99), where a zero baseline flattens the signal. Pie-family charts, negative series, empty data and stacked charts (`options.chart.stacked` — where the visible height is the per-category sum, not the largest datapoint) always autoscale; an explicit `options.yaxis.min`/`max` still wins. The ceiling rounds up to a nice number (7 → 8, 62 → 80). |
|
|
@@ -126,6 +126,12 @@
|
|
|
126
126
|
</p>
|
|
127
127
|
</slot>
|
|
128
128
|
</div>
|
|
129
|
+
<p
|
|
130
|
+
v-if="footnote && chartComponent && !showEmptyState && type !== 'bar-list' && type !== 'matrix'"
|
|
131
|
+
class="cn-chart-widget__footnote"
|
|
132
|
+
data-testid="cn-chart-widget-footnote">
|
|
133
|
+
{{ hostTranslate(footnote) }}
|
|
134
|
+
</p>
|
|
129
135
|
</div>
|
|
130
136
|
</template>
|
|
131
137
|
|
|
@@ -368,8 +374,9 @@ export default {
|
|
|
368
374
|
},
|
|
369
375
|
|
|
370
376
|
/**
|
|
371
|
-
* A line under
|
|
372
|
-
*
|
|
377
|
+
* A line under the chart ("Klik een balk om de zaken te zien"),
|
|
378
|
+
* through the host's translate: under the rows of a `bar-list` or
|
|
379
|
+
* `matrix`, and under the plot of an ApexCharts type.
|
|
373
380
|
*
|
|
374
381
|
* @spec openspec/changes/chart-bar-list-and-matrix/specs/chart-bar-list-and-matrix/spec.md#requirement-a-chart-can-draw-a-bar-list
|
|
375
382
|
*/
|
|
@@ -605,6 +612,17 @@ export default {
|
|
|
605
612
|
default: '',
|
|
606
613
|
},
|
|
607
614
|
|
|
615
|
+
/**
|
|
616
|
+
* `matrix`: the text of a cell without a value ("geen"), through the
|
|
617
|
+
* host's translate. Empty (the default) leaves the cell blank.
|
|
618
|
+
*
|
|
619
|
+
* @spec openspec/changes/chart-bar-list-and-matrix/specs/chart-bar-list-and-matrix/spec.md#requirement-a-chart-can-draw-a-matrix
|
|
620
|
+
*/
|
|
621
|
+
emptyCellLabel: {
|
|
622
|
+
type: String,
|
|
623
|
+
default: '',
|
|
624
|
+
},
|
|
625
|
+
|
|
608
626
|
/**
|
|
609
627
|
* Manifest dataSource block. When set, `series` /
|
|
610
628
|
* `categories` / `labels` are resolved from the GraphQL
|
|
@@ -1442,7 +1460,7 @@ export default {
|
|
|
1442
1460
|
const value = Number(raw)
|
|
1443
1461
|
return Number.isFinite(value) && raw !== null && raw !== undefined
|
|
1444
1462
|
? { text: this.formatPlain(value), level: levelOf(value), color: '', current: isCurrent }
|
|
1445
|
-
: { text: '', level: 0, color: '', current: isCurrent }
|
|
1463
|
+
: { text: this.emptyCellLabel ? this.hostTranslate(this.emptyCellLabel) : '', level: 0, color: '', current: isCurrent }
|
|
1446
1464
|
}),
|
|
1447
1465
|
})),
|
|
1448
1466
|
}
|
|
@@ -2388,6 +2406,8 @@ export default {
|
|
|
2388
2406
|
* Map an endpoint payload whose `rowsPath` names an array of rows:
|
|
2389
2407
|
* each row is a series (name at `rowLabelPath`, values at
|
|
2390
2408
|
* `rowValuesPath`), the categories are at `labelsPath`.
|
|
2409
|
+
* A missing value stays null; a `matrix` keeps a text value ("none")
|
|
2410
|
+
* as text, other types read it as 0.
|
|
2391
2411
|
*
|
|
2392
2412
|
* @param {unknown} payload The plucked payload.
|
|
2393
2413
|
* @param {object} es The `endpointSource` block.
|
|
@@ -2398,12 +2418,23 @@ export default {
|
|
|
2398
2418
|
const rows = getByPath(payload, es.rowsPath)
|
|
2399
2419
|
const rawLabels = es.labelsPath ? getByPath(payload, es.labelsPath) : undefined
|
|
2400
2420
|
const labels = Array.isArray(rawLabels) ? rawLabels.map((v) => ((v === null || v === undefined) ? '' : String(v))) : []
|
|
2421
|
+
const keepText = this.type === 'matrix'
|
|
2422
|
+
const cellOf = (v) => {
|
|
2423
|
+
if (v === null || v === undefined) {
|
|
2424
|
+
return null
|
|
2425
|
+
}
|
|
2426
|
+
const num = typeof v === 'string' && v.trim() === '' ? NaN : Number(v)
|
|
2427
|
+
if (Number.isFinite(num)) {
|
|
2428
|
+
return num
|
|
2429
|
+
}
|
|
2430
|
+
return keepText && typeof v === 'string' ? v : 0
|
|
2431
|
+
}
|
|
2401
2432
|
const series = (Array.isArray(rows) ? rows : []).map((row) => {
|
|
2402
2433
|
const name = getByPath(row, es.rowLabelPath || 'label')
|
|
2403
2434
|
const values = getByPath(row, es.rowValuesPath || 'values')
|
|
2404
2435
|
return {
|
|
2405
2436
|
name: (name === null || name === undefined) ? '' : String(name),
|
|
2406
|
-
data: Array.isArray(values) ? values.map(
|
|
2437
|
+
data: Array.isArray(values) ? values.map(cellOf) : [],
|
|
2407
2438
|
}
|
|
2408
2439
|
})
|
|
2409
2440
|
return { series, categories: labels, labels }
|
|
@@ -2690,6 +2721,13 @@ export default {
|
|
|
2690
2721
|
margin: 0;
|
|
2691
2722
|
}
|
|
2692
2723
|
|
|
2724
|
+
/* Under an ApexCharts plot the footnote is a sibling of the canvas, so it keeps
|
|
2725
|
+
its own line height while the canvas takes the rest of a fitted column. */
|
|
2726
|
+
.cn-chart-widget > .cn-chart-widget__footnote {
|
|
2727
|
+
flex: 0 0 auto;
|
|
2728
|
+
margin-top: 8px;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2693
2731
|
.cn-chart-widget__matrix {
|
|
2694
2732
|
display: grid;
|
|
2695
2733
|
gap: 4px;
|
|
@@ -23,6 +23,23 @@ Read-only, for a closed record:
|
|
|
23
23
|
]" />
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
Channel chips and a header action. `channel-filter` draws All plus a chip per channel with its count, `channels` lists labels that always get a chip, and the `header-actions` slot puts buttons beside the title:
|
|
27
|
+
|
|
28
|
+
```vue
|
|
29
|
+
<CnConversationThread
|
|
30
|
+
title="Contact"
|
|
31
|
+
channel-filter
|
|
32
|
+
:channels="['E-mail', 'Letter']"
|
|
33
|
+
:messages="[
|
|
34
|
+
{ id: 1, author: 'Sanne de Vries', side: 'them', channel: 'E-mail', text: 'Could I see the documents?' },
|
|
35
|
+
{ id: 2, side: 'us', channel: 'E-mail', text: 'They are on their way.' },
|
|
36
|
+
]">
|
|
37
|
+
<template #header-actions>
|
|
38
|
+
<NcButton>Log contact</NcButton>
|
|
39
|
+
</template>
|
|
40
|
+
</CnConversationThread>
|
|
41
|
+
```
|
|
42
|
+
|
|
26
43
|
No messages yet:
|
|
27
44
|
|
|
28
45
|
```vue
|
|
@@ -9,13 +9,27 @@
|
|
|
9
9
|
data-testid="cn-conversation-thread"
|
|
10
10
|
:aria-labelledby="title ? titleId : null"
|
|
11
11
|
:aria-label="title ? null : fallbackLabel">
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
<div v-if="title || $slots['header-actions']" class="cn-conversation-thread__header">
|
|
13
|
+
<component
|
|
14
|
+
:is="titleTag"
|
|
15
|
+
v-if="title"
|
|
16
|
+
:id="titleId"
|
|
17
|
+
class="cn-conversation-thread__title">
|
|
18
|
+
{{ title }}
|
|
19
|
+
</component>
|
|
20
|
+
<div v-if="$slots['header-actions']" class="cn-conversation-thread__header-actions">
|
|
21
|
+
<!-- @slot header-actions Buttons beside the title, for example "Log contact". -->
|
|
22
|
+
<slot name="header-actions" />
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<CnQuickFilterBar
|
|
27
|
+
v-if="channelChips.length > 0"
|
|
28
|
+
inline
|
|
29
|
+
class="cn-conversation-thread__channels"
|
|
30
|
+
:tabs="channelChips"
|
|
31
|
+
:activeIndex="activeChannelIndex"
|
|
32
|
+
@update:activeIndex="onChannelChip" />
|
|
19
33
|
|
|
20
34
|
<p v-if="loading && rows.length === 0"
|
|
21
35
|
class="cn-conversation-thread__empty"
|
|
@@ -27,9 +41,13 @@
|
|
|
27
41
|
{{ emptyText }}
|
|
28
42
|
</p>
|
|
29
43
|
|
|
44
|
+
<p v-else-if="shownRows.length === 0" class="cn-conversation-thread__empty" data-testid="cn-conversation-channel-empty">
|
|
45
|
+
{{ emptyText }}
|
|
46
|
+
</p>
|
|
47
|
+
|
|
30
48
|
<ol v-else class="cn-conversation-thread__messages">
|
|
31
49
|
<li
|
|
32
|
-
v-for="row in
|
|
50
|
+
v-for="row in shownRows"
|
|
33
51
|
:key="row.key"
|
|
34
52
|
class="cn-conversation-thread__message"
|
|
35
53
|
:class="`cn-conversation-thread__message--${row.side}`"
|
|
@@ -93,6 +111,7 @@
|
|
|
93
111
|
import axios from '@nextcloud/axios'
|
|
94
112
|
import { translate as t } from '@nextcloud/l10n'
|
|
95
113
|
import { NcButton } from '@nextcloud/vue'
|
|
114
|
+
import CnQuickFilterBar from '../CnQuickFilterBar/CnQuickFilterBar.vue'
|
|
96
115
|
import { prefixUrl } from '../../utils/headers.js'
|
|
97
116
|
import { userLocale } from '../../utils/userLocale.js'
|
|
98
117
|
|
|
@@ -126,6 +145,7 @@ export default {
|
|
|
126
145
|
name: 'CnConversationThread',
|
|
127
146
|
|
|
128
147
|
components: {
|
|
148
|
+
CnQuickFilterBar,
|
|
129
149
|
NcButton,
|
|
130
150
|
},
|
|
131
151
|
|
|
@@ -204,6 +224,24 @@ export default {
|
|
|
204
224
|
default: false,
|
|
205
225
|
},
|
|
206
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Draw channel chips above the messages: All, then one per channel
|
|
229
|
+
* with its count. Choosing one shows only that channel's messages.
|
|
230
|
+
*/
|
|
231
|
+
channelFilter: {
|
|
232
|
+
type: Boolean,
|
|
233
|
+
default: false,
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Channel labels that always get a chip, in this order, also when no
|
|
238
|
+
* message uses them yet. Channels the messages use follow.
|
|
239
|
+
*/
|
|
240
|
+
channels: {
|
|
241
|
+
type: Array,
|
|
242
|
+
default: () => [],
|
|
243
|
+
},
|
|
244
|
+
|
|
207
245
|
/** Shown when there are no messages yet. */
|
|
208
246
|
emptyText: {
|
|
209
247
|
type: String,
|
|
@@ -224,6 +262,8 @@ export default {
|
|
|
224
262
|
conversationThreadUid += 1
|
|
225
263
|
return {
|
|
226
264
|
draft: '',
|
|
265
|
+
/** The channel the chips narrow the thread to; '' is All. */
|
|
266
|
+
activeChannel: '',
|
|
227
267
|
sending: false,
|
|
228
268
|
errorMessage: '',
|
|
229
269
|
titleId: `cn-conversation-title-${conversationThreadUid}`,
|
|
@@ -262,6 +302,47 @@ export default {
|
|
|
262
302
|
})
|
|
263
303
|
},
|
|
264
304
|
|
|
305
|
+
/**
|
|
306
|
+
* The channel labels with a chip: the declared ones, then the ones
|
|
307
|
+
* the messages use.
|
|
308
|
+
*
|
|
309
|
+
* @return {Array<string>} The labels, each once.
|
|
310
|
+
*/
|
|
311
|
+
channelLabels() {
|
|
312
|
+
const declared = (Array.isArray(this.channels) ? this.channels : []).filter((label) => typeof label === 'string' && label !== '')
|
|
313
|
+
return [...new Set([...declared, ...this.rows.map((row) => row.channel).filter(Boolean)])]
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The chips: All, then each channel, each with its count. Empty
|
|
318
|
+
* without `channelFilter` or without channels.
|
|
319
|
+
*
|
|
320
|
+
* @return {Array<{label: string, count: number}>} The chips.
|
|
321
|
+
*/
|
|
322
|
+
channelChips() {
|
|
323
|
+
if (!this.channelFilter || this.channelLabels.length === 0) {
|
|
324
|
+
return []
|
|
325
|
+
}
|
|
326
|
+
return [
|
|
327
|
+
{ label: t('nextcloud-vue', 'All'), count: this.rows.length },
|
|
328
|
+
...this.channelLabels.map((label) => ({ label, count: this.rows.filter((row) => row.channel === label).length })),
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
/** @return {number} The pressed chip: 0 for All. */
|
|
333
|
+
activeChannelIndex() {
|
|
334
|
+
const index = this.channelLabels.indexOf(this.activeChannel)
|
|
335
|
+
return index < 0 ? 0 : index + 1
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
/** @return {Array<object>} The rows on the chosen channel, or all of them. */
|
|
339
|
+
shownRows() {
|
|
340
|
+
if (this.channelChips.length === 0 || this.activeChannelIndex === 0) {
|
|
341
|
+
return this.rows
|
|
342
|
+
}
|
|
343
|
+
return this.rows.filter((row) => row.channel === this.activeChannel)
|
|
344
|
+
},
|
|
345
|
+
|
|
265
346
|
/**
|
|
266
347
|
* Whether there is something to send and nothing in flight.
|
|
267
348
|
*
|
|
@@ -289,6 +370,16 @@ export default {
|
|
|
289
370
|
},
|
|
290
371
|
|
|
291
372
|
methods: {
|
|
373
|
+
/**
|
|
374
|
+
* Narrow the thread to the chip's channel; the All chip (0) clears it.
|
|
375
|
+
*
|
|
376
|
+
* @param {number|null} index The chip index.
|
|
377
|
+
* @return {void}
|
|
378
|
+
*/
|
|
379
|
+
onChannelChip(index) {
|
|
380
|
+
this.activeChannel = index > 0 ? (this.channelLabels[index - 1] || '') : ''
|
|
381
|
+
},
|
|
382
|
+
|
|
292
383
|
/**
|
|
293
384
|
* Up to two initials of a name, for the avatar.
|
|
294
385
|
*
|
|
@@ -370,6 +461,20 @@ export default {
|
|
|
370
461
|
gap: calc(3.5 * var(--default-grid-baseline));
|
|
371
462
|
}
|
|
372
463
|
|
|
464
|
+
.cn-conversation-thread__header {
|
|
465
|
+
align-items: flex-start;
|
|
466
|
+
display: flex;
|
|
467
|
+
flex-wrap: wrap;
|
|
468
|
+
gap: calc(3 * var(--default-grid-baseline));
|
|
469
|
+
justify-content: space-between;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.cn-conversation-thread__header-actions {
|
|
473
|
+
display: flex;
|
|
474
|
+
flex-wrap: wrap;
|
|
475
|
+
gap: calc(2 * var(--default-grid-baseline));
|
|
476
|
+
}
|
|
477
|
+
|
|
373
478
|
.cn-conversation-thread__title {
|
|
374
479
|
font-size: 1.1em;
|
|
375
480
|
font-weight: 700;
|
|
@@ -15,12 +15,27 @@
|
|
|
15
15
|
:endpoint="replyAction ? '' : endpoint"
|
|
16
16
|
:loading="loading"
|
|
17
17
|
:emptyText="content.emptyText ? tr(content.emptyText) : undefined"
|
|
18
|
+
:channelFilter="content.channelChips === true"
|
|
19
|
+
:channels="channelLabels"
|
|
18
20
|
@send="onSend"
|
|
19
|
-
@sent="onSent"
|
|
21
|
+
@sent="onSent">
|
|
22
|
+
<template v-if="headerAction" #header-actions>
|
|
23
|
+
<NcButton
|
|
24
|
+
data-testid="cn-conversation-header-action"
|
|
25
|
+
@click="onHeaderAction">
|
|
26
|
+
<template v-if="headerAction.icon" #icon>
|
|
27
|
+
<CnIcon :name="headerAction.icon" :size="20" />
|
|
28
|
+
</template>
|
|
29
|
+
{{ tr(headerAction.label) }}
|
|
30
|
+
</NcButton>
|
|
31
|
+
</template>
|
|
32
|
+
</CnConversationThread>
|
|
20
33
|
</div>
|
|
21
34
|
</template>
|
|
22
35
|
|
|
23
36
|
<script>
|
|
37
|
+
import { NcButton } from '@nextcloud/vue'
|
|
38
|
+
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
24
39
|
import CnConversationThread from './CnConversationThread.vue'
|
|
25
40
|
import { dispatchAction } from '../../utils/actionsDispatcher.js'
|
|
26
41
|
import { fetchReferenceObjects } from '../../utils/boardReferenceColumns.js'
|
|
@@ -45,12 +60,20 @@ import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveF
|
|
|
45
60
|
* `replyAction`, an action descriptor the app decides the meaning of: it is
|
|
46
61
|
* dispatched with `args: [{ text, objectId }]` and `props.text`. After a send
|
|
47
62
|
* the source is read again.
|
|
63
|
+
*
|
|
64
|
+
* `channelChips: true` draws a chip per channel above the thread (every
|
|
65
|
+
* `channelMap` label, in map order, then any other channel a message uses).
|
|
66
|
+
* `headerAction` is a button beside the title (`{ label, icon?, ...action }`,
|
|
67
|
+
* DqZaakContact's "Contact vastleggen"), dispatched like `replyAction` with
|
|
68
|
+
* `args: [{ objectId }]`; after it the source is read again.
|
|
48
69
|
*/
|
|
49
70
|
export default {
|
|
50
71
|
name: 'CnConversationWidget',
|
|
51
72
|
|
|
52
73
|
components: {
|
|
53
74
|
CnConversationThread,
|
|
75
|
+
CnIcon,
|
|
76
|
+
NcButton,
|
|
54
77
|
},
|
|
55
78
|
|
|
56
79
|
inject: {
|
|
@@ -102,6 +125,18 @@ export default {
|
|
|
102
125
|
return source && typeof source === 'object' && source.register && source.schema ? source : null
|
|
103
126
|
},
|
|
104
127
|
|
|
128
|
+
/** @return {object|null} The header action, or null without a label. */
|
|
129
|
+
headerAction() {
|
|
130
|
+
const action = this.content?.headerAction
|
|
131
|
+
return action && typeof action === 'object' && typeof action.label === 'string' && action.label !== '' ? action : null
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/** @return {Array<string>} The `channelMap` labels, translated, each once. */
|
|
135
|
+
channelLabels() {
|
|
136
|
+
const map = this.content?.channelMap && typeof this.content.channelMap === 'object' ? this.content.channelMap : {}
|
|
137
|
+
return [...new Set(Object.values(map).filter((label) => typeof label === 'string' && label !== '').map((label) => this.tr(label)))]
|
|
138
|
+
},
|
|
139
|
+
|
|
105
140
|
/** @return {object|null} The reply action, or null. */
|
|
106
141
|
replyAction() {
|
|
107
142
|
const action = this.content?.replyAction
|
|
@@ -254,6 +289,30 @@ export default {
|
|
|
254
289
|
this.onSent({ text })
|
|
255
290
|
},
|
|
256
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Run `headerAction` with the record id, then read the source again.
|
|
294
|
+
*
|
|
295
|
+
* @return {Promise<void>}
|
|
296
|
+
*/
|
|
297
|
+
async onHeaderAction() {
|
|
298
|
+
if (!this.headerAction) {
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
const { label, icon, ...rest } = resolveDeepTokens(this.headerAction, this.tokenCtx)
|
|
302
|
+
const action = { ...rest, args: [{ objectId: this.objectId }] }
|
|
303
|
+
try {
|
|
304
|
+
const outcome = await (typeof this.cnDispatchAction === 'function'
|
|
305
|
+
? this.cnDispatchAction(action)
|
|
306
|
+
: dispatchAction(action, { router: this.$router || null }))
|
|
307
|
+
if (outcome === false) {
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
} catch {
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
await this.loadSource()
|
|
314
|
+
},
|
|
315
|
+
|
|
257
316
|
/**
|
|
258
317
|
* Show an accepted reply at once, without waiting for the record.
|
|
259
318
|
*
|
|
@@ -126,12 +126,15 @@ predicate's field value into their text wherever it says `{value}` (e.g.
|
|
|
126
126
|
| `gridMargin` | Number | `12` | Grid margin (gutter) in pixels between widgets |
|
|
127
127
|
| `emptyLabel` | String | `'No widgets configured'` | Text shown in the empty state when `layout` is empty |
|
|
128
128
|
| `unavailableLabel` | String | `'Widget not available'` | Text shown for unknown or unavailable widgets |
|
|
129
|
-
| `userLayout` | Boolean | `false` | Keep a per-user arrangement of this page. Off by default: a page without it makes **no** layout request and renders exactly as before. |
|
|
129
|
+
| `userLayout` | Boolean | `false` | **Deprecated** (layout belongs to Buildiq; removed after this release). Keep a per-user arrangement of this page. Off by default: a page without it makes **no** layout request and renders exactly as before. |
|
|
130
130
|
| `appId` | String | `''` | The Nextcloud app the user layout is stored under. Required for `userLayout` to do anything. |
|
|
131
131
|
| `userLayoutStore` | Object | `null` | A store exposing `loadDashboardLayout` / `saveDashboardLayout` / `resetDashboardLayout`, usually one carrying [`dashboardLayoutsPlugin`](../../../docs/store/plugins/dashboard-layouts.md). Left unset, the page reads and writes user preferences directly. |
|
|
132
132
|
|
|
133
133
|
## Per-user layout
|
|
134
134
|
|
|
135
|
+
> **Deprecated: layout belongs to Buildiq.** Arranging a page is Buildiq's job (Ruben, 18 Oct 2026): its **Indeling aanpassen** edits the layout everybody sees. `userLayout` still works for this release and will be removed after it. While Buildiq edits the page (`cnEditingBody`), the grid shows the manifest layout, and a drag or resize goes into the manifest Buildiq saves, also on a page with `userLayout`. A user arrangement made outside an in-page edit session is saved at once, because no **Done** follows to save it.
|
|
136
|
+
|
|
137
|
+
|
|
135
138
|
With `userLayout: true` and an `appId`, this page loads the current user's arrangement before the first grid render and merges it over the manifest layout. The rules are [`mergeUserLayout`](../../../docs/utilities/merge-user-layout.md)'s: **the manifest decides which widgets exist, the user decides where they sit.**
|
|
136
139
|
|
|
137
140
|
```vue
|