@conduction/nextcloud-vue 2.77.0 → 2.78.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 (82) hide show
  1. package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js +23 -0
  2. package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue3.js.map +1 -1
  4. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +2 -1
  5. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  6. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +37 -0
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  9. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +8 -7
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  11. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +37 -0
  12. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  13. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +2 -2
  14. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -4
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  18. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +7 -2
  19. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  20. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue.js +1 -1
  22. package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js +45 -2
  23. package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js.map +1 -1
  24. package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue3.js.map +1 -1
  25. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +7 -1
  26. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  27. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  28. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +4 -1
  29. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  31. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +6 -0
  32. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  33. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  34. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +36 -0
  35. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +35 -23
  37. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  38. package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js +6 -2
  39. package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js.map +1 -1
  40. package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue3.js.map +1 -1
  41. package/dist/esm/composables/useFormDraft.js +11 -1
  42. package/dist/esm/composables/useFormDraft.js.map +1 -1
  43. package/dist/esm/composables/useObjectPresence.js +15 -4
  44. package/dist/esm/composables/useObjectPresence.js.map +1 -1
  45. package/dist/esm/l10n/en.json.js +7 -0
  46. package/dist/esm/l10n/en.json.js.map +1 -1
  47. package/dist/esm/l10n/nl.json.js +7 -0
  48. package/dist/esm/l10n/nl.json.js.map +1 -1
  49. package/dist/esm/nextcloud-vue.css +212 -1
  50. package/dist/esm/utils/badgeVariants.js +32 -0
  51. package/dist/esm/utils/badgeVariants.js.map +1 -0
  52. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  53. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  54. package/dist/nextcloud-vue.cjs.js +533 -233
  55. package/dist/nextcloud-vue.cjs.js.map +2 -2
  56. package/dist/nextcloud-vue.css +212 -1
  57. package/l10n/en.json +7 -0
  58. package/l10n/nl.json +7 -0
  59. package/package.json +1 -1
  60. package/src/components/CnAiCompanion/CnAiCompanion.vue +23 -0
  61. package/src/components/CnBoardView/CnBoardView.vue +2 -1
  62. package/src/components/CnCellRenderer/CnCellRenderer.md +1 -0
  63. package/src/components/CnCellRenderer/CnCellRenderer.vue +50 -8
  64. package/src/components/CnChartWidget/CnChartWidget.vue +39 -2
  65. package/src/components/CnDashboardPage/CnDashboardPage.vue +29 -4
  66. package/src/components/CnDataTable/CnDataTable.vue +7 -2
  67. package/src/components/CnDateRangePicker/CnDateRangePicker.vue +44 -1
  68. package/src/components/CnFormDialog/CnFormDialog.vue +7 -1
  69. package/src/components/CnFormPage/CnFormPage.vue +4 -1
  70. package/src/components/CnIndexPage/CnIndexPage.vue +6 -0
  71. package/src/components/CnStatWidget/CnStatWidget.vue +52 -2
  72. package/src/components/CnStatusBadge/CnStatusBadge.vue +6 -2
  73. package/src/composables/__tests__/useObjectPresence.spec.js +24 -0
  74. package/src/composables/useFormDraft.js +11 -1
  75. package/src/composables/useObjectPresence.js +15 -4
  76. package/src/css/badge.css +31 -0
  77. package/src/css/kpi-card.css +51 -0
  78. package/src/css/look-board-index.css +36 -0
  79. package/src/css/look-board.css +8 -0
  80. package/src/schemas/app-manifest-v2.schema.json +5 -5
  81. package/src/utils/badgeVariants.js +29 -0
  82. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/nextcloud-vue.cjs.js.map
3
- size: 38439854 bytes
4
- sha256: 786f8e09d174b307bccce4e6dd2b99e2fb7b74a581decb90037b07c807a5c510
3
+ size: 38473543 bytes
4
+ sha256: bad7882294f198a2cea044c31c0d5125039ff25b00c181370b03de2a63cead03
@@ -502,6 +502,14 @@
502
502
  color: var(--color-main-text);
503
503
  opacity: 1;
504
504
  }
505
+
506
+ /* ---- Status pills (screens-cell-pill-parity) ---------------------------- */
507
+ /* The board draws a primary pill in the deep primary ink on the light primary
508
+ ground ("Verzoek", "Nieuw"); the default look keeps the brighter
509
+ primary-element text. The other tones already match the board. */
510
+ .cn-look-board .cn-status-badge--primary:not(.cn-status-badge--solid):not(.cn-look-nextcloud *) {
511
+ color: var(--color-primary-element-light-text);
512
+ }
505
513
  /*
506
514
  * The board dialog (screens-dialog-parity, canon section 5).
507
515
  *
@@ -1192,6 +1200,42 @@
1192
1200
  margin-inline-start: 0;
1193
1201
  }
1194
1202
 
1203
+ /* The header buttons keep the board's button under a theme that styles every
1204
+ secondary or primary button with `!important` (the nldesign theme: a
1205
+ primary border, 8px 16px padding): Download and the Actions menu are 40px,
1206
+ a grey border on the main background (PqTickets, DqZaken), the primary
1207
+ button is the primary fill without a border. The buildiq square is not a
1208
+ direct child button and keeps its own rule. */
1209
+ .cn-look-board .cn-index-page .cn-index-page__header-buttons > .button-vue--secondary,
1210
+ .cn-look-board .cn-index-page .cn-index-page__header-buttons > .action-item .button-vue {
1211
+ height: var(--cn-board-control-height) !important;
1212
+ min-height: var(--cn-board-control-height) !important;
1213
+ padding: 0 14px !important;
1214
+ border: 1px solid var(--color-border-dark) !important;
1215
+ border-radius: var(--cn-board-control-radius) !important;
1216
+ background: var(--color-main-background) !important;
1217
+ color: var(--color-main-text) !important;
1218
+ }
1219
+
1220
+ .cn-look-board .cn-index-page .cn-index-page__header-buttons > .button-vue--secondary:hover,
1221
+ .cn-look-board .cn-index-page .cn-index-page__header-buttons > .action-item .button-vue:hover {
1222
+ background: var(--color-background-hover) !important;
1223
+ }
1224
+
1225
+ .cn-look-board .cn-index-page .cn-index-page__header-buttons > .button-vue--primary {
1226
+ height: var(--cn-board-control-height) !important;
1227
+ min-height: var(--cn-board-control-height) !important;
1228
+ padding: 0 16px !important;
1229
+ border: 0 !important;
1230
+ border-radius: var(--cn-board-control-radius) !important;
1231
+ background: var(--color-primary-element) !important;
1232
+ color: var(--color-primary-element-text) !important;
1233
+ }
1234
+
1235
+ .cn-look-board .cn-index-page .cn-index-page__header-buttons > .button-vue--primary:hover {
1236
+ background: var(--color-primary-element-hover) !important;
1237
+ }
1238
+
1195
1239
  /* ----------------------------------------------------------------- toolbar */
1196
1240
 
1197
1241
  /* No band, no padding, no radius. The DOM is already in reading order (row 1,
@@ -3346,6 +3390,57 @@ a.cn-kpi-card--stacked:focus-visible {
3346
3390
  gap: 6px;
3347
3391
  }
3348
3392
 
3393
+ /* A linked board tile is one link: the label is not underlined link text, in
3394
+ any layout (screens-stat-tile-parity). The NL Design theme underlines every
3395
+ `a` with `!important`, so the opt-out needs it too. */
3396
+ a.cn-kpi-card--board,
3397
+ a.cn-kpi-card--board:hover,
3398
+ a.cn-kpi-card--board:focus-visible,
3399
+ a.cn-kpi-card--board * {
3400
+ text-decoration: none !important;
3401
+ }
3402
+
3403
+ /* `content.iconPlacement: "end"` (DqDashboard): label left, the icon in a
3404
+ 32px circle right, tinted by the tile's state. */
3405
+ .cn-kpi-card--board .cn-kpi-card__title--badge-end {
3406
+ justify-content: space-between;
3407
+ gap: 8px;
3408
+ }
3409
+
3410
+ .cn-kpi-card__badge {
3411
+ display: inline-flex;
3412
+ flex: 0 0 auto;
3413
+ align-items: center;
3414
+ justify-content: center;
3415
+ width: 32px;
3416
+ height: 32px;
3417
+ margin-inline-start: auto;
3418
+ border-radius: 50%;
3419
+ color: var(--color-primary-element-light-text, var(--color-primary-element));
3420
+ background: var(--color-primary-element-light, color-mix(in srgb, var(--color-primary-element) 12%, transparent));
3421
+ }
3422
+
3423
+ .cn-kpi-card__badge svg {
3424
+ width: 18px;
3425
+ height: 18px;
3426
+ fill: currentcolor;
3427
+ }
3428
+
3429
+ .cn-kpi-card__badge--success {
3430
+ color: var(--color-text-success, var(--color-success-text));
3431
+ background: color-mix(in srgb, var(--color-text-success, var(--color-success-text)) 12%, var(--color-main-background));
3432
+ }
3433
+
3434
+ .cn-kpi-card__badge--warning {
3435
+ color: var(--color-element-warning, var(--color-warning-text));
3436
+ background: color-mix(in srgb, var(--color-element-warning, var(--color-warning-text)) 12%, var(--color-main-background));
3437
+ }
3438
+
3439
+ .cn-kpi-card__badge--error {
3440
+ color: var(--color-text-error, var(--color-error-text));
3441
+ background: color-mix(in srgb, var(--color-text-error, var(--color-error-text)) 10%, var(--color-main-background));
3442
+ }
3443
+
3349
3444
  .cn-kpi-card__glyph {
3350
3445
  display: inline-flex;
3351
3446
  flex: 0 0 auto;
@@ -3592,6 +3687,15 @@ a.cn-kpi-card--stacked:focus-visible {
3592
3687
  @conduction/nextcloud-vue — Status Badge
3593
3688
  ======================================== */
3594
3689
 
3690
+ /* The two board tones without a Nextcloud colour role (screens-cell-pill-parity).
3691
+ A theme may redefine them; the six role variants below use Nextcloud's own. */
3692
+ :root {
3693
+ --cn-status-purple-bg: #e8e6f6;
3694
+ --cn-status-purple-text: #4a3f8f;
3695
+ --cn-status-teal-bg: #e3f1f3;
3696
+ --cn-status-teal-text: #1d5e66;
3697
+ }
3698
+
3595
3699
  .cn-status-badge {
3596
3700
  display: inline-flex;
3597
3701
  align-items: center;
@@ -3641,6 +3745,18 @@ a.cn-kpi-card--stacked:focus-visible {
3641
3745
  color: var(--color-info-text, var(--color-info, #0082c9));
3642
3746
  }
3643
3747
 
3748
+ /* Work in progress ("In behandeling"), a board tone. */
3749
+ .cn-status-badge--purple {
3750
+ background-color: var(--cn-status-purple-bg);
3751
+ color: var(--cn-status-purple-text);
3752
+ }
3753
+
3754
+ /* Handed on ("Omgezet naar een zaak"), a board tone. */
3755
+ .cn-status-badge--teal {
3756
+ background-color: var(--cn-status-teal-bg);
3757
+ color: var(--cn-status-teal-text);
3758
+ }
3759
+
3644
3760
  /* Solid variants — solid background with white text for use on colored backgrounds */
3645
3761
  .cn-status-badge--solid.cn-status-badge--default {
3646
3762
  background-color: var(--color-background-dark);
@@ -3671,6 +3787,16 @@ a.cn-kpi-card--stacked:focus-visible {
3671
3787
  background-color: var(--color-info, #0082c9);
3672
3788
  color: white;
3673
3789
  }
3790
+
3791
+ .cn-status-badge--solid.cn-status-badge--purple {
3792
+ background-color: var(--cn-status-purple-text);
3793
+ color: white;
3794
+ }
3795
+
3796
+ .cn-status-badge--solid.cn-status-badge--teal {
3797
+ background-color: var(--cn-status-teal-text);
3798
+ color: white;
3799
+ }
3674
3800
  /* ========================================
3675
3801
  @conduction/nextcloud-vue — Layout & Filter Utility Styles
3676
3802
  ======================================== */
@@ -9122,8 +9248,11 @@ svg.leaflet-image-layer.leaflet-interactive path {
9122
9248
  .cn-cell-renderer__date--error[data-v-37d7b88f] {
9123
9249
  color: var(--color-text-error, var(--color-error-text));
9124
9250
  }
9251
+
9252
+ /* The text variant, not the fill: from Nextcloud 32 `--color-success` is a
9253
+ background colour and too pale for a 16px glyph on a light theme. */
9125
9254
  .cn-cell-renderer__icon--success[data-v-37d7b88f] {
9126
- color: var(--color-success);
9255
+ color: var(--color-text-success, var(--color-success-text, var(--color-success)));
9127
9256
  }
9128
9257
  .cn-cell-renderer__swatch-wrap[data-v-37d7b88f] {
9129
9258
  display: inline-flex;
@@ -20833,6 +20962,57 @@ a.cn-kpi-card--stacked:focus-visible {
20833
20962
  gap: 6px;
20834
20963
  }
20835
20964
 
20965
+ /* A linked board tile is one link: the label is not underlined link text, in
20966
+ any layout (screens-stat-tile-parity). The NL Design theme underlines every
20967
+ `a` with `!important`, so the opt-out needs it too. */
20968
+ a.cn-kpi-card--board,
20969
+ a.cn-kpi-card--board:hover,
20970
+ a.cn-kpi-card--board:focus-visible,
20971
+ a.cn-kpi-card--board * {
20972
+ text-decoration: none !important;
20973
+ }
20974
+
20975
+ /* `content.iconPlacement: "end"` (DqDashboard): label left, the icon in a
20976
+ 32px circle right, tinted by the tile's state. */
20977
+ .cn-kpi-card--board .cn-kpi-card__title--badge-end {
20978
+ justify-content: space-between;
20979
+ gap: 8px;
20980
+ }
20981
+
20982
+ .cn-kpi-card__badge {
20983
+ display: inline-flex;
20984
+ flex: 0 0 auto;
20985
+ align-items: center;
20986
+ justify-content: center;
20987
+ width: 32px;
20988
+ height: 32px;
20989
+ margin-inline-start: auto;
20990
+ border-radius: 50%;
20991
+ color: var(--color-primary-element-light-text, var(--color-primary-element));
20992
+ background: var(--color-primary-element-light, color-mix(in srgb, var(--color-primary-element) 12%, transparent));
20993
+ }
20994
+
20995
+ .cn-kpi-card__badge svg {
20996
+ width: 18px;
20997
+ height: 18px;
20998
+ fill: currentcolor;
20999
+ }
21000
+
21001
+ .cn-kpi-card__badge--success {
21002
+ color: var(--color-text-success, var(--color-success-text));
21003
+ background: color-mix(in srgb, var(--color-text-success, var(--color-success-text)) 12%, var(--color-main-background));
21004
+ }
21005
+
21006
+ .cn-kpi-card__badge--warning {
21007
+ color: var(--color-element-warning, var(--color-warning-text));
21008
+ background: color-mix(in srgb, var(--color-element-warning, var(--color-warning-text)) 12%, var(--color-main-background));
21009
+ }
21010
+
21011
+ .cn-kpi-card__badge--error {
21012
+ color: var(--color-text-error, var(--color-error-text));
21013
+ background: color-mix(in srgb, var(--color-text-error, var(--color-error-text)) 10%, var(--color-main-background));
21014
+ }
21015
+
20836
21016
  .cn-kpi-card__glyph {
20837
21017
  display: inline-flex;
20838
21018
  flex: 0 0 auto;
@@ -26550,6 +26730,15 @@ tr.file-picker__row td.row-name[data-v-1f96131b] {
26550
26730
  @conduction/nextcloud-vue — Status Badge
26551
26731
  ======================================== */
26552
26732
 
26733
+ /* The two board tones without a Nextcloud colour role (screens-cell-pill-parity).
26734
+ A theme may redefine them; the six role variants below use Nextcloud's own. */
26735
+ :root {
26736
+ --cn-status-purple-bg: #e8e6f6;
26737
+ --cn-status-purple-text: #4a3f8f;
26738
+ --cn-status-teal-bg: #e3f1f3;
26739
+ --cn-status-teal-text: #1d5e66;
26740
+ }
26741
+
26553
26742
  .cn-status-badge {
26554
26743
  display: inline-flex;
26555
26744
  align-items: center;
@@ -26599,6 +26788,18 @@ tr.file-picker__row td.row-name[data-v-1f96131b] {
26599
26788
  color: var(--color-info-text, var(--color-info, #0082c9));
26600
26789
  }
26601
26790
 
26791
+ /* Work in progress ("In behandeling"), a board tone. */
26792
+ .cn-status-badge--purple {
26793
+ background-color: var(--cn-status-purple-bg);
26794
+ color: var(--cn-status-purple-text);
26795
+ }
26796
+
26797
+ /* Handed on ("Omgezet naar een zaak"), a board tone. */
26798
+ .cn-status-badge--teal {
26799
+ background-color: var(--cn-status-teal-bg);
26800
+ color: var(--cn-status-teal-text);
26801
+ }
26802
+
26602
26803
  /* Solid variants — solid background with white text for use on colored backgrounds */
26603
26804
  .cn-status-badge--solid.cn-status-badge--default {
26604
26805
  background-color: var(--color-background-dark);
@@ -26630,6 +26831,16 @@ tr.file-picker__row td.row-name[data-v-1f96131b] {
26630
26831
  color: white;
26631
26832
  }
26632
26833
 
26834
+ .cn-status-badge--solid.cn-status-badge--purple {
26835
+ background-color: var(--cn-status-purple-text);
26836
+ color: white;
26837
+ }
26838
+
26839
+ .cn-status-badge--solid.cn-status-badge--teal {
26840
+ background-color: var(--cn-status-teal-text);
26841
+ color: white;
26842
+ }
26843
+
26633
26844
 
26634
26845
  /*
26635
26846
  * The card itself — its box, body stack, icon circle, title and value
package/l10n/en.json CHANGED
@@ -114,6 +114,7 @@
114
114
  "Saved just now": "Saved just now",
115
115
  "Saved {time}": "Saved {time}",
116
116
  "Saved": "Saved",
117
+ "Draft kept on this device": "Draft kept on this device",
117
118
  "Publish": "Publish",
118
119
  "Edit {title} (draft)": "Edit {title} (draft)",
119
120
  "Also search inside files": "Also search inside files",
@@ -453,6 +454,12 @@
453
454
  "NC Activity is currently unavailable.": "NC Activity is currently unavailable.",
454
455
  "Could not load activity.": "Could not load activity.",
455
456
  "Today": "Today",
457
+ "Last 8 hours": "Last 8 hours",
458
+ "Last 24 hours": "Last 24 hours",
459
+ "Last 7 days": "Last 7 days",
460
+ "Last 30 days": "Last 30 days",
461
+ "Last 90 days": "Last 90 days",
462
+ "Custom range": "Custom range",
456
463
  "Yesterday": "Yesterday",
457
464
  "Earlier": "Earlier",
458
465
  "System": "System",
package/l10n/nl.json CHANGED
@@ -114,6 +114,7 @@
114
114
  "Saved just now": "Zojuist opgeslagen",
115
115
  "Saved {time}": "Opgeslagen {time}",
116
116
  "Saved": "Opgeslagen",
117
+ "Draft kept on this device": "Concept bewaard op dit apparaat",
117
118
  "Edit {title} (draft)": "{title} bewerken (concept)",
118
119
  "Also search inside files": "Zoek ook in bestanden",
119
120
  "File matches are limited to the best 50.": "Bestandstreffers zijn beperkt tot de beste 50.",
@@ -452,6 +453,12 @@
452
453
  "NC Activity is currently unavailable.": "NC Activiteit is momenteel niet beschikbaar.",
453
454
  "Could not load activity.": "Kon activiteit niet laden.",
454
455
  "Today": "Vandaag",
456
+ "Last 8 hours": "Laatste 8 uur",
457
+ "Last 24 hours": "Laatste 24 uur",
458
+ "Last 7 days": "Laatste 7 dagen",
459
+ "Last 30 days": "Laatste 30 dagen",
460
+ "Last 90 days": "Laatste 90 dagen",
461
+ "Custom range": "Aangepaste periode",
455
462
  "Yesterday": "Gisteren",
456
463
  "Earlier": "Eerder",
457
464
  "System": "Systeem",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.77.0",
3
+ "version": "2.78.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -66,6 +66,7 @@ import CnAiChatPanel from './CnAiChatPanel.vue'
66
66
  import CnAiFloatingButton from './CnAiFloatingButton.vue'
67
67
  import { chatHealthUrl, DEFAULT_CHAT_APP_ID } from '../../composables/aiChatConfig.js'
68
68
  import { useAiChatStream } from '../../composables/useAiChatStream.js'
69
+ import { isAppInstalled } from '../../utils/appInstalled.js'
69
70
 
70
71
  const HEALTH_TIMEOUT = 5000
71
72
 
@@ -340,6 +341,15 @@ export default {
340
341
  }
341
342
  },
342
343
 
344
+ /**
345
+ * Ask the chat backend whether it can answer, and render only if so.
346
+ *
347
+ * Skipped without a request when the backend app is not installed for
348
+ * this user.
349
+ *
350
+ * @return {Promise<void>}
351
+ * @spec openspec/changes/no-calls-to-missing-apps-and-presence-beacon/specs/ai-companion/spec.md#requirement-the-companion-does-not-probe-a-backend-that-is-not-installed
352
+ */
343
353
  async runHealthProbe() {
344
354
  // RETRY before hiding. A single probe makes the whole companion
345
355
  // disappear on one slow response, and "slow" is normal: measured on a
@@ -351,6 +361,19 @@ export default {
351
361
  //
352
362
  // A backend that is genuinely down fails all attempts and is still
353
363
  // reported; this only stops one unlucky request from deciding.
364
+ //
365
+ // 🔴 NO PROBE AT AN APP THAT IS NOT THERE. Nextcloud already tells
366
+ // the page which apps the user has (`OC.appswebroots`), so asking a
367
+ // missing backend for its health is a guaranteed 404, three times
368
+ // per page, in every console. Measured on cloud.conduction.nl
369
+ // (round-5 check): hermiq not installed, three
370
+ // `/apps/hermiq/api/chat/health` 404s on every pipelinq and dossiq
371
+ // page. Hidden either way; now hidden without the requests.
372
+ if (!isAppInstalled(this.chatAppId || DEFAULT_CHAT_APP_ID)) {
373
+ this.probeSucceeded = false
374
+ return
375
+ }
376
+
354
377
  for (let attempt = 1; attempt <= HEALTH_PROBE_ATTEMPTS; attempt++) {
355
378
  try {
356
379
  const response = await axios.get(chatHealthUrl(this.chatAppId), {
@@ -308,6 +308,7 @@
308
308
  import { translate as t } from '@nextcloud/l10n'
309
309
  import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
310
310
  import { normalizeLook } from '../../composables/useLook.js'
311
+ import { isBadgeVariant } from '../../utils/badgeVariants.js'
311
312
  import { buildBoardColumns, resolveStageColor } from '../../utils/boardColumns.js'
312
313
  import { buildSwimlanes } from '../../utils/boardSwimlanes.js'
313
314
  import { DROP_OUTCOMES, runBoardDrop } from '../../utils/boardTransition.js'
@@ -688,7 +689,7 @@ export default {
688
689
  pillVariant(card) {
689
690
  const raw = this.textOf(readPath(card, this.roles.pill))
690
691
  const variant = this.roles.pillColors[raw] ?? this.roles.pillColors[raw.toLowerCase()]
691
- return ['default', 'primary', 'success', 'warning', 'error', 'info'].includes(variant) ? variant : 'default'
692
+ return isBadgeVariant(variant) ? variant : 'default'
692
693
  },
693
694
 
694
695
  /**
@@ -89,6 +89,7 @@ fall back to the normal rendering. See `docs/migrating-to-manifest.md` →
89
89
  Column widget — `widget="badge"`, `widget="fkResolve"` (uuid → related object label via the shared object store with per-schema caching; config `widgetProps { register, schema, labelField }`), and `widget="link"` are built-in; any other id resolves against the app's `cellWidgets` registry (`CnAppRoot`'s `cellWidgets` prop → `cnCellWidgets`), and that component receives `{ value, row, property, formatted, ...widgetProps }`. `widget` takes precedence over `formatter`; when both are set the widget gets the formatter-shaped value as `formatted`. See `docs/migrating-to-manifest.md` → "Column widgets" + "Built-in cell formatters / widgets".
90
90
 
91
91
  - `widget="badge"` — `CnStatusBadge` pill; `widgetProps.variant` picks the colour.
92
+ An enum value shows its `x-enum-labels` label, translated, as the plain enum cell does; a column `formatter` still wins. `widgetProps.colorMap` is looked up by the stored value when it has an entry for it, else by the label. The `link` widget and the `swatch` format show the same label.
92
93
  - `widget="link"` — `<router-link>` when `widgetProps.route` is a manifest page id, or when `widgetProps.routeMap` maps the row's `widgetProps.routeField` value to one (a value the map does not hold falls back to `route`, then to text); `<a target="_blank">` when `widgetProps.href` is set (with `{key}` placeholders substituted from the row); plain text + once-per-session `console.warn` when neither resolves (silence with `widgetProps.fallback:"silent"`). For non-`id` route params: `widgetProps.params: { routeParam: "rowField" }`. Default param map is `{ id: row[rowKey] }` — see the `row-key` prop below.
93
94
 
94
95
  ```vue
@@ -24,12 +24,15 @@
24
24
  </span>
25
25
  </template>
26
26
 
27
- <!-- Built-in "badge" widget — renders the (possibly formatter-shaped) value as a status pill -->
27
+ <!-- Built-in "badge" widget: renders the value as a status pill. An enum
28
+ value shows its label (x-enum-labels, translated) unless a formatter
29
+ shapes it; the colour stays keyed on the raw value. -->
28
30
  <template v-else-if="widget === 'badge'">
29
31
  <CnStatusBadge v-if="hasValue"
30
- :label="String(formattedValue)"
32
+ :label="labelledValue"
31
33
  :variant="badgeVariant"
32
- :colorMap="badgeColorMap" />
34
+ :colorMap="badgeColorMap"
35
+ :colorKey="badgeColorKey" />
33
36
  <span v-else class="cn-cell-renderer__dash">—</span>
34
37
  </template>
35
38
 
@@ -78,7 +81,7 @@
78
81
  v-if="linkRoute"
79
82
  :to="linkRoute"
80
83
  class="cn-cell-renderer__link">
81
- {{ formattedValue }}
84
+ {{ labelledValue }}
82
85
  </router-link>
83
86
  <a
84
87
  v-else-if="linkHref"
@@ -86,9 +89,9 @@
86
89
  target="_blank"
87
90
  rel="noopener"
88
91
  class="cn-cell-renderer__link">
89
- {{ formattedValue }}
92
+ {{ labelledValue }}
90
93
  </a>
91
- <span v-else :title="rawTitle">{{ formattedValue }}</span>
94
+ <span v-else :title="rawTitle">{{ labelledValue }}</span>
92
95
  </template>
93
96
 
94
97
  <!-- Built-in "avatar" widget: a person as an avatar with the name beside
@@ -141,7 +144,7 @@
141
144
  class="cn-cell-renderer__swatch-dot"
142
145
  :style="{ backgroundColor: swatchColor }"
143
146
  aria-hidden="true" />
144
- <span v-if="hasValue" :title="rawTitle">{{ formattedValue }}</span>
147
+ <span v-if="hasValue" :title="rawTitle">{{ labelledValue }}</span>
145
148
  <span v-else class="cn-cell-renderer__dash">—</span>
146
149
  </span>
147
150
  </template>
@@ -632,6 +635,43 @@ export default {
632
635
  .map((gid) => String(gid))
633
636
  },
634
637
 
638
+ /**
639
+ * The text a built-in widget (`badge`, `link`) or a swatch shows. An
640
+ * enum value reads its label the way the plain enum cell does
641
+ * (`enumLabel`: `x-enum-labels`, translated), so a list says "Active"
642
+ * where the detail page does, not the stored "active". A column
643
+ * formatter still wins: an app that shapes the value itself gets
644
+ * exactly what its formatter returns.
645
+ *
646
+ * @return {string}
647
+ * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label
648
+ */
649
+ labelledValue() {
650
+ if (!this.hasFormatter && this.isEnum && this.hasValue && typeof this.value !== 'object') {
651
+ return this.enumLabel
652
+ }
653
+ return String(this.formattedValue)
654
+ },
655
+
656
+ /**
657
+ * The key the built-in `badge` looks its colour up by: the raw value
658
+ * when `widgetProps.colorMap` has an entry for it (case-insensitive),
659
+ * so a translated label keeps the colour the manifest keyed on the
660
+ * stored code. Otherwise '' and the badge looks up its label, as before,
661
+ * which keeps a map keyed on a formatter's output working.
662
+ *
663
+ * @return {string}
664
+ * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label
665
+ */
666
+ badgeColorKey() {
667
+ const map = this.badgeColorMap
668
+ if (!map || !this.hasValue || typeof this.value === 'object') {
669
+ return ''
670
+ }
671
+ const raw = String(this.value).toLowerCase()
672
+ return Object.keys(map).some((key) => key.toLowerCase() === raw) ? String(this.value) : ''
673
+ },
674
+
635
675
  /** Variant for the built-in `badge` widget — `widgetProps.variant` or `'default'`. */
636
676
  badgeVariant() {
637
677
  return (this.widgetProps && this.widgetProps.variant) || 'default'
@@ -1103,8 +1143,10 @@ export default {
1103
1143
  color: var(--color-text-error, var(--color-error-text));
1104
1144
  }
1105
1145
 
1146
+ /* The text variant, not the fill: from Nextcloud 32 `--color-success` is a
1147
+ background colour and too pale for a 16px glyph on a light theme. */
1106
1148
  .cn-cell-renderer__icon--success {
1107
- color: var(--color-success);
1149
+ color: var(--color-text-success, var(--color-success-text, var(--color-success)));
1108
1150
  }
1109
1151
 
1110
1152
  .cn-cell-renderer__swatch-wrap {
@@ -29,7 +29,7 @@
29
29
  :aria-pressed="String(Boolean(activeView && activeView.key === view.key))"
30
30
  :data-testid="`cn-chart-widget-view-${view.key}`"
31
31
  @click="activeViewKey = view.key">
32
- {{ view.label || view.key }}
32
+ {{ view.label ? hostTranslate(view.label) : view.key }}
33
33
  </button>
34
34
  </div>
35
35
  <div v-if="showEmptyState" class="cn-chart-widget__empty" data-testid="cn-chart-widget-empty">
@@ -53,7 +53,7 @@
53
53
  :height="computedHeight"
54
54
  :width="computedWidth"
55
55
  :options="mergedOptions"
56
- :series="displayedSeries" />
56
+ :series="plottedSeries" />
57
57
  </div>
58
58
  <div v-else class="cn-chart-widget__fallback">
59
59
  <!-- @slot Rendered when the ApexCharts peer dependency is not available (defaults to the unavailableLabel text). -->
@@ -213,6 +213,11 @@ export default {
213
213
  * always a ref to inject, even outside a dashboard.
214
214
  */
215
215
  cnDashboardDateRange: { default: () => ref(null) },
216
+ /**
217
+ * Host translate function provided by CnAppRoot: manifest-authored
218
+ * view labels and series names are i18n keys (screens-dashboard-i18n).
219
+ */
220
+ cnTranslate: { default: () => (key) => key },
216
221
  },
217
222
 
218
223
  props: {
@@ -918,6 +923,25 @@ export default {
918
923
  *
919
924
  * @return {Array} The displayed series.
920
925
  */
926
+ /**
927
+ * The displayed series with each series name through the host
928
+ * translate function, so a manifest series named "Revenue" reads in
929
+ * the user's language in the legend and the tooltip. Views still
930
+ * filter on the name as written (`displayedSeries`).
931
+ *
932
+ * @spec openspec/changes/screens-dashboard-i18n/specs/dashboard-page/spec.md#requirement-chart-view-labels-and-series-names-are-translated
933
+ * @return {Array} The series handed to ApexCharts.
934
+ */
935
+ plottedSeries() {
936
+ const series = this.displayedSeries
937
+ if (!Array.isArray(series)) {
938
+ return series
939
+ }
940
+ return series.map((entry) => (entry && typeof entry === 'object' && typeof entry.name === 'string' && entry.name !== '')
941
+ ? { ...entry, name: this.hostTranslate(entry.name) }
942
+ : entry)
943
+ },
944
+
921
945
  displayedSeries() {
922
946
  const series = this.resolvedSeries
923
947
  const view = this.activeView
@@ -1417,6 +1441,19 @@ export default {
1417
1441
  },
1418
1442
 
1419
1443
  methods: {
1444
+ /**
1445
+ * A manifest-authored label through the host translate function.
1446
+ *
1447
+ * @spec openspec/changes/screens-dashboard-i18n/specs/dashboard-page/spec.md#requirement-chart-view-labels-and-series-names-are-translated
1448
+ * @param {string} key The label.
1449
+ * @return {string} The translated label.
1450
+ */
1451
+ hostTranslate(key) {
1452
+ const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : null
1453
+ const out = translate ? translate(key) : key
1454
+ return (typeof out === 'string' && out !== '') ? out : key
1455
+ },
1456
+
1420
1457
  /**
1421
1458
  * Re-query the chart's dataSource / endpointSource. Exposed as a
1422
1459
  * ref-callable method (B3 canonical refresh mode) AND invoked by the
@@ -883,7 +883,7 @@ import CnBodySections from '../CnBodySections/CnBodySections.vue'
883
883
  import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
884
884
  import CnChartWidget from '../CnChartWidget/CnChartWidget.vue'
885
885
  import CnDashboardGrid from '../CnDashboardGrid/CnDashboardGrid.vue'
886
- import CnDateRangePicker, { DEFAULT_DATE_RANGE_PRESETS, resolvePresetWindow } from '../CnDateRangePicker/CnDateRangePicker.vue'
886
+ import CnDateRangePicker, { DEFAULT_DATE_RANGE_PRESETS, resolvePresetWindow, translatePresetLabel } from '../CnDateRangePicker/CnDateRangePicker.vue'
887
887
  import CnEmptyContent from '../CnEmptyContent/CnEmptyContent.vue'
888
888
  import CnKpiGrid from '../CnKpiGrid/CnKpiGrid.vue'
889
889
  import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
@@ -2009,9 +2009,12 @@ export default {
2009
2009
  * doesn't supply one.
2010
2010
  */
2011
2011
  effectivePresets() {
2012
- return Array.isArray(this.dateRange?.presets) && this.dateRange.presets.length > 0
2012
+ const presets = Array.isArray(this.dateRange?.presets) && this.dateRange.presets.length > 0
2013
2013
  ? this.dateRange.presets
2014
- : DEFAULT_DATE_RANGE_PRESETS.map((p) => ({ ...p }))
2014
+ : DEFAULT_DATE_RANGE_PRESETS
2015
+ // Labels in the user's language (screens-dashboard-i18n): the app's
2016
+ // catalogue first, then the library's own for the default labels.
2017
+ return presets.map((p) => ({ ...p, label: translatePresetLabel(p.label, this.effectiveTranslate) }))
2015
2018
  },
2016
2019
 
2017
2020
  /**
@@ -3354,7 +3357,7 @@ export default {
3354
3357
  * @return {object}
3355
3358
  */
3356
3359
  registryWidgetBindings(item) {
3357
- const content = this.getWidgetContent(item)
3360
+ const content = this.collectionContent(this.getWidgetContent(item))
3358
3361
  const def = this.getWidgetDef(item.widgetId)
3359
3362
  if (!this.isBannerDef(def)) {
3360
3363
  return content
@@ -3363,6 +3366,28 @@ export default {
3363
3366
  return outcome ? { ...content, conditionOutcome: outcome } : content
3364
3367
  },
3365
3368
 
3369
+ /**
3370
+ * A widget's content with the board's quiet collection footer: in the
3371
+ * board look a dashboard that drops the widget Actions menu
3372
+ * (`showWidgetActions: false`) also drops the Add footer of its list
3373
+ * widgets, whose Add is the menu's create entry at the bottom of the
3374
+ * card. A widget that says `content.allowCreate` keeps its own value.
3375
+ * Otherwise the content is returned as is.
3376
+ *
3377
+ * @spec openspec/changes/screens-dashboard-legacy-widgets/specs/dashboard-page/spec.md#requirement-a-board-dashboard-without-widget-actions-drops-the-add-footer
3378
+ * @param {object} content The widget's stored content.
3379
+ * @return {object}
3380
+ */
3381
+ collectionContent(content) {
3382
+ if (!this.isBoardLook || this.showWidgetActions !== false) {
3383
+ return content
3384
+ }
3385
+ if (content && Object.hasOwn(content, 'allowCreate')) {
3386
+ return content
3387
+ }
3388
+ return { ...content, allowCreate: false }
3389
+ },
3390
+
3366
3391
  /**
3367
3392
  * Re-compact a filtered layout upward, display-only. GridStack runs
3368
3393
  * `float: true`, so removing an item does not close its row — this