@conduction/nextcloud-vue 2.105.0 → 2.106.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 (153) hide show
  1. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  2. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
  3. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  4. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
  5. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  9. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +53 -4
  12. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  13. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  14. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
  15. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
  16. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
  17. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
  18. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
  19. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  25. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  26. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  27. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  28. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  29. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
  30. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  31. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
  32. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  33. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
  34. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
  35. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +159 -50
  38. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
  40. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  42. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
  43. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
  45. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  46. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +22 -4
  47. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  48. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
  49. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  50. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
  51. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  52. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  53. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
  54. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  55. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
  56. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  58. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
  59. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  60. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
  61. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
  64. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  65. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
  66. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  67. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  68. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
  69. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  70. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
  71. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  72. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
  73. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
  74. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  75. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
  76. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  77. package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
  78. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  79. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  80. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  81. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
  82. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  83. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
  84. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  85. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
  86. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
  87. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
  88. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
  89. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
  90. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
  91. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  92. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  93. package/dist/esm/index.js +1 -0
  94. package/dist/esm/index.js.map +1 -1
  95. package/dist/esm/l10n/en.json.js +21 -1
  96. package/dist/esm/l10n/en.json.js.map +1 -1
  97. package/dist/esm/l10n/nl.json.js +21 -1
  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 +803 -453
  101. package/dist/esm/utils/dueText.js +18 -3
  102. package/dist/esm/utils/dueText.js.map +1 -1
  103. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  104. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  105. package/dist/esm/utils/sortOrder.js +11 -1
  106. package/dist/esm/utils/sortOrder.js.map +1 -1
  107. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  108. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  109. package/dist/nextcloud-vue.cjs.js +8281 -6350
  110. package/dist/nextcloud-vue.cjs.js.map +2 -2
  111. package/dist/nextcloud-vue.css +803 -453
  112. package/l10n/en.json +21 -1
  113. package/l10n/nl.json +21 -1
  114. package/package.json +1 -1
  115. package/src/components/CnCardGrid/CnCardGrid.md +2 -0
  116. package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
  117. package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
  118. package/src/components/CnDetailPage/CnDetailPage.md +2 -2
  119. package/src/components/CnDetailPage/CnDetailPage.vue +53 -4
  120. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
  121. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
  122. package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
  123. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
  124. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
  125. package/src/components/CnIndexPage/CnIndexPage.md +7 -5
  126. package/src/components/CnIndexPage/CnIndexPage.vue +187 -58
  127. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
  128. package/src/components/CnIndexPage/useSelfFetchList.js +22 -4
  129. package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
  130. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
  131. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
  132. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  133. package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
  134. package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
  135. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
  136. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
  137. package/src/components/CnTabs/CnTabs.vue +11 -0
  138. package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
  139. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
  140. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
  141. package/src/components/CnTeamWorkloadWidget/index.js +14 -0
  142. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  143. package/src/components/index.js +1 -0
  144. package/src/css/index-page.css +14 -2
  145. package/src/css/look-board-detail.css +48 -4
  146. package/src/css/look-board-index.css +64 -2
  147. package/src/css/look-board.css +25 -2
  148. package/src/index.js +1 -1
  149. package/src/schemas/app-manifest-v2.schema.json +75 -6
  150. package/src/utils/dueText.js +18 -3
  151. package/src/utils/libraryWidgetKeys.js +1 -0
  152. package/src/utils/sortOrder.js +10 -0
  153. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -25,7 +25,7 @@
25
25
  /* ---- The header: two rows on the ground ------------------------------- */
26
26
 
27
27
  .cn-look-board .cn-detail-page__header--board {
28
- align-items: center;
28
+ align-items: flex-start;
29
29
  column-gap: 16px;
30
30
  flex-wrap: wrap;
31
31
  margin-bottom: 22px;
@@ -33,10 +33,16 @@
33
33
  row-gap: 6px;
34
34
  }
35
35
 
36
- /* Row 1: the title takes what the buttons leave, from a 320px basis. */
36
+ /* Row 1: the title takes what the buttons leave, from a zero basis, and the
37
+ buttons sit at the row's top beside it (DqZaak: both at 96). From a 320px
38
+ basis the title and its follow control claimed the row first, so on a 960px
39
+ header the buttons centred against a two-line title, and below that they
40
+ dropped to a row of their own. Only when less than 16rem of title fits
41
+ beside the buttons do they still wrap.
42
+ @spec openspec/changes/board-detail-header-row/specs/board-detail-header-row/spec.md#requirement-the-board-detail-header-keeps-its-actions-on-the-title-row */
37
43
  .cn-look-board .cn-detail-page__header--board > :first-child {
38
- flex: 1 1 320px;
39
- min-width: 0;
44
+ flex: 1 1 0;
45
+ min-width: min(100%, 16rem);
40
46
  }
41
47
 
42
48
  .cn-look-board .cn-detail-page__header--board .cn-detail-page__title {
@@ -58,6 +64,44 @@
58
64
  min-height: var(--cn-board-control-height);
59
65
  }
60
66
 
67
+ /* The quick actions, Edit and the Meer menu are the board's neutral buttons
68
+ (DqZaak): the main background, a grey border, the main text at 600. They
69
+ outrank a theme that gives every secondary button a primary outline with
70
+ `!important` (nldesign), as the index header does. The buildiq square and
71
+ the primary button keep their own rules. */
72
+ .cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary,
73
+ .cn-look-board .cn-detail-page .cn-detail-page__header-actions .action-item--secondary .action-item__menutoggle {
74
+ --nldesign-color-on-surface: var(--color-main-text);
75
+ height: var(--cn-board-control-height) !important;
76
+ min-height: var(--cn-board-control-height) !important;
77
+ padding: 0 14px !important;
78
+ border: 1px solid var(--color-border-dark) !important;
79
+ border-radius: var(--cn-board-control-radius) !important;
80
+ background: var(--color-main-background) !important;
81
+ color: var(--color-main-text) !important;
82
+ font-size: 14px !important;
83
+ font-weight: 600 !important;
84
+ }
85
+
86
+ .cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary:hover,
87
+ .cn-look-board .cn-detail-page .cn-detail-page__header-actions .action-item--secondary .action-item__menutoggle:hover {
88
+ background: var(--color-background-hover) !important;
89
+ }
90
+
91
+ /* Each header icon is 16px with 8px to its label; NcButton draws 20px in a 30px slot. */
92
+ .cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary .button-vue__icon {
93
+ width: 16px;
94
+ min-width: 16px;
95
+ height: 16px;
96
+ min-height: 16px;
97
+ margin-inline-end: 8px;
98
+ }
99
+
100
+ .cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary .button-vue__icon svg {
101
+ width: 16px;
102
+ height: 16px;
103
+ }
104
+
61
105
  /* Row 2: pills, breadcrumb, dot, meta, chips. */
62
106
  .cn-look-board .cn-detail-page__header-row2 {
63
107
  align-items: center;
@@ -10,12 +10,16 @@
10
10
 
11
11
  /* ------------------------------------------------------------------ header */
12
12
 
13
- /* Row 1: the h1 and the buttons at its end. Row 2: the count line, 6px under. */
13
+ /* Row 1: the h1 and the buttons at its end, both at the row's top. Row 2:
14
+ the count line, 6px under. The header starts 4px under the content's top
15
+ (DqZaken: the title at 96, the count line at 142).
16
+ @spec openspec/changes/board-page-header-top/specs/board-page-header-top/spec.md#requirement-a-board-page-title-sits-4px-under-the-content-top */
14
17
  .cn-look-board .cn-index-page .cn-page-header {
15
18
  display: grid;
16
19
  grid-template-columns: minmax(0, 1fr) auto;
17
- align-items: center;
20
+ align-items: start;
18
21
  row-gap: 6px;
22
+ padding-top: 4px;
19
23
  margin-bottom: var(--cn-board-section-gap, 20px);
20
24
  }
21
25
 
@@ -1057,6 +1061,64 @@
1057
1061
  border-radius: 8px;
1058
1062
  }
1059
1063
 
1064
+ /* The record layout (`config.cardLayout`, DqAanMijToegewezen): a 20px 22px
1065
+ inset, 10px between its parts; the head pill at the start and the number
1066
+ in 13px muted at the end; a 17px bold title, not underlined; a 14px muted
1067
+ meta line; a footer over a hairline with a pill and the 14px semibold date.
1068
+ @spec openspec/changes/board-record-card-layout/specs/board-record-card-layout/spec.md#requirement-a-board-card-can-draw-the-record-layout-from-config */
1069
+ .cn-look-board .cn-object-card.cn-object-card--board:has(> .cn-object-card__record) {
1070
+ padding: 20px 22px;
1071
+ }
1072
+
1073
+ .cn-look-board .cn-object-card--board .cn-object-card__record {
1074
+ gap: 10px;
1075
+ }
1076
+
1077
+ .cn-look-board .cn-object-card--board .cn-object-card__record-head {
1078
+ display: flex;
1079
+ align-items: center;
1080
+ gap: 8px;
1081
+ min-height: 24px;
1082
+ }
1083
+
1084
+ .cn-look-board .cn-object-card--board .cn-object-card__record-number {
1085
+ margin-inline-start: auto;
1086
+ color: var(--color-text-maxcontrast);
1087
+ font-size: 13px;
1088
+ }
1089
+
1090
+ .cn-look-board .cn-object-card--board .cn-object-card__record .cn-object-card__title {
1091
+ margin: 0;
1092
+ font-size: 17px;
1093
+ font-weight: 700;
1094
+ line-height: 1.3;
1095
+ text-decoration: none;
1096
+ white-space: normal;
1097
+ }
1098
+
1099
+ .cn-look-board .cn-object-card--board .cn-object-card__record-meta {
1100
+ margin: 0;
1101
+ color: var(--color-text-maxcontrast);
1102
+ font-size: 14px;
1103
+ }
1104
+
1105
+ .cn-look-board .cn-object-card--board .cn-object-card__record-foot {
1106
+ display: flex;
1107
+ align-items: center;
1108
+ justify-content: space-between;
1109
+ gap: 8px;
1110
+ margin-top: auto;
1111
+ padding-top: 10px;
1112
+ border-top: 1px solid var(--cn-board-hairline);
1113
+ }
1114
+
1115
+ .cn-look-board .cn-object-card--board .cn-object-card__record-date {
1116
+ margin-inline-start: auto;
1117
+ color: var(--color-main-text);
1118
+ font-size: 14px;
1119
+ font-weight: 600;
1120
+ }
1121
+
1060
1122
  /* The catalogue card of the store page. */
1061
1123
  .cn-look-board .cn-store-page__card.cn-store-page__card--board {
1062
1124
  gap: 10px;
@@ -101,6 +101,15 @@
101
101
  margin-bottom: var(--cn-board-section-gap);
102
102
  }
103
103
 
104
+ /* The boards start a page's header 4px under the content's top, so the title
105
+ sits at 96 (DqZaak, DqMijnWerk), not 92. The index page's header carries
106
+ the same inset in look-board-index.css.
107
+ @spec openspec/changes/board-page-header-top/specs/board-page-header-top/spec.md#requirement-a-board-page-title-sits-4px-under-the-content-top */
108
+ .cn-look-board .cn-detail-page:not(.cn-look-nextcloud *) .cn-detail-page__header,
109
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header {
110
+ padding-top: 4px;
111
+ }
112
+
104
113
  /* The 56px inset clears the navigation toggle, which overlays the content
105
114
  only while the navigation is closed. The store page's own header too: it
106
115
  kept the inset with the navigation open, 56px right of the board's title. */
@@ -140,7 +149,7 @@
140
149
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__title-row {
141
150
  grid-column: 1;
142
151
  grid-row: 1;
143
- align-self: center;
152
+ align-self: start;
144
153
  }
145
154
 
146
155
  .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header-left > :not(.cn-dashboard-page__title-row) {
@@ -650,6 +659,7 @@
650
659
  }
651
660
 
652
661
  .cn-look-board .cn-admin-settings-shell__title:not(.cn-look-nextcloud *) {
662
+ min-height: var(--cn-board-control-height);
653
663
  margin: 0;
654
664
  font-size: 28px;
655
665
  font-weight: 700;
@@ -662,10 +672,23 @@
662
672
  align-items: center;
663
673
  gap: 8px;
664
674
  margin-top: 6px;
665
- font-size: 15px;
675
+ font-size: 14px;
676
+ line-height: normal;
666
677
  color: var(--color-text-maxcontrast);
667
678
  }
668
679
 
680
+ /* The admin settings header as the board draws it (DqInstellingen): 4px
681
+ under the content's top and 8px above the grid's gap, the crumbs 6px under
682
+ the 40px title row in 14px, and the tab strip 20px under the header (at
683
+ 187). The shell is a grid whose gap already spaces its rows, so the
684
+ header's own 20px bottom margin came on top of it and the strip sat at 194.
685
+ @spec openspec/changes/board-admin-settings-header-rhythm/specs/board-admin-settings-header-rhythm/spec.md#requirement-the-board-admin-settings-header-keeps-the-boards-rhythm */
686
+ .cn-look-board .cn-admin-settings-shell:not(.cn-look-nextcloud *) > .cn-admin-settings-shell__header,
687
+ .cn-look-board.cn-admin-settings-shell:not(.cn-look-nextcloud *) > .cn-admin-settings-shell__header {
688
+ margin-bottom: 0;
689
+ padding: 4px 0 8px;
690
+ }
691
+
669
692
  .cn-look-board .cn-admin-settings-shell__crumb-list:not(.cn-look-nextcloud *) {
670
693
  display: flex;
671
694
  flex-wrap: wrap;
package/src/index.js CHANGED
@@ -376,7 +376,7 @@ export { createOpenRegisterSource } from './components/index.js'
376
376
  // Workplace dashboard primitives (openspec: workplace-dashboard-primitives):
377
377
  // the week strip and stacked bar widgets with their forms, the segmented
378
378
  // control, and the brand stripe.
379
- export { CnBrandStripe, CnSegmentedControl, CnStackedBarWidget, CnStackedBarWidgetForm, CnWeekStripWidget, CnWeekStripWidgetForm } from './components/index.js'
379
+ export { CnBrandStripe, CnSegmentedControl, CnStackedBarWidget, CnStackedBarWidgetForm, CnTeamWorkloadWidget, CnWeekStripWidget, CnWeekStripWidgetForm } from './components/index.js'
380
380
 
381
381
  // Page components the page renderer mounts by `pages[].type` that were missing
382
382
  // from this entry. A component reachable from a manifest but not importable
@@ -3,7 +3,7 @@
3
3
  "$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
4
4
  "title": "Conduction App Manifest v2",
5
5
  "description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
6
- "version": "2.128.0",
6
+ "version": "2.129.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -2178,7 +2178,7 @@
2178
2178
  }
2179
2179
  },
2180
2180
  {
2181
- "description": "record-list built-in widget (CnRecordListWidget): the action under the list, `props.content.footerAction` (2.126.0). Other content keys stay free-form.",
2181
+ "description": "record-list built-in widget (CnRecordListWidget): the action under the list, `props.content.footerAction` (2.126.0), and a row's colour `dot` and `flags` (2.129.0). Other content keys stay free-form.",
2182
2182
  "if": {
2183
2183
  "properties": { "widgetKey": { "const": "record-list" } },
2184
2184
  "required": ["widgetKey"]
@@ -2202,7 +2202,9 @@
2202
2202
  "disabledWhen": { "$ref": "#/$defs/visibleWhen" },
2203
2203
  "disabledReason": { "type": "string" }
2204
2204
  }
2205
- }
2205
+ },
2206
+ "dot": { "description": "A colour dot before the title: a field name, or `{ field, colorMap? }` (2.129.0).", "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "object", "required": ["field"], "additionalProperties": false, "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string" } } } }] },
2207
+ "flags": { "type": "array", "description": "The labels of the flags a row has, at its end (\"Start\", \"End\"): `[{ field, label, value? }]` (DqZaaktypeOverzicht, 2.129.0).", "items": { "type": "object", "required": ["field", "label"], "additionalProperties": false, "properties": { "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 }, "value": {} } } }
2206
2208
  }
2207
2209
  }
2208
2210
  }
@@ -2235,6 +2237,53 @@
2235
2237
  }
2236
2238
  }
2237
2239
  },
2240
+ {
2241
+ "description": "related built-in widget (CnRelatedObjectsWidget): grouping by relation (2.129.0). Other content keys stay free-form.",
2242
+ "if": {
2243
+ "properties": { "widgetKey": { "const": "related" } },
2244
+ "required": ["widgetKey"]
2245
+ },
2246
+ "then": {
2247
+ "properties": {
2248
+ "props": {
2249
+ "type": "object",
2250
+ "properties": {
2251
+ "content": {
2252
+ "type": "object",
2253
+ "properties": {
2254
+ "groupByRelation": { "type": "boolean", "description": "Split the Objects group by the relation label OpenRegister answers per direction." },
2255
+ "layout": { "type": "string", "enum": ["tabs", "list", "sections"], "description": "`sections` draws every group under its own heading in one list." },
2256
+ "groupActions": { "type": "object", "additionalProperties": { "type": "object" }, "description": "An action on a group's heading in `layout: \"sections\"`, keyed by the group's key (DqZaakGerelateerd \"Vervolg plannen\")." }
2257
+ }
2258
+ }
2259
+ }
2260
+ }
2261
+ }
2262
+ }
2263
+ },
2264
+ {
2265
+ "description": "nc-widget built-in widget (CnNcWidgetWidget): leaving items out (2.129.0). Other content keys stay free-form.",
2266
+ "if": {
2267
+ "properties": { "widgetKey": { "const": "nc-widget" } },
2268
+ "required": ["widgetKey"]
2269
+ },
2270
+ "then": {
2271
+ "properties": {
2272
+ "props": {
2273
+ "type": "object",
2274
+ "properties": {
2275
+ "content": {
2276
+ "type": "object",
2277
+ "properties": {
2278
+ "excludeApps": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Apps whose items are left out (an item's app is its own app field, else the /apps/<app>/ part of its link or icon), e.g. [\"systemtags\"]." },
2279
+ "excludeTypes": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Item types that are left out." }
2280
+ }
2281
+ }
2282
+ }
2283
+ }
2284
+ }
2285
+ }
2286
+ },
2238
2287
  {
2239
2288
  "description": "audit-trail built-in widget (CnAuditTrailWidget, Wave 1): the object change-log card. All props are optional — register/schema/objectId normally arrive via the detail-page object-context merge; explicit props win.",
2240
2289
  "if": {
@@ -3815,7 +3864,7 @@
3815
3864
  "description": "Name a registered NON-OBJECT entity collection for a type='index' page, instead of register+schema. Deliberately NOT called `source`: that key is already taken on page config and is polymorphic (a URL string in some manifests, an object with `params` in others), so reusing it would give one key two meanings and break six deployed shillinq pages. A flow definition is deliberately not an OpenRegister object, so an index had nothing to bind to and such lists became bespoke type='custom' pages. With a source the index loads the list itself. Takes precedence over register/schema; non-empty `objects` still wins over both. Registered sources live in src/composables/indexSources.js. `tasks` lists the viewer's task inbox from OpenRegister's flow-tasks read (cn-tasks-entity-source). `organisations` lists the OpenRegister organisations the viewer belongs to. On a type='detail' page a source that reads one record (today `organisations`) loads the record whose id is the route's objectId, instead of register+schema, and feeds the header, pills, widgets and @objectId / @object.* tokens like a fetched object (organisations-entity-source)."
3816
3865
  },
3817
3866
  "bulkBandAlways": { "type": "boolean", "default": false, "description": "type='index', board look: draw the bulk band (its actions and hint) before a selection too, its buttons disabled until rows are selected (DqZaken, DqWerkbord). Maps to CnIndexPage `bulkBandAlways`." },
3818
- "quickFilterPlacement": { "type": "string", "enum": ["", "toolbar"], "default": "", "description": "type='index': where the quick-filter chips sit. `toolbar` keeps them on one row with the Filter button and the view switch beside a side panel, the chips that do not fit behind the overflow chip (DqTeamwachtrij). Maps to CnIndexPage `quickFilterPlacement`." },
3867
+ "quickFilterPlacement": { "type": "string", "enum": ["", "toolbar", "row"], "default": "", "description": "type='index': where the quick-filter chips sit. `toolbar` keeps them on one row with the Filter button and the view switch beside a side panel, the chips that do not fit behind the overflow chip (DqTeamwachtrij). `row` puts them on a row of their own above the toolbar, wrapping onto more lines. Maps to CnIndexPage `quickFilterPlacement`." },
3819
3868
  "rowClass": {
3820
3869
  "type": "array",
3821
3870
  "description": "type='index': per-row CSS class rules, compiled into the table's rowClass function as on the object-table widget. Each rule { when: { field, op?, value }, class } adds `class` to a row whose `field` (a dot-path) compares to `value` with `op` (default eq; the visibleWhen operators and tokens such as `@today`, with two dates compared as moments), e.g. an overdue task `{ when: { field: \"dueAt\", op: \"lt\", value: \"@today\" }, class: \"cn-row--overdue\" }`. Maps to CnIndexPage `rowClass`.",
@@ -4081,6 +4130,7 @@
4081
4130
  "additionalProperties": true,
4082
4131
  "description": "type='index': the opt-in folder navigation pane (CnFolderSidebar) rendered left of the list. Selecting a folder filters the list by `filterField` (or `field`); \"All\" clears it. Only the per-scope list-layout keys are typed here; the pane's own keys (source, register, schema, idField, nameField, folders, allLabel, title, allowCreate, ...) stay free-form. Maps to CnIndexPage `folderSidebar`.",
4083
4132
  "properties": {
4133
+ "scopeViews": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The view modes (`table`, `list`, `cards`, `board`, ...) a scope's look applies in; in the others the page keeps its generic look while the scope still filters (DqWerkbord, 2.129.0)." },
4084
4134
  "heading": { "type": "boolean", "description": "true heads the list with the selected folder's name, its parent folder and its count (always on a `groups` pane, 2.127.0)." },
4085
4135
  "countText": { "type": "string", "minLength": 1, "description": "The count in that heading, `{count}` filled in, through the app's translate; a groups pane defaults to \"{count} members\" (2.127.0)." },
4086
4136
  "pane": { "type": "boolean", "default": true, "description": "false keeps the folder scope (a deep link such as `?caseType=<uuid>` still applies the scope's look and filter) without drawing the pane (dossiq's simple profile, 2.126.0)." },
@@ -4175,7 +4225,7 @@
4175
4225
  "nextStep": {
4176
4226
  "type": "object",
4177
4227
  "additionalProperties": false,
4178
- "required": ["stages"],
4228
+ "anyOf": [{ "required": ["stages"] }, { "required": ["stagesFrom"] }],
4179
4229
  "description": "type='detail': the 'what now' card above the body. For the record's stage it shows a short checklist next to the primary button. A stage without an entry shows no card. Rendered by CnNextStepCard.",
4180
4230
  "properties": {
4181
4231
  "field": {
@@ -4183,6 +4233,11 @@
4183
4233
  "minLength": 1,
4184
4234
  "description": "Dot-path to the stage, when it differs from `stageField`."
4185
4235
  },
4236
+ "stagesFrom": {
4237
+ "type": "string",
4238
+ "minLength": 1,
4239
+ "description": "A field path, read like a `visibleWhen` field, whose value holds the stages in the `stages` shape, e.g. `@context.caseType.nextStep`, so each case type keeps its own stage texts and checklist. The declared `stages` apply otherwise (2.129.0)."
4240
+ },
4186
4241
  "title": {
4187
4242
  "type": "string",
4188
4243
  "description": "i18n key / heading used by a stage that names none."
@@ -4240,7 +4295,8 @@
4240
4295
  "id": { "type": "string" },
4241
4296
  "type": { "type": "string", "minLength": 1 },
4242
4297
  "title": { "type": "string" },
4243
- "content": { "type": "object", "additionalProperties": true }
4298
+ "content": { "type": "object", "additionalProperties": true },
4299
+ "tabs": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "The body tab ids this card shows beside; it is hidden while another tab is open (DqZaaktypeVelden, 2.129.0)." }
4244
4300
  }
4245
4301
  }
4246
4302
  ]
@@ -4708,6 +4764,19 @@
4708
4764
  "description": "type='index': the property keys the cards view shows as the facts list of each card, in order; defaults to the first four list columns under the board look (screens-card-parity). Maps to CnIndexPage `cardFields`.",
4709
4765
  "items": { "type": "string", "minLength": 1 }
4710
4766
  },
4767
+ "cardLayout": {
4768
+ "type": "object",
4769
+ "additionalProperties": false,
4770
+ "description": "type='index', board look: the record layout of each card in the cards view (board-record-card-layout): a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date. Each part names a field (a dot path) and is left out while it is empty; a pill maps values to a badge variant (`colorMap`, ignoring case) and to shown text (`labels`). Maps to CnIndexPage `cardLayout`.",
4771
+ "properties": {
4772
+ "headPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
4773
+ "number": { "type": "string", "minLength": 1 },
4774
+ "title": { "type": "string", "minLength": 1 },
4775
+ "meta": { "type": "array", "description": "The meta line's parts: a field name reads through the page column it names; `{ field, formatter?, formatterOptions? }` reads as plain text through its own formatter (card-layout-part-formatter).", "items": { "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "formatter": { "type": "string", "minLength": 1 }, "formatterOptions": { "type": "object" } } } ] } },
4776
+ "footerPill": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "colorMap": { "type": "object", "additionalProperties": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] } }, "labels": { "type": "object", "additionalProperties": { "type": "string" } }, "variant": { "type": "string", "enum": ["default", "primary", "success", "warning", "error", "info", "purple", "teal"] }, "due": { "type": "object", "description": "Read the field as a due date: overdue gives the error variant, within `soonWithin` days the warning one; any other date draws no pill.", "properties": { "soonWithin": { "type": "number", "minimum": 0 } } }, "variantWhen": { "type": "array", "description": "Rules on the days left until the field's date, `{ op, value, variant }`; the first match gives the variant, no match draws no pill.", "items": { "type": "object", "required": ["op", "value", "variant"], "properties": { "op": { "type": "string" }, "value": { "type": "number" }, "variant": { "type": "string" } } } } } },
4777
+ "footerDate": { "type": "object", "additionalProperties": false, "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 } } }
4778
+ }
4779
+ },
4711
4780
  "afterList": { "type": "array", "description": "type='index': cards after the list, stacked full width in the listed order, the counterpart of statRow above it (index-scope-presentation). Each entry is a widget definition resolved like statRow's. A query preset or a folder scope can set its own. Maps to CnIndexPage `afterList`.", "items": { "type": "object", "required": ["id", "type"], "properties": { "id": { "type": "string", "minLength": 1 }, "type": { "type": "string", "minLength": 1 }, "title": { "type": "string" }, "subtitle": { "type": "string" }, "content": { "type": "object" }, "headerLink": { "type": "object" } } } },
4712
4781
  "statRow": {
4713
4782
  "type": "array",
@@ -29,7 +29,8 @@ import { parseDateValue } from './dateVariant.js'
29
29
  * `upcomingWeeksLabel` `{ one, other }` (from `upcomingWeeksFrom` days left,
30
30
  * default 35, it reads the weeks left, rounded). Texts go through `translate`
31
31
  * before `{count}` is filled. Tones: `danger` (overdue), `today`, `upcoming`,
32
- * `empty`.
32
+ * `empty`. With `soonWithin` (days), today and a date at most that many days
33
+ * ahead also carry `soon: true`, drawn in the warning colour.
33
34
  *
34
35
  * @param {unknown} value A Date, a timestamp or an ISO string.
35
36
  * @param {object} [config] The `due` config.
@@ -54,9 +55,23 @@ export function resolveDue(value, config, translate = (text) => text, now = new
54
55
  }
55
56
  if (days === 0) {
56
57
  const label = typeof cfg.todayLabel === 'string' && cfg.todayLabel !== '' ? translate(cfg.todayLabel) : t('nextcloud-vue', 'Due today')
57
- return { text: label, tone: 'today' }
58
+ return withSoon({ text: label, tone: 'today' }, 0, cfg)
58
59
  }
59
- return { text: upcomingText(-days, cfg, translate) ?? formatRelativeDate(date, { now }), tone: 'upcoming' }
60
+ return withSoon({ text: upcomingText(-days, cfg, translate) ?? formatRelativeDate(date, { now }), tone: 'upcoming' }, -days, cfg)
61
+ }
62
+
63
+ /**
64
+ * Marks a due date within `soonWithin` days as soon.
65
+ *
66
+ * @param {{text: string, tone: string}} result The due text and tone.
67
+ * @param {number} left Whole days left, 0 for today.
68
+ * @param {object} cfg The `due` config.
69
+ * @return {{text: string, tone: string, soon?: boolean}} The result, with `soon: true` when it is due soon.
70
+ * @spec openspec/changes/due-text-soon/specs/due-text-soon/spec.md#requirement-a-due-date-within-a-few-days-reads-as-soon
71
+ */
72
+ function withSoon(result, left, cfg) {
73
+ const within = Number(cfg.soonWithin)
74
+ return Number.isFinite(within) && within >= 0 && left <= within ? { ...result, soon: true } : result
60
75
  }
61
76
 
62
77
  /**
@@ -150,6 +150,7 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
150
150
  'table',
151
151
  'tabs',
152
152
  'tasks',
153
+ 'team-workload',
153
154
  'text',
154
155
  'tile',
155
156
  'timeline',
@@ -56,3 +56,13 @@ export function sameSortKeys(a, b) {
56
56
  return left.length === right.length
57
57
  && left.every((entry, index) => entry.key === right[index].key && (entry.order || 'asc') === (right[index].order || 'asc'))
58
58
  }
59
+
60
+ /**
61
+ * The same keys, each direction turned round.
62
+ *
63
+ * @param {Array<{key: string, order?: string}>} keys The sort keys.
64
+ * @return {Array<{key: string, order: 'asc'|'desc'}>} The reversed keys.
65
+ */
66
+ export function reverseSortKeys(keys) {
67
+ return (Array.isArray(keys) ? keys : []).map((entry) => ({ key: entry.key, order: entry.order === 'desc' ? 'asc' : 'desc' }))
68
+ }