@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
@@ -8517,7 +8517,9 @@ export default {
8517
8517
  return [
8518
8518
  ...this.searchTermChips,
8519
8519
  ...this.quickFilterActiveChips,
8520
- ...this.filterChipsOf(this.effectiveActiveFilters, (key, value) => this.isFixedFilter(key, value) || ownToView(key, value)),
8520
+ // The folder pane's own filter is not a chip: the selected folder
8521
+ // shows in the pane and as the heading above the list (DqAfdelingen).
8522
+ ...this.filterChipsOf(this.effectiveActiveFilters, (key, value) => this.isFixedFilter(key, value) || ownToView(key, value) || this.isFolderFilter(key)),
8521
8523
  ]
8522
8524
  },
8523
8525
 
@@ -9703,6 +9705,18 @@ export default {
9703
9705
  * @return {boolean}
9704
9706
  * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-the-query-that-chose-a-preset-is-not-a-removable-filter
9705
9707
  */
9708
+ /**
9709
+ * Whether a filter key is the folder pane's (`folderSidebar.filterField`).
9710
+ *
9711
+ * @param {string} key The filter key.
9712
+ * @return {boolean}
9713
+ * @spec openspec/changes/board-folder-scope-outside-card/specs/index-page/spec.md#requirement-the-selected-folder-heads-the-list-from-outside-its-card
9714
+ */
9715
+ isFolderFilter(key) {
9716
+ const field = this.folderSidebar && this.folderSidebar.filterField
9717
+ return typeof field === 'string' && field !== '' && key === field
9718
+ },
9719
+
9706
9720
  isFixedFilter(key, value) {
9707
9721
  const fixed = this.fixedFilters
9708
9722
  if (!fixed || !Object.hasOwn(fixed, key)) {
@@ -9979,7 +9993,7 @@ export default {
9979
9993
  ]
9980
9994
  let counts
9981
9995
  try {
9982
- counts = await fetchFilterCounts({ register: this.register, schema, entries, baseFilter: base, ctx })
9996
+ counts = await fetchFilterCounts({ register: this.register, schema, entries, baseFilter: base, ctx, via: 'list' })
9983
9997
  } catch {
9984
9998
  counts = {}
9985
9999
  }
@@ -10036,6 +10050,8 @@ export default {
10036
10050
  })),
10037
10051
  baseFilter: resolveFilterMap(this.filter, params, ctx),
10038
10052
  ctx,
10053
+ // The chips count what the list shows: the list's own request.
10054
+ via: 'list',
10039
10055
  })
10040
10056
  if (requestKey !== this.countRequestKey) {
10041
10057
  return
@@ -96,6 +96,7 @@
96
96
  :key="row.note.id"
97
97
  class="cn-notes-card__note"
98
98
  :class="{ 'cn-notes-card__note--reply': row.isReply }">
99
+ <span class="cn-notes-card__initials" aria-hidden="true">{{ initialsOf(getNoteAuthorName(row.note)) }}</span>
99
100
  <div class="cn-notes-card__note-header">
100
101
  <CnUserActionMenu
101
102
  v-if="!isCurrentUser(row.note)"
@@ -175,6 +176,7 @@ import CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'
175
176
  import CnVisibilityChip from '../CnVisibilityChip/CnVisibilityChip.vue'
176
177
  import { useFileComments } from '../../composables/useFileComments.js'
177
178
  import { buildHeaders, prefixUrl } from '../../utils/index.js'
179
+ import { initialsOf } from '../../utils/initials.js'
178
180
  import { leafSourceUrl } from '../../utils/leafSourceUrl.js'
179
181
  import { extractMentionedGroupIds, extractMentionedIds } from '../../utils/mentions.js'
180
182
  import { quoteFor, supportsReplies, threadNotes, threadRootId } from '../../utils/noteThreads.js'
@@ -635,6 +637,8 @@ export default {
635
637
  return note.actorId || note.author || ''
636
638
  },
637
639
 
640
+ initialsOf,
641
+
638
642
  getNoteAuthorName(note) {
639
643
  return note.actorDisplayName || note.author || 'Unknown'
640
644
  },
@@ -870,6 +874,10 @@ export default {
870
874
  border-bottom: none;
871
875
  }
872
876
 
877
+ .cn-notes-card__initials {
878
+ display: none;
879
+ }
880
+
873
881
  .cn-notes-card__note-header {
874
882
  display: flex;
875
883
  justify-content: space-between;
@@ -4,7 +4,7 @@
4
4
  -->
5
5
 
6
6
  <template>
7
- <div class="cn-object-parties" data-testid="cn-object-parties">
7
+ <div class="cn-object-parties" :class="{ 'cn-object-parties--board': isBoard }" data-testid="cn-object-parties">
8
8
  <div v-if="canAdd" class="cn-object-parties__manage">
9
9
  <NcButton data-testid="cn-object-parties-add" @click="adding = true">
10
10
  <template #icon>
@@ -112,51 +112,89 @@
112
112
  class="cn-object-parties__party"
113
113
  :data-party="party.partyUuid || party.contactUid"
114
114
  data-testid="cn-object-parties-party">
115
- <span class="cn-object-parties__name">{{ nameOf(party) }}</span>
116
- <span
117
- v-if="primary && party.partyUuid === primary"
118
- class="cn-status-badge cn-status-badge--primary"
119
- data-testid="cn-object-parties-primary">{{ label('primary') }}</span>
120
- <span v-if="kindOf(party)" class="cn-object-parties__muted">{{ kindOf(party) }}</span>
121
- <span
122
- v-if="notAccepted(party)"
123
- class="cn-status-badge cn-status-badge--warning"
124
- data-testid="cn-object-parties-not-accepted">{{ label('notAccepted') }}</span>
125
- <span
126
- v-for="(line, index) in extraLinesOf(party)"
127
- :key="`line-${index}`"
128
- class="cn-object-parties__muted"
129
- data-testid="cn-object-parties-extra-line">{{ line }}</span>
130
- <span v-if="party.email" class="cn-object-parties__muted">{{ party.email }}</span>
131
- <span
132
- v-if="representedBy(party)"
133
- class="cn-object-parties__muted"
134
- data-testid="cn-object-parties-represented">{{ label('representedBy') }}: {{ representedBy(party) }}</span>
135
- <span
136
- v-if="correspondenceOf(party)"
137
- class="cn-object-parties__muted"
138
- :data-party-documents="party.partyUuid || party.contactUid"
139
- data-testid="cn-object-parties-documents">{{ correspondenceOf(party) }}</span>
140
- <span
141
- v-for="indicator in indicatorsFor(party)"
142
- :key="`${party.partyUuid}-${indicator.key}`"
143
- class="cn-status-badge cn-status-badge--default"
144
- :class="`cn-object-parties__indicator--${indicator.severity}`"
145
- :data-effect="indicator.effect"
146
- :title="indicator.verdict"
147
- data-testid="cn-object-parties-indicator">{{ indicator.label }} · {{ indicator.verdict }}</span>
148
- <NcButton
149
- v-if="canRemove && party.partyUuid"
150
- class="cn-object-parties__remove"
151
- variant="tertiary"
152
- :aria-label="removeLabel(party, group)"
153
- :title="removeLabel(party, group)"
154
- data-testid="cn-object-parties-remove"
155
- @click="removing = { party, group }">
156
- <template #icon>
157
- <TrashCanOutline :size="20" />
158
- </template>
159
- </NcButton>
115
+ <!-- The board look draws a party as one ruled row (DqZaakBetrokkenen
116
+ "Rollen"): an initials tile, the name over one meta line that
117
+ names the role, the pills at the end. -->
118
+ <template v-if="isBoard">
119
+ <span class="cn-object-parties__tile" aria-hidden="true">{{ initialsOf(nameOf(party)) }}</span>
120
+ <span class="cn-object-parties__text">
121
+ <span class="cn-object-parties__name">{{ nameOf(party) }}</span>
122
+ <span class="cn-object-parties__meta" data-testid="cn-object-parties-meta">{{ boardMetaOf(party, group) }}</span>
123
+ </span>
124
+ <span class="cn-object-parties__end">
125
+ <span
126
+ v-if="notAccepted(party)"
127
+ class="cn-status-badge cn-status-badge--warning"
128
+ data-testid="cn-object-parties-not-accepted">{{ label('notAccepted') }}</span>
129
+ <span
130
+ v-for="indicator in indicatorsFor(party)"
131
+ :key="`${party.partyUuid}-${indicator.key}`"
132
+ class="cn-status-badge cn-status-badge--default"
133
+ :class="`cn-object-parties__indicator--${indicator.severity}`"
134
+ :data-effect="indicator.effect"
135
+ :title="indicator.verdict"
136
+ data-testid="cn-object-parties-indicator">{{ indicator.label }}</span>
137
+ <NcButton
138
+ v-if="canRemove && party.partyUuid"
139
+ class="cn-object-parties__remove"
140
+ variant="tertiary"
141
+ :aria-label="removeLabel(party, group)"
142
+ :title="removeLabel(party, group)"
143
+ data-testid="cn-object-parties-remove"
144
+ @click="removing = { party, group }">
145
+ <template #icon>
146
+ <TrashCanOutline :size="20" />
147
+ </template>
148
+ </NcButton>
149
+ </span>
150
+ </template>
151
+ <template v-else>
152
+ <span class="cn-object-parties__name">{{ nameOf(party) }}</span>
153
+ <span
154
+ v-if="primary && party.partyUuid === primary"
155
+ class="cn-status-badge cn-status-badge--primary"
156
+ data-testid="cn-object-parties-primary">{{ label('primary') }}</span>
157
+ <span v-if="kindOf(party)" class="cn-object-parties__muted">{{ kindOf(party) }}</span>
158
+ <span
159
+ v-if="notAccepted(party)"
160
+ class="cn-status-badge cn-status-badge--warning"
161
+ data-testid="cn-object-parties-not-accepted">{{ label('notAccepted') }}</span>
162
+ <span
163
+ v-for="(line, index) in extraLinesOf(party)"
164
+ :key="`line-${index}`"
165
+ class="cn-object-parties__muted"
166
+ data-testid="cn-object-parties-extra-line">{{ line }}</span>
167
+ <span v-if="party.email" class="cn-object-parties__muted">{{ party.email }}</span>
168
+ <span
169
+ v-if="representedBy(party)"
170
+ class="cn-object-parties__muted"
171
+ data-testid="cn-object-parties-represented">{{ label('representedBy') }}: {{ representedBy(party) }}</span>
172
+ <span
173
+ v-if="correspondenceOf(party)"
174
+ class="cn-object-parties__muted"
175
+ :data-party-documents="party.partyUuid || party.contactUid"
176
+ data-testid="cn-object-parties-documents">{{ correspondenceOf(party) }}</span>
177
+ <span
178
+ v-for="indicator in indicatorsFor(party)"
179
+ :key="`${party.partyUuid}-${indicator.key}`"
180
+ class="cn-status-badge cn-status-badge--default"
181
+ :class="`cn-object-parties__indicator--${indicator.severity}`"
182
+ :data-effect="indicator.effect"
183
+ :title="indicator.verdict"
184
+ data-testid="cn-object-parties-indicator">{{ indicator.label }} · {{ indicator.verdict }}</span>
185
+ <NcButton
186
+ v-if="canRemove && party.partyUuid"
187
+ class="cn-object-parties__remove"
188
+ variant="tertiary"
189
+ :aria-label="removeLabel(party, group)"
190
+ :title="removeLabel(party, group)"
191
+ data-testid="cn-object-parties-remove"
192
+ @click="removing = { party, group }">
193
+ <template #icon>
194
+ <TrashCanOutline :size="20" />
195
+ </template>
196
+ </NcButton>
197
+ </template>
160
198
  </li>
161
199
  </ul>
162
200
  </section>
@@ -200,6 +238,7 @@ import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
200
238
  import CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'
201
239
  import CnPartyAddDialog from '../../dialogs/CnPartyAddDialog.vue'
202
240
  import { interpolateUrlTokens } from '../../composables/useEndpointSource.js'
241
+ import { normalizeLook } from '../../composables/useLook.js'
203
242
  import innerHeading from '../../mixins/innerHeading.js'
204
243
  import { conditionalLine } from '../../utils/conditionalLine.js'
205
244
  import { initialsOf } from '../../utils/initials.js'
@@ -287,6 +326,7 @@ export default {
287
326
 
288
327
  inject: {
289
328
  cnTranslate: { default: () => (key) => key },
329
+ cnLook: { default: 'nextcloud' },
290
330
  },
291
331
 
292
332
  props: {
@@ -347,6 +387,11 @@ export default {
347
387
  },
348
388
 
349
389
  computed: {
390
+ /** @return {boolean} The board look draws one ruled row per party. */
391
+ isBoard() {
392
+ return normalizeLook(this.cnLook) === 'board'
393
+ },
394
+
350
395
  /** @return {Array<object>} The role groups, primary first. */
351
396
  groups() {
352
397
  return rolesInOrder(this.listing, this.translatedMap(this.content?.roleLabels))
@@ -916,6 +961,29 @@ export default {
916
961
  * @param {object} party The listing row.
917
962
  * @return {Array<object>}
918
963
  */
964
+ /**
965
+ * The board row's meta line: the primary mark, the role, the kind,
966
+ * whom the party represents, its extra lines, e-mail and documents,
967
+ * separated by middots.
968
+ *
969
+ * @param {object} party The party.
970
+ * @param {object} group Its role group.
971
+ * @return {string}
972
+ */
973
+ boardMetaOf(party, group) {
974
+ const parts = []
975
+ if (this.primary && party.partyUuid === this.primary) {
976
+ parts.push(this.label('primary'))
977
+ }
978
+ parts.push(group.label, this.kindOf(party))
979
+ const represented = this.representedBy(party)
980
+ if (represented) {
981
+ parts.push(`${this.label('representedBy')}: ${represented}`)
982
+ }
983
+ parts.push(...this.extraLinesOf(party), party.email, this.correspondenceOf(party))
984
+ return parts.filter((part) => typeof part === 'string' && part !== '').join(' · ')
985
+ },
986
+
919
987
  indicatorsFor(party) {
920
988
  const record = this.partyRecords[party.partyUuid]
921
989
  if (!record) {
@@ -99,7 +99,8 @@ export default {
99
99
 
100
100
  /**
101
101
  * The crumbs for CnBreadcrumbs: each declared crumb as a link, then the
102
- * current page. Empty without a declared crumb.
102
+ * current page, once (a last crumb that names the page is dropped).
103
+ * Empty without a declared crumb.
103
104
  *
104
105
  * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
105
106
  * @return {Array<object>}
@@ -121,6 +122,10 @@ export default {
121
122
  return entry
122
123
  })
123
124
  if (this.current) {
125
+ // A last crumb naming the page itself is the current one, drawn once.
126
+ if (out.length > 0 && out[out.length - 1].label === this.current) {
127
+ out.pop()
128
+ }
124
129
  out.push({ label: this.current })
125
130
  }
126
131
  return out
@@ -194,6 +194,7 @@
194
194
  <ul
195
195
  v-if="group.items.length > 0"
196
196
  class="cn-record-list__list"
197
+ :class="{ 'cn-record-list__list--avatar-small': avatarSmall }"
197
198
  :aria-labelledby="group.label ? group.headingId : null"
198
199
  :data-testid="isGrouped ? 'cn-record-list-group' : null">
199
200
  <li
@@ -635,7 +636,7 @@ function unwrap(value) {
635
636
  * `rowRoute`, `pageControls`), or `endpointSource` (an app endpoint whose
636
637
  * payload, after `responsePath`, is the array of rows, as on the object table), or `objectRows`
637
638
  * (`{ path, where?, valuePath? }`: rows the page record holds, such as one answer of a case); `avatar` puts a circle with the initials of a name
638
- * template before the title; what differs is the drawing: `presentation` is `rows`, `feed`
639
+ * template before the title (`{ text, size: 32 }` draws the 32px circle 12px from the title, as DqZaakBesluiten); what differs is the drawing: `presentation` is `rows`, `feed`
639
640
  * or `inline`, and each line is a template (`"{identifier} · {caseTypeName}"`,
640
641
  * `"Termijn {deadline|date}"`). `cards` draws a framed card per row in a
641
642
  * grid, with `trailing` as the large figure (board ShBank's accounts).
@@ -831,6 +832,17 @@ export default {
831
832
  },
832
833
 
833
834
  computed: {
835
+ /**
836
+ * Whether the avatar is the 32px circle (`avatar.size: 32`).
837
+ *
838
+ * @return {boolean}
839
+ * @spec openspec/changes/record-list-avatar-size/specs/record-list-avatar/spec.md#requirement-a-record-list-avatar-can-be-the-small-circle
840
+ */
841
+ avatarSmall() {
842
+ const avatar = this.content?.avatar
843
+ return Boolean(avatar && typeof avatar === 'object' && Number(avatar.size) === 32)
844
+ },
845
+
834
846
  /** @return {boolean} Whether a row's pill comes before its title (`pillAt: "start"`). */
835
847
  pillFirst() {
836
848
  return this.content?.pillAt === 'start'
@@ -3068,6 +3080,14 @@ export default {
3068
3080
  white-space: nowrap;
3069
3081
  }
3070
3082
 
3083
+ /* `avatar.size: 32`: the smaller circle, 12px from the title where the row
3084
+ keeps 16px between its other parts (DqZaakBesluiten Adviezen). */
3085
+ .cn-record-list__list--avatar-small .cn-record-list__avatar {
3086
+ width: 32px;
3087
+ height: 32px;
3088
+ margin-inline-end: -4px;
3089
+ }
3090
+
3071
3091
  .cn-record-list__avatar {
3072
3092
  align-items: center;
3073
3093
  background: var(--color-background-hover);
@@ -85,6 +85,7 @@
85
85
  </component>
86
86
  <ul class="cn-retention__rows">
87
87
  <li v-for="hold in holds" :key="hold.id" class="cn-retention__row">
88
+ <span class="cn-retention__mark cn-retention__mark--hold" aria-hidden="true">LH</span>
88
89
  <span class="cn-retention__row-text">
89
90
  <span class="cn-retention__row-title">{{ hold.reason || t('nextcloud-vue', 'Legal hold') }}</span>
90
91
  <span class="cn-retention__muted">{{ t('nextcloud-vue', 'Placed on {date} by {by}', { date: hold.placedDate, by: hold.placedBy }) }}</span>
@@ -92,6 +93,7 @@
92
93
  <CnStatusBadge :label="t('nextcloud-vue', 'Active')" variant="warning" />
93
94
  </li>
94
95
  <li v-if="holds.length === 0" class="cn-retention__row">
96
+ <span class="cn-retention__mark cn-retention__mark--hold" aria-hidden="true">LH</span>
95
97
  <span class="cn-retention__row-text">
96
98
  <span class="cn-retention__row-title">{{ t('nextcloud-vue', 'No legal hold') }}</span>
97
99
  <span class="cn-retention__muted">{{ t('nextcloud-vue', 'If an objection or appeal runs, destruction is suspended') }}</span>
@@ -107,6 +109,7 @@
107
109
  </component>
108
110
  <ul class="cn-retention__rows">
109
111
  <li class="cn-retention__row">
112
+ <span class="cn-retention__mark" aria-hidden="true">ED</span>
110
113
  <span class="cn-retention__row-text">
111
114
  <span class="cn-retention__row-title">{{ transfers ? t('nextcloud-vue', 'Goes to the e-Depot') : t('nextcloud-vue', 'Not applicable') }}</span>
112
115
  <span class="cn-retention__muted">{{ transfers ? t('nextcloud-vue', 'After the retention period the record is transferred') : t('nextcloud-vue', 'Only records appraised to keep go to the e-Depot') }}</span>
@@ -643,17 +646,39 @@ export default {
643
646
  align-items: center;
644
647
  justify-content: space-between;
645
648
  gap: 12px;
646
- padding: 10px 0;
649
+ padding: 12px 0;
647
650
  border-top: 1px solid var(--color-border);
648
651
  }
649
652
 
650
653
  .cn-retention__row-text {
651
654
  display: flex;
655
+ flex: 1 1 auto;
652
656
  flex-direction: column;
653
657
  gap: 2px;
654
658
  min-width: 0;
655
659
  }
656
660
 
661
+ /* A round mark before a hold or transfer row: LH (legal hold) on the error
662
+ tint, ED (e-Depot) on the chip ground (DqZaakArchivering). */
663
+ .cn-retention__mark {
664
+ display: inline-flex;
665
+ flex: none;
666
+ align-items: center;
667
+ justify-content: center;
668
+ width: 32px;
669
+ height: 32px;
670
+ border-radius: 16px;
671
+ background: var(--color-background-dark);
672
+ color: var(--color-text-maxcontrast);
673
+ font-size: 12px;
674
+ font-weight: 700;
675
+ }
676
+
677
+ .cn-retention__mark--hold {
678
+ background: var(--color-error-light, #fcedec);
679
+ color: var(--color-error-text, #a30000);
680
+ }
681
+
657
682
  .cn-retention__row-title {
658
683
  font-weight: 600;
659
684
  }
@@ -243,6 +243,7 @@ export default {
243
243
  }
244
244
 
245
245
  .cn-saved-view-alert__facts {
246
+ padding: 0;
246
247
  display: grid;
247
248
  grid-template-columns: max-content 1fr;
248
249
  gap: 6px 16px;
@@ -280,6 +281,11 @@ export default {
280
281
  font-weight: 600;
281
282
  }
282
283
 
284
+ /* The switch sits flush with the card edge, as the board draws it. */
285
+ .cn-saved-view-alert :deep(.checkbox-radio-switch__content) {
286
+ padding: 0;
287
+ }
288
+
283
289
  .cn-saved-view-alert__switch-text {
284
290
  display: flex;
285
291
  flex-direction: column;