@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
|
@@ -213,6 +213,14 @@
|
|
|
213
213
|
margin-inline-start: -264px;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
/* The navigation draws the line between it and the content. NcContent draws a
|
|
217
|
+
second one on the content while the navigation is open, which doubled the
|
|
218
|
+
line and put the page 1px further from the navigation than the board's 28px.
|
|
219
|
+
Outranks NcContent's rule (six classes) with seven. */
|
|
220
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"]:not(.app-navigation--closed):not(.app-navigation--close) ~ .app-content.app-content {
|
|
221
|
+
border-inline-start: none;
|
|
222
|
+
}
|
|
223
|
+
|
|
216
224
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__content {
|
|
217
225
|
box-sizing: border-box;
|
|
218
226
|
gap: 20px;
|
|
@@ -222,6 +230,31 @@
|
|
|
222
230
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__list {
|
|
223
231
|
height: auto;
|
|
224
232
|
flex: 1 1 auto;
|
|
233
|
+
margin: 0;
|
|
234
|
+
gap: 2px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* The board's vertical rhythm (AppZijbalk): the primary action 20px from the
|
|
238
|
+
top, the list 20px under it, entries 2px apart. An empty search slot is a
|
|
239
|
+
flex child too and pushed everything 20px down, so it goes. */
|
|
240
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__search:empty {
|
|
241
|
+
display: none;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* A caption is a 12px label with 6px under it, 20px after the group above
|
|
245
|
+
and 2px before its first entry. NcAppNavigationCaption gives its name a
|
|
246
|
+
15px font on a 34px line, which made it 44px high. */
|
|
247
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation__list > .app-navigation-caption:not(:first-child) {
|
|
248
|
+
margin-top: 18px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-caption .app-navigation-caption__name {
|
|
252
|
+
margin: 0;
|
|
253
|
+
padding: 0;
|
|
254
|
+
font-size: inherit;
|
|
255
|
+
font-weight: inherit;
|
|
256
|
+
line-height: normal;
|
|
257
|
+
color: inherit;
|
|
225
258
|
}
|
|
226
259
|
|
|
227
260
|
.cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-entry,
|
|
@@ -306,6 +339,43 @@
|
|
|
306
339
|
font-size: 15px;
|
|
307
340
|
}
|
|
308
341
|
|
|
342
|
+
/* The footer of the board: Help and Advanced as two 40px rows at weight 400,
|
|
343
|
+
2px apart. The Help row kept the 42px of a list entry, and the Advanced
|
|
344
|
+
foldout drew a 34px button inside 4px of padding, 20px under Help. */
|
|
345
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list {
|
|
346
|
+
gap: 2px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list .app-navigation-entry,
|
|
350
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list .app-navigation-entry-link {
|
|
351
|
+
min-height: 40px;
|
|
352
|
+
font-weight: 400;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] {
|
|
356
|
+
padding: 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] .cn-app-nav__footer-list + [data-testid="cn-nav-settings"] {
|
|
360
|
+
margin-top: -18px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] > :first-child {
|
|
364
|
+
margin: 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.cn-look-board .app-navigation[data-testid="cn-nav"] [data-testid="cn-nav-settings"] > :first-child .button-vue {
|
|
368
|
+
box-sizing: border-box;
|
|
369
|
+
width: 100%;
|
|
370
|
+
height: 40px;
|
|
371
|
+
min-height: 40px;
|
|
372
|
+
padding: 0 10px;
|
|
373
|
+
border-radius: 8px;
|
|
374
|
+
font-size: 15px;
|
|
375
|
+
font-weight: 400;
|
|
376
|
+
justify-content: flex-start;
|
|
377
|
+
}
|
|
378
|
+
|
|
309
379
|
/* The card above the footer, pushed down. */
|
|
310
380
|
.cn-look-board .cn-app-nav__card {
|
|
311
381
|
order: 0;
|
|
@@ -510,6 +580,13 @@
|
|
|
510
580
|
.cn-look-board .cn-status-badge--primary:not(.cn-status-badge--solid):not(.cn-look-nextcloud *) {
|
|
511
581
|
color: var(--color-primary-element-light-text);
|
|
512
582
|
}
|
|
583
|
+
|
|
584
|
+
/* ---- Date and age cells (screens-cell-date-parity) ---------------------- */
|
|
585
|
+
/* The board sets every waiting time ("3 dagen", "4 uur") in weight 600; a
|
|
586
|
+
late one is red through its variant class. */
|
|
587
|
+
.cn-look-board .cn-cell-renderer__age:not(.cn-look-nextcloud *) {
|
|
588
|
+
font-weight: 600;
|
|
589
|
+
}
|
|
513
590
|
/*
|
|
514
591
|
* The board dialog (screens-dialog-parity, canon section 5).
|
|
515
592
|
*
|
|
@@ -1262,16 +1339,27 @@
|
|
|
1262
1339
|
margin-inline-start: 0;
|
|
1263
1340
|
}
|
|
1264
1341
|
|
|
1265
|
-
/* The first of Save view
|
|
1266
|
-
the
|
|
1267
|
-
|
|
1342
|
+
/* The first of Save view and the Filter + view switch unit takes the free
|
|
1343
|
+
space (the board's `flex: 1` spacer), so they sit at the end of row 1.
|
|
1344
|
+
When the line is full the unit wraps as one to the START of the next line
|
|
1345
|
+
while Save view stays at the end of row 1 (DqZaken); without Save view the
|
|
1346
|
+
unit sits at the end of row 1 (PqTickets, PqLeads, PtPortals). */
|
|
1347
|
+
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > :is(.cn-saved-views__save, .cn-actions-bar__view-controls) {
|
|
1268
1348
|
margin-inline-start: auto;
|
|
1269
1349
|
}
|
|
1270
1350
|
|
|
1271
|
-
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views >
|
|
1351
|
+
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-saved-views__save ~ .cn-actions-bar__view-controls {
|
|
1272
1352
|
margin-inline-start: 0;
|
|
1273
1353
|
}
|
|
1274
1354
|
|
|
1355
|
+
.cn-look-board .cn-actions-bar--board .cn-actions-bar__view-controls {
|
|
1356
|
+
display: flex;
|
|
1357
|
+
flex: 0 0 auto;
|
|
1358
|
+
flex-wrap: nowrap;
|
|
1359
|
+
align-items: center;
|
|
1360
|
+
gap: 8px;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1275
1363
|
/* Row 2: the search field and the active filters on a line of their own. */
|
|
1276
1364
|
.cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--search {
|
|
1277
1365
|
display: flex;
|
|
@@ -1357,16 +1445,39 @@
|
|
|
1357
1445
|
color: var(--color-primary-element-text);
|
|
1358
1446
|
}
|
|
1359
1447
|
|
|
1360
|
-
/* Save view: 34px, radius 17, on the light primary
|
|
1448
|
+
/* Save view: a ghost button, 34px, radius 17, no border, on the light primary
|
|
1449
|
+
tint, a 14px icon 6px before the label, on one line. */
|
|
1361
1450
|
.cn-look-board .cn-saved-views__save .button-vue {
|
|
1362
1451
|
min-height: 34px;
|
|
1363
1452
|
height: 34px;
|
|
1364
|
-
padding: 0
|
|
1453
|
+
padding: 0 12px;
|
|
1454
|
+
border: 0;
|
|
1365
1455
|
border-radius: 17px;
|
|
1366
1456
|
background: var(--color-primary-element-light);
|
|
1367
1457
|
color: var(--color-primary-element-light-text);
|
|
1368
1458
|
font-size: 13px;
|
|
1369
1459
|
font-weight: 600;
|
|
1460
|
+
white-space: nowrap;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
.cn-look-board .cn-saved-views__save .button-vue:hover {
|
|
1464
|
+
background: var(--color-primary-element-light-hover);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__wrapper {
|
|
1468
|
+
gap: 6px;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__icon {
|
|
1472
|
+
width: auto;
|
|
1473
|
+
min-width: 0;
|
|
1474
|
+
height: auto;
|
|
1475
|
+
min-height: 0;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
.cn-look-board .cn-saved-views__save .button-vue .button-vue__text {
|
|
1479
|
+
margin: 0;
|
|
1480
|
+
padding: 0;
|
|
1370
1481
|
}
|
|
1371
1482
|
|
|
1372
1483
|
/* Filter: a labelled secondary button, 36px, radius 8, with the active count. */
|
|
@@ -1598,6 +1709,29 @@
|
|
|
1598
1709
|
text-decoration: none;
|
|
1599
1710
|
}
|
|
1600
1711
|
|
|
1712
|
+
/* Only the title is underlined (DqZaken). An underline set on the cell
|
|
1713
|
+
reaches every in-flow child whatever its own text-decoration, so the
|
|
1714
|
+
secondary lines become inline blocks on a line of their own: an atomic
|
|
1715
|
+
inline box is the one thing the underline does not reach. */
|
|
1716
|
+
.cn-look-board .cn-table-container--board .cn-data-table td.cn-table-col--title .cn-table-cell__secondary {
|
|
1717
|
+
display: inline-block;
|
|
1718
|
+
width: 100%;
|
|
1719
|
+
vertical-align: top;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
/* `rowTitle: "plain"` (PqTickets, OcPublicaties): the title is bold text in
|
|
1723
|
+
the main text colour without an underline, the secondary line 13px muted,
|
|
1724
|
+
2px under it. The row stays clickable through its stretched row link. */
|
|
1725
|
+
.cn-look-board .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title {
|
|
1726
|
+
font-weight: 700;
|
|
1727
|
+
text-decoration: none;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
.cn-look-board .cn-table-container--board.cn-table-container--title-plain .cn-data-table td.cn-table-col--title .cn-table-cell__secondary {
|
|
1731
|
+
margin-top: 2px;
|
|
1732
|
+
font-size: 13px;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1601
1735
|
/* A status pill: 3px 10px, radius 12, 13px weight 600. The colours stay the
|
|
1602
1736
|
status colour map's. */
|
|
1603
1737
|
.cn-look-board .cn-table-container--board .cn-data-table .cn-status-badge {
|
|
@@ -1620,15 +1754,36 @@
|
|
|
1620
1754
|
--default-clickable-area: 34px;
|
|
1621
1755
|
}
|
|
1622
1756
|
|
|
1757
|
+
/* The row menu: a 34px square, three dots, a grey border on the main
|
|
1758
|
+
background (DqZaken, PqTickets). A theme that styles every secondary button
|
|
1759
|
+
with `!important` (the nldesign theme: a primary border, 8px 16px padding,
|
|
1760
|
+
`min-width: max-content`) would otherwise turn it into a 64px wide button
|
|
1761
|
+
with a blue border, so the board values are `!important` too. */
|
|
1762
|
+
.cn-look-board .cn-row-actions--board .button-vue,
|
|
1763
|
+
.cn-look-board .cn-row-actions--board .button-vue:hover,
|
|
1764
|
+
.cn-look-board .cn-row-actions--board .button-vue:focus {
|
|
1765
|
+
box-sizing: border-box !important;
|
|
1766
|
+
width: 34px !important;
|
|
1767
|
+
min-width: 34px !important;
|
|
1768
|
+
height: 34px !important;
|
|
1769
|
+
min-height: 34px !important;
|
|
1770
|
+
padding: 0 !important;
|
|
1771
|
+
border: 1px solid var(--color-border-dark) !important;
|
|
1772
|
+
border-radius: 8px !important;
|
|
1773
|
+
color: var(--cn-board-text-soft) !important;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1623
1776
|
.cn-look-board .cn-row-actions--board .button-vue {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1777
|
+
background: var(--color-main-background) !important;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
.cn-look-board .cn-row-actions--board .button-vue:hover {
|
|
1781
|
+
background: var(--color-background-hover) !important;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
.cn-look-board .cn-row-actions--board .button-vue:focus-visible {
|
|
1785
|
+
outline: 2px solid var(--color-main-text);
|
|
1786
|
+
outline-offset: 2px;
|
|
1632
1787
|
}
|
|
1633
1788
|
|
|
1634
1789
|
/* ------------------------------------------------------------------ footer */
|
|
@@ -2188,6 +2343,34 @@
|
|
|
2188
2343
|
min-width: 0;
|
|
2189
2344
|
}
|
|
2190
2345
|
|
|
2346
|
+
/* Fit to the container (CnDataTable's `fitWidth`, on in CnWidgetObjectTable).
|
|
2347
|
+
|
|
2348
|
+
In a narrow tile the auto layout made the table as wide as its one-line
|
|
2349
|
+
cells, so it ran past the tile and clipped the trailing column (a date).
|
|
2350
|
+
Here every column but one takes the width of its content (`width: 1%` asks
|
|
2351
|
+
for as little as the content allows), and the grow column takes what is
|
|
2352
|
+
left. `max-width: 0` is what lets that column go below its content's width:
|
|
2353
|
+
the table no longer counts its text, so the text is cut with an ellipsis
|
|
2354
|
+
instead of widening the table. The full value stays in the cell's tooltip. */
|
|
2355
|
+
.cn-data-table--fit {
|
|
2356
|
+
width: 100%;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
.cn-data-table--fit th,
|
|
2360
|
+
.cn-data-table--fit td {
|
|
2361
|
+
width: 1%;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
.cn-data-table--fit th.cn-table-col--grow,
|
|
2365
|
+
.cn-data-table--fit td.cn-table-col--grow {
|
|
2366
|
+
width: auto;
|
|
2367
|
+
min-width: 0;
|
|
2368
|
+
max-width: 0;
|
|
2369
|
+
overflow: hidden;
|
|
2370
|
+
text-overflow: ellipsis;
|
|
2371
|
+
white-space: nowrap;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2191
2374
|
.cn-data-table th {
|
|
2192
2375
|
background: var(--color-background-dark);
|
|
2193
2376
|
font-weight: 500;
|
|
@@ -2536,6 +2719,15 @@
|
|
|
2536
2719
|
color: var(--color-text-maxcontrast);
|
|
2537
2720
|
}
|
|
2538
2721
|
|
|
2722
|
+
/* The empty row holds a sentence (the empty text, or why a lens cannot
|
|
2723
|
+
answer), not a cell value: it wraps instead of being cut to one line. The
|
|
2724
|
+
selector carries the table class so it outranks the one-line cell rule. */
|
|
2725
|
+
.cn-data-table .cn-table-empty td {
|
|
2726
|
+
max-width: none;
|
|
2727
|
+
white-space: normal;
|
|
2728
|
+
overflow-wrap: anywhere;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2539
2731
|
/* ========================================
|
|
2540
2732
|
Backwards compatibility aliases
|
|
2541
2733
|
(legacy OpenRegister CSS class names)
|
|
@@ -5692,6 +5884,16 @@ a.cn-kpi-card--board * {
|
|
|
5692
5884
|
gap: 20px;
|
|
5693
5885
|
}
|
|
5694
5886
|
|
|
5887
|
+
/* GridStack insets every cell by its 12px margin, the outer edge included, so
|
|
5888
|
+
the tab strip started 12px below the top of the body column and stopped 12px
|
|
5889
|
+
short of its end, while the side column started at the top. Pulling the grid
|
|
5890
|
+
out by that margin puts its first card on the column's edges: the side column
|
|
5891
|
+
is level with the strip, and the strip ends where the body column ends. */
|
|
5892
|
+
.cn-look-board.cn-detail-page--with-side > .cn-detail-page__body > .cn-detail-page__grid {
|
|
5893
|
+
margin: -12px;
|
|
5894
|
+
width: auto;
|
|
5895
|
+
}
|
|
5896
|
+
|
|
5695
5897
|
/* ---- Folder tabs ------------------------------------------------------- */
|
|
5696
5898
|
|
|
5697
5899
|
/* The strip draws no line of its own (design-system pull request 147). The card under it
|
|
@@ -9236,9 +9438,24 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
|
|
9236
9438
|
does not rest on colour alone. */
|
|
9237
9439
|
.cn-cell-renderer__date--success[data-v-37d7b88f],
|
|
9238
9440
|
.cn-cell-renderer__date--warning[data-v-37d7b88f],
|
|
9239
|
-
.cn-cell-renderer__date--error[data-v-37d7b88f]
|
|
9441
|
+
.cn-cell-renderer__date--error[data-v-37d7b88f],
|
|
9442
|
+
.cn-cell-renderer__age--success[data-v-37d7b88f],
|
|
9443
|
+
.cn-cell-renderer__age--warning[data-v-37d7b88f],
|
|
9444
|
+
.cn-cell-renderer__age--error[data-v-37d7b88f] {
|
|
9240
9445
|
font-weight: 600;
|
|
9241
9446
|
}
|
|
9447
|
+
.cn-cell-renderer__age[data-v-37d7b88f] {
|
|
9448
|
+
white-space: nowrap;
|
|
9449
|
+
}
|
|
9450
|
+
.cn-cell-renderer__age--success[data-v-37d7b88f] {
|
|
9451
|
+
color: var(--color-text-success, var(--color-success-text));
|
|
9452
|
+
}
|
|
9453
|
+
.cn-cell-renderer__age--warning[data-v-37d7b88f] {
|
|
9454
|
+
color: var(--color-text-warning, var(--color-warning-text));
|
|
9455
|
+
}
|
|
9456
|
+
.cn-cell-renderer__age--error[data-v-37d7b88f] {
|
|
9457
|
+
color: var(--color-text-error, var(--color-error-text));
|
|
9458
|
+
}
|
|
9242
9459
|
.cn-cell-renderer__date--success[data-v-37d7b88f] {
|
|
9243
9460
|
color: var(--color-text-success, var(--color-success-text));
|
|
9244
9461
|
}
|
|
@@ -9534,6 +9751,34 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
|
|
9534
9751
|
min-width: 0;
|
|
9535
9752
|
}
|
|
9536
9753
|
|
|
9754
|
+
/* Fit to the container (CnDataTable's `fitWidth`, on in CnWidgetObjectTable).
|
|
9755
|
+
|
|
9756
|
+
In a narrow tile the auto layout made the table as wide as its one-line
|
|
9757
|
+
cells, so it ran past the tile and clipped the trailing column (a date).
|
|
9758
|
+
Here every column but one takes the width of its content (`width: 1%` asks
|
|
9759
|
+
for as little as the content allows), and the grow column takes what is
|
|
9760
|
+
left. `max-width: 0` is what lets that column go below its content's width:
|
|
9761
|
+
the table no longer counts its text, so the text is cut with an ellipsis
|
|
9762
|
+
instead of widening the table. The full value stays in the cell's tooltip. */
|
|
9763
|
+
.cn-data-table--fit {
|
|
9764
|
+
width: 100%;
|
|
9765
|
+
}
|
|
9766
|
+
|
|
9767
|
+
.cn-data-table--fit th,
|
|
9768
|
+
.cn-data-table--fit td {
|
|
9769
|
+
width: 1%;
|
|
9770
|
+
}
|
|
9771
|
+
|
|
9772
|
+
.cn-data-table--fit th.cn-table-col--grow,
|
|
9773
|
+
.cn-data-table--fit td.cn-table-col--grow {
|
|
9774
|
+
width: auto;
|
|
9775
|
+
min-width: 0;
|
|
9776
|
+
max-width: 0;
|
|
9777
|
+
overflow: hidden;
|
|
9778
|
+
text-overflow: ellipsis;
|
|
9779
|
+
white-space: nowrap;
|
|
9780
|
+
}
|
|
9781
|
+
|
|
9537
9782
|
.cn-data-table th {
|
|
9538
9783
|
background: var(--color-background-dark);
|
|
9539
9784
|
font-weight: 500;
|
|
@@ -9882,6 +10127,15 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
|
|
9882
10127
|
color: var(--color-text-maxcontrast);
|
|
9883
10128
|
}
|
|
9884
10129
|
|
|
10130
|
+
/* The empty row holds a sentence (the empty text, or why a lens cannot
|
|
10131
|
+
answer), not a cell value: it wraps instead of being cut to one line. The
|
|
10132
|
+
selector carries the table class so it outranks the one-line cell rule. */
|
|
10133
|
+
.cn-data-table .cn-table-empty td {
|
|
10134
|
+
max-width: none;
|
|
10135
|
+
white-space: normal;
|
|
10136
|
+
overflow-wrap: anywhere;
|
|
10137
|
+
}
|
|
10138
|
+
|
|
9885
10139
|
/* ========================================
|
|
9886
10140
|
Backwards compatibility aliases
|
|
9887
10141
|
(legacy OpenRegister CSS class names)
|
|
@@ -14545,6 +14799,13 @@ button.cn-files-card__open[data-v-46f7e3c9]:hover {
|
|
|
14545
14799
|
min-width: 0;
|
|
14546
14800
|
flex: 1 1 auto;
|
|
14547
14801
|
}
|
|
14802
|
+
|
|
14803
|
+
/* The crumbs give way, the controls do not: a long crumb (a folder named
|
|
14804
|
+
after a uuid) shrank the upload button to "Bestanden..." while the crumbs
|
|
14805
|
+
kept their width. */
|
|
14806
|
+
.cn-files-browser__bar[data-v-f5bdd9e2] > :not(.cn-files-browser__crumbs) {
|
|
14807
|
+
flex-shrink: 0;
|
|
14808
|
+
}
|
|
14548
14809
|
.cn-files-browser__file-input[data-v-f5bdd9e2] {
|
|
14549
14810
|
display: none;
|
|
14550
14811
|
}
|
|
@@ -35021,6 +35282,44 @@ button.cn-object-calendar__overflow[data-v-bea31862] {
|
|
|
35021
35282
|
height: var(--cn-board-control-height, 40px);
|
|
35022
35283
|
}
|
|
35023
35284
|
|
|
35285
|
+
/* The switch row under the header (screens-dashboard-greeting-header): the
|
|
35286
|
+
view switch left, the `viewLinks` pills right (DqMijnWerk: pills 34px high,
|
|
35287
|
+
radius 17, 13px/600, a 14px icon, primary light on primary deep). */
|
|
35288
|
+
.cn-dashboard-page__switch-row[data-v-824052fe] {
|
|
35289
|
+
display: flex;
|
|
35290
|
+
flex-wrap: wrap;
|
|
35291
|
+
align-items: center;
|
|
35292
|
+
gap: 10px;
|
|
35293
|
+
margin-bottom: var(--cn-board-section-gap, 20px);
|
|
35294
|
+
}
|
|
35295
|
+
.cn-dashboard-page__switch-row-spacer[data-v-824052fe] {
|
|
35296
|
+
flex: 1 1 auto;
|
|
35297
|
+
}
|
|
35298
|
+
.cn-dashboard-page__view-link[data-v-824052fe] {
|
|
35299
|
+
display: inline-flex;
|
|
35300
|
+
align-items: center;
|
|
35301
|
+
gap: 6px;
|
|
35302
|
+
height: 34px;
|
|
35303
|
+
padding: 0 12px;
|
|
35304
|
+
border-radius: 17px;
|
|
35305
|
+
background: var(--color-primary-element-light);
|
|
35306
|
+
color: var(--color-primary-element-light-text);
|
|
35307
|
+
font-size: 13px;
|
|
35308
|
+
font-weight: 600;
|
|
35309
|
+
white-space: nowrap;
|
|
35310
|
+
text-decoration: none !important;
|
|
35311
|
+
}
|
|
35312
|
+
.cn-dashboard-page__view-link[data-v-824052fe]:hover {
|
|
35313
|
+
background: var(--color-primary-element-light-hover, var(--color-primary-element-light));
|
|
35314
|
+
}
|
|
35315
|
+
.cn-dashboard-page__view-link[data-v-824052fe]:focus-visible {
|
|
35316
|
+
outline: 2px solid var(--color-main-text);
|
|
35317
|
+
outline-offset: 2px;
|
|
35318
|
+
}
|
|
35319
|
+
.cn-dashboard-page__view-link-icon[data-v-824052fe] {
|
|
35320
|
+
display: inline-flex;
|
|
35321
|
+
}
|
|
35322
|
+
|
|
35024
35323
|
/* The period group, in a row of its own between the header and the KPI row. */
|
|
35025
35324
|
.cn-dashboard-page__date-range--segmented[data-v-824052fe] {
|
|
35026
35325
|
margin-bottom: 16px;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { translatePlural, getLanguage, getCanonicalLocale } from '@nextcloud/l10n';
|
|
2
|
+
import { parseDateValue } from './dateVariant.js';
|
|
3
|
+
import { compareVisibleWhen } from './visibleWhen.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
7
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
8
|
+
*
|
|
9
|
+
* boardDate: the two short time readings the drawn boards put in a table cell.
|
|
10
|
+
*
|
|
11
|
+
* - A date ("5 okt", "30 okt", "14 feb 2024"): day and short month in the
|
|
12
|
+
* user's language, the year only when it is not the current year.
|
|
13
|
+
* - An age ("4 uur", "1 dag", "3 dagen"): how long something has been
|
|
14
|
+
* waiting, in hours under a day and in whole calendar days after that.
|
|
15
|
+
*
|
|
16
|
+
* Both read the Nextcloud user LANGUAGE for the month and unit names. A
|
|
17
|
+
* Nextcloud account keeps its locale (date order) apart from its language,
|
|
18
|
+
* and a Dutch reader whose locale was left at the en_US default saw
|
|
19
|
+
* "Feb 14, 2024": the language decides the words, so it decides here, and the
|
|
20
|
+
* locale is only used when it is a region of that same language.
|
|
21
|
+
*
|
|
22
|
+
* @module utils/boardDate
|
|
23
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const MS_PER_HOUR = 3600000;
|
|
28
|
+
const MS_PER_DAY = 86400000;
|
|
29
|
+
|
|
30
|
+
/** Variants an age rule may name. `danger` is accepted as an alias of `error`. */
|
|
31
|
+
const AGE_VARIANTS = Object.freeze(['default', 'success', 'warning', 'error']);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read a language or locale tag from an @nextcloud/l10n getter, which throws
|
|
35
|
+
* when Nextcloud has not set one (outside a Nextcloud page, in a test).
|
|
36
|
+
*
|
|
37
|
+
* @param {() => string} getter `getLanguage` or `getCanonicalLocale`.
|
|
38
|
+
* @return {string} The tag with `-` separators, or ''.
|
|
39
|
+
*/
|
|
40
|
+
function readTag(getter) {
|
|
41
|
+
try {
|
|
42
|
+
return String(getter() || '').replace(/_/g, '-')
|
|
43
|
+
} catch {
|
|
44
|
+
return ''
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The BCP 47 tag the board dates are written in: the user's locale when it is
|
|
50
|
+
* a region of the user's language (`nl-BE` for language `nl`), else the
|
|
51
|
+
* language itself.
|
|
52
|
+
*
|
|
53
|
+
* @return {string|undefined} The tag, or undefined to let Intl choose.
|
|
54
|
+
* @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
|
|
55
|
+
*/
|
|
56
|
+
function boardDateLocale() {
|
|
57
|
+
const language = readTag(getLanguage);
|
|
58
|
+
const locale = readTag(getCanonicalLocale);
|
|
59
|
+
const base = (tag) => tag.split('-')[0].toLowerCase();
|
|
60
|
+
if (language && locale && base(language) === base(locale)) {
|
|
61
|
+
return locale
|
|
62
|
+
}
|
|
63
|
+
return language || locale || undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A date in the board's short form: "5 okt", or "14 feb 2024" outside the
|
|
68
|
+
* current year.
|
|
69
|
+
*
|
|
70
|
+
* @param {unknown} value A Date, a timestamp or an ISO string.
|
|
71
|
+
* @param {Date} [now] The reference moment (defaults to the current time).
|
|
72
|
+
* @param {string} [locale] BCP 47 tag (defaults to `boardDateLocale()`).
|
|
73
|
+
* @return {string} The short date, or '' when the value is not a date.
|
|
74
|
+
* @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
|
|
75
|
+
*/
|
|
76
|
+
function formatBoardDate(value, now = new Date(), locale = boardDateLocale()) {
|
|
77
|
+
const date = parseDateValue(value);
|
|
78
|
+
if (date === null) {
|
|
79
|
+
return ''
|
|
80
|
+
}
|
|
81
|
+
const options = { day: 'numeric', month: 'short' };
|
|
82
|
+
if (date.getFullYear() !== now.getFullYear()) {
|
|
83
|
+
options.year = 'numeric';
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return new Intl.DateTimeFormat(locale, options).format(date)
|
|
87
|
+
} catch {
|
|
88
|
+
return new Intl.DateTimeFormat(undefined, options).format(date)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Whole calendar days from the date until `now` (0 today, 1 yesterday), or
|
|
94
|
+
* null when the value is not a date. A future date gives a negative number.
|
|
95
|
+
*
|
|
96
|
+
* @param {unknown} value The date.
|
|
97
|
+
* @param {Date} [now] The reference moment.
|
|
98
|
+
* @return {number|null} The day count.
|
|
99
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited
|
|
100
|
+
*/
|
|
101
|
+
function ageInDays(value, now = new Date()) {
|
|
102
|
+
const date = parseDateValue(value);
|
|
103
|
+
if (date === null) {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
// UTC midnights of the two LOCAL days, so a daylight-saving hour never
|
|
107
|
+
// turns one day into zero or two.
|
|
108
|
+
const start = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate());
|
|
109
|
+
const today = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate());
|
|
110
|
+
return Math.round((today - start) / MS_PER_DAY)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* How long something has waited: "4 uur" under 24 hours (at least 1), else
|
|
115
|
+
* "1 dag" / "3 dagen" in whole calendar days. Plural forms come from the
|
|
116
|
+
* library's own catalogue (`nextcloud-vue`). A date in the future reads as
|
|
117
|
+
* waiting no time: "0 hours".
|
|
118
|
+
*
|
|
119
|
+
* @param {unknown} value The moment the wait started.
|
|
120
|
+
* @param {Date} [now] The reference moment.
|
|
121
|
+
* @return {string} The age, or '' when the value is not a date.
|
|
122
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited
|
|
123
|
+
*/
|
|
124
|
+
function formatAge(value, now = new Date()) {
|
|
125
|
+
const date = parseDateValue(value);
|
|
126
|
+
if (date === null) {
|
|
127
|
+
return ''
|
|
128
|
+
}
|
|
129
|
+
const elapsed = now.getTime() - date.getTime();
|
|
130
|
+
if (elapsed < MS_PER_DAY) {
|
|
131
|
+
const hours = elapsed <= 0 ? 0 : Math.max(1, Math.floor(elapsed / MS_PER_HOUR));
|
|
132
|
+
return translatePlural('nextcloud-vue', '%n hour', '%n hours', hours)
|
|
133
|
+
}
|
|
134
|
+
const days = shownAgeDays(date, now);
|
|
135
|
+
return translatePlural('nextcloud-vue', '%n day', '%n days', days)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The number of days an age cell shows: 0 under 24 hours (the cell then reads
|
|
140
|
+
* hours), else the whole calendar days, at least 1. The threshold rules
|
|
141
|
+
* compare against this number, so the colour always agrees with the text.
|
|
142
|
+
*
|
|
143
|
+
* @param {unknown} value The moment the wait started.
|
|
144
|
+
* @param {Date} [now] The reference moment.
|
|
145
|
+
* @return {number|null} The day count, or null when the value is not a date.
|
|
146
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold
|
|
147
|
+
*/
|
|
148
|
+
function shownAgeDays(value, now = new Date()) {
|
|
149
|
+
const date = parseDateValue(value);
|
|
150
|
+
if (date === null) {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
if (now.getTime() - date.getTime() < MS_PER_DAY) {
|
|
154
|
+
return 0
|
|
155
|
+
}
|
|
156
|
+
return Math.max(1, ageInDays(date, now))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The variant of the first `variantWhen` rule the age in days matches, or ''
|
|
161
|
+
* when none does. Same rule shape as the `date` widget (`{ op, value,
|
|
162
|
+
* variant }`, first match wins), compared against the number of days the cell
|
|
163
|
+
* shows (`shownAgeDays`): `[{ op: "gte", value: 3, variant: "error" }]` turns a wait of three
|
|
164
|
+
* days or more red.
|
|
165
|
+
*
|
|
166
|
+
* @param {unknown} value The moment the wait started.
|
|
167
|
+
* @param {Array<{op: string, value: number, variant: string}>} rules The rules.
|
|
168
|
+
* @param {Date} [now] The reference moment.
|
|
169
|
+
* @return {string} `success | warning | error | default`, or ''.
|
|
170
|
+
* @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold
|
|
171
|
+
*/
|
|
172
|
+
function resolveAgeVariant(value, rules, now = new Date()) {
|
|
173
|
+
if (!Array.isArray(rules) || rules.length === 0) {
|
|
174
|
+
return ''
|
|
175
|
+
}
|
|
176
|
+
const days = shownAgeDays(value, now);
|
|
177
|
+
if (days === null) {
|
|
178
|
+
return ''
|
|
179
|
+
}
|
|
180
|
+
for (const rule of rules) {
|
|
181
|
+
if (!rule || typeof rule !== 'object') {
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
if (compareVisibleWhen(days, rule.op || 'eq', rule.value) === true) {
|
|
185
|
+
const variant = rule.variant === 'danger' ? 'error' : rule.variant;
|
|
186
|
+
return AGE_VARIANTS.includes(variant) ? variant : ''
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return ''
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export { AGE_VARIANTS, ageInDays, boardDateLocale, formatAge, formatBoardDate, resolveAgeVariant, shownAgeDays };
|
|
193
|
+
//# sourceMappingURL=boardDate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardDate.js","sources":["../../../src/utils/boardDate.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * boardDate: the two short time readings the drawn boards put in a table cell.\n *\n * - A date (\"5 okt\", \"30 okt\", \"14 feb 2024\"): day and short month in the\n * user's language, the year only when it is not the current year.\n * - An age (\"4 uur\", \"1 dag\", \"3 dagen\"): how long something has been\n * waiting, in hours under a day and in whole calendar days after that.\n *\n * Both read the Nextcloud user LANGUAGE for the month and unit names. A\n * Nextcloud account keeps its locale (date order) apart from its language,\n * and a Dutch reader whose locale was left at the en_US default saw\n * \"Feb 14, 2024\": the language decides the words, so it decides here, and the\n * locale is only used when it is a region of that same language.\n *\n * @module utils/boardDate\n * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md\n */\n\nimport { getCanonicalLocale, getLanguage, translatePlural as n } from '@nextcloud/l10n'\nimport { parseDateValue } from './dateVariant.js'\nimport { compareVisibleWhen } from './visibleWhen.js'\n\nconst MS_PER_HOUR = 3600000\nconst MS_PER_DAY = 86400000\n\n/** Variants an age rule may name. `danger` is accepted as an alias of `error`. */\nexport const AGE_VARIANTS = Object.freeze(['default', 'success', 'warning', 'error'])\n\n/**\n * Read a language or locale tag from an @nextcloud/l10n getter, which throws\n * when Nextcloud has not set one (outside a Nextcloud page, in a test).\n *\n * @param {() => string} getter `getLanguage` or `getCanonicalLocale`.\n * @return {string} The tag with `-` separators, or ''.\n */\nfunction readTag(getter) {\n\ttry {\n\t\treturn String(getter() || '').replace(/_/g, '-')\n\t} catch {\n\t\treturn ''\n\t}\n}\n\n/**\n * The BCP 47 tag the board dates are written in: the user's locale when it is\n * a region of the user's language (`nl-BE` for language `nl`), else the\n * language itself.\n *\n * @return {string|undefined} The tag, or undefined to let Intl choose.\n * @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\n */\nexport function boardDateLocale() {\n\tconst language = readTag(getLanguage)\n\tconst locale = readTag(getCanonicalLocale)\n\tconst base = (tag) => tag.split('-')[0].toLowerCase()\n\tif (language && locale && base(language) === base(locale)) {\n\t\treturn locale\n\t}\n\treturn language || locale || undefined\n}\n\n/**\n * A date in the board's short form: \"5 okt\", or \"14 feb 2024\" outside the\n * current year.\n *\n * @param {unknown} value A Date, a timestamp or an ISO string.\n * @param {Date} [now] The reference moment (defaults to the current time).\n * @param {string} [locale] BCP 47 tag (defaults to `boardDateLocale()`).\n * @return {string} The short date, or '' when the value is not a date.\n * @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\n */\nexport function formatBoardDate(value, now = new Date(), locale = boardDateLocale()) {\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\treturn ''\n\t}\n\tconst options = { day: 'numeric', month: 'short' }\n\tif (date.getFullYear() !== now.getFullYear()) {\n\t\toptions.year = 'numeric'\n\t}\n\ttry {\n\t\treturn new Intl.DateTimeFormat(locale, options).format(date)\n\t} catch {\n\t\treturn new Intl.DateTimeFormat(undefined, options).format(date)\n\t}\n}\n\n/**\n * Whole calendar days from the date until `now` (0 today, 1 yesterday), or\n * null when the value is not a date. A future date gives a negative number.\n *\n * @param {unknown} value The date.\n * @param {Date} [now] The reference moment.\n * @return {number|null} The day count.\n * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited\n */\nexport function ageInDays(value, now = new Date()) {\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\treturn null\n\t}\n\t// UTC midnights of the two LOCAL days, so a daylight-saving hour never\n\t// turns one day into zero or two.\n\tconst start = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())\n\tconst today = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate())\n\treturn Math.round((today - start) / MS_PER_DAY)\n}\n\n/**\n * How long something has waited: \"4 uur\" under 24 hours (at least 1), else\n * \"1 dag\" / \"3 dagen\" in whole calendar days. Plural forms come from the\n * library's own catalogue (`nextcloud-vue`). A date in the future reads as\n * waiting no time: \"0 hours\".\n *\n * @param {unknown} value The moment the wait started.\n * @param {Date} [now] The reference moment.\n * @return {string} The age, or '' when the value is not a date.\n * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited\n */\nexport function formatAge(value, now = new Date()) {\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\treturn ''\n\t}\n\tconst elapsed = now.getTime() - date.getTime()\n\tif (elapsed < MS_PER_DAY) {\n\t\tconst hours = elapsed <= 0 ? 0 : Math.max(1, Math.floor(elapsed / MS_PER_HOUR))\n\t\treturn n('nextcloud-vue', '%n hour', '%n hours', hours)\n\t}\n\tconst days = shownAgeDays(date, now)\n\treturn n('nextcloud-vue', '%n day', '%n days', days)\n}\n\n/**\n * The number of days an age cell shows: 0 under 24 hours (the cell then reads\n * hours), else the whole calendar days, at least 1. The threshold rules\n * compare against this number, so the colour always agrees with the text.\n *\n * @param {unknown} value The moment the wait started.\n * @param {Date} [now] The reference moment.\n * @return {number|null} The day count, or null when the value is not a date.\n * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold\n */\nexport function shownAgeDays(value, now = new Date()) {\n\tconst date = parseDateValue(value)\n\tif (date === null) {\n\t\treturn null\n\t}\n\tif (now.getTime() - date.getTime() < MS_PER_DAY) {\n\t\treturn 0\n\t}\n\treturn Math.max(1, ageInDays(date, now))\n}\n\n/**\n * The variant of the first `variantWhen` rule the age in days matches, or ''\n * when none does. Same rule shape as the `date` widget (`{ op, value,\n * variant }`, first match wins), compared against the number of days the cell\n * shows (`shownAgeDays`): `[{ op: \"gte\", value: 3, variant: \"error\" }]` turns a wait of three\n * days or more red.\n *\n * @param {unknown} value The moment the wait started.\n * @param {Array<{op: string, value: number, variant: string}>} rules The rules.\n * @param {Date} [now] The reference moment.\n * @return {string} `success | warning | error | default`, or ''.\n * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold\n */\nexport function resolveAgeVariant(value, rules, now = new Date()) {\n\tif (!Array.isArray(rules) || rules.length === 0) {\n\t\treturn ''\n\t}\n\tconst days = shownAgeDays(value, now)\n\tif (days === null) {\n\t\treturn ''\n\t}\n\tfor (const rule of rules) {\n\t\tif (!rule || typeof rule !== 'object') {\n\t\t\tcontinue\n\t\t}\n\t\tif (compareVisibleWhen(days, rule.op || 'eq', rule.value) === true) {\n\t\t\tconst variant = rule.variant === 'danger' ? 'error' : rule.variant\n\t\t\treturn AGE_VARIANTS.includes(variant) ? variant : ''\n\t\t}\n\t}\n\treturn ''\n}\n"],"names":["n"],"mappings":"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"}
|