@conduction/nextcloud-vue 2.141.0 → 2.143.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 (140) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +27 -0
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js +2 -1
  8. package/dist/esm/components/CnAiCompanion/CnAiChatPanel.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js +9 -0
  10. package/dist/esm/components/CnAiCompanion/CnAiInput.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js +21 -22
  12. package/dist/esm/components/CnAiCompanion/CnAiInput.vue3.js.map +1 -1
  13. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  14. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +29 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js +28 -13
  17. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  18. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue.js +1 -1
  19. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  20. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +2 -1
  21. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  22. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  23. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +13 -0
  24. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  25. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +3 -3
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  27. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +1 -1
  28. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -1
  30. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  31. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  32. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +21 -0
  34. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +1 -1
  36. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  38. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  42. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +34 -3
  43. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFactsWidget/CnFactsWidget.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 +70 -5
  47. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +5 -3
  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 +36 -2
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -1
  57. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  58. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +14 -0
  59. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  60. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  61. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +3 -1
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
  64. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  65. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +6 -1
  66. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -1
  67. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -1
  68. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -1
  69. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +31 -1
  70. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
  71. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
  72. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  73. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +12 -1
  74. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  75. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +2 -2
  76. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  77. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +1 -1
  78. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -1
  79. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -1
  80. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +1 -1
  81. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +4 -0
  82. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -1
  83. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +18 -8
  84. package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -1
  85. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue.js +1 -1
  86. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js +52 -1
  87. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js.map +1 -1
  88. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js +119 -65
  89. package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js.map +1 -1
  90. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
  91. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +86 -4
  92. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  93. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +67 -5
  94. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  95. package/dist/esm/l10n/en.json.js +10 -1
  96. package/dist/esm/l10n/en.json.js.map +1 -1
  97. package/dist/esm/l10n/nl.json.js +12 -3
  98. package/dist/esm/l10n/nl.json.js.map +1 -1
  99. package/dist/esm/library-style.js +1 -1
  100. package/dist/esm/nextcloud-vue.css +976 -592
  101. package/dist/esm/utils/buildViewTree.js +23 -8
  102. package/dist/esm/utils/buildViewTree.js.map +1 -1
  103. package/dist/nextcloud-vue.cjs.js +1363 -734
  104. package/dist/nextcloud-vue.cjs.js.map +2 -2
  105. package/dist/nextcloud-vue.css +976 -592
  106. package/l10n/en.json +10 -1
  107. package/l10n/nl.json +12 -3
  108. package/package.json +1 -1
  109. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +28 -1
  110. package/src/components/CnAiCompanion/CnAiChatPanel.vue +12 -0
  111. package/src/components/CnAiCompanion/CnAiInput.vue +32 -1
  112. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +34 -4
  113. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +1 -0
  114. package/src/components/CnCellRenderer/CnCellRenderer.vue +15 -1
  115. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +8 -0
  116. package/src/components/CnChoiceCards/CnChoiceCards.vue +1 -0
  117. package/src/components/CnDataTable/CnDataTable.md +1 -0
  118. package/src/components/CnDataTable/CnDataTable.vue +22 -1
  119. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +1 -0
  120. package/src/components/CnFactsWidget/CnFactsWidget.vue +34 -3
  121. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +72 -7
  122. package/src/components/CnFilesBrowser/filesBrowser.js +17 -4
  123. package/src/components/CnFilesBrowser/filesBrowserColumns.js +20 -1
  124. package/src/components/CnIndexPage/CnIndexPage.vue +37 -2
  125. package/src/components/CnIndexPage/useSelfFetchList.js +14 -0
  126. package/src/components/CnObjectSidebar/CnFilesTab.vue +4 -1
  127. package/src/components/CnPageHeader/CnHeaderTrail.vue +6 -1
  128. package/src/components/CnPartyPicker/CnPartyPicker.vue +31 -1
  129. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +21 -1
  130. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +6 -0
  131. package/src/components/CnTimelineWidget/CnTimelineManage.vue +10 -1
  132. package/src/css/dialog.css +18 -0
  133. package/src/css/look-board-detail.css +126 -0
  134. package/src/css/look-board-index.css +95 -1
  135. package/src/css/look-board.css +90 -1
  136. package/src/integrations/builtin/contacts/CnContactsTab.vue +101 -26
  137. package/src/integrations/builtin/contacts/__tests__/CnContactsTabBoardRows.spec.js +67 -0
  138. package/src/integrations/builtin/deck/CnDeckCard.vue +121 -2
  139. package/src/integrations/builtin/deck/__tests__/CnDeckCardHeaderActions.spec.js +68 -0
  140. package/src/utils/buildViewTree.js +23 -8
package/l10n/en.json CHANGED
@@ -2382,7 +2382,16 @@
2382
2382
  "Open profile": "Open profile",
2383
2383
  "Link a contact from your address book.": "Link a contact from your address book.",
2384
2384
  "The value could not be shown.": "The value could not be shown.",
2385
- "No value": "No value"
2385
+ "No value": "No value",
2386
+ "Link an existing card": "Link an existing card",
2387
+ "New card": "New card",
2388
+ "Could not link card.": "Could not link card.",
2389
+ "Could not create card.": "Could not create card.",
2390
+ "This card is already linked.": "This card is already linked.",
2391
+ "From {date}": "From {date}",
2392
+ "Until {date}": "Until {date}",
2393
+ "More actions for {name}": "More actions for {name}",
2394
+ "Unlink contact": "Unlink contact"
2386
2395
  },
2387
2396
  "plurals": {
2388
2397
  "{count} member": [
package/l10n/nl.json CHANGED
@@ -1910,7 +1910,7 @@
1910
1910
  "Now": "Nu",
1911
1911
  "Tell me above a number": "Melden vanaf een aantal",
1912
1912
  "Threshold": "Grens",
1913
- "A message to you when there are {threshold} or more": "Bericht aan u als er {threshold} of meer zijn",
1913
+ "A message to you when there are {threshold} or more": "Bericht aan u als er {threshold} of meer open zijn",
1914
1914
  "Save this search or open a saved one to set an alert on it.": "Sla deze zoekopdracht op of open een opgeslagen zoekopdracht om er een melding op te zetten.",
1915
1915
  "The alert could not be saved.": "De melding kon niet worden opgeslagen.",
1916
1916
  "Your exports could not be loaded.": "Uw exports konden niet worden geladen.",
@@ -1989,7 +1989,7 @@
1989
1989
  "Save current view": "Huidige weergave opslaan",
1990
1990
  "Share view: {name}": "Weergave delen: {name}",
1991
1991
  "The view \"{name}\" leaves your list and the menu on the left.": "De weergave \"{name}\" verdwijnt uit uw lijst en uit het menu links.",
1992
- "Whoever gets the view sees it under Shared with me. It is yours and stays yours.": "Wie de weergave krijgt, ziet die onder Gedeeld met mij. Die is van u en blijft dat.",
1992
+ "Whoever gets the view sees it under Shared with me. It is yours and stays yours.": "Wie de weergave krijgt, ziet hem onder Gedeeld met mij. Hij is van u en blijft dat.",
1993
1993
  "Whoever you share the view with sees the change at once.": "Iedereen met wie u de weergave deelt, ziet de wijziging meteen.",
1994
1994
  "Reason": "Reden",
1995
1995
  "Could not be read": "Kon niet worden gelezen",
@@ -2635,7 +2635,16 @@
2635
2635
  "Save as draft": "Opslaan als concept",
2636
2636
  "Draft saved at {time}": "Concept opgeslagen om {time}",
2637
2637
  "The draft could not be saved": "Het concept kon niet worden opgeslagen",
2638
- "No value": "Geen waarde"
2638
+ "No value": "Geen waarde",
2639
+ "Link an existing card": "Bestaande kaart koppelen",
2640
+ "New card": "Nieuwe kaart",
2641
+ "Could not link card.": "De kaart kon niet worden gekoppeld.",
2642
+ "Could not create card.": "De kaart kon niet worden gemaakt.",
2643
+ "This card is already linked.": "Deze kaart is al gekoppeld.",
2644
+ "From {date}": "Vanaf {date}",
2645
+ "Until {date}": "Tot {date}",
2646
+ "More actions for {name}": "Meer acties voor {name}",
2647
+ "Unlink contact": "Contact ontkoppelen"
2639
2648
  },
2640
2649
  "plurals": {
2641
2650
  "{count} member": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.141.0",
3
+ "version": "2.143.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -2,7 +2,7 @@
2
2
  <div
3
3
  class="cn-admin-settings-shell"
4
4
  :class="[ownLookClass, sectionColumns ? `cn-admin-settings-shell--columns-${sectionColumns}` : '']"
5
- :style="versionTitleSize ? { '--cn-board-version-title-size': `${versionTitleSize}px` } : null">
5
+ :style="shellStyle">
6
6
  <!-- Names a development, test or acceptance environment; cannot be dismissed. Production shows nothing. -->
7
7
  <CnEnvironmentBanner v-if="environmentName" :environment="environmentName" />
8
8
 
@@ -780,6 +780,16 @@ export default {
780
780
  type: Number,
781
781
  default: 0,
782
782
  },
783
+
784
+ /**
785
+ * The board look's gap in px between a card's title and its
786
+ * description, when the app's board draws it other than 4px; the
787
+ * content follows 10px further (DqBeheerinstellingen 6, so 16).
788
+ */
789
+ descGap: {
790
+ type: Number,
791
+ default: 0,
792
+ },
783
793
  },
784
794
 
785
795
  emits: ['update', 'reimported', 'reimport-error', 'tab-change'],
@@ -818,6 +828,23 @@ export default {
818
828
  },
819
829
 
820
830
  computed: {
831
+ /**
832
+ * The board look sizes an app's board sets on the shell
833
+ * (`versionTitleSize`, `descGap`), as CSS custom properties.
834
+ *
835
+ * @return {object|null} The style, or null when none is set.
836
+ */
837
+ shellStyle() {
838
+ const style = {}
839
+ if (this.versionTitleSize) {
840
+ style['--cn-board-version-title-size'] = `${this.versionTitleSize}px`
841
+ }
842
+ if (this.descGap) {
843
+ style['--cn-board-settings-desc-gap'] = `${this.descGap}px`
844
+ }
845
+ return Object.keys(style).length > 0 ? style : null
846
+ },
847
+
821
848
  /**
822
849
  * The tabs that have an id and a label.
823
850
  *
@@ -310,6 +310,7 @@
310
310
  :disabled="streamState.isStreaming"
311
311
  :placeholder="inputPlaceholder"
312
312
  :sendLabel="sendLabel"
313
+ :stacked="titled"
313
314
  :chatAppId="chatAppId"
314
315
  :speechInputEngine="speechPolicy.inputEngine"
315
316
  :dictationSilenceTimeout="speechPolicy.silenceTimeout"
@@ -1599,10 +1600,21 @@ export default {
1599
1600
  }
1600
1601
 
1601
1602
  .cn-ai-chat-window--titled .cn-ai-chat-window__input {
1603
+ padding: 0;
1604
+ border-top: 0;
1602
1605
  border-radius: 0;
1603
1606
  background: transparent;
1604
1607
  }
1605
1608
 
1609
+ /* In a card the panel's body takes the card's 16px 18px inset. */
1610
+ .cn-ai-chat-window--titled .cn-ai-chat-window__messages {
1611
+ padding: 16px 18px;
1612
+ }
1613
+
1614
+ .cn-ai-chat-window--titled .cn-ai-chat-window__note {
1615
+ margin: 0 18px 16px;
1616
+ }
1617
+
1606
1618
  .cn-ai-chat-window__input {
1607
1619
  flex: 0 0 auto;
1608
1620
  padding: 8px;
@@ -23,7 +23,7 @@
23
23
  for the parent's async request to resolve before clearing either).
24
24
  -->
25
25
  <template>
26
- <div class="cn-ai-input" data-testid="cn-ai-input">
26
+ <div class="cn-ai-input" :class="{ 'cn-ai-input--stacked': stacked }" data-testid="cn-ai-input">
27
27
  <NcNoteCard
28
28
  v-if="uploadError"
29
29
  type="error"
@@ -298,6 +298,15 @@ export default {
298
298
  default: '',
299
299
  },
300
300
 
301
+ /**
302
+ * The narrow layout of a card: the text box and the send button on the
303
+ * first line, dictation and attach on a line under it.
304
+ */
305
+ stacked: {
306
+ type: Boolean,
307
+ default: false,
308
+ },
309
+
301
310
  /**
302
311
  * Backend app id the attach control uploads files to
303
312
  * (`POST /index.php/apps/{chatAppId}/api/chat/attachments`). See
@@ -1184,6 +1193,28 @@ export default {
1184
1193
  align-items: flex-end;
1185
1194
  }
1186
1195
 
1196
+ /* `stacked`: in a card the box keeps the line, dictation and attach wrap
1197
+ under it (DqZaakAssistent "Vraag iets over deze zaak" + "Vragen"). */
1198
+ .cn-ai-input--stacked {
1199
+ padding: 12px 18px;
1200
+ }
1201
+
1202
+ .cn-ai-input--stacked .cn-ai-input__row {
1203
+ flex-wrap: wrap;
1204
+ align-items: center;
1205
+ }
1206
+
1207
+ .cn-ai-input--stacked .cn-ai-input__textarea {
1208
+ order: -2;
1209
+ flex: 1 1 calc(100% - 104px);
1210
+ min-width: 0;
1211
+ min-height: 40px;
1212
+ }
1213
+
1214
+ .cn-ai-input--stacked .cn-ai-input__send-button {
1215
+ order: -1;
1216
+ }
1217
+
1187
1218
  .cn-ai-input__file-input {
1188
1219
  display: none;
1189
1220
  }
@@ -30,13 +30,15 @@
30
30
  <li
31
31
  v-for="entry in displayedEntries"
32
32
  :key="entry.id"
33
- class="cn-audit-card__row">
33
+ class="cn-audit-card__row"
34
+ :class="{ 'cn-audit-card__row--stacked': layout === 'stacked' }">
34
35
  <div class="cn-audit-card__row-head">
35
36
  <strong class="cn-audit-card__action">{{ formatAction(entry) }}</strong>
36
- <span class="cn-audit-card__when">{{ formatWhen(entry) }}</span>
37
+ <span v-if="layout !== 'stacked'" class="cn-audit-card__when">{{ formatWhen(entry) }}</span>
37
38
  </div>
38
39
  <div class="cn-audit-card__row-body">
39
- <span class="cn-audit-card__actor">{{ formatActor(entry) }}</span>
40
+ <span v-if="layout === 'stacked'" class="cn-audit-card__when" data-testid="cn-audit-card-when-below">{{ stackedLine(entry) }}</span>
41
+ <span v-else class="cn-audit-card__actor">{{ formatActor(entry) }}</span>
40
42
  <span
41
43
  v-if="entry.authorized === false"
42
44
  class="cn-audit-card__refused"
@@ -64,8 +66,10 @@ import { NcLoadingIcon } from '@nextcloud/vue'
64
66
  import { markRaw } from 'vue'
65
67
  import History from 'vue-material-design-icons/History.vue'
66
68
  import CnDetailCard from '../CnDetailCard/CnDetailCard.vue'
69
+ import { formatBoardDate, formatTimeOfDay } from '../../utils/boardDate.js'
67
70
  import { buildHeaders, prefixUrl } from '../../utils/index.js'
68
71
  import { leafSourceUrl } from '../../utils/leafSourceUrl.js'
72
+ import { userDisplayName } from '../../utils/userDisplayName.js'
69
73
  import { userLocale } from '../../utils/userLocale.js'
70
74
 
71
75
  /**
@@ -154,6 +158,12 @@ export default {
154
158
 
155
159
  /** Maximum rows to render. */
156
160
  maxDisplay: { type: Number, default: 5 },
161
+ /**
162
+ * `stacked` (DqTaak "Historie"): the action, then one muted line under
163
+ * it with the moment ("6 okt 09:05") and who, when the action does not
164
+ * already name them. Default: the moment beside the action, who under it.
165
+ */
166
+ layout: { type: String, default: '' },
157
167
  /**
158
168
  * Actions the card leaves out (case-insensitive), such as `['read']`
159
169
  * for a history of what changed. It then reads more entries than it
@@ -440,7 +450,27 @@ export default {
440
450
  * @return {string} Who did it, by name where the entry carries one.
441
451
  */
442
452
  actorName(entry) {
443
- return entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''
453
+ if (entry.actorDisplayName) {
454
+ return entry.actorDisplayName
455
+ }
456
+ const id = entry.actor || entry.userId || entry.user || ''
457
+ // A person's user id reads as their display name once it is known.
458
+ return (id && (!entry.performerType || entry.performerType === 'user') && userDisplayName(String(id))) || id
459
+ },
460
+
461
+ /**
462
+ * The muted line of a stacked row: the moment in the board's short
463
+ * form ("6 okt 09:05") and who, unless the action names them already.
464
+ *
465
+ * @param {object} entry The entry.
466
+ * @return {string} The line.
467
+ * @spec openspec/changes/audit-card-stacked-rows/specs/audit-trail/spec.md#requirement-an-audit-row-can-put-its-moment-under-the-action
468
+ */
469
+ stackedLine(entry) {
470
+ const raw = entry.creationDateTime || entry.created || entry.timestamp
471
+ const date = raw ? new Date(raw) : null
472
+ const when = date && !Number.isNaN(date.getTime()) ? `${formatBoardDate(date)} ${formatTimeOfDay(date)}` : ''
473
+ return [when, this.formatActor(entry)].filter((part) => part !== '').join(' · ')
444
474
  },
445
475
 
446
476
  /**
@@ -16,6 +16,7 @@
16
16
  :scope="resolvedScope"
17
17
  :title="resolvedTitle"
18
18
  :maxDisplay="resolvedMaxDisplay"
19
+ :layout="content && typeof content.layout === 'string' ? content.layout : ''"
19
20
  :excludeActions="resolvedExcludeActions"
20
21
  :actionLabels="actionLabels"
21
22
  :valueLabels="valueLabels"
@@ -175,7 +175,7 @@
175
175
  </template>
176
176
  <template v-else-if="widget === 'avatar'">
177
177
  <span v-if="avatarName || avatarUserId" class="cn-cell-renderer__avatar">
178
- <span v-if="avatarUserId" class="cn-cell-renderer__avatar-picture" aria-hidden="true">
178
+ <span v-if="avatarUserId && !isBoardLook" class="cn-cell-renderer__avatar-picture" aria-hidden="true">
179
179
  <NcAvatar
180
180
  :user="avatarUserId"
181
181
  :displayName="avatarName || avatarUserId"
@@ -186,6 +186,7 @@
186
186
  <span
187
187
  v-else
188
188
  class="cn-cell-renderer__avatar-initials"
189
+ :class="avatarToneClass"
189
190
  :style="{ width: avatarSize + 'px', height: avatarSize + 'px' }"
190
191
  aria-hidden="true">{{ avatarInitials }}</span>
191
192
  <span class="cn-cell-renderer__avatar-text">
@@ -942,6 +943,19 @@ export default {
942
943
  * @return {number}
943
944
  * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-avatar-and-date-cells
944
945
  */
946
+ /**
947
+ * `avatar` widget: the chip's tone class in the board look,
948
+ * `widgetProps.tone` `neutral` (DqContacten's grey chip) or the
949
+ * default light primary.
950
+ *
951
+ * @return {string}
952
+ * @spec openspec/changes/board-avatar-chip/specs/data-table/spec.md#requirement-a-board-avatar-cell-draws-the-initials-chip
953
+ */
954
+ avatarToneClass() {
955
+ const tone = this.widgetProps && this.widgetProps.tone
956
+ return tone === 'neutral' ? 'cn-cell-renderer__avatar-initials--neutral' : ''
957
+ },
958
+
945
959
  avatarSize() {
946
960
  const size = Number(this.widgetProps && this.widgetProps.size)
947
961
  return Number.isFinite(size) && size > 0 ? size : (this.isBoardLook ? 32 : 24)
@@ -1195,18 +1195,25 @@ export default {
1195
1195
  }
1196
1196
 
1197
1197
  .cn-checklist-run__legend {
1198
+ font-size: 15px;
1198
1199
  font-weight: 600;
1200
+ line-height: 1.2;
1199
1201
  padding: 0 4px;
1200
1202
  }
1201
1203
 
1202
1204
  .cn-checklist-run__help {
1203
1205
  color: var(--color-text-maxcontrast);
1206
+ font-size: 14px;
1204
1207
  margin: 0;
1205
1208
  }
1206
1209
 
1210
+ /* An option reads as text, not as a field label (a theme makes every label
1211
+ bold); 15px as the board draws it (DqInspectie). */
1207
1212
  .cn-checklist-run__choice {
1208
1213
  align-items: center;
1209
1214
  display: flex;
1215
+ font-size: 15px;
1216
+ font-weight: 400;
1210
1217
  gap: 10px;
1211
1218
  min-height: 34px;
1212
1219
  }
@@ -1225,6 +1232,7 @@ export default {
1225
1232
  }
1226
1233
 
1227
1234
  .cn-checklist-run__group {
1235
+ font-size: 14px;
1228
1236
  font-weight: 600;
1229
1237
  margin: 0;
1230
1238
  }
@@ -622,6 +622,7 @@ export default {
622
622
  .cn-choice-cards__row-title {
623
623
  font-size: 15px;
624
624
  font-weight: 600;
625
+ line-height: 1.2;
625
626
  }
626
627
 
627
628
  /* Regular weight on the span itself: a theme that sets every label bold
@@ -194,6 +194,7 @@ export default {
194
194
  | `scrollable` | Boolean | `false` | Constrain height and make the table vertically scrollable |
195
195
  | `loadingText` | String | `'Loading…'` | Text shown below the spinner during loading |
196
196
  | `rowIcon` | String \| Function | `null` | Optional leading icon for every row: a static MDI name applied to all rows, or `(row) => iconName` to vary it per row (resolved via the CnIcon registry). Unset = no icon column. |
197
+ | `rowIconTone` | String | `'primary'` | The tone of the leading icon's tile in the board look: `primary`, `teal` or `neutral` (DqOrganisaties draws a teal tile). |
197
198
  | `rowIndicators` | Array | `[]` | State indicators the page declares for its rows: `{ id, field, equals?, in?, icon, text, tooltip? }`. The condition is `equals`, `in`, or plain truthiness when neither is given. `text` is required, so an indicator is never colour and shape alone. The page declares which indicators exist; a record cannot add one it has not declared. |
198
199
  | `rowIndicatorCap` | Number | `3` | How many declared indicators render on the row before the rest move into the row menu, reachable as `indicatorsFor(row).overflow`. |
199
200
  | `selectAllLabel` | String | `'Select all rows'` | Accessible name (`aria-label`) for the header select-all checkbox, so screen readers announce a named control (WCAG 4.1.2) |
@@ -276,7 +276,7 @@
276
276
  <td
277
277
  v-if="rowIcon"
278
278
  class="cn-table-col--icon"
279
- :class="pinClass(selectable ? 1 : 0)"
279
+ :class="[pinClass(selectable ? 1 : 0), 'cn-table-col--icon-' + iconTone]"
280
280
  :style="pinStyle(selectable ? 1 : 0)">
281
281
  <CnIcon :name="getRowIcon(row)" :size="20" />
282
282
  </td>
@@ -650,6 +650,17 @@ export default {
650
650
  default: null,
651
651
  },
652
652
 
653
+ /**
654
+ * The tone of the leading icon's tile in the board look: `primary`
655
+ * (default), `teal` or `neutral` (DqOrganisaties draws a teal tile).
656
+ *
657
+ * @type {string}
658
+ */
659
+ rowIconTone: {
660
+ type: String,
661
+ default: 'primary',
662
+ },
663
+
653
664
  /**
654
665
  * State indicators a page declares for its rows. Each entry is
655
666
  * `{ id, field, equals?, in?, icon, text, tooltip? }`: `field` is a
@@ -1176,6 +1187,16 @@ export default {
1176
1187
  },
1177
1188
 
1178
1189
  computed: {
1190
+ /**
1191
+ * The leading icon tile's tone, one the board look draws.
1192
+ *
1193
+ * @return {string} `primary`, `teal` or `neutral`.
1194
+ * @spec openspec/changes/board-row-icon-tile/specs/data-table/spec.md#requirement-a-board-tables-leading-icon-sits-on-a-tile
1195
+ */
1196
+ iconTone() {
1197
+ return ['primary', 'teal', 'neutral'].includes(this.rowIconTone) ? this.rowIconTone : 'primary'
1198
+ },
1199
+
1179
1200
  /**
1180
1201
  * Whether the table is drawn as the board look's white card.
1181
1202
  *
@@ -86,6 +86,7 @@
86
86
  <CnObjectDataWidget
87
87
  v-else-if="isData && schemaObject"
88
88
  :title="resolvedTitle"
89
+ :subtitle="widgetSubtitle"
89
90
  :chromeless="isBare"
90
91
  :showActions="!isBare && actionsShown"
91
92
  :icon="widget.icon || null"
@@ -249,7 +249,7 @@ import { formatBoardDate } from '../../utils/boardDate.js'
249
249
  import { conditionalLine } from '../../utils/conditionalLine.js'
250
250
  import { parseDateValue } from '../../utils/dateVariant.js'
251
251
  import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
252
- import { fillTemplate } from '../../utils/fillTemplate.js'
252
+ import { fillTemplate, formatLongDate } from '../../utils/fillTemplate.js'
253
253
  import { formatMoney, metricLocale, unwrapAppConfig } from '../../utils/formatMetric.js'
254
254
  import { readPath } from '../../utils/readPath.js'
255
255
  import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveFilterTokens.js'
@@ -416,6 +416,8 @@ export default {
416
416
  cnDispatchAction: { default: null },
417
417
  /** The page's object sidebar channel; a `link.sidebarTab` opens one of its tabs. */
418
418
  objectSidebarState: { default: null },
419
+ /** The records the detail page reads beside the record (`@context.<name>`), for a `labelPath`. */
420
+ cnDetailVisibilityRows: { default: null },
419
421
  },
420
422
 
421
423
  props: {
@@ -432,13 +434,14 @@ export default {
432
434
  * `route` (`{ name, params?, query? }`, `@source.<path>` in a param) on a `title` or a `row` links its value to that route; a param that does not fill leaves it plain.
433
435
  * `format: 'user'` shows a Nextcloud user id as the user's display name, read once per id; the entry stays empty until the name is known.
434
436
  * `template` ("{available} {unit}") fills its `{path}` placeholders from the record instead of reading `path`; it is empty when every placeholder is. `suffix` (" kg") is added to a value that is not empty.
437
+ * `labelPath` ("@context.caseType.handlerLabel") names a value on the record or the page's `@context` records that labels the row when it holds text; `label` is the fallback.
435
438
  * `emptyValue` ("No") is shown, translated, when the entry has no value, so the entry stays drawn whatever `hideEmpty` says (an override that was never set reads "No").
436
439
  * - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
437
440
  * - `actions` — action buttons under the facts in the `headerActions` shape (`api-call`, `open-form`, `navigate`, `open-modal`, ...; `confirm`, `variant`). `@source.<field>` names a field of the record shown, and an action whose token names an empty field is not drawn. `visibleWhen` reads the record shown; `adminOnly: true` draws the action for an instance admin only (visibility, never authorisation). An api-call or a saved form refreshes the page, and the facts read their source again.
438
441
  * - `rowsPath` — a path to a list of `{ label, value }` the record holds, drawn as rows after `fields` (a preview the server builds).
439
442
  * - `headerPill` — a status pill in the card header (reported through the `header` event): `{ path?, text?, map?, variant?, variantMap?, colorMap? }`, read from the record like a `pill` entry (DqBezwaar "Advies ontvangen"); nothing shows while the value is empty.
440
443
  * - `notice` — `{ text, variant? }`, a note band inside the card under the facts (`info` by default; `success`, `warning` or `error`); the text is translated.
441
- * `format` is `text` (default), `date` (board short date), `date-long` (always with the year), `list` (an array joined by ", ", each item read at `itemPath`), `bytes` (a size, "1.5 MB"), `number` (in the reader's language, `decimals` fraction digits when set) or `currency` (a money amount in the reader's language, "€ 1.234,50": the code in `currency`, else the record's `currencyField`, else the app's reporting currency, else EUR; `decimals` defaults to 2).
444
+ * `format` is `text` (default), `date` (board short date), `date-long` (always with the year), `longdate` (the month written out, the year only outside this year: "3 oktober"), `list` (an array joined by ", ", each item read at `itemPath`), `bytes` (a size, "1.5 MB"), `number` (in the reader's language, `decimals` fraction digits when set) or `currency` (a money amount in the reader's language, "€ 1.234,50": the code in `currency`, else the record's `currencyField`, else the app's reporting currency, else EUR; `decimals` defaults to 2).
442
445
  * `map` turns a raw value into a label (translated). `parts` (on a `line`) lists paths, or `{ path, label?, format? }`, joined by `separator` (default " · "); empty parts are dropped.
443
446
  * - `source` — `{ register, schema, id }` or `{ url }`; omitted = the current object. `{ register, schema, filter, sort? }` without `id` reads the first object that matches `filter` in `sort` order (`{ field, dir }`): the latest count of a shift. An entry with `from: "object"` reads the current object even then (a client card that also shows the ticket's channel).
444
447
  * - `endpointSource` — `{ url, method?, params?, responsePath? }`, the block a stat or chart takes: the facts read an app endpoint, shared and cached like those.
@@ -722,7 +725,7 @@ export default {
722
725
  }
723
726
  if (kind === 'row') {
724
727
  const last = out[out.length - 1]
725
- const row = { key, label: this.tr(field.label || ''), value, ...this.rowExtras(record, field, value) }
728
+ const row = { key, label: this.labelOf(record, field), value, ...this.rowExtras(record, field, value) }
726
729
  if (field.route && !row.href) {
727
730
  row.to = this.resolveRoute(field.route, record)
728
731
  }
@@ -1406,6 +1409,30 @@ export default {
1406
1409
  }
1407
1410
  },
1408
1411
 
1412
+ /**
1413
+ * A row's label: the value at `labelPath` (the record, or the page's
1414
+ * `@context.<name>` records) when it holds text, else `label`, both
1415
+ * through the host translate. A case type can name its handler
1416
+ * ("Regisseur") while another keeps "Behandelaar".
1417
+ *
1418
+ * @param {object} record The record shown.
1419
+ * @param {object} field The entry.
1420
+ * @return {string}
1421
+ * @spec openspec/changes/facts-label-from-path/specs/facts-widget/spec.md#requirement-a-facts-row-can-take-its-label-from-the-record-or-its-context
1422
+ */
1423
+ labelOf(record, field) {
1424
+ if (typeof field.labelPath === 'string' && field.labelPath !== '') {
1425
+ const rows = this.cnDetailVisibilityRows && typeof this.cnDetailVisibilityRows === 'object' && 'value' in this.cnDetailVisibilityRows
1426
+ ? this.cnDetailVisibilityRows.value
1427
+ : this.cnDetailVisibilityRows
1428
+ const named = readPath({ ...(record || {}), '@context': rows || {} }, field.labelPath)
1429
+ if (typeof named === 'string' && named.trim() !== '') {
1430
+ return this.tr(named.trim())
1431
+ }
1432
+ }
1433
+ return this.tr(field.label || '')
1434
+ },
1435
+
1409
1436
  /**
1410
1437
  * Copy a row's value to the clipboard.
1411
1438
  *
@@ -1775,6 +1802,10 @@ export default {
1775
1802
  .filter((item) => item !== '')
1776
1803
  return items.join(', ')
1777
1804
  }
1805
+ // The month written out, the year only outside this year: "3 oktober".
1806
+ if (format === 'longdate') {
1807
+ return formatLongDate(raw)
1808
+ }
1778
1809
  if (format === 'date' || format === 'date-long') {
1779
1810
  const date = parseDateValue(raw)
1780
1811
  if (date === null) {
@@ -5,7 +5,7 @@
5
5
  <template>
6
6
  <div
7
7
  class="cn-files-browser"
8
- :class="{ 'cn-files-browser--dragging': dragging, 'cn-files-browser--drop-panel': dropOverlay && dropDetail !== '' }"
8
+ :class="{ 'cn-files-browser--dragging': dragging, 'cn-files-browser--drop-panel': dropOverlay && dropDetailTemplate !== '' }"
9
9
  data-testid="cn-files-browser"
10
10
  @dragenter.prevent="onDragEnter"
11
11
  @dragover.prevent="onDragOver"
@@ -38,7 +38,7 @@
38
38
  Files app and its plugins registered for this folder (a new folder,
39
39
  a template, a file request) beside a plain upload. -->
40
40
  <div class="cn-files-browser__bar">
41
- <NcBreadcrumbs class="cn-files-browser__crumbs">
41
+ <NcBreadcrumbs class="cn-files-browser__crumbs" :class="{ 'cn-files-browser__crumbs--at-root': atRoot }">
42
42
  <!-- The whole trail from the user's files root, as the Files app
43
43
  draws it. A crumb above the browser's root links into the Files
44
44
  app (the browser never leaves its root); the root and everything
@@ -1094,10 +1094,22 @@ export default {
1094
1094
  * @spec openspec/changes/files-drop-panel/specs/files-browser/spec.md#requirement-the-drop-state-fills-the-panel-and-says-where-the-files-go
1095
1095
  */
1096
1096
  dropDetail: {
1097
- type: String,
1097
+ type: [String, Array],
1098
1098
  default: '',
1099
1099
  },
1100
1100
 
1101
+ /**
1102
+ * Named values filled into `dropDetail` as `{name}`, such as the case
1103
+ * number (`{ "case": "@object.identifier" }` in a detail page's props).
1104
+ * While a value is unknown the line is left out.
1105
+ *
1106
+ * @spec openspec/changes/files-drop-detail-values/specs/files-drop-detail-values/spec.md#requirement-the-drop-detail-line-can-name-the-record
1107
+ */
1108
+ dropDetailValues: {
1109
+ type: Object,
1110
+ default: null,
1111
+ },
1112
+
1101
1113
  /** Show a line under the list saying files can be dropped on it. */
1102
1114
  dropHint: {
1103
1115
  type: Boolean,
@@ -1390,6 +1402,22 @@ export default {
1390
1402
  },
1391
1403
 
1392
1404
  computed: {
1405
+ /**
1406
+ * The `dropDetail` text: the string, or the text of the first variant
1407
+ * `{ when, text }` whose `when` is `true` or that has none.
1408
+ *
1409
+ * @return {string}
1410
+ * @spec openspec/changes/files-drop-detail-values/specs/files-drop-detail-values/spec.md#requirement-the-drop-detail-line-can-name-the-record
1411
+ */
1412
+ dropDetailTemplate() {
1413
+ if (typeof this.dropDetail === 'string') {
1414
+ return this.dropDetail
1415
+ }
1416
+ const variants = Array.isArray(this.dropDetail) ? this.dropDetail : []
1417
+ const chosen = variants.find((item) => item && typeof item.text === 'string' && (!('when' in item) || item.when === true))
1418
+ return chosen ? chosen.text : ''
1419
+ },
1420
+
1393
1421
  /**
1394
1422
  * `dropDetail` through the host translate with `{count}` filled, '' when
1395
1423
  * there is none or the count it needs is unknown.
@@ -1398,10 +1426,22 @@ export default {
1398
1426
  * @return {string}
1399
1427
  */
1400
1428
  dropDetailText() {
1401
- if (!this.dropDetail) {
1429
+ const template = this.dropDetailTemplate
1430
+ if (!template) {
1402
1431
  return ''
1403
1432
  }
1404
- const text = this.cnTranslate(this.dropDetail)
1433
+ let text = this.cnTranslate(template)
1434
+ // `dropDetailValues`: named values such as the case number; a value
1435
+ // still unknown (an unresolved `@` token, or empty) leaves the line out.
1436
+ // @spec openspec/changes/files-drop-detail-values/specs/files-drop-detail-values/spec.md#requirement-the-drop-detail-line-can-name-the-record
1437
+ const values = this.dropDetailValues && typeof this.dropDetailValues === 'object' ? this.dropDetailValues : {}
1438
+ for (const [name, value] of Object.entries(values)) {
1439
+ const shown = value === null || value === undefined ? '' : String(value)
1440
+ if (shown === '' || shown.startsWith('@')) {
1441
+ return ''
1442
+ }
1443
+ text = text.split(`{${name}}`).join(shown)
1444
+ }
1405
1445
  if (!text.includes('{count}')) {
1406
1446
  return text
1407
1447
  }
@@ -1431,6 +1471,19 @@ export default {
1431
1471
  return crumbsFor(this.rootPath, this.currentPath, this.rootLabel)
1432
1472
  },
1433
1473
 
1474
+ /**
1475
+ * Whether the open folder is the browser's root: the trail then ends on
1476
+ * the root itself (the board look draws no trail there).
1477
+ *
1478
+ * @return {boolean}
1479
+ * @spec openspec/changes/board-files-root-crumb/specs/board-files-root-crumb/spec.md#requirement-the-board-files-bar-draws-no-trail-at-the-root
1480
+ */
1481
+ atRoot() {
1482
+ const root = String(this.rootPath || '/').replace(/\/+$/, '') || '/'
1483
+ const last = this.crumbs[this.crumbs.length - 1]
1484
+ return Boolean(last) && last.path === root
1485
+ },
1486
+
1434
1487
  /**
1435
1488
  * The children in the chosen order, folders first the way the Files app
1436
1489
  * lists them.
@@ -2728,7 +2781,8 @@ export default {
2728
2781
  }
2729
2782
  })
2730
2783
  .filter((field) => field.value !== null && field.value !== undefined && field.value !== ''),
2731
- actions: (Array.isArray(card.actions) ? card.actions : []).filter((action) => action && action.id),
2784
+ actions: (Array.isArray(card.actions) ? card.actions : [])
2785
+ .filter((action) => action && action.id && hostActionApplies(action, node, this.rowValueOf(node))),
2732
2786
  }))
2733
2787
  },
2734
2788
 
@@ -3024,7 +3078,18 @@ export default {
3024
3078
  * @spec openspec/changes/files-browser-hosts-a-documents-list/specs/files-browser/spec.md
3025
3079
  */
3026
3080
  hostActionsFor(node) {
3027
- return this.rowActions.filter((action) => hostActionApplies(action, node))
3081
+ return this.rowActions.filter((action) => hostActionApplies(action, node, this.rowValueOf(node)))
3082
+ },
3083
+
3084
+ /**
3085
+ * A reader of the host's row fields for one file, for `visibleIf.row`.
3086
+ *
3087
+ * @param {object} node The file.
3088
+ * @return {(key: string) => unknown} `(key) => value` over the row the host gave for the file.
3089
+ * @spec openspec/changes/files-browser-action-visible-if-row/specs/files-browser/spec.md#requirement-a-host-action-can-be-offered-by-a-field-of-the-files-row
3090
+ */
3091
+ rowValueOf(node) {
3092
+ return (key) => fileColumnValue({ key, source: 'row' }, node, this.resolvedRowData)
3028
3093
  },
3029
3094
 
3030
3095
  /**