@conduction/nextcloud-vue 2.105.0 → 2.106.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/CnCardGrid/CnCardGrid.vue.js +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +53 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +159 -50
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +22 -4
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.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 +21 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +21 -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 +803 -453
- package/dist/esm/utils/dueText.js +18 -3
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/sortOrder.js +11 -1
- package/dist/esm/utils/sortOrder.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +8281 -6350
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +803 -453
- package/l10n/en.json +21 -1
- package/l10n/nl.json +21 -1
- package/package.json +1 -1
- package/src/components/CnCardGrid/CnCardGrid.md +2 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -2
- package/src/components/CnDetailPage/CnDetailPage.vue +53 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
- package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
- package/src/components/CnIndexPage/CnIndexPage.md +7 -5
- package/src/components/CnIndexPage/CnIndexPage.vue +187 -58
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
- package/src/components/CnIndexPage/useSelfFetchList.js +22 -4
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
- package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
- package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
- package/src/components/CnTabs/CnTabs.vue +11 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
- package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
- package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
- package/src/components/CnTeamWorkloadWidget/index.js +14 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
- package/src/components/index.js +1 -0
- package/src/css/index-page.css +14 -2
- package/src/css/look-board-detail.css +48 -4
- package/src/css/look-board-index.css +64 -2
- package/src/css/look-board.css +25 -2
- package/src/index.js +1 -1
- package/src/schemas/app-manifest-v2.schema.json +75 -6
- package/src/utils/dueText.js +18 -3
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/sortOrder.js +10 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/en.json
CHANGED
|
@@ -1727,7 +1727,23 @@
|
|
|
1727
1727
|
"On": "On",
|
|
1728
1728
|
"Select a step on the canvas to see its settings.": "Select a step on the canvas to see its settings.",
|
|
1729
1729
|
"Show runs": "Show runs",
|
|
1730
|
-
"Back to the step settings": "Back to the step settings"
|
|
1730
|
+
"Back to the step settings": "Back to the step settings",
|
|
1731
|
+
"Sort": "Sort",
|
|
1732
|
+
"Direction": "Direction",
|
|
1733
|
+
"Ascending": "Ascending",
|
|
1734
|
+
"Descending": "Descending",
|
|
1735
|
+
"ascending": "ascending",
|
|
1736
|
+
"descending": "descending",
|
|
1737
|
+
"Not sorted": "Not sorted",
|
|
1738
|
+
"Current order: {order}": "Current order: {order}",
|
|
1739
|
+
"Soon": "Soon",
|
|
1740
|
+
"{name} (you)": "{name} (you)",
|
|
1741
|
+
"{present} of {total} present": "{present} of {total} present",
|
|
1742
|
+
"Absent today": "Absent today",
|
|
1743
|
+
"{message} until {time}": "{message} until {time}",
|
|
1744
|
+
"Away": "Away",
|
|
1745
|
+
"Busy": "Busy",
|
|
1746
|
+
"No one in the team": "No one in the team"
|
|
1731
1747
|
},
|
|
1732
1748
|
"plurals": {
|
|
1733
1749
|
"{count} member": [
|
|
@@ -1845,6 +1861,10 @@
|
|
|
1845
1861
|
"downloaded %n time": [
|
|
1846
1862
|
"downloaded %n time",
|
|
1847
1863
|
"downloaded %n times"
|
|
1864
|
+
],
|
|
1865
|
+
"{count} open": [
|
|
1866
|
+
"{count} open",
|
|
1867
|
+
"{count} open"
|
|
1848
1868
|
]
|
|
1849
1869
|
}
|
|
1850
1870
|
}
|
package/l10n/nl.json
CHANGED
|
@@ -1940,7 +1940,23 @@
|
|
|
1940
1940
|
"On": "Aan",
|
|
1941
1941
|
"Select a step on the canvas to see its settings.": "Kies een stap op het canvas om de instellingen te zien.",
|
|
1942
1942
|
"Show runs": "Uitvoeringen tonen",
|
|
1943
|
-
"Back to the step settings": "Terug naar de stapinstellingen"
|
|
1943
|
+
"Back to the step settings": "Terug naar de stapinstellingen",
|
|
1944
|
+
"Sort": "Sorteren",
|
|
1945
|
+
"Direction": "Richting",
|
|
1946
|
+
"Ascending": "Oplopend",
|
|
1947
|
+
"Descending": "Aflopend",
|
|
1948
|
+
"ascending": "oplopend",
|
|
1949
|
+
"descending": "aflopend",
|
|
1950
|
+
"Not sorted": "Niet gesorteerd",
|
|
1951
|
+
"Current order: {order}": "Huidige volgorde: {order}",
|
|
1952
|
+
"Soon": "Bijna",
|
|
1953
|
+
"{name} (you)": "{name} (u)",
|
|
1954
|
+
"{present} of {total} present": "{present} van {total} aanwezig",
|
|
1955
|
+
"Absent today": "Vandaag afwezig",
|
|
1956
|
+
"{message} until {time}": "{message} tot {time}",
|
|
1957
|
+
"Away": "Afwezig",
|
|
1958
|
+
"Busy": "Bezet",
|
|
1959
|
+
"No one in the team": "Niemand in het team"
|
|
1944
1960
|
},
|
|
1945
1961
|
"plurals": {
|
|
1946
1962
|
"{count} member": [
|
|
@@ -2058,6 +2074,10 @@
|
|
|
2058
2074
|
"downloaded %n time": [
|
|
2059
2075
|
"%n keer gedownload",
|
|
2060
2076
|
"%n keer gedownload"
|
|
2077
|
+
],
|
|
2078
|
+
"{count} open": [
|
|
2079
|
+
"{count} open",
|
|
2080
|
+
"{count} open"
|
|
2061
2081
|
]
|
|
2062
2082
|
}
|
|
2063
2083
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.106.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": {
|
|
@@ -113,6 +113,8 @@ Custom card slot — replace the default CnObjectCard with your own rendering:
|
|
|
113
113
|
| Prop | Type | Default | Description |
|
|
114
114
|
|---|---|---|---|
|
|
115
115
|
| `cardFields` | Array | `null` | Property keys each card's facts list shows |
|
|
116
|
+
| `cardFieldColumns` | Object | `null` | The page's columns by key, passed to each CnObjectCard (see CnObjectCard `cardFieldColumns`) |
|
|
117
|
+
| `cardLayout` | Object | `null` | Board look: the record layout each card draws, passed to CnObjectCard `layout` |
|
|
116
118
|
| `statusOf` | Function | `null` | `(object) => status` for each card's status pill |
|
|
117
119
|
| `leadingOf` | Function | `null` | `(object) => { initials | icon }` for each card's leading element |
|
|
118
120
|
| `footerActionOf` | Function | `null` | `(object) => { label, meta? }` for each card's footer action |
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
:clickToView="clickToView"
|
|
38
38
|
:selected="isSelected(object)"
|
|
39
39
|
:cardFields="cardFields"
|
|
40
|
+
:layout="cardLayout"
|
|
41
|
+
:cardFieldColumns="cardFieldColumns"
|
|
40
42
|
:status="statusOf ? statusOf(object) : null"
|
|
41
43
|
:leading="leadingOf ? leadingOf(object) : null"
|
|
42
44
|
:footerAction="footerActionOf ? footerActionOf(object) : null"
|
|
@@ -165,6 +167,24 @@ export default {
|
|
|
165
167
|
default: null,
|
|
166
168
|
},
|
|
167
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Board look: the record layout each card draws (manifest key
|
|
172
|
+
* `config.cardLayout`), passed through to CnObjectCard `layout`.
|
|
173
|
+
*
|
|
174
|
+
* @type {object}
|
|
175
|
+
* @spec openspec/changes/board-record-card-layout/specs/board-record-card-layout/spec.md#requirement-a-board-card-can-draw-the-record-layout-from-config
|
|
176
|
+
*/
|
|
177
|
+
cardLayout: {
|
|
178
|
+
type: Object,
|
|
179
|
+
default: null,
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
/** The page's columns by key, passed through to CnObjectCard. */
|
|
183
|
+
cardFieldColumns: {
|
|
184
|
+
type: Object,
|
|
185
|
+
default: null,
|
|
186
|
+
},
|
|
187
|
+
|
|
168
188
|
/** Board look: `(object) => status` for each card's status pill. See CnObjectCard `status`. */
|
|
169
189
|
statusOf: {
|
|
170
190
|
type: Function,
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
<time
|
|
195
195
|
v-if="dateTimestamp"
|
|
196
196
|
class="cn-cell-renderer__date"
|
|
197
|
-
:class="
|
|
197
|
+
:class="dueClass"
|
|
198
198
|
:datetime="dateTimestamp.toISOString()"
|
|
199
199
|
:title="absoluteDateLabel"
|
|
200
200
|
data-testid="cn-cell-renderer-due">{{ dueResult.text }}</time>
|
|
@@ -780,6 +780,24 @@ export default {
|
|
|
780
780
|
return Number.isFinite(size) && size > 0 ? size : (this.isBoardLook ? 32 : 24)
|
|
781
781
|
},
|
|
782
782
|
|
|
783
|
+
/**
|
|
784
|
+
* `date` widget with `widgetProps.due`: the colour class. The
|
|
785
|
+
* `variantWhen` rules win when they match; else an overdue date reads
|
|
786
|
+
* as an error and a soon one (`due.soonWithin`) as a warning.
|
|
787
|
+
*
|
|
788
|
+
* @return {string|null} The class, or null for the plain colour.
|
|
789
|
+
* @spec openspec/changes/due-text-soon/specs/due-text-soon/spec.md#requirement-a-due-date-within-a-few-days-reads-as-soon
|
|
790
|
+
*/
|
|
791
|
+
dueClass() {
|
|
792
|
+
if (this.dateVariant) {
|
|
793
|
+
return 'cn-cell-renderer__date--' + this.dateVariant
|
|
794
|
+
}
|
|
795
|
+
if (this.dueResult && this.dueResult.tone === 'danger') {
|
|
796
|
+
return 'cn-cell-renderer__date--error'
|
|
797
|
+
}
|
|
798
|
+
return this.dueResult && this.dueResult.soon ? 'cn-cell-renderer__date--warning' : null
|
|
799
|
+
},
|
|
800
|
+
|
|
783
801
|
/**
|
|
784
802
|
* `date` widget: the variant of the first `widgetProps.variantWhen`
|
|
785
803
|
* rule the date matches, or '' for none.
|
|
@@ -421,11 +421,11 @@ Four levels of action and the case surfaces around them. All opt in. See [the re
|
|
|
421
421
|
| `stageField` | String | `'status'` | Dot-path to the record's stage. |
|
|
422
422
|
| `quickActions` | Array | `[]` | At most three always visible header buttons. |
|
|
423
423
|
| `actionsMenu` | Object \| null | `null` | `{ showRefresh?, showHelpLinks?, label? }` for the overflow menu. |
|
|
424
|
-
| `nextStep` | Object \| null | `null` | The "what now" card per stage, rendered by `CnNextStepCard`. |
|
|
424
|
+
| `nextStep` | Object \| null | `null` | The "what now" card per stage, rendered by `CnNextStepCard`. `stagesFrom` (a field path such as `@context.caseType.nextStep`) reads the stages from the record when it holds them. |
|
|
425
425
|
| `typePill` | Object \| null | `null` | `{ field, colorMap?, labels?, variant? }`, a pill above the title. Instead of `field`: `text` (a constant) or `countFrom` + `countLabel` (a count of a related list). |
|
|
426
426
|
| `statusPill` | Object \| null | `null` | A second pill, same shape. |
|
|
427
427
|
| `headerFields` | Array | `[]` | Fields shown as chips under the title (manifest `config.headerFields`). Each entry is a property key, or `{ key, format, labelField, colorField, warnWhenPast }`; `format` is `text` (default), `mono`, `badge`, `user` or `date`. A `$ref` value shows `labelField` of the referenced object and falls back to the raw id in mono; a badge maps `colorField` (`success`, `warning`, `error`, `info`, `neutral`) to a variant; `warnWhenPast` gives a past date the error variant. An empty value shows no chip, a page without the key shows no row. Each chip reads "Title: value" to a screen reader, and the row prints as plain text. |
|
|
428
|
-
| `sideColumn` | Array | `[]` | Widget definitions or widget ids, rendered as a column of cards beside the body. |
|
|
428
|
+
| `sideColumn` | Array | `[]` | Widget definitions or widget ids, rendered as a column of cards beside the body. An entry with `tabs` shows only while one of those body tabs is open. |
|
|
429
429
|
| `sideColumnFrom` | String | `''` | A field path, read like a `visibleWhen` field (e.g. `@context.caseType.sideCards`), whose list of ids picks the `sideColumn` entries this record shows and their order. Any other value keeps `sideColumn` as declared. |
|
|
430
430
|
| `tabsFrom` | String | `''` | A field path, read like a `visibleWhen` field, whose list of tab ids picks the tabs of the page's `tabs` widgets and their order for this record: `@context.caseType.tabs` reads the list a case type keeps (DqAanbesteding: Inschrijvingen, Publicaties, Bestanden, Historie). A tab's id is its `id`, else its `widgetId`, or the string entry. An empty or missing list keeps the declared tabs; edit mode shows them all. |
|
|
431
431
|
| `isAdmin` | Boolean \| null | `null` | Shows `adminOnly` header actions. `null` reads it from Nextcloud. |
|
|
@@ -1540,6 +1540,12 @@ export default {
|
|
|
1540
1540
|
},
|
|
1541
1541
|
},
|
|
1542
1542
|
|
|
1543
|
+
// The open body tab, reported by a `tabs` widget, so a side entry
|
|
1544
|
+
// with `tabs` shows only beside those tabs.
|
|
1545
|
+
cnDetailReportTab: (id) => {
|
|
1546
|
+
this.activeBodyTab = typeof id === 'string' ? id : ''
|
|
1547
|
+
},
|
|
1548
|
+
|
|
1543
1549
|
// The records `visibilityContext` names, keyed `@context.<name>`, so a tab's
|
|
1544
1550
|
// `visibleWhen` reads the case type as a grid widget's does. Null
|
|
1545
1551
|
// while they are on their way.
|
|
@@ -2462,8 +2468,12 @@ export default {
|
|
|
2462
2468
|
|
|
2463
2469
|
/**
|
|
2464
2470
|
* The "what now" card (manifest `config.nextStep`):
|
|
2465
|
-
* `{ field?, stages: { <stage>: { title?, checklist, after? } } }`.
|
|
2471
|
+
* `{ field?, stagesFrom?, stages: { <stage>: { title?, checklist, after? } } }`.
|
|
2466
2472
|
* Each checklist item is `{ label, doneField?, doneWhen?, hint? }`.
|
|
2473
|
+
* `stagesFrom` is a field path read like a `visibleWhen` field
|
|
2474
|
+
* (`@context.caseType.nextStep`): when it holds a stages map (or an
|
|
2475
|
+
* object with `stages`), that map replaces `stages` for this record, so
|
|
2476
|
+
* a case type keeps its own stage texts. Otherwise `stages` stays.
|
|
2467
2477
|
* Rendered above the body for a stage that declares a checklist, with
|
|
2468
2478
|
* the primary button inside it. See `CnNextStepCard`.
|
|
2469
2479
|
*
|
|
@@ -2589,7 +2599,9 @@ export default {
|
|
|
2589
2599
|
* entry is a widget definition (`{ id?, type, title?, content? }`) or
|
|
2590
2600
|
* the id of a widget declared in `widgets`. The column stacks under
|
|
2591
2601
|
* the body on a narrow page. Empty (the default) leaves the layout as
|
|
2592
|
-
* it is.
|
|
2602
|
+
* it is. An entry with `tabs` (tab ids of the body's `tabs` widget)
|
|
2603
|
+
* shows only while one of those tabs is open (DqZaaktypeVelden: the
|
|
2604
|
+
* option list beside Velden).
|
|
2593
2605
|
*
|
|
2594
2606
|
* @type {Array<string|object>}
|
|
2595
2607
|
*/
|
|
@@ -2902,6 +2914,8 @@ export default {
|
|
|
2902
2914
|
|
|
2903
2915
|
data() {
|
|
2904
2916
|
return {
|
|
2917
|
+
/** The id of the open body tab a `tabs` widget reported, '' before one did. */
|
|
2918
|
+
activeBodyTab: '',
|
|
2905
2919
|
/**
|
|
2906
2920
|
* The records `visibilityContext` names, keyed `@context.<name>`: the row,
|
|
2907
2921
|
* `null` when it could not be read, absent while it is on its way.
|
|
@@ -3402,6 +3416,25 @@ export default {
|
|
|
3402
3416
|
return !this.objectNotFound && !this.showLoadingState && !this.error && !this.empty
|
|
3403
3417
|
},
|
|
3404
3418
|
|
|
3419
|
+
/**
|
|
3420
|
+
* `nextStep` with its stages read from the record when `stagesFrom`
|
|
3421
|
+
* names a stages map there (a case type's own texts), else as declared.
|
|
3422
|
+
*
|
|
3423
|
+
* @spec openspec/changes/detail-next-step-from-record/specs/detail-page/spec.md#requirement-the-next-step-stages-can-come-from-the-record
|
|
3424
|
+
* @return {object|null}
|
|
3425
|
+
*/
|
|
3426
|
+
effectiveNextStep() {
|
|
3427
|
+
const config = this.nextStep
|
|
3428
|
+
if (!config || typeof config !== 'object' || typeof config.stagesFrom !== 'string' || config.stagesFrom === '' || !this.widgetVisibilityData) {
|
|
3429
|
+
return config
|
|
3430
|
+
}
|
|
3431
|
+
const found = readVisibleWhenPath(this.widgetVisibilityData, config.stagesFrom)
|
|
3432
|
+
const stages = found && typeof found === 'object' && !Array.isArray(found)
|
|
3433
|
+
? (found.stages && typeof found.stages === 'object' && !Array.isArray(found.stages) ? found.stages : found)
|
|
3434
|
+
: null
|
|
3435
|
+
return stages && Object.keys(stages).length > 0 ? { ...config, stages } : config
|
|
3436
|
+
},
|
|
3437
|
+
|
|
3405
3438
|
/**
|
|
3406
3439
|
* The "what now" card for the record's stage, labels translated.
|
|
3407
3440
|
*
|
|
@@ -3409,7 +3442,7 @@ export default {
|
|
|
3409
3442
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
|
|
3410
3443
|
*/
|
|
3411
3444
|
nextStepCard() {
|
|
3412
|
-
const card = resolveNextStep(this.
|
|
3445
|
+
const card = resolveNextStep(this.effectiveNextStep, this.currentObject, this.stageField)
|
|
3413
3446
|
if (!card) {
|
|
3414
3447
|
return null
|
|
3415
3448
|
}
|
|
@@ -3524,7 +3557,7 @@ export default {
|
|
|
3524
3557
|
* @spec openspec/changes/detail-side-visible-when/specs/detail-page/spec.md#requirement-a-side-column-widget-shows-only-while-its-visiblewhen-holds
|
|
3525
3558
|
*/
|
|
3526
3559
|
sideColumnWidgets() {
|
|
3527
|
-
const resolved = this.pickedSideWidgets.filter((widget) => this.sideWidgetVisible(widget))
|
|
3560
|
+
const resolved = this.pickedSideWidgets.filter((widget) => this.sideWidgetOnTab(widget) && this.sideWidgetVisible(widget))
|
|
3528
3561
|
if (!this.isBoard) {
|
|
3529
3562
|
return resolved
|
|
3530
3563
|
}
|
|
@@ -6216,6 +6249,22 @@ export default {
|
|
|
6216
6249
|
return compactLayoutRows(drawable)
|
|
6217
6250
|
},
|
|
6218
6251
|
|
|
6252
|
+
/**
|
|
6253
|
+
* Whether a side entry belongs beside the open body tab: an entry with
|
|
6254
|
+
* `tabs` (tab ids) shows only while one of them is open; an entry
|
|
6255
|
+
* without shows beside every tab. Edit mode shows them all.
|
|
6256
|
+
*
|
|
6257
|
+
* @spec openspec/changes/detail-side-cards-per-tab/specs/detail-page/spec.md#requirement-a-side-card-can-follow-the-open-tab
|
|
6258
|
+
* @param {object} widget The side entry's definition.
|
|
6259
|
+
* @return {boolean}
|
|
6260
|
+
*/
|
|
6261
|
+
sideWidgetOnTab(widget) {
|
|
6262
|
+
if (this.editingBody || !Array.isArray(widget.tabs) || widget.tabs.length === 0) {
|
|
6263
|
+
return true
|
|
6264
|
+
}
|
|
6265
|
+
return widget.tabs.map((id) => String(id)).includes(this.activeBodyTab)
|
|
6266
|
+
},
|
|
6267
|
+
|
|
6219
6268
|
/**
|
|
6220
6269
|
* Whether a side column widget shows: always in edit mode or without a
|
|
6221
6270
|
* `visibleWhen`, else while it holds for the record and the records
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<span class="cn-detail-widget-header-items">
|
|
7
|
+
<CnStatusBadge
|
|
8
|
+
v-if="header.pill"
|
|
9
|
+
:label="header.pill.label"
|
|
10
|
+
:variant="header.pill.variant"
|
|
11
|
+
data-testid="cn-detail-widget-header-pill" />
|
|
12
|
+
<span
|
|
13
|
+
v-if="header.note"
|
|
14
|
+
class="cn-detail-page__catalog-note"
|
|
15
|
+
data-testid="cn-detail-widget-header-note">{{ header.note }}</span>
|
|
16
|
+
<button
|
|
17
|
+
v-for="(action, index) in header.actions"
|
|
18
|
+
:key="`${index}-${action.label}`"
|
|
19
|
+
type="button"
|
|
20
|
+
class="cn-detail-page__catalog-action"
|
|
21
|
+
data-testid="cn-detail-widget-header-action"
|
|
22
|
+
@click="action.run()">
|
|
23
|
+
<CnIcon v-if="action.icon" :name="action.icon" :size="16" />
|
|
24
|
+
{{ action.label }}
|
|
25
|
+
</button>
|
|
26
|
+
</span>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
31
|
+
import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The extras a widget reports for its card's title row through its `header`
|
|
35
|
+
* event: a status pill, a note and its actions. Drawn by CnDetailWidgetHost in
|
|
36
|
+
* the `#actions` slot of the card it wraps a widget in.
|
|
37
|
+
*/
|
|
38
|
+
export default {
|
|
39
|
+
name: 'CnDetailWidgetHeaderItems',
|
|
40
|
+
|
|
41
|
+
components: { CnIcon, CnStatusBadge },
|
|
42
|
+
|
|
43
|
+
props: {
|
|
44
|
+
/** The reported header: `{ pill, note, actions: [{ label, icon?, run }] }`. */
|
|
45
|
+
header: {
|
|
46
|
+
type: Object,
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<style scoped>
|
|
54
|
+
.cn-detail-widget-header-items {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 12px;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -184,27 +184,10 @@
|
|
|
184
184
|
<CnIcon :name="widget.icon" :size="20" />
|
|
185
185
|
</template>
|
|
186
186
|
<!-- A renderer that reports its own header (`header` event:
|
|
187
|
-
`{ link, note, action, pill }`, e.g. record-list's "3 in
|
|
188
|
-
a facts card's status pill). -->
|
|
189
|
-
<template v-if="
|
|
190
|
-
<
|
|
191
|
-
v-if="rendererHeader.pill"
|
|
192
|
-
:label="rendererHeader.pill.label"
|
|
193
|
-
:variant="rendererHeader.pill.variant"
|
|
194
|
-
data-testid="cn-detail-widget-header-pill" />
|
|
195
|
-
<span
|
|
196
|
-
v-if="rendererHeader.note"
|
|
197
|
-
class="cn-detail-page__catalog-note"
|
|
198
|
-
data-testid="cn-detail-widget-header-note">{{ rendererHeader.note }}</span>
|
|
199
|
-
<button
|
|
200
|
-
v-if="rendererHeader.action"
|
|
201
|
-
type="button"
|
|
202
|
-
class="cn-detail-page__catalog-action"
|
|
203
|
-
data-testid="cn-detail-widget-header-action"
|
|
204
|
-
@click="rendererHeader.action.run()">
|
|
205
|
-
<CnIcon v-if="rendererHeader.action.icon" :name="rendererHeader.action.icon" :size="16" />
|
|
206
|
-
{{ rendererHeader.action.label }}
|
|
207
|
-
</button>
|
|
187
|
+
`{ link, note, action, actions, pill }`, e.g. record-list's "3 in
|
|
188
|
+
totaal", a facts card's status pill). -->
|
|
189
|
+
<template v-if="hasRendererHeaderItems" #actions>
|
|
190
|
+
<CnDetailWidgetHeaderItems :header="rendererHeader" />
|
|
208
191
|
</template>
|
|
209
192
|
<template v-if="catalogAddEnabled" #action-items>
|
|
210
193
|
<NcActionButton @click="invokeCatalogAdd">
|
|
@@ -264,10 +247,16 @@
|
|
|
264
247
|
<template v-if="widget.icon" #title-icon>
|
|
265
248
|
<CnIcon :name="widget.icon" :size="20" />
|
|
266
249
|
</template>
|
|
250
|
+
<!-- The app widget reports its title row the way the built-in
|
|
251
|
+
cards do (`header` event). -->
|
|
252
|
+
<template v-if="hasRendererHeaderItems" #actions>
|
|
253
|
+
<CnDetailWidgetHeaderItems :header="rendererHeader" />
|
|
254
|
+
</template>
|
|
267
255
|
<component
|
|
268
256
|
:is="renderer"
|
|
269
257
|
ref="renderer"
|
|
270
258
|
v-bind="rendererProps"
|
|
259
|
+
@header="onRendererHeader"
|
|
271
260
|
@selectObject="onSelectObject" />
|
|
272
261
|
</CnWidgetWrapper>
|
|
273
262
|
|
|
@@ -304,7 +293,7 @@ import CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'
|
|
|
304
293
|
import CnObjectFilesWidget from '../CnObjectFilesWidget/CnObjectFilesWidget.vue'
|
|
305
294
|
import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
|
|
306
295
|
import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
|
|
307
|
-
import
|
|
296
|
+
import CnDetailWidgetHeaderItems from './CnDetailWidgetHeaderItems.vue'
|
|
308
297
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
309
298
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
310
299
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
@@ -378,12 +367,12 @@ export default {
|
|
|
378
367
|
name: 'CnDetailWidgetHost',
|
|
379
368
|
|
|
380
369
|
components: {
|
|
370
|
+
CnDetailWidgetHeaderItems,
|
|
381
371
|
CnIcon,
|
|
382
372
|
CnLeafMountHost,
|
|
383
373
|
CnObjectDataWidget,
|
|
384
374
|
CnObjectGeoWidget,
|
|
385
375
|
CnRelatedObjectsWidget,
|
|
386
|
-
CnStatusBadge,
|
|
387
376
|
CnWidgetWrapper,
|
|
388
377
|
NcActionButton,
|
|
389
378
|
CnEmptyContent,
|
|
@@ -639,7 +628,7 @@ export default {
|
|
|
639
628
|
* `header` event (screens-relation-pages): a link and a note,
|
|
640
629
|
* with the list's total already filled in.
|
|
641
630
|
*/
|
|
642
|
-
rendererHeader: { link: null, note: '',
|
|
631
|
+
rendererHeader: { link: null, note: '', actions: [], pill: null },
|
|
643
632
|
}
|
|
644
633
|
},
|
|
645
634
|
|
|
@@ -672,6 +661,12 @@ export default {
|
|
|
672
661
|
return this.isBare ? undefined : widgetTitleOf(this.widget)
|
|
673
662
|
},
|
|
674
663
|
|
|
664
|
+
/** @return {boolean} Whether the reported header has anything for the title row. */
|
|
665
|
+
hasRendererHeaderItems() {
|
|
666
|
+
const h = this.rendererHeader
|
|
667
|
+
return !!(h.note || h.pill || (h.actions && h.actions.length > 0))
|
|
668
|
+
},
|
|
669
|
+
|
|
675
670
|
/**
|
|
676
671
|
* The card header's link: the one the renderer reports, else, with
|
|
677
672
|
* `showHeaderLink`, the one the manifest declares for the widget.
|
|
@@ -1072,11 +1067,20 @@ export default {
|
|
|
1072
1067
|
* override a value the widget computes there; the whole content still
|
|
1073
1068
|
* arrives as `content`.
|
|
1074
1069
|
*
|
|
1070
|
+
* `content.chartKind` is read as the chart's `type`, as the dashboard
|
|
1071
|
+
* dispatcher reads it, so a chart placed on a detail page draws.
|
|
1072
|
+
*
|
|
1075
1073
|
* @return {object} The declared content keys.
|
|
1076
1074
|
* @spec openspec/changes/declared-props-detail-index-sidebar/specs/widget-hosts/spec.md#requirement-a-widget-host-passes-only-the-keys-a-widget-declares
|
|
1075
|
+
* @spec openspec/changes/detail-chart-kind/specs/widget-hosts/spec.md#requirement-a-detail-chart-reads-chartkind-as-its-type
|
|
1077
1076
|
*/
|
|
1078
1077
|
declaredContent() {
|
|
1079
|
-
|
|
1078
|
+
let bindings = this.content
|
|
1079
|
+
if (bindings && typeof bindings === 'object' && bindings.chartKind !== undefined && bindings.type === undefined) {
|
|
1080
|
+
const { chartKind, ...rest } = bindings
|
|
1081
|
+
bindings = { ...rest, type: chartKind }
|
|
1082
|
+
}
|
|
1083
|
+
return pickDeclaredProps(this.renderer, bindings, this.widget?.type || '')
|
|
1080
1084
|
},
|
|
1081
1085
|
|
|
1082
1086
|
/**
|
|
@@ -1217,17 +1221,20 @@ export default {
|
|
|
1217
1221
|
|
|
1218
1222
|
methods: {
|
|
1219
1223
|
/**
|
|
1220
|
-
* Take the header a content-only renderer
|
|
1224
|
+
* Take the header a content-only renderer, or an app widget in the
|
|
1225
|
+
* titled card, reports.
|
|
1221
1226
|
*
|
|
1222
|
-
* @param {{link?: object|null, note?: string, action?: {label: string, icon?: string, run: () => unknown}|null}} header The header parts.
|
|
1227
|
+
* @param {{link?: object|null, note?: string, action?: {label: string, icon?: string, run: () => unknown}|null, actions?: Array<{label: string, icon?: string, run: () => unknown}>}} header The header parts.
|
|
1223
1228
|
* @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
|
|
1224
1229
|
*/
|
|
1225
1230
|
onRendererHeader(header) {
|
|
1226
1231
|
const link = header && header.link && typeof header.link === 'object' ? header.link : null
|
|
1227
1232
|
const note = header && typeof header.note === 'string' ? header.note : ''
|
|
1228
|
-
const
|
|
1233
|
+
const usable = (action) => action && typeof action.run === 'function' && action.label
|
|
1234
|
+
// `action` (one) and `actions` (several) both count, `action` first.
|
|
1235
|
+
const actions = [header && header.action, ...((header && Array.isArray(header.actions)) ? header.actions : [])].filter(usable)
|
|
1229
1236
|
const pill = header && header.pill && typeof header.pill.label === 'string' && header.pill.label !== '' ? header.pill : null
|
|
1230
|
-
this.rendererHeader = { link, note,
|
|
1237
|
+
this.rendererHeader = { link, note, actions, pill }
|
|
1231
1238
|
},
|
|
1232
1239
|
|
|
1233
1240
|
/**
|
|
@@ -1206,11 +1206,15 @@ export default {
|
|
|
1206
1206
|
min-width: 0;
|
|
1207
1207
|
}
|
|
1208
1208
|
|
|
1209
|
+
/* `padding: 0`: Nextcloud's core stylesheet pads every `dl` 12px at the top
|
|
1210
|
+
and bottom, which put a side card's facts 24px under its title, against
|
|
1211
|
+
the board's 12 (DqZaak "Behandeling", DqZaakDocumenten). */
|
|
1209
1212
|
.cn-facts-widget__rows {
|
|
1210
1213
|
display: grid;
|
|
1211
1214
|
grid-template-columns: max-content 1fr;
|
|
1212
1215
|
gap: 8px 16px;
|
|
1213
1216
|
margin: 0;
|
|
1217
|
+
padding: 0;
|
|
1214
1218
|
font-size: 14px;
|
|
1215
1219
|
}
|
|
1216
1220
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<template>
|
|
6
6
|
<div
|
|
7
7
|
class="cn-files-browser"
|
|
8
|
-
:class="{ 'cn-files-browser--dragging': dragging }"
|
|
8
|
+
:class="{ 'cn-files-browser--dragging': dragging, 'cn-files-browser--drop-panel': dropOverlay && dropDetail !== '' }"
|
|
9
9
|
data-testid="cn-files-browser"
|
|
10
10
|
@dragenter.prevent="onDragEnter"
|
|
11
11
|
@dragover.prevent="onDragOver"
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
aria-hidden="true"
|
|
23
23
|
data-testid="cn-files-browser-drop-overlay">
|
|
24
24
|
<span class="cn-files-browser__drop-icon"><Upload :size="28" /></span>
|
|
25
|
-
<strong class="cn-files-browser__drop-label">{{ dropLabel }}</strong>
|
|
25
|
+
<strong class="cn-files-browser__drop-label">{{ cnTranslate(dropLabel) }}</strong>
|
|
26
|
+
<span v-if="dropDetailText"
|
|
27
|
+
class="cn-files-browser__drop-detail"
|
|
28
|
+
data-testid="cn-files-browser-drop-detail">{{ dropDetailText }}</span>
|
|
26
29
|
</div>
|
|
27
30
|
<p v-if="dropOverlay"
|
|
28
31
|
class="hidden-visually"
|
|
@@ -1026,12 +1029,26 @@ export default {
|
|
|
1026
1029
|
default: false,
|
|
1027
1030
|
},
|
|
1028
1031
|
|
|
1029
|
-
/** The line `dropOverlay` draws and announces. */
|
|
1032
|
+
/** The line `dropOverlay` draws and announces, through the host translate. */
|
|
1030
1033
|
dropLabel: {
|
|
1031
1034
|
type: String,
|
|
1032
1035
|
default: () => t('nextcloud-vue', 'Drop to add'),
|
|
1033
1036
|
},
|
|
1034
1037
|
|
|
1038
|
+
/**
|
|
1039
|
+
* A second line under `dropLabel` in the drop state, through the host
|
|
1040
|
+
* translate: where the files go ("{count} bestanden komen bij zaak
|
|
1041
|
+
* 2026-0082, als nog te beoordelen document."). `{count}` is the number
|
|
1042
|
+
* of files dragged when the browser can tell, and the line is left out
|
|
1043
|
+
* when it cannot. The overlay then also fills the panel.
|
|
1044
|
+
*
|
|
1045
|
+
* @spec openspec/changes/files-drop-panel/specs/files-browser/spec.md#requirement-the-drop-state-fills-the-panel-and-says-where-the-files-go
|
|
1046
|
+
*/
|
|
1047
|
+
dropDetail: {
|
|
1048
|
+
type: String,
|
|
1049
|
+
default: '',
|
|
1050
|
+
},
|
|
1051
|
+
|
|
1035
1052
|
/** Show a line under the list saying files can be dropped on it. */
|
|
1036
1053
|
dropHint: {
|
|
1037
1054
|
type: Boolean,
|
|
@@ -1259,6 +1276,8 @@ export default {
|
|
|
1259
1276
|
*/
|
|
1260
1277
|
visibleColumnKeys: null,
|
|
1261
1278
|
dragging: false,
|
|
1279
|
+
/** The number of files the current drag carries, null when unknown. */
|
|
1280
|
+
dragCount: null,
|
|
1262
1281
|
/** Nested dragenter/dragleave pairs, so a child does not end the drag. */
|
|
1263
1282
|
dragDepth: 0,
|
|
1264
1283
|
/** What the live region says: the drop label, then what was added. */
|
|
@@ -1294,6 +1313,24 @@ export default {
|
|
|
1294
1313
|
},
|
|
1295
1314
|
|
|
1296
1315
|
computed: {
|
|
1316
|
+
/**
|
|
1317
|
+
* `dropDetail` through the host translate with `{count}` filled, '' when
|
|
1318
|
+
* there is none or the count it needs is unknown.
|
|
1319
|
+
*
|
|
1320
|
+
* @spec openspec/changes/files-drop-panel/specs/files-browser/spec.md#requirement-the-drop-state-fills-the-panel-and-says-where-the-files-go
|
|
1321
|
+
* @return {string}
|
|
1322
|
+
*/
|
|
1323
|
+
dropDetailText() {
|
|
1324
|
+
if (!this.dropDetail) {
|
|
1325
|
+
return ''
|
|
1326
|
+
}
|
|
1327
|
+
const text = this.cnTranslate(this.dropDetail)
|
|
1328
|
+
if (!text.includes('{count}')) {
|
|
1329
|
+
return text
|
|
1330
|
+
}
|
|
1331
|
+
return this.dragCount === null ? '' : text.replace(/\{count\}/g, String(this.dragCount))
|
|
1332
|
+
},
|
|
1333
|
+
|
|
1297
1334
|
/**
|
|
1298
1335
|
* Whether this person may add to and change the open folder. Over
|
|
1299
1336
|
* WebDAV the server answers each write on its own, so this is true;
|
|
@@ -2247,7 +2284,8 @@ export default {
|
|
|
2247
2284
|
this.dragDepth++
|
|
2248
2285
|
if (!this.dragging) {
|
|
2249
2286
|
this.dragging = true
|
|
2250
|
-
this.
|
|
2287
|
+
this.dragCount = this.draggedFileCount(event)
|
|
2288
|
+
this.dropStatus = this.dropOverlay ? [this.cnTranslate(this.dropLabel), this.dropDetailText].filter(Boolean).join(' ') : ''
|
|
2251
2289
|
}
|
|
2252
2290
|
},
|
|
2253
2291
|
|
|
@@ -2277,6 +2315,22 @@ export default {
|
|
|
2277
2315
|
}
|
|
2278
2316
|
},
|
|
2279
2317
|
|
|
2318
|
+
/**
|
|
2319
|
+
* How many files a drag carries, or null when the browser does not
|
|
2320
|
+
* say (it lists the items only once they are dropped in some browsers).
|
|
2321
|
+
*
|
|
2322
|
+
* @param {DragEvent} event The drag.
|
|
2323
|
+
* @return {number|null}
|
|
2324
|
+
*/
|
|
2325
|
+
draggedFileCount(event) {
|
|
2326
|
+
const items = event?.dataTransfer?.items
|
|
2327
|
+
if (!items || typeof items.length !== 'number') {
|
|
2328
|
+
return null
|
|
2329
|
+
}
|
|
2330
|
+
const count = Array.from(items).filter((item) => item && item.kind === 'file').length
|
|
2331
|
+
return count > 0 ? count : null
|
|
2332
|
+
},
|
|
2333
|
+
|
|
2280
2334
|
/**
|
|
2281
2335
|
* Whether a drag carries files (not text or a link from the page).
|
|
2282
2336
|
* A drag whose types cannot be read counts as files, as before.
|
|
@@ -2888,6 +2942,18 @@ export default {
|
|
|
2888
2942
|
outline-offset: 4px;
|
|
2889
2943
|
}
|
|
2890
2944
|
|
|
2945
|
+
/* With a `dropDetail` the drop state fills the panel it sits in, as the
|
|
2946
|
+
board draws it (DqZaakDocumentenSlepen), not only the list's own height. */
|
|
2947
|
+
.cn-files-browser--dragging.cn-files-browser--drop-panel {
|
|
2948
|
+
min-height: max(320px, 100%);
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
.cn-files-browser__drop-detail {
|
|
2952
|
+
max-width: 520px;
|
|
2953
|
+
font-size: 14px;
|
|
2954
|
+
color: var(--color-main-text);
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2891
2957
|
.cn-files-browser__drop-overlay {
|
|
2892
2958
|
position: absolute;
|
|
2893
2959
|
inset: 0;
|