@conduction/nextcloud-vue 2.142.0 → 2.144.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 (167) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +7 -2
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  6. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +27 -0
  7. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js +2 -1
  12. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js.map +1 -1
  13. package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js +9 -0
  14. package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js.map +1 -1
  15. package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js +21 -22
  16. package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js.map +1 -1
  17. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  18. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +29 -1
  19. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  20. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js +28 -13
  21. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  22. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  23. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  24. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +2 -1
  25. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  26. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +1 -1
  27. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -1
  28. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -1
  29. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  30. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  31. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  32. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  33. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  34. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +1 -1
  35. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  36. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  38. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  40. package/dist/esm/components/CnEventsCard/CnEventsCard.vue.js +1 -1
  41. package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js +9 -0
  42. package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js.map +1 -1
  43. package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js +24 -13
  44. package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  46. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +29 -3
  47. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +4 -2
  49. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +17 -4
  51. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  52. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +21 -1
  53. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +18 -2
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  57. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  58. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +3 -0
  59. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  60. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js +19 -14
  61. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  62. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
  63. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +30 -0
  64. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
  65. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +158 -102
  66. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
  67. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +6 -1
  68. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -1
  69. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -1
  70. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  71. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +12 -1
  72. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  73. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +2 -2
  74. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
  76. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +12 -0
  78. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
  79. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +1 -1
  80. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -1
  82. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue.js +1 -1
  83. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +110 -2
  84. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  85. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +146 -80
  86. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  87. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +1 -1
  88. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +4 -0
  89. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -1
  90. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +18 -8
  91. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -1
  92. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
  93. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +45 -0
  94. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
  95. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js +67 -34
  96. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
  97. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
  98. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +3 -22
  99. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
  100. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
  101. package/dist/esm/integrations/builtin/calendar/eventStatus.js +67 -0
  102. package/dist/esm/integrations/builtin/calendar/eventStatus.js.map +1 -0
  103. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
  104. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +39 -0
  105. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  106. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +81 -32
  107. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  108. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
  109. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +5 -6
  110. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  111. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/deck/deckCardParts.js +59 -0
  113. package/dist/esm/integrations/builtin/deck/deckCardParts.js.map +1 -0
  114. package/dist/esm/l10n/en.json.js +6 -0
  115. package/dist/esm/l10n/en.json.js.map +1 -1
  116. package/dist/esm/l10n/nl.json.js +7 -1
  117. package/dist/esm/l10n/nl.json.js.map +1 -1
  118. package/dist/esm/library-style.js +1 -1
  119. package/dist/esm/nextcloud-vue.css +1264 -597
  120. package/dist/esm/utils/fetchFilterCounts.js +5 -2
  121. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  122. package/dist/esm/utils/nextcloudDirectory.js +19 -6
  123. package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
  124. package/dist/nextcloud-vue.cjs.js +1619 -849
  125. package/dist/nextcloud-vue.cjs.js.map +2 -2
  126. package/dist/nextcloud-vue.css +1264 -597
  127. package/l10n/en.json +6 -0
  128. package/l10n/nl.json +7 -1
  129. package/package.json +1 -1
  130. package/src/components/CnActionDialog/CnActionDialog.vue +28 -3
  131. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +28 -1
  132. package/src/components/CnAiCompanion/CnAiChatPanel.vue +12 -0
  133. package/src/components/CnAiCompanion/CnAiInput.vue +32 -1
  134. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +34 -4
  135. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +1 -0
  136. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +8 -0
  137. package/src/components/CnChoiceCards/CnChoiceCards.vue +1 -0
  138. package/src/components/CnConversationThread/CnConversationThread.vue +5 -2
  139. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +1 -0
  140. package/src/components/CnEventsCard/CnEventsCard.vue +27 -5
  141. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +43 -4
  142. package/src/components/CnFilesBrowser/filesBrowser.js +17 -4
  143. package/src/components/CnFilesBrowser/filesBrowserColumns.js +20 -1
  144. package/src/components/CnIndexPage/CnIndexPage.vue +18 -2
  145. package/src/components/CnNotesCard/CnNotesCard.vue +8 -0
  146. package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +114 -46
  147. package/src/components/CnPageHeader/CnHeaderTrail.vue +6 -1
  148. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +21 -1
  149. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +26 -1
  150. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +6 -0
  151. package/src/components/CnTasksWidget/CnTasksWidget.vue +170 -13
  152. package/src/components/CnTimelineWidget/CnTimelineManage.vue +10 -1
  153. package/src/css/dialog.css +25 -1
  154. package/src/css/kpi-card.css +3 -0
  155. package/src/css/look-board-detail.css +348 -0
  156. package/src/css/look-board-index.css +56 -1
  157. package/src/css/look-board.css +98 -1
  158. package/src/integrations/builtin/calendar/CnCalendarCard.vue +76 -11
  159. package/src/integrations/builtin/calendar/CnCalendarTab.vue +3 -22
  160. package/src/integrations/builtin/calendar/__tests__/CnCalendarCardBoardRows.spec.js +52 -0
  161. package/src/integrations/builtin/calendar/eventStatus.js +63 -0
  162. package/src/integrations/builtin/deck/CnDeckCard.vue +66 -0
  163. package/src/integrations/builtin/deck/CnDeckTab.vue +5 -6
  164. package/src/integrations/builtin/deck/__tests__/CnDeckCardBoardRows.spec.js +49 -0
  165. package/src/integrations/builtin/deck/deckCardParts.js +56 -0
  166. package/src/utils/fetchFilterCounts.js +5 -2
  167. package/src/utils/nextcloudDirectory.js +19 -6
@@ -47,17 +47,42 @@
47
47
  <table v-else class="cn-tasks-widget__table" data-testid="cn-tasks-widget-table">
48
48
  <thead>
49
49
  <tr>
50
- <th scope="col">
51
- {{ tr('Task') }}
52
- </th>
53
- <th scope="col">
54
- {{ tr('Handler') }}
55
- </th>
56
- <th scope="col">
57
- {{ tr('Task deadline') }}
58
- </th>
59
- <th scope="col">
60
- {{ tr('Status') }}
50
+ <!-- `content.sortable` / `content.filterable`: a head sorts by its
51
+ column and filters on its values (DqZaakTaken). -->
52
+ <th v-for="column in headColumns"
53
+ :key="column.key"
54
+ scope="col"
55
+ :aria-sort="headSort.key === column.key ? (headSort.dir === 'asc' ? 'ascending' : 'descending') : null"
56
+ :data-testid="`cn-tasks-widget-head-${column.key}`">
57
+ <span v-if="content.sortable === true || content.filterable === true" class="cn-tasks-widget__head">
58
+ <button v-if="content.sortable === true"
59
+ type="button"
60
+ class="cn-tasks-widget__head-sort"
61
+ :data-testid="`cn-tasks-widget-sort-${column.key}`"
62
+ @click="sortBy(column.key)">
63
+ {{ column.label }}
64
+ <ChevronUp v-if="headSort.key === column.key && headSort.dir === 'asc'" :size="14" />
65
+ <ChevronDown v-else :size="14" />
66
+ </button>
67
+ <template v-else>{{ column.label }}</template>
68
+ <NcActions v-if="content.filterable === true"
69
+ class="cn-tasks-widget__head-filter"
70
+ :aria-label="tr('Filter by {column}').replace('{column}', column.label)"
71
+ :data-testid="`cn-tasks-widget-filter-${column.key}`">
72
+ <template #icon>
73
+ <FilterOutline :size="14" />
74
+ </template>
75
+ <NcActionCheckbox v-for="option in headOptions(column.key)"
76
+ :key="option.value"
77
+ :modelValue="(headFilters[column.key] || []).includes(option.value)"
78
+ @update:modelValue="toggleHeadFilter(column.key, option.value)">
79
+ {{ option.label }}
80
+ </NcActionCheckbox>
81
+ </NcActions>
82
+ </span>
83
+ <template v-else>
84
+ {{ column.label }}
85
+ </template>
61
86
  </th>
62
87
  <th scope="col">
63
88
  <span class="hidden-visually">{{ tr('Task actions') }}</span>
@@ -375,10 +400,13 @@ import axios from '@nextcloud/axios'
375
400
  import { showError, showSuccess } from '@nextcloud/dialogs'
376
401
  import { translate as t } from '@nextcloud/l10n'
377
402
  import { generateUrl } from '@nextcloud/router'
378
- import { NcActionButton, NcActions, NcAvatar, NcButton, NcLoadingIcon, NcTextField } from '@nextcloud/vue'
403
+ import { NcActionButton, NcActionCheckbox, NcActions, NcAvatar, NcButton, NcLoadingIcon, NcTextField } from '@nextcloud/vue'
379
404
  import { inject, ref } from 'vue'
380
405
  import CheckCircle from 'vue-material-design-icons/CheckCircle.vue'
406
+ import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
407
+ import ChevronUp from 'vue-material-design-icons/ChevronUp.vue'
381
408
  import CircleOutline from 'vue-material-design-icons/CircleOutline.vue'
409
+ import FilterOutline from 'vue-material-design-icons/FilterOutline.vue'
382
410
  import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
383
411
  import CnTaskFormDialog from '../CnTaskFormDialog/CnTaskFormDialog.vue'
384
412
  import CnTaskWorkCard from './CnTaskWorkCard.vue'
@@ -458,12 +486,16 @@ export default {
458
486
 
459
487
  components: {
460
488
  CheckCircle,
489
+ ChevronDown,
490
+ ChevronUp,
461
491
  CircleOutline,
462
492
  CnStatusBadge,
463
493
  CnTaskFormDialog,
464
494
  CnTaskWorkCard,
495
+ FilterOutline,
465
496
  NcActions,
466
497
  NcActionButton,
498
+ NcActionCheckbox,
467
499
  NcAvatar,
468
500
  NcButton,
469
501
  NcLoadingIcon,
@@ -618,6 +650,10 @@ export default {
618
650
  data() {
619
651
  return {
620
652
  pollTimer: null,
653
+ /** The table's head sort, `{ key, dir }`; no key keeps open tasks first. */
654
+ headSort: { key: '', dir: 'asc' },
655
+ /** The table's head filters, `{ column: values[] }`. */
656
+ headFilters: {},
621
657
  /** Uuids of the tasks a checkbox is completing, until the next read. */
622
658
  completing: [],
623
659
  /** Whether the table's create form is open. */
@@ -777,7 +813,26 @@ export default {
777
813
  * @spec openspec/changes/record-task-table/specs/record-task-table/spec.md#requirement-a-records-tasks-show-as-a-table-with-their-summary-and-new-task-in-the-header
778
814
  */
779
815
  tableRows() {
780
- return [...this.rows.filter((task) => task.isTerminal !== true), ...this.rows.filter((task) => task.isTerminal === true)]
816
+ const kept = this.rows.filter((task) => Object.entries(this.headFilters).every(([key, values]) => values.length === 0 || values.includes(this.headValue(task, key))))
817
+ if (this.headSort.key) {
818
+ const dir = this.headSort.dir === 'desc' ? -1 : 1
819
+ return [...kept].sort((a, b) => {
820
+ const x = this.headSortValue(a, this.headSort.key)
821
+ const y = this.headSortValue(b, this.headSort.key)
822
+ return (x < y ? -1 : (x > y ? 1 : 0)) * dir
823
+ })
824
+ }
825
+ return [...kept.filter((task) => task.isTerminal !== true), ...kept.filter((task) => task.isTerminal === true)]
826
+ },
827
+
828
+ /** @return {Array<{key: string, label: string}>} The table's data columns. */
829
+ headColumns() {
830
+ return [
831
+ { key: 'title', label: this.tr('Task') },
832
+ { key: 'handler', label: this.tr('Handler') },
833
+ { key: 'due', label: this.tr('Task deadline') },
834
+ { key: 'state', label: this.tr('Status') },
835
+ ]
781
836
  },
782
837
 
783
838
  /**
@@ -1158,6 +1213,84 @@ export default {
1158
1213
  * @param {object} task The task row.
1159
1214
  * @return {string} The name.
1160
1215
  */
1216
+ /**
1217
+ * A row's value in a column, as a head filter keeps it.
1218
+ *
1219
+ * @param {object} task The task row.
1220
+ * @param {string} key The column.
1221
+ * @return {string}
1222
+ */
1223
+ headValue(task, key) {
1224
+ if (key === 'title') {
1225
+ return this.titleOf(task)
1226
+ }
1227
+ if (key === 'handler') {
1228
+ return String(task.assignee || '')
1229
+ }
1230
+ if (key === 'due') {
1231
+ return this.content.dueFormat === 'date' ? this.dueDate(task) : this.dueLabel(task)
1232
+ }
1233
+ return String(task.state || '')
1234
+ },
1235
+
1236
+ /**
1237
+ * A row's value in a column, as its head sorts it: the deadline by date.
1238
+ *
1239
+ * @param {object} task The task row.
1240
+ * @param {string} key The column.
1241
+ * @return {string|number}
1242
+ */
1243
+ headSortValue(task, key) {
1244
+ if (key === 'due') {
1245
+ const time = Date.parse(task.dueAt || task.dueDate || '')
1246
+ return Number.isNaN(time) ? Number.POSITIVE_INFINITY : time
1247
+ }
1248
+ if (key === 'handler') {
1249
+ return this.handlerName(task).toLowerCase()
1250
+ }
1251
+ if (key === 'state') {
1252
+ return this.stateLabel(task).toLowerCase()
1253
+ }
1254
+ return this.titleOf(task).toLowerCase()
1255
+ },
1256
+
1257
+ /**
1258
+ * The values a column's head filter offers, one per value the rows hold.
1259
+ *
1260
+ * @param {string} key The column.
1261
+ * @return {Array<{value: string, label: string}>}
1262
+ */
1263
+ headOptions(key) {
1264
+ const seen = new Map()
1265
+ for (const task of this.rows) {
1266
+ const value = this.headValue(task, key)
1267
+ if (value !== '' && !seen.has(value)) {
1268
+ seen.set(value, key === 'handler' ? this.handlerName(task) : (key === 'state' ? this.stateLabel(task) : value))
1269
+ }
1270
+ }
1271
+ return [...seen].map(([value, label]) => ({ value, label }))
1272
+ },
1273
+
1274
+ /**
1275
+ * Sort by a column: ascending first, then descending.
1276
+ *
1277
+ * @param {string} key The column.
1278
+ */
1279
+ sortBy(key) {
1280
+ this.headSort = this.headSort.key === key && this.headSort.dir === 'asc' ? { key, dir: 'desc' } : { key, dir: 'asc' }
1281
+ },
1282
+
1283
+ /**
1284
+ * Keep or drop one value of a column's head filter.
1285
+ *
1286
+ * @param {string} key The column.
1287
+ * @param {string} value The value.
1288
+ */
1289
+ toggleHeadFilter(key, value) {
1290
+ const current = this.headFilters[key] || []
1291
+ this.headFilters = { ...this.headFilters, [key]: current.includes(value) ? current.filter((v) => v !== value) : [...current, value] }
1292
+ },
1293
+
1161
1294
  handlerName(task) {
1162
1295
  return userDisplayName(task.assignee) || String(task.assignee || '')
1163
1296
  },
@@ -1594,6 +1727,30 @@ function recordUuid(configured, objectId) {
1594
1727
  font-size: 14px;
1595
1728
  }
1596
1729
 
1730
+ .cn-tasks-widget__head {
1731
+ display: inline-flex;
1732
+ align-items: center;
1733
+ gap: 4px;
1734
+ }
1735
+
1736
+ .cn-tasks-widget__head-sort {
1737
+ display: inline-flex;
1738
+ align-items: center;
1739
+ gap: 4px;
1740
+ min-height: 0;
1741
+ margin: 0;
1742
+ padding: 0;
1743
+ border: 0;
1744
+ background: transparent;
1745
+ color: inherit;
1746
+ font: inherit;
1747
+ cursor: pointer;
1748
+ }
1749
+
1750
+ .cn-tasks-widget__head-filter {
1751
+ --button-size: 22px;
1752
+ }
1753
+
1597
1754
  .cn-tasks-widget__table th {
1598
1755
  padding: 12px 16px;
1599
1756
  background: color-mix(in srgb, var(--color-background-hover) 40%, var(--color-main-background));
@@ -199,13 +199,18 @@
199
199
  {{ t('nextcloud-vue', 'Goes on the timeline above, internal unless you make it visible') }}
200
200
  </p>
201
201
  </template>
202
+ <label v-if="textBlocks.length" class="cn-timeline-manage__draft-label" :for="blockId">
203
+ {{ t('nextcloud-vue', 'Template') }}
204
+ </label>
202
205
  <NcSelect v-if="textBlocks.length"
203
206
  v-model="chosenBlock"
204
207
  class="cn-timeline-manage__filter"
205
208
  :options="blockOptions"
206
209
  :clearable="true"
207
210
  label="label"
208
- :inputLabel="t('nextcloud-vue', 'Template')"
211
+ :inputId="blockId"
212
+ :labelOutside="true"
213
+ :ariaLabelCombobox="t('nextcloud-vue', 'Template')"
209
214
  :placeholder="t('nextcloud-vue', 'Write your own')"
210
215
  data-testid="cn-timeline-block"
211
216
  @update:modelValue="pickBlock" />
@@ -437,6 +442,10 @@ export default {
437
442
  return `cn-timeline-draft-${this.uid}`
438
443
  },
439
444
 
445
+ blockId() {
446
+ return `cn-timeline-block-${this.uid}`
447
+ },
448
+
440
449
  /**
441
450
  * The kind options, the plain note first. A note is an entry with NO
442
451
  * kind, so "Note" filters on its absence.
@@ -168,13 +168,22 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
168
168
  goes to those tokens too (DqOverdragen, DqDocumentGenereren). */
169
169
  .cn-dialog-header__subtitle {
170
170
  --nldesign-component-paragraph-font-size: 14px;
171
+ --nldesign-component-paragraph-line-height: 1.5;
171
172
  --nldesign-component-paragraph-color: var(--color-text-maxcontrast);
172
173
  --nldesign-color-on-surface: var(--color-text-maxcontrast);
173
174
  margin: 4px 0 0;
174
175
  font-size: 14px;
176
+ line-height: 1.5;
175
177
  color: var(--color-text-maxcontrast);
176
178
  }
177
179
 
180
+ /* The saved-view edit and share forms start 22px under the subtitle, as
181
+ the Dq dialog boards draw a first field: the body's 16px gap and 6px. */
182
+ .cn-dialog-board .dialog__content .cn-edit-view,
183
+ .cn-dialog-board .dialog__content .cn-share-view {
184
+ padding-top: 6px;
185
+ }
186
+
178
187
  /* The close button is a 36px circle. NcButton sizes an icon-only button by
179
188
  --button-size with `!important`, so the size goes through that; the radius
180
189
  also goes to the nldesign theme's button radius token. */
@@ -207,9 +216,15 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
207
216
  /* A choice card with a checkbox (CnAssignDialog's toggles) is as tight as
208
217
  the board draws it (DqOverdragen, 76px for a label and a two-line note):
209
218
  10px 14px padding, the checkbox row at the 24px minimum target instead of
210
- NcCheckboxRadioSwitch's 34px, the note at 1.2 line height. */
219
+ NcCheckboxRadioSwitch's 34px, the note at 1.2 line height; the card's
220
+ corners 8px and the label on an 18px line, as the board draws them. */
211
221
  .cn-dialog-board .cn-assign-dialog__toggle {
212
222
  padding: 10px 14px;
223
+ border-radius: 8px;
224
+ }
225
+
226
+ .cn-dialog-board .cn-assign-dialog__toggle .checkbox-content__text {
227
+ line-height: 18px;
213
228
  }
214
229
 
215
230
  .cn-dialog-board .cn-assign-dialog__toggle .checkbox-radio-switch-checkbox .checkbox-content,
@@ -539,6 +554,15 @@ body:has(.cn-look-board):not(:has(.cn-look-nextcloud)) .modal-mask {
539
554
  }
540
555
  }
541
556
 
557
+ /* A choice legend in a board dialog is 14px/600 on the normal line height, as
558
+ the dialog boards draw it (DqDocumentGenereren "Sjabloon").
559
+ @spec openspec/changes/board-choice-legend-type/specs/board-choice-legend-type/spec.md#requirement-a-board-dialogs-choice-legend-is-14px-600-on-the-normal-line-height */
560
+ .cn-dialog-board .cn-choice-cards__legend {
561
+ font-size: 14px;
562
+ font-weight: 600;
563
+ line-height: normal;
564
+ }
565
+
542
566
  /* Above a phone the boards top-align a dialog (DqNieuweZaak, DqUwWachtrij,
543
567
  DqVolgendeStap: the top edge sits about 90px down at 1440 wide) instead of
544
568
  centring it. `--cn-dialog-top` moves it. */
@@ -547,6 +547,9 @@ a.cn-kpi-card--board * {
547
547
  .cn-kpi-card--board.cn-kpi-card--stacked:has(.cn-kpi-card__title--badge-end) {
548
548
  --cn-kpi-stacked-padding: 20px 22px;
549
549
  --cn-kpi-stacked-gap: 8px;
550
+ /* Its label is set at 600, as every board draws this tile (DqDashboard,
551
+ DqMijnTeam, DqPersoonlijkeInstellingen); the plain stacked tile keeps 400. */
552
+ --cn-kpi-stacked-title-weight: 600;
550
553
  }
551
554
 
552
555
  .cn-kpi-card--board.cn-kpi-card--stacked:has(.cn-kpi-card__title--badge-end) .cn-kpi-card__value {