@conduction/nextcloud-vue 2.139.0 → 2.141.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 (144) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +9 -2
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  5. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +10 -2
  6. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  9. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +28 -4
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +2 -2
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  13. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  14. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +64 -1
  15. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  16. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +76 -37
  17. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  18. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
  19. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +106 -0
  20. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  21. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +30 -7
  22. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  24. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +5 -3
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +1 -0
  30. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  31. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  32. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +59 -3
  33. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +44 -34
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  37. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +28 -1
  38. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  40. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +7 -1
  41. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  42. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
  43. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -2
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  46. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  47. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +65 -1
  48. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  49. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +120 -69
  50. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  51. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  52. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +47 -9
  53. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  54. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  55. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  56. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -3
  57. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  60. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +3 -3
  61. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  62. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  63. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  64. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +2 -0
  65. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  66. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +1 -1
  67. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
  69. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +20 -0
  70. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
  71. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +34 -26
  72. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
  73. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +1 -1
  74. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +12 -1
  75. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
  76. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +1 -1
  77. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
  78. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +1 -1
  79. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +23 -1
  80. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -1
  81. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +43 -13
  82. package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -1
  83. package/dist/esm/composables/indexSources.js +5 -2
  84. package/dist/esm/composables/indexSources.js.map +1 -1
  85. package/dist/esm/composables/useTaskInboxStore.js +4 -3
  86. package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
  87. package/dist/esm/l10n/en.json.js +3 -1
  88. package/dist/esm/l10n/en.json.js.map +1 -1
  89. package/dist/esm/l10n/nl.json.js +6 -1
  90. package/dist/esm/l10n/nl.json.js.map +1 -1
  91. package/dist/esm/library-style.js +1 -1
  92. package/dist/esm/nextcloud-vue.css +961 -605
  93. package/dist/esm/utils/builtInFormatters.js +23 -1
  94. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  95. package/dist/esm/utils/fillTemplate.js +7 -1
  96. package/dist/esm/utils/fillTemplate.js.map +1 -1
  97. package/dist/esm/utils/groupSublines.js +120 -0
  98. package/dist/esm/utils/groupSublines.js.map +1 -0
  99. package/dist/esm/utils/myTeam.js +24 -4
  100. package/dist/esm/utils/myTeam.js.map +1 -1
  101. package/dist/esm/utils/resolveFilterTokens.js +5 -4
  102. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  103. package/dist/esm/utils/transitionInputs.js +43 -2
  104. package/dist/esm/utils/transitionInputs.js.map +1 -1
  105. package/dist/nextcloud-vue.cjs.js +1731 -846
  106. package/dist/nextcloud-vue.cjs.js.map +2 -2
  107. package/dist/nextcloud-vue.css +961 -605
  108. package/l10n/en.json +3 -1
  109. package/l10n/nl.json +6 -1
  110. package/package.json +1 -1
  111. package/src/components/CnAppRoot/CnAppRoot.vue +9 -2
  112. package/src/components/CnAssignDialog/CnAssignDialog.vue +10 -2
  113. package/src/components/CnCellRenderer/CnCellRenderer.vue +30 -6
  114. package/src/components/CnChartWidget/CnChartWidget.md +2 -0
  115. package/src/components/CnChartWidget/CnChartWidget.vue +121 -5
  116. package/src/components/CnConversationThread/CnConversationWidget.vue +126 -1
  117. package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
  118. package/src/components/CnDataTable/CnDataTable.vue +6 -3
  119. package/src/components/CnFactsWidget/CnFactsWidget.vue +72 -5
  120. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +28 -1
  121. package/src/components/CnFilesBrowser/filesBrowser.js +7 -1
  122. package/src/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
  123. package/src/components/CnIndexPage/CnIndexPage.vue +30 -2
  124. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +101 -3
  125. package/src/components/CnMapWidget/CnMapWidget.vue +47 -9
  126. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +28 -3
  127. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +3 -3
  128. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +20 -1
  129. package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +43 -9
  130. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +68 -2
  131. package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +43 -1
  132. package/src/composables/indexSources.js +5 -2
  133. package/src/composables/useTaskInboxStore.js +4 -3
  134. package/src/css/dialog.css +13 -1
  135. package/src/css/look-board-detail.css +12 -9
  136. package/src/css/look-board-index.css +90 -7
  137. package/src/css/look-board.css +102 -2
  138. package/src/css/table.css +25 -0
  139. package/src/utils/builtInFormatters.js +22 -0
  140. package/src/utils/fillTemplate.js +7 -1
  141. package/src/utils/groupSublines.js +125 -0
  142. package/src/utils/myTeam.js +24 -4
  143. package/src/utils/resolveFilterTokens.js +5 -4
  144. package/src/utils/transitionInputs.js +42 -1
@@ -669,14 +669,14 @@ export default {
669
669
  },
670
670
 
671
671
  /**
672
- * How the table draws its title column (`content.rowTitle`: link, plain
673
- * or text), link when unset or unknown.
672
+ * How the table draws its title column (`content.rowTitle`: link, plain,
673
+ * text or strong), link when unset or unknown.
674
674
  *
675
675
  * @return {string}
676
676
  * @spec openspec/changes/board-log-table-plain-cells/specs/board-log-table-plain-cells/spec.md#requirement-a-tables-title-column-can-read-as-plain-text
677
677
  */
678
678
  rowTitle() {
679
- return ['plain', 'text'].includes(this.content.rowTitle) ? this.content.rowTitle : 'link'
679
+ return ['plain', 'text', 'strong'].includes(this.content.rowTitle) ? this.content.rowTitle : 'link'
680
680
  },
681
681
 
682
682
  /**
@@ -6,7 +6,7 @@
6
6
  <template>
7
7
  <div
8
8
  class="cn-record-list"
9
- :class="[`cn-record-list--${presentation}`, { 'cn-record-list--compact': content.density === 'compact', 'cn-record-list--tiles': presentation === 'rows' && content.tiles === true, 'cn-record-list--rule-above': content.ruleAbove === true, 'cn-record-list--data-table': presentation === 'table' && content.tableLook === 'data' }]"
9
+ :class="[`cn-record-list--${presentation}`, { 'cn-record-list--compact': content.density === 'compact', 'cn-record-list--stacked': content.density === 'stacked', 'cn-record-list--tiles': presentation === 'rows' && content.tiles === true, 'cn-record-list--rule-above': content.ruleAbove === true, 'cn-record-list--data-table': presentation === 'table' && content.tableLook === 'data' }]"
10
10
  data-testid="cn-record-list">
11
11
  <p v-if="intro && !absent" class="cn-record-list__intro" data-testid="cn-record-list-intro">
12
12
  {{ intro }}
@@ -713,6 +713,8 @@ export default {
713
713
  * without a value.
714
714
  * `density: "compact"` draws the rows 10px above and below, at least
715
715
  * 56px tall, with the title at 500 (DqProcesanalyse).
716
+ * `density: "stacked"` stands the rows 10px apart with no rule and no
717
+ * row padding (DqDagAfsluiten "Morgen eerst").
716
718
  * In `rows`, `text` is a line under the meta line ("12 dagen stil"),
717
719
  * and `chevron: false` leaves the chevron off a linked row
718
720
  * (DqDashboard).
@@ -3103,6 +3105,23 @@ export default {
3103
3105
  font-weight: 500;
3104
3106
  }
3105
3107
 
3108
+ /* `density: "stacked"`: the rows stand 10px apart with no rule and no row
3109
+ padding, a short side list (DqDagAfsluiten "Morgen eerst"). */
3110
+ .cn-record-list--stacked .cn-record-list__list {
3111
+ display: flex;
3112
+ flex-direction: column;
3113
+ gap: 10px;
3114
+ }
3115
+
3116
+ .cn-record-list--stacked .cn-record-list__row {
3117
+ min-height: 0;
3118
+ padding: 0;
3119
+ }
3120
+
3121
+ .cn-record-list--stacked .cn-record-list__item + .cn-record-list__item {
3122
+ border-top: 0;
3123
+ }
3124
+
3106
3125
  .cn-record-list__meta {
3107
3126
  color: var(--color-text-maxcontrast);
3108
3127
  font-size: 13px;
@@ -37,15 +37,20 @@
37
37
  data-testid="cn-record-list-due">{{ item.due.text }}</span>
38
38
  </div>
39
39
  <div class="cn-record-notes__fields">
40
- <NcTextField
41
- class="cn-record-notes__field"
42
- :modelValue="drafts[item.key] || ''"
43
- :label="fieldLabel"
44
- :labelOutside="false"
45
- :placeholder="placeholder"
46
- :disabled="busy"
47
- :data-testid="`cn-record-list-note-${item.key}`"
48
- @update:modelValue="setDraft(item.key, $event)" />
40
+ <!-- The label stands above the field, as the leaf's own beside it
41
+ (humaniq "Uren"), so both fields share one line. -->
42
+ <div class="cn-record-notes__field">
43
+ <label :for="fieldId(item.key)" class="cn-record-notes__field-label">{{ fieldLabel }}</label>
44
+ <NcTextField
45
+ :id="fieldId(item.key)"
46
+ :modelValue="drafts[item.key] || ''"
47
+ :label="fieldLabel"
48
+ labelOutside
49
+ :placeholder="placeholder"
50
+ :disabled="busy"
51
+ :data-testid="`cn-record-list-note-${item.key}`"
52
+ @update:modelValue="setDraft(item.key, $event)" />
53
+ </div>
49
54
  <div v-if="leaf && leafProvider" class="cn-record-notes__leaf" :data-testid="`cn-record-list-leaf-${item.key}`">
50
55
  <CnLeafMountHost
51
56
  v-if="leafIsMount"
@@ -117,6 +122,13 @@ import { readPath } from '../../utils/readPath.js'
117
122
  /** The bus channel every endpoint-bound widget on the page refetches on. */
118
123
  const PAGE_REFRESH_CHANNEL = 'cn:page:refresh'
119
124
 
125
+ /**
126
+ * The document event a recorded note sends, so a row's leaf (humaniq's
127
+ * hours) can record what it holds for the same object at the same moment.
128
+ * `detail` is the row's leaf props plus `note`.
129
+ */
130
+ const NOTE_RECORDED_EVENT = 'cn-record-notes:recorded'
131
+
120
132
  /**
121
133
  * The rows of a record list drawn as notes to record (`presentation:
122
134
  * "notes"`, DqDagAfsluiten "Nog open van vandaag"): per row its pill, title,
@@ -294,6 +306,16 @@ export default {
294
306
  this.drafts = { ...this.drafts, [key]: String(value ?? '') }
295
307
  },
296
308
 
309
+ /**
310
+ * The id of a row's note field, for its label.
311
+ *
312
+ * @param {string} key The row key.
313
+ * @return {string} The id.
314
+ */
315
+ fieldId(key) {
316
+ return `cn-record-notes-${this.$.uid}-${String(key).replace(/[^A-Za-z0-9_-]/g, '-')}`
317
+ },
318
+
297
319
  /**
298
320
  * @param {string} key The row key.
299
321
  * @return {boolean} Whether the row has a note typed.
@@ -351,6 +373,9 @@ export default {
351
373
  try {
352
374
  const { default: axios } = await import('@nextcloud/axios')
353
375
  await axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text })
376
+ if (this.leaf) {
377
+ document.dispatchEvent(new CustomEvent(NOTE_RECORDED_EVENT, { detail: { ...this.leafProps(item.row), note: text } }))
378
+ }
354
379
  const { [item.key]: done, ...rest } = this.drafts
355
380
  this.drafts = rest
356
381
  if (refresh) {
@@ -462,7 +487,16 @@ export default {
462
487
  }
463
488
 
464
489
  .cn-record-notes__field {
490
+ display: flex;
465
491
  flex: 1 1 260px;
492
+ flex-direction: column;
493
+ gap: 6px;
494
+ }
495
+
496
+ .cn-record-notes__field-label {
497
+ color: var(--color-main-text);
498
+ font-size: 14px;
499
+ font-weight: 600;
466
500
  }
467
501
 
468
502
  .cn-record-notes__leaf {
@@ -3,7 +3,7 @@
3
3
  SPDX-License-Identifier: EUPL-1.2
4
4
  -->
5
5
  <template>
6
- <div class="cn-stacked-bar" :class="{ 'cn-stacked-bar--inset': inset }" data-testid="cn-stacked-bar">
6
+ <div class="cn-stacked-bar" :class="{ 'cn-stacked-bar--inset': inset, 'cn-stacked-bar--compact': compact }" data-testid="cn-stacked-bar">
7
7
  <div v-if="isLoading" class="cn-stacked-bar__state" role="status">
8
8
  <NcLoadingIcon :size="20" />
9
9
  <span>{{ loadingLabel }}</span>
@@ -183,7 +183,7 @@ export default {
183
183
  * segments to the card (`header` event, `count`), for a chip after the
184
184
  * title or a `{count}` in it.
185
185
  *
186
- * @type {{inset?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
186
+ * @type {{inset?: boolean, compact?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
187
187
  */
188
188
  content: {
189
189
  type: Object,
@@ -241,6 +241,17 @@ export default {
241
241
  return Boolean(this.content && this.content.inset === true)
242
242
  },
243
243
 
244
+ /**
245
+ * Whether the widget is the compact summary box (`content.compact`):
246
+ * a 15px heading, one thin bar and the legend on one line.
247
+ *
248
+ * @spec openspec/changes/stacked-bar-compact/specs/stacked-bar-compact/spec.md#requirement-a-stacked-bar-can-be-drawn-as-the-compact-summary-box
249
+ * @return {boolean}
250
+ */
251
+ compact() {
252
+ return Boolean(this.content && this.content.compact === true)
253
+ },
254
+
244
255
  /**
245
256
  * The translate function in use.
246
257
  *
@@ -651,4 +662,59 @@ export default {
651
662
  line-height: 1.2;
652
663
  color: var(--color-main-text);
653
664
  }
665
+
666
+ /* `content.compact: true`: the summary box (DqZaakDocumenten "4 van 7
667
+ beoordeeld"): the sunken ground, a 15px heading, one 8px bar of joined
668
+ segments and the legend on one line, each count beside its label. */
669
+ .cn-stacked-bar--compact {
670
+ box-sizing: border-box;
671
+ gap: 10px;
672
+ padding: 14px 16px;
673
+ border-radius: 10px;
674
+ background: var(--cn-board-sunken-ground, var(--color-background-hover));
675
+ }
676
+
677
+ .cn-stacked-bar--compact .cn-stacked-bar__heading {
678
+ flex-wrap: wrap;
679
+ gap: 8px;
680
+ font-size: 15px;
681
+ }
682
+
683
+ .cn-stacked-bar--compact .cn-stacked-bar__bar {
684
+ gap: 0;
685
+ height: 8px;
686
+ border-radius: 4px;
687
+ overflow: hidden;
688
+ }
689
+
690
+ .cn-stacked-bar--compact .cn-stacked-bar__segment {
691
+ border-radius: 0;
692
+ }
693
+
694
+ .cn-stacked-bar--compact .cn-stacked-bar__legend {
695
+ display: flex;
696
+ flex-wrap: wrap;
697
+ gap: 8px 18px;
698
+ font-size: 14px;
699
+ }
700
+
701
+ .cn-stacked-bar--compact .cn-stacked-bar__legend-item {
702
+ flex-direction: row;
703
+ align-items: center;
704
+ gap: 8px;
705
+ }
706
+
707
+ .cn-stacked-bar--compact .cn-stacked-bar__label {
708
+ color: var(--color-main-text);
709
+ font-size: 1em;
710
+ }
711
+
712
+ .cn-stacked-bar--compact .cn-stacked-bar__dot {
713
+ border-radius: 50%;
714
+ }
715
+
716
+ .cn-stacked-bar--compact .cn-stacked-bar__count {
717
+ font-size: 1em;
718
+ line-height: inherit;
719
+ }
654
720
  </style>
@@ -162,6 +162,26 @@
162
162
  </p>
163
163
  </div>
164
164
 
165
+ <!-- A days input set as a date (`dateField`): today plus the days,
166
+ and a chosen date sets the days. -->
167
+ <div v-if="field.input.dateField"
168
+ :key="`${field.key}-date`"
169
+ class="cn-transition-input-fields__field cn-transition-input-fields__wide"
170
+ :data-testid="field.input.dateField.testid || `cn-transition-input-date-${field.key}`">
171
+ <label class="cn-transition-input-fields__label"
172
+ :for="`${inputId(field)}-date`">{{ dateFieldLabel(field) }}</label>
173
+ <NcTextField :id="`${inputId(field)}-date`"
174
+ :modelValue="field.dateValue"
175
+ type="date"
176
+ :label="dateFieldLabel(field)"
177
+ :labelOutside="true"
178
+ :disabled="disabled"
179
+ @update:modelValue="setValue(field.key, daysUntilIso($event))" />
180
+ <span v-if="field.input.dateField.note" class="cn-transition-input-fields__muted">
181
+ {{ field.input.dateField.note }}
182
+ </span>
183
+ </div>
184
+
165
185
  <!-- What the input means once filled in, such as the day a paused
166
186
  term comes back. Shown only once it can be worked out. -->
167
187
  <div v-if="field.previewText"
@@ -209,7 +229,7 @@ import { NcCheckboxRadioSwitch, NcSelect, NcTextArea, NcTextField } from '@nextc
209
229
  import CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'
210
230
  import CnTemplateSelect from '../CnTemplateSelect/CnTemplateSelect.vue'
211
231
  import { readPath } from '../../utils/readPath.js'
212
- import { applyTemplateText, isInputFilled, resolveInputFields } from '../../utils/transitionInputs.js'
232
+ import { applyTemplateText, daysUntilIso, isInputFilled, resolveInputFields } from '../../utils/transitionInputs.js'
213
233
  import { CnIcon } from '../CnIcon/index.js'
214
234
 
215
235
  let uid = 0
@@ -505,6 +525,28 @@ export default {
505
525
  return this.markOptional && !field.required ? `${field.label} (${t('nextcloud-vue', 'optional')})` : field.label
506
526
  },
507
527
 
528
+ /**
529
+ * The label of a days input's date field (`dateField`), with the
530
+ * optional marker when asked for: the date is never required.
531
+ *
532
+ * @param {object} field The descriptor.
533
+ * @return {string}
534
+ */
535
+ dateFieldLabel(field) {
536
+ const label = String(field.input.dateField.label || '')
537
+ return this.markOptional ? `${label} (${t('nextcloud-vue', 'optional')})` : label
538
+ },
539
+
540
+ /**
541
+ * The days a chosen date stands for.
542
+ *
543
+ * @param {string} iso The date input's value.
544
+ * @return {string} The days, '' without a date.
545
+ */
546
+ daysUntilIso(iso) {
547
+ return daysUntilIso(iso)
548
+ },
549
+
508
550
  /**
509
551
  * Label of a sub-field, with the required marker.
510
552
  *
@@ -702,12 +702,15 @@ function tasksSource() {
702
702
  *
703
703
  * @param {string} key The column key.
704
704
  * @param {string} order `asc` or `desc`.
705
- * @return {{sort: string}|null} The parameter, or null for a column the inbox cannot sort by.
705
+ * Open tasks come before finished ones (`openFirst`), each in that order.
706
+ *
707
+ * @return {{sort: string, openFirst: boolean}|null} The parameters, or null for a column the inbox cannot sort by.
706
708
  * @spec openspec/changes/tasks-source-sorts-on-server/specs/index-sources/spec.md#requirement-a-named-source-can-sort-on-the-server
709
+ * @spec openspec/changes/tasks-source-open-first/specs/index-sources/spec.md#requirement-a-sorted-task-list-puts-open-tasks-first
707
710
  */
708
711
  sortParam: (key, order) => {
709
712
  const field = TASK_SORT_FIELDS[key]
710
- return field ? { sort: (order === 'desc' ? '-' : '') + field } : null
713
+ return field ? { sort: (order === 'desc' ? '-' : '') + field, openFirst: true } : null
711
714
  },
712
715
 
713
716
  // The scope tabs. Each tab's filter is merged over the page's
@@ -41,7 +41,8 @@ export const FLOW_TASKS_URL = '/apps/openregister/api/flow-tasks'
41
41
  // creating app named it, so it is the one filter that lets a Tasks page say
42
42
  // "the reminders" without the engine learning what a reminder is. It widens
43
43
  // nothing: it narrows within the inbox the session already decides.
44
- const ALLOWED_PARAMS = ['scope', 'state', 'isTerminal', 'priority', 'kind', 'overdue', 'dueAfter', 'dueBefore', 'objectUuid', 'sort', 'limit', 'offset']
44
+ // `openFirst` asks open tasks before finished ones, a sort the engine applies.
45
+ const ALLOWED_PARAMS = ['scope', 'state', 'isTerminal', 'priority', 'kind', 'overdue', 'dueAfter', 'dueBefore', 'objectUuid', 'sort', 'openFirst', 'limit', 'offset']
45
46
 
46
47
  /**
47
48
  * The flat body `POST /api/flow-tasks` takes for a task on a record.
@@ -178,7 +179,7 @@ export const useTaskInboxStore = defineStore('cnTaskInbox', {
178
179
  if (value === undefined || value === null || value === '') {
179
180
  continue
180
181
  }
181
- params[key] = (key === 'overdue' || key === 'isTerminal') ? String(value) : value
182
+ params[key] = (key === 'overdue' || key === 'isTerminal' || key === 'openFirst') ? String(value) : value
182
183
  }
183
184
  const response = await axios.get(generateUrl(FLOW_TASKS_URL), { params })
184
185
  const results = response.data?.results || []
@@ -358,7 +359,7 @@ export const useTaskInboxStore = defineStore('cnTaskInbox', {
358
359
  }
359
360
  // The endpoint reads `overdue` as a boolean-ish string; a quick
360
361
  // filter authors it as `true` for legibility.
361
- params[key] = (key === 'overdue' || key === 'isTerminal') ? String(value) : value
362
+ params[key] = (key === 'overdue' || key === 'isTerminal' || key === 'openFirst') ? String(value) : value
362
363
  }
363
364
 
364
365
  this.loading = true
@@ -238,8 +238,11 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
238
238
  padding-inline-end: 24px;
239
239
  }
240
240
 
241
+ /* The body starts 22px under the header, as most dialog boards draw it
242
+ (header padding-bottom 4px + form padding-top 18px; DqBerichtVersturen,
243
+ DqOverdragen, DqNieuweZaak, DqBesluit): the 18px gap plus 4px. */
241
244
  .cn-dialog-board .dialog__content > .cn-dialog-header {
242
- margin-bottom: -2px;
245
+ margin-bottom: 4px;
243
246
  }
244
247
 
245
248
  /* Footer: 16px 24px, hairline on top, right aligned, 40px buttons, 10px gap. */
@@ -344,6 +347,15 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
344
347
  margin-block-start: 26px;
345
348
  }
346
349
 
350
+ /* A select in a form dialog sits under its label and over its help line
351
+ as a text field does (DqNieuweZaak "Zaaktype", "Intakekanaal"): the
352
+ form's label stands outside the select, so NcSelect's own 6px/4px margins
353
+ only pushed the control 6px below the text fields beside it and its help
354
+ line 4px further down. */
355
+ .cn-dialog-board .dialog__content .cn-form-dialog__select-wrapper > .v-select.select:not(:has(.input-field__label)) {
356
+ margin-block: 0;
357
+ }
358
+
347
359
  /* A multiple select is the board's 40px field too (DqFilterDelen "Delen
348
360
  met"). The edge goes on the toggle, which holds the chips and the search,
349
361
  so the chosen people and groups sit inside the field; the search inside it
@@ -721,6 +721,13 @@
721
721
  padding-top: calc(14px - var(--cn-board-first-line-leading));
722
722
  }
723
723
 
724
+ /* A table (an object list) starts 12px below the title's box: its head has
725
+ a fill and no leading, so the glyph-to-glyph gap above left it 2px short
726
+ (DqZaaktypeStatussen "Statussen", "Resultaten"). */
727
+ .cn-look-board .cn-detail-page .cn-widget-wrapper__content:has(> .cn-object-list-widget):not(.cn-detail-page__side *) {
728
+ padding-top: 12px;
729
+ }
730
+
724
731
  /* Running text (DqZaak "Het verzoek") sits 12px below the title's box and
725
732
  keeps its own half-leading, at 1.6 as drawn.
726
733
  @spec openspec/changes/board-card-title-gap-variants/specs/board-card-title-gap-variants/spec.md#requirement-a-board-card-starts-its-body-as-drawn-for-its-kind-of-body */
@@ -1527,12 +1534,12 @@
1527
1534
  }
1528
1535
 
1529
1536
  /* ---- A card's Add ------------------------------------------------------ */
1530
- /* The board draws a list card's Add as the neutral secondary at the 40px
1531
- control height, right-aligned in the title row (DqZaakGerelateerd
1532
- "Deelzaak aanmaken", DqZaakTaken "Toevoegen"). CnDetailWidgetHost renders
1537
+ /* The board draws a list card's Add as the neutral secondary right-aligned
1538
+ in the title row, at the title row's 34px (DqZaaktypeStatussen "Status
1539
+ toevoegen", DqZaaktype "Taak toevoegen", DqCatalogus "Objecttype
1540
+ toevoegen": 13 of the 15 boards that draw one). CnDetailWidgetHost renders
1533
1541
  that button in the board look, so the list's footer Add bar, which ADR-062
1534
- draws in the Nextcloud look, is hidden in the card. The size outranks a
1535
- card title row's 34px buttons.
1542
+ draws in the Nextcloud look, is hidden in the card.
1536
1543
  @spec openspec/changes/board-object-list-add-button/specs/board-object-list-add-button/spec.md#requirement-the-board-look-draws-a-cards-add-as-a-title-row-button */
1537
1544
  .cn-look-board .cn-detail-page__catalog-card:not(.cn-look-nextcloud *) .cn-object-list-widget__add {
1538
1545
  display: none;
@@ -1544,10 +1551,6 @@
1544
1551
  display: none;
1545
1552
  }
1546
1553
 
1547
- .cn-look-board .cn-widget-wrapper__actions .button-vue.cn-detail-page__catalog-add:has(> .button-vue__wrapper > .button-vue__text:not(:empty)):where(:not(.cn-look-nextcloud *)) {
1548
- --button-size: var(--cn-board-control-height);
1549
- }
1550
-
1551
1554
  /* A map in a board card has the board's control corners (DqZaak, DqZaakMeer
1552
1555
  "Locaties"); overflow keeps the tiles inside them. */
1553
1556
  .cn-look-board .cn-map-widget:not(.cn-map-widget--fullscreen) {
@@ -52,13 +52,11 @@
52
52
  grid-row: 2;
53
53
  }
54
54
 
55
- /* A list page's breadcrumb and note read 15px; the case page's row stays 14px. */
56
- .cn-look-board .cn-index-page .cn-page-header .cn-header-trail.cn-detail-page__header-row2 {
57
- font-size: 15px;
58
- }
59
-
60
- .cn-look-board .cn-index-page .cn-header-trail.cn-detail-page__header-row2 .cn-detail-page__breadcrumbs .vue-crumb .button-vue.button-vue {
61
- font-size: 15px;
55
+ /* The note after the breadcrumb ("38 contracten bij 21 leveranciers") is 14px
56
+ muted on every Dq list board (37 of 37).
57
+ @spec openspec/changes/board-index-header-meta-14/specs/index-page/spec.md */
58
+ .cn-look-board .cn-index-page .cn-page-header .cn-header-trail .cn-detail-page__header-meta {
59
+ font-size: 14px;
62
60
  }
63
61
 
64
62
  .cn-look-board .cn-index-page .cn-page-header .cn-header-trail ~ .cn-page-header__description {
@@ -982,6 +980,17 @@
982
980
  text-decoration: none;
983
981
  }
984
982
 
983
+ /* `rowTitle: "strong"` (DqZaaktypeStatussen "Statussen", "Resultaten"): the
984
+ name column of a card's list, the cell's 14px at weight 600 in the main
985
+ ink, no underline; the row stays a link.
986
+ @spec openspec/changes/table-strong-title/specs/table-strong-title/spec.md#requirement-a-tables-title-column-can-read-as-a-strong-name */
987
+ .cn-look-board .cn-table-container--board.cn-table-container--title-strong .cn-data-table td.cn-table-col--title {
988
+ color: var(--color-main-text);
989
+ font-size: 14px;
990
+ font-weight: 600;
991
+ text-decoration: none;
992
+ }
993
+
985
994
  /* An avatar in the title column (PtAccounts): a 32px chip circle, 10px before
986
995
  the name, the e-mail under the name beside it. The name keeps the title's
987
996
  weight and size, and its underline: an inline-flex box is atomic, so the
@@ -2101,3 +2110,77 @@
2101
2110
  .cn-look-board .cn-index-page--with-side-panel:not(.cn-look-nextcloud *) > .cn-index-page__quick-filter-row {
2102
2111
  margin-bottom: calc(var(--cn-board-section-gap) + 12px);
2103
2112
  }
2113
+
2114
+ /* ---- The side panel's filters as checkbox lists (DqZakenSorteren) ------- */
2115
+ /* "Filters" with "Alles wissen" beside it; per field its 14px semibold label
2116
+ over rows of a checkbox, the value and its count in 13px muted at the end,
2117
+ 32px each.
2118
+ @spec openspec/changes/board-sidebar-filters-as-checkbox-lists/specs/index-sidebar/spec.md#requirement-the-board-look-draws-a-short-filter-as-a-checkbox-list-with-counts */
2119
+ .cn-look-board .cn-index-sidebar__filters-head {
2120
+ display: flex;
2121
+ align-items: center;
2122
+ justify-content: space-between;
2123
+ gap: 8px;
2124
+ margin-bottom: 14px;
2125
+ }
2126
+
2127
+ .cn-look-board .cn-index-sidebar__filters-head h3 {
2128
+ margin: 0;
2129
+ font-size: 15px;
2130
+ font-weight: 700;
2131
+ }
2132
+
2133
+ .cn-look-board .cn-index-sidebar__filters-clear {
2134
+ min-height: 0;
2135
+ margin: 0;
2136
+ padding: 0;
2137
+ border: 0;
2138
+ background: transparent;
2139
+ color: var(--color-primary-element);
2140
+ font-size: 14px;
2141
+ font-weight: 600;
2142
+ text-decoration: underline;
2143
+ cursor: pointer;
2144
+ }
2145
+
2146
+ .cn-look-board .cn-index-sidebar__filter-group:has(> .cn-index-sidebar__checklist) .cn-index-sidebar__filter-label {
2147
+ font-size: 14px;
2148
+ font-weight: 600;
2149
+ }
2150
+
2151
+ .cn-look-board .cn-index-sidebar__checklist {
2152
+ display: flex;
2153
+ flex-direction: column;
2154
+ gap: 4px;
2155
+ margin: 0;
2156
+ padding: 0;
2157
+ border: 0;
2158
+ }
2159
+
2160
+ .cn-look-board .cn-index-sidebar__checklist-row {
2161
+ display: flex;
2162
+ align-items: center;
2163
+ gap: 10px;
2164
+ min-height: 32px;
2165
+ font-size: 14px;
2166
+ cursor: pointer;
2167
+ }
2168
+
2169
+ .cn-look-board .cn-index-sidebar__checklist-row > input[type='checkbox'] {
2170
+ width: 18px;
2171
+ min-width: 18px;
2172
+ height: 18px;
2173
+ min-height: 18px;
2174
+ margin: 0;
2175
+ accent-color: var(--color-primary-element);
2176
+ }
2177
+
2178
+ .cn-look-board .cn-index-sidebar__checklist-label {
2179
+ flex: 1;
2180
+ min-width: 0;
2181
+ }
2182
+
2183
+ .cn-look-board .cn-index-sidebar__checklist-count {
2184
+ color: var(--color-text-maxcontrast);
2185
+ font-size: 13px;
2186
+ }