@conduction/nextcloud-vue 2.78.0 → 2.79.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.
- package/dist/bundled-packages.json +0 -2
- package/dist/esm/bundled-packages.json +0 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +113 -104
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +80 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +115 -91
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +61 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
- package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +25 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +3 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
- package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +30 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/composables/useAppManifest.js +8 -3
- package/dist/esm/composables/useAppManifest.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +61 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +106 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +314 -15
- package/dist/esm/utils/boardDate.js +193 -0
- package/dist/esm/utils/boardDate.js.map +1 -0
- package/dist/esm/utils/dynamicProperties.js +5 -1
- package/dist/esm/utils/dynamicProperties.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2039 -581
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +314 -15
- package/l10n/en.json +61 -1
- package/l10n/nl.json +106 -2
- package/package.json +1 -1
- package/scripts/build-validators.js +26 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +48 -39
- package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +119 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
- package/src/components/CnDataTable/CnDataTable.md +2 -0
- package/src/components/CnDataTable/CnDataTable.vue +81 -2
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +61 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
- package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
- package/src/components/CnFilesBrowser/index.js +1 -0
- package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +27 -3
- package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
- package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
- package/src/components/CnTabsWidget/CnTabsWidget.vue +30 -1
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
- package/src/components/index.js +1 -1
- package/src/composables/useAppManifest.js +8 -3
- package/src/css/look-board-detail.css +10 -0
- package/src/css/look-board-index.css +92 -14
- package/src/css/look-board.css +77 -0
- package/src/css/table.css +37 -0
- package/src/index.js +4 -0
- package/src/schemas/app-manifest-v2.schema.json +46 -1
- package/src/utils/boardDate.js +189 -0
- package/src/utils/dynamicProperties.js +5 -1
- package/src/utils/validateManifestV2.compiled.js +57 -2
- package/dist/esm/_virtual/equal.js +0 -4
- package/dist/esm/_virtual/equal.js.map +0 -1
- package/dist/esm/_virtual/ucs2length.js +0 -4
- package/dist/esm/_virtual/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
- package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
- package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
- package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
- package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
|
@@ -107,16 +107,27 @@
|
|
|
107
107
|
margin-inline-start: 0;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
/* The first of Save view
|
|
111
|
-
the
|
|
112
|
-
|
|
110
|
+
/* The first of Save view and the Filter + view switch unit takes the free
|
|
111
|
+
space (the board's `flex: 1` spacer), so they sit at the end of row 1.
|
|
112
|
+
When the line is full the unit wraps as one to the START of the next line
|
|
113
|
+
while Save view stays at the end of row 1 (DqZaken); without Save view the
|
|
114
|
+
unit sits at the end of row 1 (PqTickets, PqLeads, PtPortals). */
|
|
115
|
+
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > :is(.cn-saved-views__save, .cn-actions-bar__view-controls) {
|
|
113
116
|
margin-inline-start: auto;
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views >
|
|
119
|
+
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-saved-views__save ~ .cn-actions-bar__view-controls {
|
|
117
120
|
margin-inline-start: 0;
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
.cn-look-board .cn-actions-bar--board .cn-actions-bar__view-controls {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex: 0 0 auto;
|
|
126
|
+
flex-wrap: nowrap;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: 8px;
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
/* Row 2: the search field and the active filters on a line of their own. */
|
|
121
132
|
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--search {
|
|
122
133
|
display: flex;
|
|
@@ -202,16 +213,39 @@
|
|
|
202
213
|
color: var(--color-primary-element-text);
|
|
203
214
|
}
|
|
204
215
|
|
|
205
|
-
/* Save view: 34px, radius 17, on the light primary
|
|
216
|
+
/* Save view: a ghost button, 34px, radius 17, no border, on the light primary
|
|
217
|
+
tint, a 14px icon 6px before the label, on one line. */
|
|
206
218
|
.cn-look-board .cn-saved-views__save .button-vue {
|
|
207
219
|
min-height: 34px;
|
|
208
220
|
height: 34px;
|
|
209
|
-
padding: 0
|
|
221
|
+
padding: 0 12px;
|
|
222
|
+
border: 0;
|
|
210
223
|
border-radius: 17px;
|
|
211
224
|
background: var(--color-primary-element-light);
|
|
212
225
|
color: var(--color-primary-element-light-text);
|
|
213
226
|
font-size: 13px;
|
|
214
227
|
font-weight: 600;
|
|
228
|
+
white-space: nowrap;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.cn-look-board .cn-saved-views__save .button-vue:hover {
|
|
232
|
+
background: var(--color-primary-element-light-hover);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__wrapper {
|
|
236
|
+
gap: 6px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__icon {
|
|
240
|
+
width: auto;
|
|
241
|
+
min-width: 0;
|
|
242
|
+
height: auto;
|
|
243
|
+
min-height: 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__text {
|
|
247
|
+
margin: 0;
|
|
248
|
+
padding: 0;
|
|
215
249
|
}
|
|
216
250
|
|
|
217
251
|
/* Filter: a labelled secondary button, 36px, radius 8, with the active count. */
|
|
@@ -443,6 +477,29 @@
|
|
|
443
477
|
text-decoration: none;
|
|
444
478
|
}
|
|
445
479
|
|
|
480
|
+
/* Only the title is underlined (DqZaken). An underline set on the cell
|
|
481
|
+
reaches every in-flow child whatever its own text-decoration, so the
|
|
482
|
+
secondary lines become inline blocks on a line of their own: an atomic
|
|
483
|
+
inline box is the one thing the underline does not reach. */
|
|
484
|
+
.cn-look-board .cn-table-container--board .cn-data-table td.cn-table-col--title .cn-table-cell__secondary {
|
|
485
|
+
display: inline-block;
|
|
486
|
+
width: 100%;
|
|
487
|
+
vertical-align: top;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* `rowTitle: "plain"` (PqTickets, OcPublicaties): the title is bold text in
|
|
491
|
+
the main text colour without an underline, the secondary line 13px muted,
|
|
492
|
+
2px under it. The row stays clickable through its stretched row link. */
|
|
493
|
+
.cn-look-board .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title {
|
|
494
|
+
font-weight: 700;
|
|
495
|
+
text-decoration: none;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.cn-look-board .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title .cn-table-cell__secondary {
|
|
499
|
+
margin-top: 2px;
|
|
500
|
+
font-size: 13px;
|
|
501
|
+
}
|
|
502
|
+
|
|
446
503
|
/* A status pill: 3px 10px, radius 12, 13px weight 600. The colours stay the
|
|
447
504
|
status colour map's. */
|
|
448
505
|
.cn-look-board .cn-table-container--board .cn-data-table .cn-status-badge {
|
|
@@ -465,15 +522,36 @@
|
|
|
465
522
|
--default-clickable-area: 34px;
|
|
466
523
|
}
|
|
467
524
|
|
|
525
|
+
/* The row menu: a 34px square, three dots, a grey border on the main
|
|
526
|
+
background (DqZaken, PqTickets). A theme that styles every secondary button
|
|
527
|
+
with `!important` (the nldesign theme: a primary border, 8px 16px padding,
|
|
528
|
+
`min-width: max-content`) would otherwise turn it into a 64px wide button
|
|
529
|
+
with a blue border, so the board values are `!important` too. */
|
|
530
|
+
.cn-look-board .cn-row-actions--board .button-vue,
|
|
531
|
+
.cn-look-board .cn-row-actions--board .button-vue:hover,
|
|
532
|
+
.cn-look-board .cn-row-actions--board .button-vue:focus {
|
|
533
|
+
box-sizing: border-box !important;
|
|
534
|
+
width: 34px !important;
|
|
535
|
+
min-width: 34px !important;
|
|
536
|
+
height: 34px !important;
|
|
537
|
+
min-height: 34px !important;
|
|
538
|
+
padding: 0 !important;
|
|
539
|
+
border: 1px solid var(--color-border-dark) !important;
|
|
540
|
+
border-radius: 8px !important;
|
|
541
|
+
color: var(--cn-board-text-soft) !important;
|
|
542
|
+
}
|
|
543
|
+
|
|
468
544
|
.cn-look-board .cn-row-actions--board .button-vue {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
545
|
+
background: var(--color-main-background) !important;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.cn-look-board .cn-row-actions--board .button-vue:hover {
|
|
549
|
+
background: var(--color-background-hover) !important;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.cn-look-board .cn-row-actions--board .button-vue:focus-visible {
|
|
553
|
+
outline: 2px solid var(--color-main-text);
|
|
554
|
+
outline-offset: 2px;
|
|
477
555
|
}
|
|
478
556
|
|
|
479
557
|
/* ------------------------------------------------------------------ footer */
|
package/src/css/look-board.css
CHANGED
|
@@ -146,6 +146,14 @@
|
|
|
146
146
|
margin-inline-start: -264px;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/* The navigation draws the line between it and the content. NcContent draws a
|
|
150
|
+
second one on the content while the navigation is open, which doubled the
|
|
151
|
+
line and put the page 1px further from the navigation than the board's 28px.
|
|
152
|
+
Outranks NcContent's rule (six classes) with seven. */
|
|
153
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"]:not(.app-navigation--closed):not(.app-navigation--close) ~ .app-content.app-content {
|
|
154
|
+
border-inline-start: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
149
157
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__content {
|
|
150
158
|
box-sizing: border-box;
|
|
151
159
|
gap: 20px;
|
|
@@ -155,6 +163,31 @@
|
|
|
155
163
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__list {
|
|
156
164
|
height: auto;
|
|
157
165
|
flex: 1 1 auto;
|
|
166
|
+
margin: 0;
|
|
167
|
+
gap: 2px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* The board's vertical rhythm (AppZijbalk): the primary action 20px from the
|
|
171
|
+
top, the list 20px under it, entries 2px apart. An empty search slot is a
|
|
172
|
+
flex child too and pushed everything 20px down, so it goes. */
|
|
173
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__search:empty {
|
|
174
|
+
display: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* A caption is a 12px label with 6px under it, 20px after the group above
|
|
178
|
+
and 2px before its first entry. NcAppNavigationCaption gives its name a
|
|
179
|
+
15px font on a 34px line, which made it 44px high. */
|
|
180
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__list > .app-navigation-caption:not(:first-child) {
|
|
181
|
+
margin-top: 18px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-caption .app-navigation-caption__name {
|
|
185
|
+
margin: 0;
|
|
186
|
+
padding: 0;
|
|
187
|
+
font-size: inherit;
|
|
188
|
+
font-weight: inherit;
|
|
189
|
+
line-height: normal;
|
|
190
|
+
color: inherit;
|
|
158
191
|
}
|
|
159
192
|
|
|
160
193
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-entry,
|
|
@@ -239,6 +272,43 @@
|
|
|
239
272
|
font-size: 15px;
|
|
240
273
|
}
|
|
241
274
|
|
|
275
|
+
/* The footer of the board: Help and Advanced as two 40px rows at weight 400,
|
|
276
|
+
2px apart. The Help row kept the 42px of a list entry, and the Advanced
|
|
277
|
+
foldout drew a 34px button inside 4px of padding, 20px under Help. */
|
|
278
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list {
|
|
279
|
+
gap: 2px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list .app-navigation-entry,
|
|
283
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list .app-navigation-entry-link {
|
|
284
|
+
min-height: 40px;
|
|
285
|
+
font-weight: 400;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] {
|
|
289
|
+
padding: 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list + [data-testid="cn-nav-settings"] {
|
|
293
|
+
margin-top: -18px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] > :first-child {
|
|
297
|
+
margin: 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] > :first-child .button-vue {
|
|
301
|
+
box-sizing: border-box;
|
|
302
|
+
width: 100%;
|
|
303
|
+
height: 40px;
|
|
304
|
+
min-height: 40px;
|
|
305
|
+
padding: 0 10px;
|
|
306
|
+
border-radius: 8px;
|
|
307
|
+
font-size: 15px;
|
|
308
|
+
font-weight: 400;
|
|
309
|
+
justify-content: flex-start;
|
|
310
|
+
}
|
|
311
|
+
|
|
242
312
|
/* The card above the footer, pushed down. */
|
|
243
313
|
.cn-look-board .cn-app-nav__card {
|
|
244
314
|
order: 0;
|
|
@@ -443,3 +513,10 @@
|
|
|
443
513
|
.cn-look-board .cn-status-badge--primary:not(.cn-status-badge--solid):not(.cn-look-nextcloud *) {
|
|
444
514
|
color: var(--color-primary-element-light-text);
|
|
445
515
|
}
|
|
516
|
+
|
|
517
|
+
/* ---- Date and age cells (screens-cell-date-parity) ---------------------- */
|
|
518
|
+
/* The board sets every waiting time ("3 dagen", "4 uur") in weight 600; a
|
|
519
|
+
late one is red through its variant class. */
|
|
520
|
+
.cn-look-board .cn-cell-renderer__age:not(.cn-look-nextcloud *) {
|
|
521
|
+
font-weight: 600;
|
|
522
|
+
}
|
package/src/css/table.css
CHANGED
|
@@ -230,6 +230,34 @@
|
|
|
230
230
|
min-width: 0;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/* Fit to the container (CnDataTable's `fitWidth`, on in CnWidgetObjectTable).
|
|
234
|
+
|
|
235
|
+
In a narrow tile the auto layout made the table as wide as its one-line
|
|
236
|
+
cells, so it ran past the tile and clipped the trailing column (a date).
|
|
237
|
+
Here every column but one takes the width of its content (`width: 1%` asks
|
|
238
|
+
for as little as the content allows), and the grow column takes what is
|
|
239
|
+
left. `max-width: 0` is what lets that column go below its content's width:
|
|
240
|
+
the table no longer counts its text, so the text is cut with an ellipsis
|
|
241
|
+
instead of widening the table. The full value stays in the cell's tooltip. */
|
|
242
|
+
.cn-data-table--fit {
|
|
243
|
+
width: 100%;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.cn-data-table--fit th,
|
|
247
|
+
.cn-data-table--fit td {
|
|
248
|
+
width: 1%;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.cn-data-table--fit th.cn-table-col--grow,
|
|
252
|
+
.cn-data-table--fit td.cn-table-col--grow {
|
|
253
|
+
width: auto;
|
|
254
|
+
min-width: 0;
|
|
255
|
+
max-width: 0;
|
|
256
|
+
overflow: hidden;
|
|
257
|
+
text-overflow: ellipsis;
|
|
258
|
+
white-space: nowrap;
|
|
259
|
+
}
|
|
260
|
+
|
|
233
261
|
.cn-data-table th {
|
|
234
262
|
background: var(--color-background-dark);
|
|
235
263
|
font-weight: 500;
|
|
@@ -578,6 +606,15 @@
|
|
|
578
606
|
color: var(--color-text-maxcontrast);
|
|
579
607
|
}
|
|
580
608
|
|
|
609
|
+
/* The empty row holds a sentence (the empty text, or why a lens cannot
|
|
610
|
+
answer), not a cell value: it wraps instead of being cut to one line. The
|
|
611
|
+
selector carries the table class so it outranks the one-line cell rule. */
|
|
612
|
+
.cn-data-table .cn-table-empty td {
|
|
613
|
+
max-width: none;
|
|
614
|
+
white-space: normal;
|
|
615
|
+
overflow-wrap: anywhere;
|
|
616
|
+
}
|
|
617
|
+
|
|
581
618
|
/* ========================================
|
|
582
619
|
Backwards compatibility aliases
|
|
583
620
|
(legacy OpenRegister CSS class names)
|
package/src/index.js
CHANGED
|
@@ -338,6 +338,10 @@ export {
|
|
|
338
338
|
// @deprecated Use `CnBuildiqEditButton`.
|
|
339
339
|
export { CnOpenBuildEditButton } from './components/index.js'
|
|
340
340
|
|
|
341
|
+
// A CnFilesBrowser data source that reads an OpenRegister object's folder
|
|
342
|
+
// through OpenRegister's files API (openspec: files-browser-openregister-source).
|
|
343
|
+
export { createOpenRegisterSource } from './components/index.js'
|
|
344
|
+
|
|
341
345
|
// Workplace dashboard primitives (openspec: workplace-dashboard-primitives):
|
|
342
346
|
// the week strip and stacked bar widgets with their forms, the segmented
|
|
343
347
|
// control, and the brand stripe.
|
|
@@ -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.
|
|
6
|
+
"version": "2.79.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -4004,6 +4004,40 @@
|
|
|
4004
4004
|
"default": [],
|
|
4005
4005
|
"description": "type='dashboard': widget ids drawn as a KPI row above the grid, in the listed order, in an auto-fit grid (`repeat(auto-fit, minmax(200px, 1fr))`, gap 16). The named widgets are not GridStack items, so they do not reflow with the grid and are not part of the emitted layout; an id that names no widget is skipped with a development warning (screens-dashboard-parity). Maps to CnDashboardPage `kpiRow`. `dateRange.control` also accepts \"segmented\": the presets as one compact segmented group under the header."
|
|
4006
4006
|
},
|
|
4007
|
+
"greeting": {
|
|
4008
|
+
"oneOf": [
|
|
4009
|
+
{ "type": "boolean" },
|
|
4010
|
+
{ "type": "string", "enum": ["first", "full"] }
|
|
4011
|
+
],
|
|
4012
|
+
"default": false,
|
|
4013
|
+
"description": "type='dashboard': greet the reader in the header subtitle. `true` or \"first\" writes \"Good afternoon, Pieter · Monday 5 October 2026\", \"full\" uses the whole display name; the page `description` follows after another middle dot (screens-dashboard-greeting-header). Maps to CnDashboardPage `greeting`."
|
|
4014
|
+
},
|
|
4015
|
+
"viewLinks": {
|
|
4016
|
+
"type": "array",
|
|
4017
|
+
"default": [],
|
|
4018
|
+
"description": "type='dashboard': links drawn as small pills on the switch row under the header, right of the view switch (screens-dashboard-greeting-header). `route` is a route name or a `{ name, params, query }` location, `href` a plain URL; `label` is an i18n key. An entry without a label or a target is skipped. Maps to CnDashboardPage `viewLinks`.",
|
|
4019
|
+
"items": {
|
|
4020
|
+
"type": "object",
|
|
4021
|
+
"required": ["label"],
|
|
4022
|
+
"additionalProperties": false,
|
|
4023
|
+
"properties": {
|
|
4024
|
+
"label": { "type": "string", "minLength": 1 },
|
|
4025
|
+
"icon": { "type": "string", "minLength": 1 },
|
|
4026
|
+
"route": {
|
|
4027
|
+
"oneOf": [
|
|
4028
|
+
{ "type": "string", "minLength": 1 },
|
|
4029
|
+
{ "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "minLength": 1 }, "params": { "type": "object" }, "query": { "type": "object" } } }
|
|
4030
|
+
]
|
|
4031
|
+
},
|
|
4032
|
+
"href": { "type": "string", "minLength": 1 }
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
},
|
|
4036
|
+
"showActionsMenu": {
|
|
4037
|
+
"type": "boolean",
|
|
4038
|
+
"default": true,
|
|
4039
|
+
"description": "type='dashboard': `false` removes the page Actions menu from the header, for a board page whose header carries only the buildiq square and the primary (screens-dashboard-greeting-header). Maps to CnDashboardPage `showActionsMenu`."
|
|
4040
|
+
},
|
|
4007
4041
|
"gridFloat": {
|
|
4008
4042
|
"type": "boolean",
|
|
4009
4043
|
"default": true,
|
|
@@ -4038,6 +4072,11 @@
|
|
|
4038
4072
|
"minLength": 1,
|
|
4039
4073
|
"description": "type='index', board look: free text after the footer's count, e.g. \"click a column header to sort\" (screens-index-list-parity). Maps to CnIndexPage `footerNote`."
|
|
4040
4074
|
},
|
|
4075
|
+
"searchPlaceholder": {
|
|
4076
|
+
"type": "string",
|
|
4077
|
+
"minLength": 1,
|
|
4078
|
+
"description": "type='index': the placeholder (and accessible name) of the list's search field, e.g. \"Search by case, number or requester\". Run through the app's translate, so an English key with a Dutch catalogue entry reads Dutch. Absent: the library's \"Search…\" (screens-index-toolbar-parity). Maps to CnIndexPage `searchPlaceholder`."
|
|
4079
|
+
},
|
|
4041
4080
|
"bulkHint": {
|
|
4042
4081
|
"type": "string",
|
|
4043
4082
|
"minLength": 1,
|
|
@@ -4065,6 +4104,12 @@
|
|
|
4065
4104
|
}
|
|
4066
4105
|
}
|
|
4067
4106
|
},
|
|
4107
|
+
"rowTitle": {
|
|
4108
|
+
"type": "string",
|
|
4109
|
+
"enum": ["link", "plain"],
|
|
4110
|
+
"default": "link",
|
|
4111
|
+
"description": "type='index' (screens-table-rows-parity): how the board look draws the table's title column. `link` (the default) is the 15px weight 600 underlined title of the DqZaken board; `plain` is the 15px bold title without an underline over a 13px muted secondary line, as the PqTickets and OcPublicaties boards draw it. The row stays clickable either way. The Nextcloud look ignores it. Maps to CnIndexPage `rowTitle`."
|
|
4112
|
+
},
|
|
4068
4113
|
"showTypeEyebrow": {
|
|
4069
4114
|
"type": "boolean",
|
|
4070
4115
|
"default": true,
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* boardDate: the two short time readings the drawn boards put in a table cell.
|
|
6
|
+
*
|
|
7
|
+
* - A date ("5 okt", "30 okt", "14 feb 2024"): day and short month in the
|
|
8
|
+
* user's language, the year only when it is not the current year.
|
|
9
|
+
* - An age ("4 uur", "1 dag", "3 dagen"): how long something has been
|
|
10
|
+
* waiting, in hours under a day and in whole calendar days after that.
|
|
11
|
+
*
|
|
12
|
+
* Both read the Nextcloud user LANGUAGE for the month and unit names. A
|
|
13
|
+
* Nextcloud account keeps its locale (date order) apart from its language,
|
|
14
|
+
* and a Dutch reader whose locale was left at the en_US default saw
|
|
15
|
+
* "Feb 14, 2024": the language decides the words, so it decides here, and the
|
|
16
|
+
* locale is only used when it is a region of that same language.
|
|
17
|
+
*
|
|
18
|
+
* @module utils/boardDate
|
|
19
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { getCanonicalLocale, getLanguage, translatePlural as n } from '@nextcloud/l10n'
|
|
23
|
+
import { parseDateValue } from './dateVariant.js'
|
|
24
|
+
import { compareVisibleWhen } from './visibleWhen.js'
|
|
25
|
+
|
|
26
|
+
const MS_PER_HOUR = 3600000
|
|
27
|
+
const MS_PER_DAY = 86400000
|
|
28
|
+
|
|
29
|
+
/** Variants an age rule may name. `danger` is accepted as an alias of `error`. */
|
|
30
|
+
export const AGE_VARIANTS = Object.freeze(['default', 'success', 'warning', 'error'])
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read a language or locale tag from an @nextcloud/l10n getter, which throws
|
|
34
|
+
* when Nextcloud has not set one (outside a Nextcloud page, in a test).
|
|
35
|
+
*
|
|
36
|
+
* @param {() => string} getter `getLanguage` or `getCanonicalLocale`.
|
|
37
|
+
* @return {string} The tag with `-` separators, or ''.
|
|
38
|
+
*/
|
|
39
|
+
function readTag(getter) {
|
|
40
|
+
try {
|
|
41
|
+
return String(getter() || '').replace(/_/g, '-')
|
|
42
|
+
} catch {
|
|
43
|
+
return ''
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The BCP 47 tag the board dates are written in: the user's locale when it is
|
|
49
|
+
* a region of the user's language (`nl-BE` for language `nl`), else the
|
|
50
|
+
* language itself.
|
|
51
|
+
*
|
|
52
|
+
* @return {string|undefined} The tag, or undefined to let Intl choose.
|
|
53
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language
|
|
54
|
+
*/
|
|
55
|
+
export function boardDateLocale() {
|
|
56
|
+
const language = readTag(getLanguage)
|
|
57
|
+
const locale = readTag(getCanonicalLocale)
|
|
58
|
+
const base = (tag) => tag.split('-')[0].toLowerCase()
|
|
59
|
+
if (language && locale && base(language) === base(locale)) {
|
|
60
|
+
return locale
|
|
61
|
+
}
|
|
62
|
+
return language || locale || undefined
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A date in the board's short form: "5 okt", or "14 feb 2024" outside the
|
|
67
|
+
* current year.
|
|
68
|
+
*
|
|
69
|
+
* @param {unknown} value A Date, a timestamp or an ISO string.
|
|
70
|
+
* @param {Date} [now] The reference moment (defaults to the current time).
|
|
71
|
+
* @param {string} [locale] BCP 47 tag (defaults to `boardDateLocale()`).
|
|
72
|
+
* @return {string} The short date, or '' when the value is not a date.
|
|
73
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language
|
|
74
|
+
*/
|
|
75
|
+
export function formatBoardDate(value, now = new Date(), locale = boardDateLocale()) {
|
|
76
|
+
const date = parseDateValue(value)
|
|
77
|
+
if (date === null) {
|
|
78
|
+
return ''
|
|
79
|
+
}
|
|
80
|
+
const options = { day: 'numeric', month: 'short' }
|
|
81
|
+
if (date.getFullYear() !== now.getFullYear()) {
|
|
82
|
+
options.year = 'numeric'
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
return new Intl.DateTimeFormat(locale, options).format(date)
|
|
86
|
+
} catch {
|
|
87
|
+
return new Intl.DateTimeFormat(undefined, options).format(date)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whole calendar days from the date until `now` (0 today, 1 yesterday), or
|
|
93
|
+
* null when the value is not a date. A future date gives a negative number.
|
|
94
|
+
*
|
|
95
|
+
* @param {unknown} value The date.
|
|
96
|
+
* @param {Date} [now] The reference moment.
|
|
97
|
+
* @return {number|null} The day count.
|
|
98
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited
|
|
99
|
+
*/
|
|
100
|
+
export function ageInDays(value, now = new Date()) {
|
|
101
|
+
const date = parseDateValue(value)
|
|
102
|
+
if (date === null) {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
// UTC midnights of the two LOCAL days, so a daylight-saving hour never
|
|
106
|
+
// turns one day into zero or two.
|
|
107
|
+
const start = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())
|
|
108
|
+
const today = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate())
|
|
109
|
+
return Math.round((today - start) / MS_PER_DAY)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* How long something has waited: "4 uur" under 24 hours (at least 1), else
|
|
114
|
+
* "1 dag" / "3 dagen" in whole calendar days. Plural forms come from the
|
|
115
|
+
* library's own catalogue (`nextcloud-vue`). A date in the future reads as
|
|
116
|
+
* waiting no time: "0 hours".
|
|
117
|
+
*
|
|
118
|
+
* @param {unknown} value The moment the wait started.
|
|
119
|
+
* @param {Date} [now] The reference moment.
|
|
120
|
+
* @return {string} The age, or '' when the value is not a date.
|
|
121
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited
|
|
122
|
+
*/
|
|
123
|
+
export function formatAge(value, now = new Date()) {
|
|
124
|
+
const date = parseDateValue(value)
|
|
125
|
+
if (date === null) {
|
|
126
|
+
return ''
|
|
127
|
+
}
|
|
128
|
+
const elapsed = now.getTime() - date.getTime()
|
|
129
|
+
if (elapsed < MS_PER_DAY) {
|
|
130
|
+
const hours = elapsed <= 0 ? 0 : Math.max(1, Math.floor(elapsed / MS_PER_HOUR))
|
|
131
|
+
return n('nextcloud-vue', '%n hour', '%n hours', hours)
|
|
132
|
+
}
|
|
133
|
+
const days = shownAgeDays(date, now)
|
|
134
|
+
return n('nextcloud-vue', '%n day', '%n days', days)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The number of days an age cell shows: 0 under 24 hours (the cell then reads
|
|
139
|
+
* hours), else the whole calendar days, at least 1. The threshold rules
|
|
140
|
+
* compare against this number, so the colour always agrees with the text.
|
|
141
|
+
*
|
|
142
|
+
* @param {unknown} value The moment the wait started.
|
|
143
|
+
* @param {Date} [now] The reference moment.
|
|
144
|
+
* @return {number|null} The day count, or null when the value is not a date.
|
|
145
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold
|
|
146
|
+
*/
|
|
147
|
+
export function shownAgeDays(value, now = new Date()) {
|
|
148
|
+
const date = parseDateValue(value)
|
|
149
|
+
if (date === null) {
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
if (now.getTime() - date.getTime() < MS_PER_DAY) {
|
|
153
|
+
return 0
|
|
154
|
+
}
|
|
155
|
+
return Math.max(1, ageInDays(date, now))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The variant of the first `variantWhen` rule the age in days matches, or ''
|
|
160
|
+
* when none does. Same rule shape as the `date` widget (`{ op, value,
|
|
161
|
+
* variant }`, first match wins), compared against the number of days the cell
|
|
162
|
+
* shows (`shownAgeDays`): `[{ op: "gte", value: 3, variant: "error" }]` turns a wait of three
|
|
163
|
+
* days or more red.
|
|
164
|
+
*
|
|
165
|
+
* @param {unknown} value The moment the wait started.
|
|
166
|
+
* @param {Array<{op: string, value: number, variant: string}>} rules The rules.
|
|
167
|
+
* @param {Date} [now] The reference moment.
|
|
168
|
+
* @return {string} `success | warning | error | default`, or ''.
|
|
169
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold
|
|
170
|
+
*/
|
|
171
|
+
export function resolveAgeVariant(value, rules, now = new Date()) {
|
|
172
|
+
if (!Array.isArray(rules) || rules.length === 0) {
|
|
173
|
+
return ''
|
|
174
|
+
}
|
|
175
|
+
const days = shownAgeDays(value, now)
|
|
176
|
+
if (days === null) {
|
|
177
|
+
return ''
|
|
178
|
+
}
|
|
179
|
+
for (const rule of rules) {
|
|
180
|
+
if (!rule || typeof rule !== 'object') {
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
if (compareVisibleWhen(days, rule.op || 'eq', rule.value) === true) {
|
|
184
|
+
const variant = rule.variant === 'danger' ? 'error' : rule.variant
|
|
185
|
+
return AGE_VARIANTS.includes(variant) ? variant : ''
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return ''
|
|
189
|
+
}
|
|
@@ -101,6 +101,7 @@ const DEFAULT_MAP = {
|
|
|
101
101
|
enum: 'enumValues',
|
|
102
102
|
required: 'isRequired',
|
|
103
103
|
default: 'defaultValue',
|
|
104
|
+
hideOnCreate: 'hideOnCreate',
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/**
|
|
@@ -312,6 +313,8 @@ export function definitionQueryParams(config, value, formData = {}) {
|
|
|
312
313
|
* @param {object} [config] The `x-openregister-extends-form` block (`map`, `typeMap`).
|
|
313
314
|
* @param {object} [options] Extra options.
|
|
314
315
|
* @param {number} [options.orderFrom] Order assigned to the first field; each subsequent field takes the next integer. Dynamic fields sort after the schema's own, so pass a number above every declared `order`.
|
|
316
|
+
* @param {boolean} [options.create] The form creates a new object: a definition whose `hideOnCreate` (or the field `config.map.hideOnCreate` names) is `true` gets no field, because the app fills that answer itself at creation. Edit forms still show it.
|
|
317
|
+
* @spec openspec/changes/dynamic-question-hide-on-create/specs/dynamic-question-hide-on-create/spec.md#requirement-a-question-can-stay-off-the-create-form
|
|
315
318
|
* @return {{properties: object, required: string[]}} Properties keyed by prefixed id, plus the required key list.
|
|
316
319
|
*/
|
|
317
320
|
export function propertiesFromDefinitions(definitions, config = {}, options = {}) {
|
|
@@ -321,7 +324,8 @@ export function propertiesFromDefinitions(definitions, config = {}, options = {}
|
|
|
321
324
|
|
|
322
325
|
const properties = {}
|
|
323
326
|
const required = []
|
|
324
|
-
const list = Array.isArray(definitions) ? definitions : []
|
|
327
|
+
const list = (Array.isArray(definitions) ? definitions : [])
|
|
328
|
+
.filter((record) => !(options.create === true && record && mapped(record, map, 'hideOnCreate') === true))
|
|
325
329
|
|
|
326
330
|
list.forEach((record, index) => {
|
|
327
331
|
if (!record || !record.id) {
|