@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
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<div class="cn-search-select" :class="{ 'cn-search-select--label-outside': labelOutside }" data-testid="cn-search-select">
|
|
7
|
+
<NcSelect
|
|
8
|
+
:inputId="inputId"
|
|
9
|
+
:inputLabel="inputLabel"
|
|
10
|
+
:labelOutside="labelOutside"
|
|
11
|
+
:modelValue="selectedOption"
|
|
12
|
+
:options="options"
|
|
13
|
+
:loading="loading"
|
|
14
|
+
:clearable="clearable"
|
|
15
|
+
:disabled="disabled"
|
|
16
|
+
:placeholder="resolvedPlaceholder"
|
|
17
|
+
:filterable="filterable"
|
|
18
|
+
label="label"
|
|
19
|
+
@search="onSearch"
|
|
20
|
+
@update:modelValue="onInput">
|
|
21
|
+
<template #option="option">
|
|
22
|
+
<span class="cn-search-select__row" data-testid="cn-search-select-row">
|
|
23
|
+
<span
|
|
24
|
+
v-if="option.avatar || option.icon"
|
|
25
|
+
class="cn-search-select__lead"
|
|
26
|
+
:class="{ 'cn-search-select__lead--avatar': option.avatar && !option.icon }"
|
|
27
|
+
aria-hidden="true">
|
|
28
|
+
<CnIcon v-if="option.icon" :name="option.icon" :size="18" />
|
|
29
|
+
<template v-else>{{ initialsOf(option) }}</template>
|
|
30
|
+
</span>
|
|
31
|
+
<span class="cn-search-select__text">
|
|
32
|
+
<span class="cn-search-select__name">{{ option.label }}</span>
|
|
33
|
+
<span v-if="option.secondary" class="cn-search-select__secondary">{{ option.secondary }}</span>
|
|
34
|
+
</span>
|
|
35
|
+
<Check
|
|
36
|
+
v-if="isSelected(option)"
|
|
37
|
+
class="cn-search-select__check"
|
|
38
|
+
:size="18"
|
|
39
|
+
data-testid="cn-search-select-check" />
|
|
40
|
+
</span>
|
|
41
|
+
</template>
|
|
42
|
+
</NcSelect>
|
|
43
|
+
<Magnify class="cn-search-select__magnifier" :size="18" aria-hidden="true" />
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script>
|
|
48
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
49
|
+
import { NcSelect } from '@nextcloud/vue'
|
|
50
|
+
import Check from 'vue-material-design-icons/Check.vue'
|
|
51
|
+
import Magnify from 'vue-material-design-icons/Magnify.vue'
|
|
52
|
+
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
53
|
+
import { initialsOf } from '../../utils/initials.js'
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* CnSearchSelect — a single picker drawn as a search field: a magnifier
|
|
57
|
+
* instead of the chevron, and results as rows with an icon or initials, a
|
|
58
|
+
* name, a secondary line and a check on the chosen one (boards DqOverdragen
|
|
59
|
+
* "Nieuw team", DqNieuweZaak "Aanvrager").
|
|
60
|
+
*
|
|
61
|
+
* Each option is `{ id, label, secondary?, icon?, avatar? }`: `icon` names a
|
|
62
|
+
* CnIcon, `avatar: true` draws the label's initials in a circle. The value is
|
|
63
|
+
* the option's `id` (or `value`); `update:modelValue` emits that id, or null
|
|
64
|
+
* when cleared. Searching is the host's: listen to `search` and pass new
|
|
65
|
+
* `options`, with `filterable: false` when the host already filtered them.
|
|
66
|
+
*
|
|
67
|
+
* ```vue
|
|
68
|
+
* <CnSearchSelect
|
|
69
|
+
* v-model="team"
|
|
70
|
+
* inputLabel="Nieuw team"
|
|
71
|
+
* :options="[{ id: 'legal', label: 'Juridische zaken', secondary: '6 leden · bezwaar, beroep en Woo-advies', avatar: true }]" />
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export default {
|
|
75
|
+
name: 'CnSearchSelect',
|
|
76
|
+
|
|
77
|
+
components: { Check, CnIcon, Magnify, NcSelect },
|
|
78
|
+
|
|
79
|
+
props: {
|
|
80
|
+
/** The chosen option's id, or the option itself. */
|
|
81
|
+
modelValue: {
|
|
82
|
+
type: [String, Number, Object],
|
|
83
|
+
default: null,
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The options: `{ id, label, secondary?, icon?, avatar? }`.
|
|
88
|
+
*
|
|
89
|
+
* @type {Array<{id: (string|number), label: string, secondary?: string, icon?: string, avatar?: boolean}>}
|
|
90
|
+
*/
|
|
91
|
+
options: {
|
|
92
|
+
type: Array,
|
|
93
|
+
default: () => [],
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/** The field's label. */
|
|
97
|
+
inputLabel: {
|
|
98
|
+
type: String,
|
|
99
|
+
default: '',
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/** Draw the label above the field instead of inside it. */
|
|
103
|
+
labelOutside: {
|
|
104
|
+
type: Boolean,
|
|
105
|
+
default: false,
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/** The input's id. */
|
|
109
|
+
inputId: {
|
|
110
|
+
type: String,
|
|
111
|
+
default: undefined,
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/** Placeholder text; defaults to "Search…". */
|
|
115
|
+
placeholder: {
|
|
116
|
+
type: String,
|
|
117
|
+
default: '',
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/** Show the loading spinner. */
|
|
121
|
+
loading: {
|
|
122
|
+
type: Boolean,
|
|
123
|
+
default: false,
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/** Offer a clear button. */
|
|
127
|
+
clearable: {
|
|
128
|
+
type: Boolean,
|
|
129
|
+
default: true,
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/** Disable the field. */
|
|
133
|
+
disabled: {
|
|
134
|
+
type: Boolean,
|
|
135
|
+
default: false,
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/** Filter the options on the typed text; false when the host searches. */
|
|
139
|
+
filterable: {
|
|
140
|
+
type: Boolean,
|
|
141
|
+
default: true,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
emits: ['update:modelValue', 'search'],
|
|
146
|
+
|
|
147
|
+
computed: {
|
|
148
|
+
/**
|
|
149
|
+
* The option matching `modelValue`, or a bare one carrying it.
|
|
150
|
+
*
|
|
151
|
+
* @return {object|null}
|
|
152
|
+
*/
|
|
153
|
+
selectedOption() {
|
|
154
|
+
const value = this.modelValue
|
|
155
|
+
if (value === null || value === undefined || value === '') {
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
if (typeof value === 'object') {
|
|
159
|
+
return value
|
|
160
|
+
}
|
|
161
|
+
return this.options.find((o) => this.idOf(o) === String(value)) || { id: value, label: String(value) }
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/** @return {string} The placeholder. */
|
|
165
|
+
resolvedPlaceholder() {
|
|
166
|
+
return this.placeholder || t('nextcloud-vue', 'Search…')
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
methods: {
|
|
171
|
+
initialsOf(option) {
|
|
172
|
+
return initialsOf(option.label || '')
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {object} option An option.
|
|
177
|
+
* @return {string} Its id as a string, '' without one.
|
|
178
|
+
*/
|
|
179
|
+
idOf(option) {
|
|
180
|
+
const id = option && (option.id ?? option.value)
|
|
181
|
+
return id === null || id === undefined ? '' : String(id)
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @param {object} option An option.
|
|
186
|
+
* @return {boolean} Whether it is the chosen one.
|
|
187
|
+
*/
|
|
188
|
+
isSelected(option) {
|
|
189
|
+
return this.selectedOption !== null && this.idOf(option) !== '' && this.idOf(option) === this.idOf(this.selectedOption)
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @param {object|null} option The picked option.
|
|
194
|
+
*/
|
|
195
|
+
onInput(option) {
|
|
196
|
+
const id = option ? (option.id ?? option.value ?? null) : null
|
|
197
|
+
/** @event update:modelValue The picked option's id, or null when cleared. */
|
|
198
|
+
this.$emit('update:modelValue', id)
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {string} query The typed text.
|
|
203
|
+
*/
|
|
204
|
+
onSearch(query) {
|
|
205
|
+
/** @event search The typed text, for a host that searches itself. */
|
|
206
|
+
this.$emit('search', query)
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<style scoped>
|
|
213
|
+
.cn-search-select {
|
|
214
|
+
position: relative;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* The magnifier sits where the chevron was: in the field, at its end. */
|
|
218
|
+
.cn-search-select__magnifier {
|
|
219
|
+
position: absolute;
|
|
220
|
+
inset-inline-end: 12px;
|
|
221
|
+
bottom: 0;
|
|
222
|
+
height: var(--default-clickable-area, 34px);
|
|
223
|
+
display: flex;
|
|
224
|
+
align-items: center;
|
|
225
|
+
color: var(--color-text-maxcontrast);
|
|
226
|
+
pointer-events: none;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.cn-search-select :deep(.vs__open-indicator),
|
|
230
|
+
.cn-search-select :deep(.vs__open-indicator-button) {
|
|
231
|
+
display: none;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.cn-search-select :deep(.vs__actions) {
|
|
235
|
+
padding-inline-end: 36px;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.cn-search-select__row {
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
gap: 10px;
|
|
242
|
+
width: 100%;
|
|
243
|
+
min-width: 0;
|
|
244
|
+
padding: 4px 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.cn-search-select__lead {
|
|
248
|
+
flex: 0 0 32px;
|
|
249
|
+
width: 32px;
|
|
250
|
+
height: 32px;
|
|
251
|
+
border-radius: 50%;
|
|
252
|
+
display: inline-flex;
|
|
253
|
+
align-items: center;
|
|
254
|
+
justify-content: center;
|
|
255
|
+
background: var(--color-primary-element-light);
|
|
256
|
+
color: var(--color-primary-element-light-text);
|
|
257
|
+
font-size: 13px;
|
|
258
|
+
font-weight: 600;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.cn-search-select__text {
|
|
262
|
+
display: flex;
|
|
263
|
+
flex-direction: column;
|
|
264
|
+
flex: 1 1 auto;
|
|
265
|
+
min-width: 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.cn-search-select__name {
|
|
269
|
+
font-weight: 600;
|
|
270
|
+
overflow-wrap: anywhere;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.cn-search-select__secondary {
|
|
274
|
+
font-size: 13px;
|
|
275
|
+
color: var(--color-text-maxcontrast);
|
|
276
|
+
overflow-wrap: anywhere;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.cn-search-select__check {
|
|
280
|
+
flex: 0 0 auto;
|
|
281
|
+
color: var(--color-primary-element);
|
|
282
|
+
}
|
|
283
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* CnSearchSelect — a single picker drawn as a search field with rows that
|
|
6
|
+
* carry an icon, a name, a secondary line and a check on the chosen one.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import CnSearchSelect from './CnSearchSelect.vue'
|
|
10
|
+
|
|
11
|
+
export { CnSearchSelect }
|
|
12
|
+
export default CnSearchSelect
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
ref="wizard"
|
|
6
6
|
:steps="wizardSteps"
|
|
7
7
|
:dialogTitle="resolvedDialogTitle"
|
|
8
|
+
stepHeading
|
|
8
9
|
:submitLabel="submitLabel"
|
|
9
10
|
:cancelLabel="cancelLabel"
|
|
10
11
|
:nextLabel="nextLabel"
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
</NcNoteCard>
|
|
80
81
|
<CnChoiceCards
|
|
81
82
|
v-if="isCardChoice(step)"
|
|
83
|
+
:layout="step.display === 'rows' ? 'rows' : 'cards'"
|
|
82
84
|
:label="stepTitle(step)"
|
|
83
85
|
:options="optionsFor(step)"
|
|
84
86
|
:multiple="step.multiple === true"
|
|
@@ -464,14 +466,16 @@ export default {
|
|
|
464
466
|
computed: {
|
|
465
467
|
/**
|
|
466
468
|
* The title actually rendered by CnWizardDialog: "Set up {appName}"
|
|
467
|
-
* when a display name was passed
|
|
468
|
-
* resolves to (a caller
|
|
469
|
+
* when a display name was passed (the name through the host's
|
|
470
|
+
* translate), otherwise whatever `dialogTitle` resolves to (a caller
|
|
471
|
+
* override, or its own generic default). In the board look it is the
|
|
472
|
+
* eyebrow's context and the current step's title is the heading.
|
|
469
473
|
*
|
|
470
474
|
* @return {string}
|
|
471
475
|
*/
|
|
472
476
|
resolvedDialogTitle() {
|
|
473
477
|
return this.appName
|
|
474
|
-
? t('nextcloud-vue', 'Set up {app}', { app: this.appName })
|
|
478
|
+
? t('nextcloud-vue', 'Set up {app}', { app: this.cnTranslate(this.appName) })
|
|
475
479
|
: this.dialogTitle
|
|
476
480
|
},
|
|
477
481
|
|
|
@@ -727,6 +731,12 @@ export default {
|
|
|
727
731
|
},
|
|
728
732
|
},
|
|
729
733
|
|
|
734
|
+
created() {
|
|
735
|
+
for (const step of this.setupSteps) {
|
|
736
|
+
this.applyDefault(step)
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
|
|
730
740
|
mounted() {
|
|
731
741
|
// Covers a RESUMED wizard opening straight onto its run-action step
|
|
732
742
|
// (`initialStepId`) — `onStepChange` only fires on later navigation,
|
|
@@ -944,13 +954,14 @@ export default {
|
|
|
944
954
|
},
|
|
945
955
|
|
|
946
956
|
/**
|
|
947
|
-
* Whether a choice step renders as a card grid
|
|
957
|
+
* Whether a choice step renders as CnChoiceCards (a card grid, or radio
|
|
958
|
+
* rows with `display: "rows"`) rather than a dropdown.
|
|
948
959
|
*
|
|
949
960
|
* @param {object} step The choice step.
|
|
950
|
-
* @return {boolean} True for `display: "cards"`.
|
|
961
|
+
* @return {boolean} True for `display: "cards"` and `display: "rows"`.
|
|
951
962
|
*/
|
|
952
963
|
isCardChoice(step) {
|
|
953
|
-
return step.display === 'cards'
|
|
964
|
+
return step.display === 'cards' || step.display === 'rows'
|
|
954
965
|
},
|
|
955
966
|
|
|
956
967
|
/**
|
|
@@ -1054,6 +1065,36 @@ export default {
|
|
|
1054
1065
|
}
|
|
1055
1066
|
},
|
|
1056
1067
|
|
|
1068
|
+
/**
|
|
1069
|
+
* Preselect a `choice` step's `default` option. Skipped once the user
|
|
1070
|
+
* picked here, when a value is already chosen, and for a step the
|
|
1071
|
+
* server already completed (its stored value must not be overwritten
|
|
1072
|
+
* by a Next). A card step holds the plain value; a dropdown holds the
|
|
1073
|
+
* option object, or the value while the options are still loading.
|
|
1074
|
+
*
|
|
1075
|
+
* @param {object} step The choice step.
|
|
1076
|
+
* @return {void}
|
|
1077
|
+
*/
|
|
1078
|
+
applyDefault(step) {
|
|
1079
|
+
if (!step || step.type !== 'choice' || step.default === undefined || step.default === null) {
|
|
1080
|
+
return
|
|
1081
|
+
}
|
|
1082
|
+
if (this.userTouched[step.id] === true || this.hasChoice(step) || this.isServerDone(step.id)) {
|
|
1083
|
+
return
|
|
1084
|
+
}
|
|
1085
|
+
const wanted = step.multiple === true ? [].concat(step.default) : step.default
|
|
1086
|
+
if (this.isCardChoice(step)) {
|
|
1087
|
+
this.choiceModel[step.id] = wanted
|
|
1088
|
+
} else {
|
|
1089
|
+
const options = this.optionsFor(step)
|
|
1090
|
+
const pick = (value) => options.find((o) => o.value === value) || value
|
|
1091
|
+
this.choiceModel[step.id] = Array.isArray(wanted) ? wanted.map(pick) : pick(wanted)
|
|
1092
|
+
}
|
|
1093
|
+
if (step.configKey) {
|
|
1094
|
+
this.choiceValues[step.configKey] = this.scalarChoice(step)
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1097
|
+
|
|
1057
1098
|
/**
|
|
1058
1099
|
* Auto-fill a `choice` step's suggested default from an earlier choice
|
|
1059
1100
|
* (its `suggestFrom` configKey → `suggestMap[parentValue]`). No-op once
|
|
@@ -1360,6 +1401,15 @@ export default {
|
|
|
1360
1401
|
if (step && step.suggestFrom) {
|
|
1361
1402
|
this.applySuggestion(step)
|
|
1362
1403
|
}
|
|
1404
|
+
// A default seeded before a dropdown's options arrived is a bare
|
|
1405
|
+
// value; swap in its option object now the step opens.
|
|
1406
|
+
if (step && step.default !== undefined && this.userTouched[step.id] !== true && !this.isCardChoice(step)) {
|
|
1407
|
+
const held = this.choiceModel[step.id]
|
|
1408
|
+
const option = this.optionsFor(step).find((o) => o.value === held)
|
|
1409
|
+
if (option && held !== null && typeof held !== 'object') {
|
|
1410
|
+
this.choiceModel[step.id] = option
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1363
1413
|
this.maybeAutoRunStep(payload.stepId)
|
|
1364
1414
|
/**
|
|
1365
1415
|
* @event step-change Emitted when the active step changes.
|
|
@@ -624,6 +624,7 @@ export default {
|
|
|
624
624
|
// is already going under "More" does not use one up.
|
|
625
625
|
let placed = pinActivity ? 1 : 0
|
|
626
626
|
const resolved = []
|
|
627
|
+
const usedKeys = new Set()
|
|
627
628
|
tabs.forEach((tab, position) => {
|
|
628
629
|
const index = originalIndex[position]
|
|
629
630
|
// A tab whose condition is false is absent, not empty.
|
|
@@ -654,8 +655,14 @@ export default {
|
|
|
654
655
|
placed += 1
|
|
655
656
|
}
|
|
656
657
|
}
|
|
658
|
+
// Keyed on the tab's own id, else its place in `content.tabs`:
|
|
659
|
+
// never on the widget the panel starts with, which changes when
|
|
660
|
+
// a stack's first card turns visible and would remount the panel.
|
|
661
|
+
const ownId = tab && typeof tab === 'object' && typeof tab.id === 'string' ? tab.id : ''
|
|
662
|
+
const key = ownId !== '' && !usedKeys.has(ownId) ? ownId : `tab-${index}`
|
|
663
|
+
usedKeys.add(key)
|
|
657
664
|
resolved.push({
|
|
658
|
-
key
|
|
665
|
+
key,
|
|
659
666
|
id: (tab && typeof tab === 'object' && tab.id) || widgetId || '',
|
|
660
667
|
widgetId,
|
|
661
668
|
label: this.tabLabel(tab, widget, widgetId),
|
|
@@ -88,8 +88,8 @@ import { dispatchAction, resolveObjectOpType } from '../../utils/actionsDispatch
|
|
|
88
88
|
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
|
|
89
89
|
import { pageFilter, pageSort } from '../../utils/pageControls.js'
|
|
90
90
|
import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
91
|
+
import { compileRowClass } from '../../utils/rowClassRules.js'
|
|
91
92
|
import { fieldsFromSchema } from '../../utils/schema.js'
|
|
92
|
-
import { compareVisibleWhen, readVisibleWhenPath } from '../../utils/visibleWhen.js'
|
|
93
93
|
import { CnIcon } from '../CnIcon/index.js'
|
|
94
94
|
import { CnRowActions } from '../CnRowActions/index.js'
|
|
95
95
|
import { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'
|
|
@@ -762,28 +762,7 @@ export default {
|
|
|
762
762
|
* @return {?(row: object) => string} The row-class function, or null.
|
|
763
763
|
*/
|
|
764
764
|
compiledRowClass() {
|
|
765
|
-
|
|
766
|
-
if (typeof rc === 'function') {
|
|
767
|
-
return rc
|
|
768
|
-
}
|
|
769
|
-
if (!Array.isArray(rc) || rc.length === 0) {
|
|
770
|
-
return null
|
|
771
|
-
}
|
|
772
|
-
const rules = rc
|
|
773
|
-
return (row) => {
|
|
774
|
-
const classes = []
|
|
775
|
-
for (const rule of rules) {
|
|
776
|
-
if (!rule || !rule.class) {
|
|
777
|
-
continue
|
|
778
|
-
}
|
|
779
|
-
const when = rule.when || {}
|
|
780
|
-
const actual = readVisibleWhenPath(row, when.field)
|
|
781
|
-
if (compareVisibleWhen(actual, when.op || 'eq', when.value)) {
|
|
782
|
-
classes.push(rule.class)
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
return classes.join(' ')
|
|
786
|
-
}
|
|
765
|
+
return compileRowClass(this.rowClass)
|
|
787
766
|
},
|
|
788
767
|
|
|
789
768
|
/**
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
defaultWidth="wizard"
|
|
6
6
|
:eyebrow="boardEyebrow"
|
|
7
7
|
:subtitle="subtitle"
|
|
8
|
-
:name="
|
|
8
|
+
:name="shownTitle"
|
|
9
9
|
size="large"
|
|
10
10
|
:open="dialogOpen"
|
|
11
11
|
:noClose="loading || !cancellable"
|
|
@@ -265,6 +265,12 @@ export default {
|
|
|
265
265
|
* Empty uses the dialog title.
|
|
266
266
|
*/
|
|
267
267
|
eyebrowContext: { type: String, default: '' },
|
|
268
|
+
/**
|
|
269
|
+
* Board look: head the dialog with the current step's label, the
|
|
270
|
+
* dialog title moving to the eyebrow's context. The result phase and
|
|
271
|
+
* the Nextcloud look keep `dialogTitle`.
|
|
272
|
+
*/
|
|
273
|
+
stepHeading: { type: Boolean, default: false },
|
|
268
274
|
/**
|
|
269
275
|
* The eyebrow sentence, with `{context}`, `{step}` and `{total}`
|
|
270
276
|
* placeholders, so it can be translated.
|
|
@@ -356,6 +362,19 @@ export default {
|
|
|
356
362
|
.replace('{total}', String(this.steps.length))
|
|
357
363
|
},
|
|
358
364
|
|
|
365
|
+
/**
|
|
366
|
+
* The dialog's heading: the current step's label with `stepHeading`
|
|
367
|
+
* in the board look, otherwise `dialogTitle`.
|
|
368
|
+
*
|
|
369
|
+
* @return {string} The heading.
|
|
370
|
+
*/
|
|
371
|
+
shownTitle() {
|
|
372
|
+
if (this.stepHeading && this.isBoardLook && this.result === null && this.currentStep && this.currentStep.label) {
|
|
373
|
+
return this.currentStep.label
|
|
374
|
+
}
|
|
375
|
+
return this.dialogTitle
|
|
376
|
+
},
|
|
377
|
+
|
|
359
378
|
/** @return {object} The check icon, the default finishing icon. */
|
|
360
379
|
Check() {
|
|
361
380
|
return Check
|
package/src/components/index.js
CHANGED
|
@@ -130,6 +130,7 @@ export { default as CnContactsTab } from '../integrations/builtin/contacts/CnCon
|
|
|
130
130
|
export { CnContactPicker } from './CnContactPicker/index.js'
|
|
131
131
|
export { CnContactCreate } from './CnContactCreate/index.js'
|
|
132
132
|
export { CnResourceSelect } from './CnResourceSelect/index.js'
|
|
133
|
+
export { CnSearchSelect } from './CnSearchSelect/index.js'
|
|
133
134
|
export { CnPropertySourceField } from './CnPropertySourceField/index.js'
|
|
134
135
|
export { CnDurationField } from './CnDurationField/index.js'
|
|
135
136
|
export { CnFavouriteToggle } from './CnFavouriteToggle/index.js'
|
|
@@ -530,6 +530,15 @@ function tasksSource() {
|
|
|
530
530
|
*/
|
|
531
531
|
loading: () => !!store.loading,
|
|
532
532
|
|
|
533
|
+
// The inbox pages: the page sends `limit` and `offset` and draws the
|
|
534
|
+
// pager from the datastore total.
|
|
535
|
+
pages: true,
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* @return {number} The datastore total, independent of the page size.
|
|
539
|
+
*/
|
|
540
|
+
total: () => Number(store.total) || 0,
|
|
541
|
+
|
|
533
542
|
/**
|
|
534
543
|
* Count the inbox for a config without touching the rows on screen,
|
|
535
544
|
* for a quick-filter tab with `showCount`.
|
package/src/css/dialog.css
CHANGED
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
* Theme hooks (all optional, defaults below):
|
|
10
10
|
* --cn-dialog-danger fill of a destructive primary
|
|
11
11
|
* --cn-dialog-eyebrow-color eyebrow colour
|
|
12
|
-
* --cn-dialog-eyebrow-transform
|
|
12
|
+
* --cn-dialog-eyebrow-transform none (default, sentence case) or uppercase
|
|
13
|
+
* --cn-dialog-eyebrow-tracking letter spacing; 0 by default, 0.06em suits capitals
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
.cn-dialog-board {
|
|
16
17
|
--cn-dialog-width: 640px;
|
|
17
18
|
--cn-dialog-danger: var(--color-error);
|
|
18
19
|
--cn-dialog-eyebrow-color: var(--color-primary-element-text, var(--color-main-text));
|
|
19
|
-
--cn-dialog-eyebrow-transform:
|
|
20
|
+
--cn-dialog-eyebrow-transform: none;
|
|
21
|
+
--cn-dialog-eyebrow-tracking: 0;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/* Dimmed page behind the dialog: text colour at 45%. */
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
margin-bottom: 4px;
|
|
74
76
|
font-size: 13px;
|
|
75
77
|
font-weight: 700;
|
|
76
|
-
letter-spacing: 0.06em;
|
|
78
|
+
letter-spacing: var(--cn-dialog-eyebrow-tracking, 0.06em);
|
|
77
79
|
text-transform: var(--cn-dialog-eyebrow-transform, uppercase);
|
|
78
80
|
color: var(--cn-dialog-eyebrow-color, var(--color-main-text));
|
|
79
81
|
}
|
|
@@ -128,6 +128,16 @@
|
|
|
128
128
|
gap: 8px;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/* `quickFilterPlacement: "toolbar"` (DqTeamwachtrij): the chips take the room
|
|
132
|
+
the Filter button and the view switch leave on row 1, so the three stay on
|
|
133
|
+
one row in the column beside a side panel; the chips that do not fit move
|
|
134
|
+
into the overflow chip (CnQuickFilterBar `fit`) instead of pushing the
|
|
135
|
+
buttons onto a row of their own. */
|
|
136
|
+
.cn-look-board.cn-index-page--quick-filters-toolbar .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-quick-filter-bar {
|
|
137
|
+
flex: 1 1 0;
|
|
138
|
+
min-width: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
/* Row 2: the search field and the active filters on a line of their own. */
|
|
132
142
|
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--search {
|
|
133
143
|
display: flex;
|
package/src/index.js
CHANGED
package/src/mixins/pageViews.js
CHANGED
|
@@ -270,12 +270,14 @@ export const pageViews = {
|
|
|
270
270
|
|
|
271
271
|
/**
|
|
272
272
|
* Whether a user arranges the views for themselves (`userLayout`).
|
|
273
|
+
* Not while Buildiq edits the page (`editingBody`): Buildiq is the
|
|
274
|
+
* layout editor, and it shows and changes the manifest layout.
|
|
273
275
|
*
|
|
274
276
|
* @return {boolean}
|
|
275
277
|
* @spec openspec/changes/page-view-user-layouts/specs/view-switch-containers/spec.md#requirement-a-user-arranges-each-view-for-themselves
|
|
276
278
|
*/
|
|
277
279
|
viewUserLayoutOn() {
|
|
278
|
-
return this.userLayout === true && this.hasViews
|
|
280
|
+
return this.userLayout === true && this.hasViews && this.editingBody !== true
|
|
279
281
|
},
|
|
280
282
|
|
|
281
283
|
/**
|
|
@@ -640,6 +642,11 @@ export const pageViews = {
|
|
|
640
642
|
this.viewUserLayouts = { ...this.viewUserLayouts, [view.id]: arranged }
|
|
641
643
|
this.viewUserLayoutsDirty = { ...this.viewUserLayoutsDirty, [view.id]: true }
|
|
642
644
|
this.$emit('view-layout-change', { view: view.id, layout: arranged })
|
|
645
|
+
// Outside an in-page edit session (the dashboard's edit mode, the
|
|
646
|
+
// detail page's Arrange) no Done will come to save it.
|
|
647
|
+
if (this.isEditing !== true && this.arrangingView !== true) {
|
|
648
|
+
this.saveViewUserLayouts()
|
|
649
|
+
}
|
|
643
650
|
return
|
|
644
651
|
}
|
|
645
652
|
for (const u of updated) {
|