@conduction/nextcloud-vue 2.124.0 → 2.125.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.
Files changed (66) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +120 -11
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +78 -32
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +8 -7
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  10. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +2 -1
  11. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -2
  13. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  14. package/dist/esm/components/CnDialog/CnDialog.vue2.js +5 -0
  15. package/dist/esm/components/CnDialog/CnDialog.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDialog/CnDialog.vue3.js +1 -1
  17. package/dist/esm/components/CnDialog/CnDialog.vue3.js.map +1 -1
  18. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +13 -0
  19. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -1
  21. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  22. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  23. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +7 -2
  24. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  25. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  26. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  27. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +159 -10
  28. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +151 -90
  30. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  31. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +12 -0
  32. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  33. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
  34. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  35. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
  36. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +39 -68
  37. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
  38. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +70 -116
  39. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
  40. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  41. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +19 -3
  42. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  43. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  44. package/dist/esm/l10n/en.json.js +8 -8
  45. package/dist/esm/l10n/nl.json.js +8 -8
  46. package/dist/esm/library-style.js +1 -1
  47. package/dist/esm/nextcloud-vue.css +316 -185
  48. package/dist/nextcloud-vue.cjs.js +1149 -808
  49. package/dist/nextcloud-vue.cjs.js.map +2 -2
  50. package/dist/nextcloud-vue.css +316 -185
  51. package/l10n/en.json +8 -8
  52. package/l10n/nl.json +8 -8
  53. package/package.json +1 -1
  54. package/src/components/CnActionDialog/CnActionDialog.vue +173 -14
  55. package/src/components/CnAppRoot/CnAppRoot.vue +8 -7
  56. package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -3
  57. package/src/components/CnDialog/CnDialog.vue +6 -1
  58. package/src/components/CnIndexPage/CnIndexPage.vue +14 -0
  59. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +7 -2
  60. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +237 -23
  61. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +13 -1
  62. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +147 -145
  63. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +19 -3
  64. package/src/css/dialog.css +23 -0
  65. package/src/css/look-board-index.css +5 -1
  66. package/src/css/look-board.css +19 -0
package/l10n/en.json CHANGED
@@ -2220,6 +2220,13 @@
2220
2220
  "This could not be read: {reason}": "This could not be read: {reason}",
2221
2221
  "Still open": "Still open",
2222
2222
  "The checklist could not be read.": "The checklist could not be read.",
2223
+ "Flag it with a reason, so it stands out to everyone who works on it.": "Flag it with a reason, so it stands out to everyone who works on it.",
2224
+ "Clear the flag with a reason once it no longer needs attention.": "Clear the flag with a reason once it no longer needs attention.",
2225
+ "Compose mail": "Compose mail",
2226
+ "Link mail": "Link mail",
2227
+ "Enter a quarter as YYYY-Qn, for example 2026-Q3.": "Enter a quarter as YYYY-Qn, for example 2026-Q3.",
2228
+ "Enter a year as four digits, for example 2026.": "Enter a year as four digits, for example 2026.",
2229
+ "This value has the wrong form.": "This value has the wrong form.",
2223
2230
  "The value for {key} must be above {min}.": "The value for {key} must be above {min}.",
2224
2231
  "Add a row": "Add a row",
2225
2232
  "Key": "Key",
@@ -2229,14 +2236,7 @@
2229
2236
  "How this app compares": "How this app compares",
2230
2237
  "This field is required": "This field is required",
2231
2238
  "Creating…": "Creating…",
2232
- "This could not be created. Try again.": "This could not be created. Try again.",
2233
- "Flag it with a reason, so it stands out to everyone who works on it.": "Flag it with a reason, so it stands out to everyone who works on it.",
2234
- "Clear the flag with a reason once it no longer needs attention.": "Clear the flag with a reason once it no longer needs attention.",
2235
- "Compose mail": "Compose mail",
2236
- "Link mail": "Link mail",
2237
- "Enter a quarter as YYYY-Qn, for example 2026-Q3.": "Enter a quarter as YYYY-Qn, for example 2026-Q3.",
2238
- "Enter a year as four digits, for example 2026.": "Enter a year as four digits, for example 2026.",
2239
- "This value has the wrong form.": "This value has the wrong form."
2239
+ "This could not be created. Try again.": "This could not be created. Try again."
2240
2240
  },
2241
2241
  "plurals": {
2242
2242
  "{count} member": [
package/l10n/nl.json CHANGED
@@ -2470,6 +2470,13 @@
2470
2470
  "This could not be read: {reason}": "Dit kon niet worden gelezen: {reason}",
2471
2471
  "Still open": "Nog open",
2472
2472
  "The checklist could not be read.": "De lijst kon niet worden gelezen.",
2473
+ "Flag it with a reason, so it stands out to everyone who works on it.": "Markeer het met een reden, zodat het opvalt bij iedereen die eraan werkt.",
2474
+ "Clear the flag with a reason once it no longer needs attention.": "Haal de markering weg met een reden zodra het geen aandacht meer nodig heeft.",
2475
+ "Compose mail": "Mail opstellen",
2476
+ "Link mail": "Mail koppelen",
2477
+ "Enter a quarter as YYYY-Qn, for example 2026-Q3.": "Vul een kwartaal in als JJJJ-Qn, bijvoorbeeld 2026-Q3.",
2478
+ "Enter a year as four digits, for example 2026.": "Vul een jaar in met vier cijfers, bijvoorbeeld 2026.",
2479
+ "This value has the wrong form.": "Deze waarde heeft niet de goede vorm.",
2473
2480
  "The value for {key} must be above {min}.": "De waarde voor {key} moet hoger zijn dan {min}.",
2474
2481
  "Add a row": "Een rij toevoegen",
2475
2482
  "Key": "Sleutel",
@@ -2479,14 +2486,7 @@
2479
2486
  "How this app compares": "Hoe deze app zich verhoudt",
2480
2487
  "This field is required": "Dit veld is verplicht",
2481
2488
  "Creating…": "Aanmaken…",
2482
- "This could not be created. Try again.": "Dit kon niet worden aangemaakt. Probeer het opnieuw.",
2483
- "Flag it with a reason, so it stands out to everyone who works on it.": "Markeer het met een reden, zodat het opvalt bij iedereen die eraan werkt.",
2484
- "Clear the flag with a reason once it no longer needs attention.": "Haal de markering weg met een reden zodra het geen aandacht meer nodig heeft.",
2485
- "Compose mail": "Mail opstellen",
2486
- "Link mail": "Mail koppelen",
2487
- "Enter a quarter as YYYY-Qn, for example 2026-Q3.": "Vul een kwartaal in als JJJJ-Qn, bijvoorbeeld 2026-Q3.",
2488
- "Enter a year as four digits, for example 2026.": "Vul een jaar in met vier cijfers, bijvoorbeeld 2026.",
2489
- "This value has the wrong form.": "Deze waarde heeft niet de goede vorm."
2489
+ "This could not be created. Try again.": "Dit kon niet worden aangemaakt. Probeer het opnieuw."
2490
2490
  },
2491
2491
  "plurals": {
2492
2492
  "{count} member": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.124.0",
3
+ "version": "2.125.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": {
@@ -68,10 +68,35 @@
68
68
  :key="field.field"
69
69
  class="cn-action-dialog__field"
70
70
  :data-testid="`cn-action-dialog-field-${field.field}`">
71
- <NcSelect v-if="field.type === 'select'"
71
+ <!-- A checkbox field: a tinted box, the label filled from the
72
+ record, a hint under it (DqArchiveren "Sanne de Vries laten
73
+ weten..."). -->
74
+ <NcCheckboxRadioSwitch v-if="field.type === 'checkbox'"
75
+ class="cn-action-dialog__checkbox"
76
+ :modelValue="checkboxValue(field)"
77
+ type="checkbox"
78
+ @update:modelValue="(on) => setChecked(field.field, on)">
79
+ <span class="cn-action-dialog__checkbox-text">
80
+ <strong class="cn-action-dialog__checkbox-label">{{ fill(field.label) || field.field }}</strong>
81
+ <span v-if="field.hint" class="cn-action-dialog__muted">{{ fill(field.hint) }}</span>
82
+ </span>
83
+ </NcCheckboxRadioSwitch>
84
+ <!-- A search field: records of `source` found as the reader
85
+ types (DqZaakKoppelen "Zaak koppelen"). -->
86
+ <CnSearchSelect v-else-if="field.type === 'search'"
87
+ :modelValue="values[field.field] || null"
88
+ :options="searchOptions[field.field] || []"
89
+ :inputLabel="fill(field.label) || field.field"
90
+ :labelOutside="true"
91
+ :loading="searching === field.field"
92
+ :filterable="false"
93
+ :placeholder="fill(field.placeholder)"
94
+ @search="(typed) => onSearch(field, typed)"
95
+ @update:modelValue="(value) => setValue(field.field, value && typeof value === 'object' ? value.id : value)" />
96
+ <NcSelect v-else-if="field.type === 'select'"
72
97
  :modelValue="selectedOption(field)"
73
98
  :options="optionsOf(field)"
74
- :inputLabel="text(field.label) || field.field"
99
+ :inputLabel="fill(field.label) || field.field"
75
100
  :clearable="field.required === false"
76
101
  @update:modelValue="(option) => pick(field, option)" />
77
102
  <NcTextField v-else-if="field.type === 'secret'"
@@ -84,8 +109,9 @@
84
109
  <NcTextField v-else
85
110
  :modelValue="values[field.field] || ''"
86
111
  :type="field.type === 'datetime' ? 'datetime-local' : (field.type === 'date' ? 'date' : 'text')"
87
- :label="text(field.label) || field.field"
112
+ :label="fill(field.label) || field.field"
88
113
  @update:modelValue="(value) => setValue(field.field, value)" />
114
+ <span v-if="field.hint && field.type !== 'checkbox'" class="cn-action-dialog__muted">{{ fill(field.hint) }}</span>
89
115
  </div>
90
116
 
91
117
  <div v-if="config.reason" class="cn-action-dialog__field">
@@ -142,7 +168,7 @@ import axios from '@nextcloud/axios'
142
168
  import { emit } from '@nextcloud/event-bus'
143
169
  import { translate as t } from '@nextcloud/l10n'
144
170
  import { generateUrl } from '@nextcloud/router'
145
- import { NcButton, NcNoteCard, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'
171
+ import { NcButton, NcCheckboxRadioSwitch, NcNoteCard, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'
146
172
  import CheckCircle from 'vue-material-design-icons/CheckCircle.vue'
147
173
  import CircleOutline from 'vue-material-design-icons/CircleOutline.vue'
148
174
  import HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'
@@ -151,6 +177,10 @@ import { fillTemplate } from '../../utils/fillTemplate.js'
151
177
  import { readPath } from '../../utils/readPath.js'
152
178
  import { CnDialog } from '../CnDialog/index.js'
153
179
  import { CnIcon } from '../CnIcon/index.js'
180
+ import { CnSearchSelect } from '../CnSearchSelect/index.js'
181
+
182
+ /** How long typing rests before a search field searches, in ms. */
183
+ const SEARCH_DELAY = 250
154
184
 
155
185
  /** The page refresh channel the built-in widgets listen on. */
156
186
  const PAGE_REFRESH = 'cn:page:refresh'
@@ -180,10 +210,17 @@ let reasonSeq = 0
180
210
  * the action runs only while `allowedPath` reads true; `reasonPath` says
181
211
  * why not. With `listPath` the paths read the entry of that list whose
182
212
  * fields equal `match` (`{ act: "archive" }`).
183
- * - `fields`: `[{ field, type?: "text"|"date"|"datetime"|"select", label?,
184
- * required?, options?: [{ value, label, fills? }] }]`: inputs sent beside
185
- * the reason, required by default; a select option's `fills` sets other
186
- * fields that are still empty.
213
+ * - `fields`: `[{ field, type?: "text"|"date"|"datetime"|"select"|"checkbox", label?,
214
+ * required?, options?: [{ value, label, fills? }], hint?, default? }]`: inputs
215
+ * sent beside the reason, required by default; a select option's `fills`
216
+ * sets other fields that are still empty. A `checkbox` is sent as a boolean,
217
+ * starts at `default` (false), fills its `label` and `hint` from the record,
218
+ * and is optional unless `required: true` (then it must be ticked). A
219
+ * `search` field finds records of `source: { register, schema }` as the
220
+ * reader types, shows each as `optionLabel` with `optionSecondary` under
221
+ * it (templates on the found record), leaves the record in hand out with
222
+ * `excludeSelf`, and sends the chosen record's id. Every field's `label`,
223
+ * `hint` and `placeholder` are templates filled from the record.
187
224
  * - `reason`: `{ label?, hint?, required?, field?, rows? }`: a text area; required by
188
225
  * default; sent as `field` (default `reason`). `rows` is its height in lines
189
226
  * (default 2; board DqBesluit draws 4).
@@ -197,7 +234,7 @@ let reasonSeq = 0
197
234
  export default {
198
235
  name: 'CnActionDialog',
199
236
 
200
- components: { CheckCircle, CircleOutline, CnDialog, CnIcon, HelpCircleOutline, NcButton, NcNoteCard, NcSelect, NcTextArea, NcTextField },
237
+ components: { CheckCircle, CircleOutline, CnDialog, CnIcon, CnSearchSelect, HelpCircleOutline, NcButton, NcCheckboxRadioSwitch, NcNoteCard, NcSelect, NcTextArea, NcTextField },
201
238
 
202
239
  inject: {
203
240
  /** The host app's translate (CnAppRoot `cnTranslate`). */
@@ -244,6 +281,10 @@ export default {
244
281
  allowed: true,
245
282
  reason: '',
246
283
  values: {},
284
+ /** A search field's found options, by field name. */
285
+ searchOptions: {},
286
+ /** The search field whose search runs, or ''. */
287
+ searching: '',
247
288
  busy: false,
248
289
  refusal: '',
249
290
  done: false,
@@ -362,6 +403,20 @@ export default {
362
403
  return this.record ? this.fill(this.config.eyebrow) || this.text(this.config.eyebrow) : this.text(this.config.eyebrow)
363
404
  },
364
405
 
406
+ /**
407
+ * The value of every field as sent: a checkbox as a boolean, the
408
+ * others as entered ('' when empty).
409
+ *
410
+ * @return {object} Values by field name.
411
+ */
412
+ fieldValues() {
413
+ const out = { ...this.values }
414
+ for (const field of this.fields) {
415
+ out[field.field] = field.type === 'checkbox' ? this.checkboxValue(field) : (this.values[field.field] ?? '')
416
+ }
417
+ return out
418
+ },
419
+
365
420
  /** @return {Array<object>} The fields with a name. */
366
421
  fields() {
367
422
  return (Array.isArray(this.config.fields) ? this.config.fields : []).filter((f) => f && f.field)
@@ -374,7 +429,12 @@ export default {
374
429
  * @return {boolean}
375
430
  */
376
431
  fieldsFilled() {
377
- return this.fields.every((f) => f.required === false || this.secretStored(f) || String(this.values[f.field] ?? '').trim() !== '')
432
+ return this.fields.every((f) => {
433
+ if (f.type === 'checkbox') {
434
+ return f.required !== true || this.checkboxValue(f)
435
+ }
436
+ return f.required === false || this.secretStored(f) || String(this.values[f.field] ?? '').trim() !== ''
437
+ })
378
438
  },
379
439
  },
380
440
 
@@ -392,6 +452,10 @@ export default {
392
452
  await Promise.all([this.loadRecord(), this.loadChecklist(), this.loadPermission()])
393
453
  },
394
454
 
455
+ beforeUnmount() {
456
+ clearTimeout(this.searchTimer)
457
+ },
458
+
395
459
  methods: {
396
460
  t,
397
461
 
@@ -401,6 +465,76 @@ export default {
401
465
  * @param {unknown} value The text.
402
466
  * @return {string}
403
467
  */
468
+ /**
469
+ * Search a search field's records once typing rests.
470
+ *
471
+ * @param {object} field The field.
472
+ * @param {string} typed What the reader typed.
473
+ * @return {void}
474
+ * @spec openspec/changes/action-dialog-search-field/specs/action-dialog-search-field/spec.md#requirement-an-action-dialog-field-can-search-records
475
+ */
476
+ onSearch(field, typed) {
477
+ clearTimeout(this.searchTimer)
478
+ this.searchTimer = setTimeout(() => this.searchRecords(field, String(typed || '').trim()), SEARCH_DELAY)
479
+ },
480
+
481
+ /**
482
+ * Read the records of a search field's `source` that match the term.
483
+ *
484
+ * @param {object} field The field.
485
+ * @param {string} term The term.
486
+ * @return {Promise<void>}
487
+ */
488
+ async searchRecords(field, term) {
489
+ const source = field.source || {}
490
+ if (!term || !source.register || !source.schema) {
491
+ this.searchOptions = { ...this.searchOptions, [field.field]: [] }
492
+ return
493
+ }
494
+ this.searching = field.field
495
+ try {
496
+ const url = generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(source.register)}/${encodeURIComponent(source.schema)}`)
497
+ const { data } = await axios.get(url, { params: { _search: term, _limit: 10 } })
498
+ const rows = Array.isArray(data) ? data : (data && Array.isArray(data.results) ? data.results : [])
499
+ const self = String(this.recordId || '')
500
+ const options = rows
501
+ .map((row) => ({ row, id: String(row.id || (row['@self'] && row['@self'].id) || '') }))
502
+ .filter(({ id }) => id !== '' && !(field.excludeSelf && id === self))
503
+ .map(({ row, id }) => {
504
+ const option = { id, label: this.fill(field.optionLabel || '{title}', row) || id }
505
+ const secondary = this.fill(field.optionSecondary, row)
506
+ return secondary ? { ...option, secondary } : option
507
+ })
508
+ this.searchOptions = { ...this.searchOptions, [field.field]: options }
509
+ } catch {
510
+ this.searchOptions = { ...this.searchOptions, [field.field]: [] }
511
+ } finally {
512
+ this.searching = ''
513
+ }
514
+ },
515
+
516
+ /**
517
+ * A checkbox field's state: what was ticked, else its `default`.
518
+ *
519
+ * @param {object} field The field.
520
+ * @return {boolean}
521
+ * @spec openspec/changes/action-dialog-checkbox-field/specs/action-dialog-checkbox-field/spec.md#requirement-an-action-dialog-field-can-be-a-checkbox
522
+ */
523
+ checkboxValue(field) {
524
+ return field.field in this.values ? this.values[field.field] === true : field.default === true
525
+ },
526
+
527
+ /**
528
+ * Tick or untick a checkbox field.
529
+ *
530
+ * @param {string} name The field.
531
+ * @param {boolean} on Ticked.
532
+ * @return {void}
533
+ */
534
+ setChecked(name, on) {
535
+ this.values = { ...this.values, [name]: on === true }
536
+ },
537
+
404
538
  text(value) {
405
539
  return typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''
406
540
  },
@@ -557,21 +691,22 @@ export default {
557
691
  if (this.config.reason) {
558
692
  body[this.config.reason.field || 'reason'] = reason
559
693
  }
694
+ const values = { ...this.fieldValues }
560
695
  for (const field of this.fields) {
561
696
  if (field.type === 'secret' && String(this.values[field.field] ?? '') === '') {
562
- continue
697
+ delete values[field.field]
563
698
  }
564
- body[field.field] = this.values[field.field] ?? ''
565
699
  }
700
+ Object.assign(body, values)
566
701
  const method = String(request.method || 'POST').toLowerCase()
567
702
  await axios[['post', 'put', 'patch', 'delete'].includes(method) ? method : 'post'](/^https?:\/\//i.test(url) ? url : generateUrl(url), body)
568
703
  } else if (this.confirmConfig.action && typeof this.cnDispatchAction === 'function') {
569
- await this.cnDispatchAction({ ...this.confirmConfig.action, args: [...(this.confirmConfig.action.args || []), { ...this.values, reason }] })
704
+ await this.cnDispatchAction({ ...this.confirmConfig.action, args: [...(this.confirmConfig.action.args || []), { ...this.fieldValues, reason }] })
570
705
  }
571
706
  this.done = true
572
707
  emit(PAGE_REFRESH, {})
573
708
  /** @event done The action ran. Payload: the field values and `reason`. */
574
- this.$emit('done', { ...this.values, reason })
709
+ this.$emit('done', { ...this.fieldValues, reason })
575
710
  } catch (failure) {
576
711
  const data = failure && failure.response && failure.response.data
577
712
  this.refusal = String((data && (data.message || data.error)) || t('nextcloud-vue', 'The action could not be completed.'))
@@ -729,6 +864,30 @@ export default {
729
864
  font-size: 14px;
730
865
  }
731
866
 
867
+ /* A checkbox field is a tinted box with a 2px edge, its label bold and its
868
+ hint under it (DqArchiveren). */
869
+ .cn-action-dialog__checkbox {
870
+ padding: 12px 14px;
871
+ border: 2px solid var(--color-primary-element);
872
+ border-radius: var(--border-radius-element, 8px);
873
+ background: var(--color-primary-element-light);
874
+ }
875
+
876
+ .cn-action-dialog__checkbox-text {
877
+ display: flex;
878
+ flex-direction: column;
879
+ gap: 2px;
880
+ }
881
+
882
+ .cn-action-dialog__checkbox-label {
883
+ font-size: 15px;
884
+ font-weight: 600;
885
+ }
886
+
887
+ .cn-action-dialog__checkbox .cn-action-dialog__muted {
888
+ font-size: 13px;
889
+ }
890
+
732
891
  .cn-action-dialog__field {
733
892
  display: flex;
734
893
  flex-direction: column;
@@ -1690,9 +1690,10 @@ export default {
1690
1690
  },
1691
1691
 
1692
1692
  /**
1693
- * Board look: the line above the user-settings title. Empty uses
1694
- * the library's "Your account".
1693
+ * Deprecated and ignored: the board user-settings dialog draws no
1694
+ * line above its title (DqPersoonlijkeInstellingen).
1695
1695
  *
1696
+ * @deprecated
1696
1697
  * @type {string}
1697
1698
  */
1698
1699
  userSettingsEyebrow: {
@@ -1701,10 +1702,10 @@ export default {
1701
1702
  },
1702
1703
 
1703
1704
  /**
1704
- * Board look: the sentence under the user-settings title. Empty
1705
- * uses the library's "Only for you. Colleagues keep their own
1706
- * settings."
1705
+ * Deprecated and ignored: the board user-settings dialog draws no
1706
+ * sentence under its title (DqPersoonlijkeInstellingen).
1707
1707
  *
1708
+ * @deprecated
1708
1709
  * @type {string}
1709
1710
  */
1710
1711
  userSettingsSubtitle: {
@@ -3205,8 +3206,8 @@ export default {
3205
3206
 
3206
3207
  /**
3207
3208
  * The props of the user-settings dialog: `NcAppSettingsDialog`'s, or
3208
- * under the board look CnUserSettingsDialog's (eyebrow, subtitle and
3209
- * the app's name for the footer note).
3209
+ * under the board look CnUserSettingsDialog's (title, the app's name
3210
+ * for the note under the sections, and the Nextcloud links).
3210
3211
  *
3211
3212
  * @spec openspec/changes/user-settings-board-dialog/specs/user-settings-board-dialog/spec.md#requirement-the-user-settings-dialog-takes-the-board-look
3212
3213
  * @return {object}
@@ -46,9 +46,9 @@
46
46
  :class="{ 'cn-assign-dialog__toggle--on': flags[toggle.field] }"
47
47
  :data-testid="`cn-assign-dialog-toggle-${toggle.field}`">
48
48
  <NcCheckboxRadioSwitch :modelValue="flags[toggle.field] === true" @update:modelValue="flags[toggle.field] = $event">
49
- <span class="cn-assign-dialog__toggle-title">{{ text(toggle.label) }}</span>
49
+ <span class="cn-assign-dialog__toggle-title">{{ fill(toggle.label) }}</span>
50
50
  </NcCheckboxRadioSwitch>
51
- <span v-if="toggle.note" class="cn-assign-dialog__hint cn-assign-dialog__toggle-note">{{ text(toggle.note) }}</span>
51
+ <span v-if="toggle.note" class="cn-assign-dialog__hint cn-assign-dialog__toggle-note">{{ fill(toggle.note) }}</span>
52
52
  </div>
53
53
 
54
54
  <p v-if="refusal"
@@ -144,7 +144,8 @@ export function withTypedAssignee(found, typed, label) {
144
144
  * sent as `field` (default `note`); `false` drops it. `rows` is the text
145
145
  * area's height in lines (default 3, as board DqOverdragen draws it).
146
146
  * - `toggles`: `[{ field, label, note? }]`: switches that start off and are
147
- * sent as booleans.
147
+ * sent as booleans; `label` and `note` are templates filled from the record
148
+ * ("{initiatorDisplayName} then gets a message").
148
149
  * - `confirm`: `{ label?, icon?, request?: { method?, url, body? }, action? }`.
149
150
  * `request` sends the choice itself (`body` templates filled from the
150
151
  * record); `action` dispatches a manifest action with the choice as its
@@ -10,7 +10,7 @@
10
10
  :class="frameClass"
11
11
  :style="frameStyle"
12
12
  @closing="onClosing">
13
- <CnDialogHeader v-if="isBoard"
13
+ <CnDialogHeader v-if="isBoard && !headless"
14
14
  :title="name"
15
15
  :eyebrow="eyebrow"
16
16
  :subtitle="subtitle"
@@ -74,6 +74,11 @@ export default {
74
74
  eyebrow: { type: String, default: '' },
75
75
  /** Sentence under the title (board look). */
76
76
  subtitle: { type: String, default: '' },
77
+ /**
78
+ * Leave out the board header: the body draws its own title and close
79
+ * button (CnUserSettingsDialog). `name` stays the accessible name.
80
+ */
81
+ headless: { type: Boolean, default: false },
77
82
  },
78
83
 
79
84
  emits: ['closing'],
@@ -340,6 +340,7 @@
340
340
  :currentUserId="currentSavedViewsUserId"
341
341
  :counts="savedViewCounts"
342
342
  :selectedViewId="appliedSavedViewId"
343
+ :chips="savedViewChips"
343
344
  @apply="onApplySavedView"
344
345
  @saveRequest="showSaveViewDialog = true" />
345
346
  <!-- Native Export menu (opt-in via `allowExport` + schema.exportable):
@@ -2316,6 +2317,19 @@ export default {
2316
2317
  default: null,
2317
2318
  },
2318
2319
 
2320
+ /**
2321
+ * Whether the board look draws the saved views as chips in the toolbar
2322
+ * (manifest `config.savedViewChips`). `false` keeps only Save view,
2323
+ * for a page whose views live in the side panel's Weergaven tab
2324
+ * (DqZaken, DqZakenSorteren, DqZakenKaart, DqWerkbord, DqFilterBeheren).
2325
+ *
2326
+ * @spec openspec/changes/saved-view-chips-switch/specs/saved-views/spec.md#requirement-an-index-page-can-leave-the-saved-view-chips-out-of-the-toolbar
2327
+ */
2328
+ savedViewChips: {
2329
+ type: Boolean,
2330
+ default: true,
2331
+ },
2332
+
2319
2333
  /**
2320
2334
  * Where the quick-filter chips sit (manifest `config.quickFilterPlacement`).
2321
2335
  * `''` (the default) as before. `'toolbar'` keeps them on one row with
@@ -353,6 +353,7 @@ import { buildExportUrl } from '../../utils/indexExportHelpers.js'
353
353
  import { followLinkClick, openRowTarget, resolveHref } from '../../utils/linkNavigation.js'
354
354
  import { createActionState } from '../../utils/listCreateAction.js'
355
355
  import { objectDisplayName, objectFieldValue } from '../../utils/objectName.js'
356
+ import { readPath } from '../../utils/readPath.js'
356
357
  import { moveIndex, renumberRows, rowIdOf } from '../../utils/reorderRows.js'
357
358
  import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
358
359
  import { markNewTabHandled } from '../../utils/rowAuxClick.js'
@@ -497,13 +498,15 @@ export default {
497
498
  * `hideHeader: true` drops the table's column header row (CnDataTable's
498
499
  * `hideHeader`), for a short list that reads as rows, not as a table.
499
500
  *
501
+ * `rowIdField` (a dot-path) names the row field holding the id
502
+ * `rowRoute` opens, for a row that links to another record.
500
503
  * `footnote` is a line under the table, through the host translate.
501
504
  *
502
505
  * `export` (`{ label?, format?, group? }`) offers the filtered list as a
503
506
  * download in the card's header; `group` shows it to one group only.
504
507
  *
505
508
  * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-small-opt-ins-from-the-decidiq-and-learniq-lanes
506
- * @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, footnote?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean, export?: {label?: string, format?: ('csv'|'excel'), group?: string}}}
509
+ * @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, rowIdField?: string, prompt?: string, emptyText?: string, footnote?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean, export?: {label?: string, format?: ('csv'|'excel'), group?: string}}}
507
510
  */
508
511
  content: {
509
512
  type: Object,
@@ -2145,7 +2148,9 @@ export default {
2145
2148
  */
2146
2149
  openRowRoute(row, event) {
2147
2150
  const route = this.content.rowRoute
2148
- const id = row && (row.id || (row['@self'] && row['@self'].id))
2151
+ const field = this.content.rowIdField
2152
+ const raw = field ? readPath(row, field) : row && (row.id || (row['@self'] && row['@self'].id))
2153
+ const id = typeof raw === 'string' || typeof raw === 'number' ? String(raw) : ''
2149
2154
  if (route && id && this.$router) {
2150
2155
  markNewTabHandled(event, openRowTarget(event, { name: route, params: { id } }, this.$router))
2151
2156
  }