@conduction/nextcloud-vue 2.60.0 → 2.62.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 (87) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +169 -1
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  4. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +88 -4
  5. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  6. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +38 -25
  7. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +21 -1
  9. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +4 -1
  11. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -1
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +40 -8
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +40 -15
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +2 -2
  18. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +6 -5
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  22. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +3 -1
  23. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  24. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  25. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
  26. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
  27. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
  28. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
  29. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
  30. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
  31. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +53 -0
  32. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  33. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +22 -5
  34. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  35. package/dist/esm/components/CnPageRenderer/pageTypes.js +1 -0
  36. package/dist/esm/components/CnPageRenderer/pageTypes.js.map +1 -1
  37. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  38. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  39. package/dist/esm/components/CnStorePage/CnStorePage.vue2.js.map +1 -1
  40. package/dist/esm/components/CnStorePage/CnStorePage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnWikiPage/CnWikiPage.vue2.js.map +1 -1
  42. package/dist/esm/components/CnWikiPage/CnWikiPage.vue3.js.map +1 -1
  43. package/dist/esm/index.js +6 -0
  44. package/dist/esm/index.js.map +1 -1
  45. package/dist/esm/l10n/en.json.js +6 -0
  46. package/dist/esm/l10n/en.json.js.map +1 -1
  47. package/dist/esm/l10n/nl.json.js +6 -0
  48. package/dist/esm/l10n/nl.json.js.map +1 -1
  49. package/dist/esm/nextcloud-vue.css +198 -9
  50. package/dist/esm/utils/headers.js +28 -2
  51. package/dist/esm/utils/headers.js.map +1 -1
  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 +9162 -8283
  55. package/dist/nextcloud-vue.cjs.js.map +2 -2
  56. package/dist/nextcloud-vue.css +198 -9
  57. package/l10n/en.json +6 -0
  58. package/l10n/nl.json +6 -0
  59. package/package.json +3 -2
  60. package/scripts/.jsdoc-baselines.json +1 -0
  61. package/scripts/manifest-schema-hash.js +41 -0
  62. package/scripts/record-manifest-schema-hash.js +52 -0
  63. package/src/components/CnAppNav/CnAppNav.vue +169 -1
  64. package/src/components/CnBannerWidget/CnBannerWidget.vue +122 -4
  65. package/src/components/CnBrandStripe/CnBrandStripe.md +16 -0
  66. package/src/components/CnBrandStripe/CnBrandStripe.vue +58 -5
  67. package/src/components/CnDashboardPage/CnDashboardPage.vue +40 -8
  68. package/src/components/CnDataTable/CnDataTable.md +1 -1
  69. package/src/components/CnDataTable/CnDataTable.vue +42 -17
  70. package/src/components/CnDetailPage/CnDetailPage.vue +4 -1
  71. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +3 -1
  72. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +517 -0
  73. package/src/components/CnLinkCardsPage/index.js +4 -0
  74. package/src/components/CnObjectSidebar/CnFilesTab.vue +65 -0
  75. package/src/components/CnPageRenderer/pageTypes.js +1 -0
  76. package/src/components/CnReportsPage/CnReportsPage.vue +7 -0
  77. package/src/components/CnStorePage/CnStorePage.vue +7 -0
  78. package/src/components/CnWikiPage/CnWikiPage.vue +7 -0
  79. package/src/components/index.js +1 -0
  80. package/src/css/detail-page.css +10 -2
  81. package/src/css/table.css +1 -2
  82. package/src/index.js +7 -0
  83. package/src/public/index.js +5 -0
  84. package/src/schemas/app-manifest-v2.schema.json +45 -3
  85. package/src/types/manifest.d.ts +1 -1
  86. package/src/utils/headers.js +28 -2
  87. 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: 34515119 bytes
4
- sha256: 5f3fcfa87667eab7bfc0a4c99e86f6e46f2b136b344fa184d53d9e04aff4db7e
3
+ size: 34658160 bytes
4
+ sha256: 39b2808fa6e91c4a4c4e4dce5008a2efbcbb57754f60422fbac1771331bc070e
@@ -325,8 +325,7 @@
325
325
  outline-offset: -2px;
326
326
  }
327
327
 
328
- /* Multi-column sort priority badge (1, 2, 3) — only rendered once more than
329
- one sort key is active; a single active sort shows the arrow alone. */
328
+ /* Multi-column sort priority badge (1, 2, 3), rendered only when two or more rendered, sortable columns are sorted. */
330
329
  .cn-table-sort-badge {
331
330
  display: inline-flex;
332
331
  align-items: center;
@@ -2660,9 +2659,17 @@
2660
2659
  font-weight: 700;
2661
2660
  line-height: 1.3;
2662
2661
  color: var(--color-main-text);
2663
- white-space: nowrap;
2662
+ /* Two lines, then an ellipsis. One line cut a long case title short on a
2663
+ page with room to spare, and the title is the one thing that says which
2664
+ record this is. The element carries the full text as its `title`, so a
2665
+ title cut after two lines is still reachable. `overflow-wrap` breaks a
2666
+ word longer than the line, which nowrap used to hide. */
2667
+ display: -webkit-box;
2668
+ -webkit-box-orient: vertical;
2669
+ -webkit-line-clamp: 2;
2670
+ line-clamp: 2;
2664
2671
  overflow: hidden;
2665
- text-overflow: ellipsis;
2672
+ overflow-wrap: anywhere;
2666
2673
  }
2667
2674
 
2668
2675
  .cn-detail-page__subtitle {
@@ -6352,8 +6359,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
6352
6359
  outline-offset: -2px;
6353
6360
  }
6354
6361
 
6355
- /* Multi-column sort priority badge (1, 2, 3) — only rendered once more than
6356
- one sort key is active; a single active sort shows the arrow alone. */
6362
+ /* Multi-column sort priority badge (1, 2, 3), rendered only when two or more rendered, sortable columns are sorted. */
6357
6363
  .cn-table-sort-badge {
6358
6364
  display: inline-flex;
6359
6365
  align-items: center;
@@ -17505,6 +17511,30 @@ to { background-position: -200% 0;
17505
17511
  color: inherit;
17506
17512
  }
17507
17513
 
17514
+ /* The check failed. Muted text, no fill, no severity colour: it is a note
17515
+ that something is not known, not a warning. */
17516
+ .cn-banner-widget--unchecked[data-v-3531692f] {
17517
+ box-sizing: border-box;
17518
+ margin: 0;
17519
+ padding: 8px 12px;
17520
+ color: var(--color-text-maxcontrast);
17521
+ font-size: 0.9em;
17522
+ cursor: help;
17523
+ }
17524
+
17525
+ /* The reason, for assistive technology; sighted readers get the tooltip. */
17526
+ .cn-banner-widget__unchecked-reason[data-v-3531692f] {
17527
+ position: absolute;
17528
+ width: 1px;
17529
+ height: 1px;
17530
+ margin: -1px;
17531
+ padding: 0;
17532
+ overflow: hidden;
17533
+ clip-path: inset(50%);
17534
+ white-space: nowrap;
17535
+ border: 0;
17536
+ }
17537
+
17508
17538
  /* Attention card. The severity edge is an inset shadow, so it takes no width. */
17509
17539
  .cn-banner-widget--attention[data-v-3531692f] {
17510
17540
  --cn-banner-accent: var(--color-primary-element);
@@ -31807,6 +31837,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
31807
31837
  .cn-wiki-page__header[data-v-25a3d3cf] {
31808
31838
  margin-bottom: 16px;
31809
31839
  }
31840
+
31841
+ /* Without a sidebar the title starts at the left edge of the content, under
31842
+ the Nextcloud navigation toggle button (44px wide). Clear it as CnPageHeader
31843
+ does. With a sidebar the tree sits there instead and the title is clear. */
31844
+ .cn-wiki-page:not(.cn-wiki-page--has-sidebar) .cn-wiki-page__header[data-v-25a3d3cf] {
31845
+ padding-inline-start: 56px;
31846
+ }
31810
31847
  .cn-wiki-page__title[data-v-25a3d3cf] {
31811
31848
  font-size: 1.6em;
31812
31849
  margin: 0;
@@ -31866,6 +31903,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
31866
31903
  .cn-reports-page[data-v-41525752] {
31867
31904
  padding: 16px;
31868
31905
  }
31906
+
31907
+ /* Clear the Nextcloud navigation toggle button (44px wide, absolutely
31908
+ positioned at the left edge of .app-content) plus 12px breathing room, as
31909
+ CnPageHeader does. Only the header shifts; the body keeps the full width. */
31910
+ .cn-reports-page__header[data-v-41525752] {
31911
+ padding-inline-start: 56px;
31912
+ }
31869
31913
  .cn-reports-page__title[data-v-41525752] {
31870
31914
  margin: 0 0 4px;
31871
31915
  }
@@ -31922,6 +31966,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
31922
31966
  padding: 16px;
31923
31967
  max-width: 1200px;
31924
31968
  }
31969
+
31970
+ /* Clear the Nextcloud navigation toggle button (44px wide, absolutely
31971
+ positioned at the left edge of .app-content) plus 12px breathing room, as
31972
+ CnPageHeader does. Only the header shifts; the body keeps the full width. */
31973
+ .cn-store-page__header[data-v-25c8171b] {
31974
+ padding-inline-start: 56px;
31975
+ }
31925
31976
  .cn-store-page__heading[data-v-25c8171b] {
31926
31977
  display: flex;
31927
31978
  flex-wrap: wrap;
@@ -32005,6 +32056,122 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
32005
32056
  }
32006
32057
 
32007
32058
 
32059
+ .cn-link-cards-page[data-v-2f9a546b] {
32060
+ display: flex;
32061
+ flex-direction: column;
32062
+ gap: calc(6 * var(--default-grid-baseline));
32063
+ padding: calc(5 * var(--default-grid-baseline));
32064
+ }
32065
+
32066
+ /* Clear the Nextcloud navigation toggle button (44px wide, absolutely
32067
+ positioned at the left edge of .app-content) plus 12px breathing room, as
32068
+ CnPageHeader does. Only the header shifts; the cards keep the full width. */
32069
+ .cn-link-cards-page__header[data-v-2f9a546b] {
32070
+ padding-inline-start: 56px;
32071
+ }
32072
+ .cn-link-cards-page__title[data-v-2f9a546b] {
32073
+ margin: 0 0 var(--default-grid-baseline);
32074
+ }
32075
+ .cn-link-cards-page__description[data-v-2f9a546b],
32076
+ .cn-link-cards-page__empty[data-v-2f9a546b] {
32077
+ color: var(--color-text-maxcontrast);
32078
+ margin: 0;
32079
+ max-width: 70ch;
32080
+ }
32081
+ .cn-link-cards-page__group[data-v-2f9a546b] {
32082
+ display: flex;
32083
+ flex-direction: column;
32084
+ gap: calc(3 * var(--default-grid-baseline));
32085
+ }
32086
+ .cn-link-cards-page__caption[data-v-2f9a546b] {
32087
+ color: var(--color-text-maxcontrast);
32088
+ font-size: 0.85em;
32089
+ font-weight: 600;
32090
+ letter-spacing: 0.06em;
32091
+ margin: 0;
32092
+ text-transform: uppercase;
32093
+ }
32094
+ .cn-link-cards-page__grid[data-v-2f9a546b] {
32095
+ display: grid;
32096
+ gap: calc(3 * var(--default-grid-baseline));
32097
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
32098
+ list-style: none;
32099
+ margin: 0;
32100
+ padding: 0;
32101
+ }
32102
+ .cn-link-cards-page__cell[data-v-2f9a546b] {
32103
+ display: flex;
32104
+ }
32105
+ .cn-link-cards-page__card[data-v-2f9a546b] {
32106
+ align-items: flex-start;
32107
+ background: var(--color-main-background);
32108
+ border: 1px solid var(--color-border-dark);
32109
+ border-radius: var(--border-radius-large);
32110
+ color: var(--color-main-text);
32111
+ display: flex;
32112
+ flex: 1;
32113
+ gap: calc(3 * var(--default-grid-baseline));
32114
+ min-width: 0;
32115
+ padding: calc(4 * var(--default-grid-baseline));
32116
+ transition: background-color var(--animation-quick, 100ms) ease, border-color var(--animation-quick, 100ms) ease, box-shadow var(--animation-quick, 100ms) ease;
32117
+ }
32118
+
32119
+ /* A card is a link, and it must not look like a line of prose. A theme may
32120
+ underline every link with `!important` (thematiq: `a { text-decoration:
32121
+ underline !important }`, specificity 0,0,1). Among `!important` rules the
32122
+ higher specificity wins, so this class rule has to carry it too; without it
32123
+ the label and the description both rendered underlined on a live Nextcloud. */
32124
+ .cn-link-cards-page__card[data-v-2f9a546b],
32125
+ .cn-link-cards-page__card[data-v-2f9a546b]:hover,
32126
+ .cn-link-cards-page__card[data-v-2f9a546b]:focus,
32127
+ .cn-link-cards-page__card[data-v-2f9a546b]:active {
32128
+ text-decoration: none !important;
32129
+ }
32130
+
32131
+ /* Hover moves the whole card: border, background and a soft lift. */
32132
+ .cn-link-cards-page__card[data-v-2f9a546b]:hover {
32133
+ background: var(--color-background-hover);
32134
+ border-color: var(--color-primary-element);
32135
+ box-shadow: 0 2px 6px var(--color-box-shadow);
32136
+ }
32137
+ @media (prefers-reduced-motion: reduce) {
32138
+ .cn-link-cards-page__card[data-v-2f9a546b] {
32139
+ transition: none;
32140
+ }
32141
+ }
32142
+ .cn-link-cards-page__card[data-v-2f9a546b]:focus-visible {
32143
+ border-color: var(--color-primary-element);
32144
+ outline: 2px solid var(--color-primary-element);
32145
+ outline-offset: 2px;
32146
+ }
32147
+ .cn-link-cards-page__icon[data-v-2f9a546b] {
32148
+ color: var(--color-primary-element);
32149
+ flex: none;
32150
+ }
32151
+ .cn-link-cards-page__body[data-v-2f9a546b] {
32152
+ display: flex;
32153
+ flex-direction: column;
32154
+ gap: var(--default-grid-baseline);
32155
+ min-width: 0;
32156
+ }
32157
+
32158
+ /* The label is the card's title. Its colour is set here, on the span, because
32159
+ a theme may force its link colour on the `a` itself; an inherited link
32160
+ colour made the title read as a hyperlink. */
32161
+ .cn-link-cards-page__label[data-v-2f9a546b] {
32162
+ color: var(--color-main-text);
32163
+ font-size: 1.05em;
32164
+ font-weight: 700;
32165
+ line-height: 1.3;
32166
+ overflow-wrap: anywhere;
32167
+ }
32168
+ .cn-link-cards-page__card-description[data-v-2f9a546b] {
32169
+ color: var(--color-text-maxcontrast);
32170
+ font-size: 0.9em;
32171
+ line-height: 1.4;
32172
+ }
32173
+
32174
+
32008
32175
  /* NcDialog supplies the padding and the heading (via `name`); this div keeps only
32009
32176
  the body's own column layout. */
32010
32177
  .cn-page-config[data-v-9e9a9cf8] {
@@ -34218,15 +34385,40 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
34218
34385
 
34219
34386
 
34220
34387
  .cn-brand-stripe[data-v-6fdfa763] {
34388
+ --cn-brand-stripe-first: var(--cn-brand-stripe-ratio-1, 1);
34389
+ --cn-brand-stripe-second: var(--cn-brand-stripe-ratio-2, 1);
34390
+ --cn-brand-stripe-total: calc(
34391
+ var(--cn-brand-stripe-first) + var(--cn-brand-stripe-second) + var(--cn-brand-stripe-ratio-3, 1)
34392
+ );
34393
+ --cn-brand-stripe-stop-1: calc(var(--cn-brand-stripe-first) / var(--cn-brand-stripe-total) * 100%);
34394
+ --cn-brand-stripe-stop-2: calc(
34395
+ (var(--cn-brand-stripe-first) + var(--cn-brand-stripe-second)) / var(--cn-brand-stripe-total) * 100%
34396
+ );
34397
+ --cn-brand-stripe-direction: to right;
34398
+ --cn-brand-stripe-bands: linear-gradient(
34399
+ var(--cn-brand-stripe-direction),
34400
+ var(--cn-brand-stripe-color-1, var(--color-primary-element)) 0 var(--cn-brand-stripe-stop-1),
34401
+ var(--cn-brand-stripe-color-2, var(--color-primary-element)) var(--cn-brand-stripe-stop-1) var(--cn-brand-stripe-stop-2),
34402
+ var(--cn-brand-stripe-color-3, var(--color-primary-element)) var(--cn-brand-stripe-stop-2) 100%
34403
+ );
34221
34404
  display: flex;
34222
34405
  flex: none;
34223
34406
  width: 100%;
34224
34407
  height: var(--cn-brand-stripe-height, 4px);
34408
+ background-image: var(--cn-brand-stripe-image, var(--cn-brand-stripe-bands));
34409
+ background-repeat: repeat-x;
34410
+ background-size: auto 100%;
34411
+ }
34412
+ .cn-brand-stripe--inverse[data-v-6fdfa763] {
34413
+ background-image: var(--cn-brand-stripe-image-inverse, var(--cn-brand-stripe-image, var(--cn-brand-stripe-bands)));
34225
34414
  }
34226
34415
  .cn-brand-stripe--vertical[data-v-6fdfa763] {
34416
+ --cn-brand-stripe-direction: to bottom;
34227
34417
  flex-direction: column;
34228
34418
  width: var(--cn-brand-stripe-height, 4px);
34229
34419
  height: 100%;
34420
+ background-repeat: repeat-y;
34421
+ background-size: 100% auto;
34230
34422
  }
34231
34423
  .cn-brand-stripe__band[data-v-6fdfa763] {
34232
34424
  flex-basis: 0;
@@ -34234,15 +34426,12 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
34234
34426
  }
34235
34427
  .cn-brand-stripe__band--1[data-v-6fdfa763] {
34236
34428
  flex-grow: var(--cn-brand-stripe-ratio-1, 1);
34237
- background-color: var(--cn-brand-stripe-color-1, var(--color-primary-element));
34238
34429
  }
34239
34430
  .cn-brand-stripe__band--2[data-v-6fdfa763] {
34240
34431
  flex-grow: var(--cn-brand-stripe-ratio-2, 1);
34241
- background-color: var(--cn-brand-stripe-color-2, var(--color-primary-element));
34242
34432
  }
34243
34433
  .cn-brand-stripe__band--3[data-v-6fdfa763] {
34244
34434
  flex-grow: var(--cn-brand-stripe-ratio-3, 1);
34245
- background-color: var(--cn-brand-stripe-color-3, var(--color-primary-element));
34246
34435
  }
34247
34436
  @media print {
34248
34437
  .cn-brand-stripe[data-v-6fdfa763] {
package/l10n/en.json CHANGED
@@ -294,6 +294,11 @@
294
294
  "JSON pointer": "JSON pointer",
295
295
  "Last": "Last",
296
296
  "Load more": "Load more",
297
+ "Nothing to open here.": "Nothing to open here.",
298
+ "Could not check": "Could not check",
299
+ "Could not check: {subject}": "Could not check: {subject}",
300
+ "The check failed.": "The check failed.",
301
+ "The check failed: {reason}": "The check failed: {reason}",
297
302
  "To do": "To do",
298
303
  "Next step": "Next step",
299
304
  "Review": "Review",
@@ -621,6 +626,7 @@
621
626
  "No linked emails yet": "No linked emails yet",
622
627
  "Could not load emails": "Could not load emails",
623
628
  "Open in Mail": "Open in Mail",
629
+ "Open in Office": "Open in Office",
624
630
  "(no subject)": "(no subject)",
625
631
  "Unknown sender": "Unknown sender",
626
632
  "No conversations linked yet": "No conversations linked yet",
package/l10n/nl.json CHANGED
@@ -294,6 +294,11 @@
294
294
  "JSON pointer": "JSON-aanwijzer",
295
295
  "Last": "Laatste",
296
296
  "Load more": "Meer laden",
297
+ "Nothing to open here.": "Hier is niets te openen.",
298
+ "Could not check": "Kon niet controleren",
299
+ "Could not check: {subject}": "Kon niet controleren: {subject}",
300
+ "The check failed.": "De controle is mislukt.",
301
+ "The check failed: {reason}": "De controle is mislukt: {reason}",
297
302
  "To do": "Nog doen",
298
303
  "Next step": "Volgende stap",
299
304
  "Review": "Beoordelen",
@@ -613,6 +618,7 @@
613
618
  "No linked emails yet": "Nog geen gekoppelde e-mails",
614
619
  "Could not load emails": "Kon e-mails niet laden",
615
620
  "Open in Mail": "Openen in Mail",
621
+ "Open in Office": "Openen in Office",
616
622
  "(no subject)": "(geen onderwerp)",
617
623
  "Unknown sender": "Onbekende afzender",
618
624
  "No conversations linked yet": "Nog geen gesprekken gekoppeld",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.60.0",
3
+ "version": "2.62.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": {
@@ -94,7 +94,8 @@
94
94
  "test": "jest",
95
95
  "test:e2e": "playwright test",
96
96
  "test:types": "tsd",
97
- "update:manifest-fixtures": "node scripts/update-fleet-manifest-fixtures.js"
97
+ "update:manifest-fixtures": "node scripts/update-fleet-manifest-fixtures.js",
98
+ "update:manifest-schema-hash": "node scripts/record-manifest-schema-hash.js"
98
99
  },
99
100
  "overrides": {
100
101
  "@vue/vue3-jest": {
@@ -130,6 +130,7 @@
130
130
  "CnLifecycleActions": 1,
131
131
  "CnLinkButtonWidget": 1,
132
132
  "CnLinkButtonWidgetForm": 1,
133
+ "CnLinkCardsPage": 1,
133
134
  "CnLinksWidget": 1,
134
135
  "CnLinksWidgetForm": 1,
135
136
  "CnLockedBanner": 1,
@@ -0,0 +1,41 @@
1
+ /**
2
+ * SPDX-License-Identifier: EUPL-1.2
3
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
4
+ *
5
+ * The content hash of the v2 manifest schema, shared by the recording script
6
+ * (`record-manifest-schema-hash.js`) and the test that checks it
7
+ * (`tests/schemas/app-manifest-v2.schema-version.spec.js`).
8
+ */
9
+
10
+ const crypto = require('crypto')
11
+ const fs = require('fs')
12
+ const path = require('path')
13
+
14
+ const ROOT = path.resolve(__dirname, '..')
15
+ const SCHEMA_PATH = path.join(ROOT, 'src/schemas/app-manifest-v2.schema.json')
16
+ const LEDGER_PATH = path.join(ROOT, 'tests/schemas/app-manifest-v2.schema.hashes.json')
17
+
18
+ /**
19
+ * Hash a parsed schema. The hash is over the parsed document written back
20
+ * without whitespace, so reformatting the file is not a content change while
21
+ * any key, value or ordering change is. `version` is part of the content.
22
+ *
23
+ * @param {object} schema The parsed schema.
24
+ * @return {string} `sha256:<hex>`.
25
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-the-manifest-schema-version-moves-with-its-content
26
+ */
27
+ function hashSchema(schema) {
28
+ return 'sha256:' + crypto.createHash('sha256').update(JSON.stringify(schema)).digest('hex')
29
+ }
30
+
31
+ /**
32
+ * Read the ledger of recorded versions.
33
+ *
34
+ * @return {{versions: Record<string, string>}} The ledger.
35
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-the-manifest-schema-version-moves-with-its-content
36
+ */
37
+ function readLedger() {
38
+ return JSON.parse(fs.readFileSync(LEDGER_PATH, 'utf8'))
39
+ }
40
+
41
+ module.exports = { SCHEMA_PATH, LEDGER_PATH, hashSchema, readLedger }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * SPDX-License-Identifier: EUPL-1.2
3
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
4
+ *
5
+ * Records the content hash of `src/schemas/app-manifest-v2.schema.json` under
6
+ * its current `version` in `tests/schemas/app-manifest-v2.schema.hashes.json`
7
+ * (`npm run update:manifest-schema-hash`).
8
+ *
9
+ * Why a ledger. The schema's `version` is the only machine-readable signal a
10
+ * consumer gets that the vocabulary moved: ConductionNL/.github vendors this
11
+ * file into the gate tooling and compares versions to decide whether its copy
12
+ * is stale. 2.61.0 shipped a new page type under an unchanged 2.42.0, so that
13
+ * comparison read "already current" against a schema that had moved. The
14
+ * ledger makes that state fail a test here instead: a version has one content
15
+ * hash, so changing the content means taking a new version.
16
+ *
17
+ * This script therefore REFUSES to replace the hash of a version that is
18
+ * already recorded. Bump `version` in the schema first, then run it.
19
+ *
20
+ * The hash and the ledger shape live in `manifest-schema-hash.js`, which the
21
+ * test requires too, so the two cannot disagree about what "content" means.
22
+ */
23
+
24
+ const fs = require('fs')
25
+ const { SCHEMA_PATH, LEDGER_PATH, hashSchema, readLedger } = require('./manifest-schema-hash.js')
26
+
27
+ const schema = JSON.parse(fs.readFileSync(SCHEMA_PATH, 'utf8'))
28
+ const version = schema.version
29
+ if (typeof version !== 'string' || version === '') {
30
+ console.error(`${SCHEMA_PATH} has no "version". Nothing to record.`)
31
+ process.exit(1)
32
+ }
33
+
34
+ const ledger = readLedger()
35
+ const hash = hashSchema(schema)
36
+ const recorded = ledger.versions[version]
37
+
38
+ if (recorded === hash) {
39
+ console.log(`Version ${version} is already recorded with this content. Nothing to do.`)
40
+ process.exit(0)
41
+ }
42
+ if (recorded !== undefined) {
43
+ const message = `Version ${version} is already recorded with different content.\n`
44
+ + 'A version has one content. Bump "version" in the schema (minor for an\n'
45
+ + 'additive change), then run this script again.'
46
+ console.error(message)
47
+ process.exit(1)
48
+ }
49
+
50
+ ledger.versions[version] = hash
51
+ fs.writeFileSync(LEDGER_PATH, JSON.stringify(ledger, null, '\t') + '\n')
52
+ console.log(`Recorded ${version} = ${hash}`)
@@ -346,6 +346,7 @@
346
346
  import { translate as t } from '@nextcloud/l10n'
347
347
  import { generateUrl } from '@nextcloud/router'
348
348
  import { NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'
349
+ import { toRaw } from 'vue'
349
350
  import BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'
350
351
  import Cog from 'vue-material-design-icons/Cog.vue'
351
352
  // ADR-077 rule 4: visible fallback for an unresolvable icon name.
@@ -576,6 +577,13 @@ export default {
576
577
 
577
578
  emits: ['primary-action', 'primary-action-click'],
578
579
 
580
+ /**
581
+ * The navigation's own state: which groups are open, and which entry
582
+ * of a route the reader last used.
583
+ *
584
+ * @return {object} The state.
585
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
586
+ */
579
587
  data() {
580
588
  return {
581
589
  /**
@@ -589,6 +597,15 @@ export default {
589
597
  */
590
598
  openState: {},
591
599
 
600
+ /**
601
+ * Per route name, the key of the entry that was last active on
602
+ * that route in this session. Read by `subRouteParent` to keep
603
+ * "the list I came from" marked on a page below it. In memory
604
+ * only: a reload starts empty and falls back to menu order.
605
+ *
606
+ * @type {Record<string, string>}
607
+ */
608
+ lastActiveEntryByRoute: {},
592
609
  }
593
610
  },
594
611
 
@@ -946,6 +963,67 @@ export default {
946
963
  }
947
964
  return best ?? routeName ?? null
948
965
  },
966
+
967
+ /**
968
+ * Every visible entry and child, flat, in menu order.
969
+ *
970
+ * @return {Array<object>} The entries.
971
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
972
+ */
973
+ flatEntries() {
974
+ const flat = []
975
+ for (const item of this.visibleItems) {
976
+ flat.push(item, ...this.visibleChildren(item))
977
+ }
978
+ return flat
979
+ },
980
+
981
+ /**
982
+ * The entry the query rule marks while the reader is on a menu
983
+ * route itself (not on a page below it). Watched, so the nav can
984
+ * remember which entry of a route the reader last used.
985
+ *
986
+ * @return {object|null} The entry, or null on a page below a route.
987
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
988
+ */
989
+ entryActiveOnOwnRoute() {
990
+ const routeName = this.$route?.name
991
+ if (!routeName || routeName !== this.activeRouteName) {
992
+ return null
993
+ }
994
+ return this.flatEntries.find((entry) => this.isActiveByRule(entry)) ?? null
995
+ },
996
+
997
+ /**
998
+ * The one entry to mark on a page BELOW a menu route when the query
999
+ * rule marks none.
1000
+ *
1001
+ * On a detail page (`/cases/123`) the active route is found by path
1002
+ * prefix, and the address carries the detail page's query, not the
1003
+ * list's. When every entry on that list has a `query` ("My work",
1004
+ * "Queue"), none of them matched and the menu showed no place at
1005
+ * all. Marking all of them is the defect the query rule ended, so
1006
+ * this picks exactly one: the entry the reader last had active on
1007
+ * that route, else the first in menu order.
1008
+ *
1009
+ * Null whenever the existing rule already marks an entry, so a list
1010
+ * with an entry without a `query` behaves as it did.
1011
+ *
1012
+ * @return {object|null} The entry to mark, or null.
1013
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
1014
+ */
1015
+ subRouteParent() {
1016
+ const active = this.activeRouteName
1017
+ if (!active || this.$route?.name === active) {
1018
+ return null
1019
+ }
1020
+ const entries = this.flatEntries.filter((entry) => !entry.href && entry.route === active)
1021
+ if (entries.length === 0 || entries.some((entry) => this.isActiveByRule(entry))) {
1022
+ return null
1023
+ }
1024
+ const remembered = this.lastActiveEntryByRoute[active]
1025
+ return entries.find((entry) => this.entryKey(entry) === remembered) ?? entries[0]
1026
+ },
949
1027
  },
950
1028
 
951
1029
  watch: {
@@ -960,6 +1038,23 @@ export default {
960
1038
  visibleItems() {
961
1039
  this.pinGroupsEntered(this.activeRouteName)
962
1040
  },
1041
+
1042
+ entryActiveOnOwnRoute: {
1043
+ immediate: true,
1044
+ /**
1045
+ * Remember which entry of a route the reader used, for
1046
+ * `subRouteParent`.
1047
+ *
1048
+ * @param {object|null} entry The entry active on its own route.
1049
+ * @return {void}
1050
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
1051
+ */
1052
+ handler(entry) {
1053
+ if (entry && entry.route) {
1054
+ this.lastActiveEntryByRoute[entry.route] = this.entryKey(entry)
1055
+ }
1056
+ },
1057
+ },
963
1058
  },
964
1059
 
965
1060
  created() {
@@ -1184,11 +1279,84 @@ export default {
1184
1279
  return this.effectiveTranslate(item.label)
1185
1280
  },
1186
1281
 
1282
+ /**
1283
+ * Whether a menu entry is the page the reader is on.
1284
+ *
1285
+ * Two entries may share a route and differ only in `query` ("My
1286
+ * work" and "Queue" on one list). The route name alone lit both. An
1287
+ * entry with a `query` is active only when the address carries that
1288
+ * query; an entry without one steps back when a sibling's query
1289
+ * matches, so exactly one of them is marked.
1290
+ *
1291
+ * On a page below a list where that rule marks nothing, the one
1292
+ * entry `subRouteParent` picks is active instead.
1293
+ *
1294
+ * @param {object} item Menu entry.
1295
+ * @return {boolean} True when the entry is the current page.
1296
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-menu-entries-that-differ-in-query
1297
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
1298
+ */
1187
1299
  isActive(item) {
1300
+ if (this.isActiveByRule(item)) {
1301
+ return true
1302
+ }
1303
+ const parent = this.subRouteParent
1304
+ return parent !== null && toRaw(parent) === toRaw(item)
1305
+ },
1306
+
1307
+ /**
1308
+ * The route and query rule on its own, without the fallback for a
1309
+ * page below a list. `subRouteParent` asks this to see whether the
1310
+ * rule already marks an entry.
1311
+ *
1312
+ * @param {object} item Menu entry.
1313
+ * @return {boolean} True when the rule marks the entry.
1314
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-menu-entries-that-differ-in-query
1315
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
1316
+ */
1317
+ isActiveByRule(item) {
1188
1318
  if (item.href || !item.route) {
1189
1319
  return false
1190
1320
  }
1191
- return item.route === this.activeRouteName
1321
+ if (item.route !== this.activeRouteName) {
1322
+ return false
1323
+ }
1324
+ if (item.query && typeof item.query === 'object') {
1325
+ return this.queryMatches(item)
1326
+ }
1327
+ const siblings = []
1328
+ for (const entry of this.visibleItems) {
1329
+ siblings.push(entry, ...this.visibleChildren(entry))
1330
+ }
1331
+ return !siblings.some((entry) => entry !== item
1332
+ && entry.route === item.route
1333
+ && entry.query && typeof entry.query === 'object'
1334
+ && this.queryMatches(entry))
1335
+ },
1336
+
1337
+ /**
1338
+ * What an entry is remembered by: its `id`, else its label.
1339
+ *
1340
+ * @param {object} item Menu entry.
1341
+ * @return {string} The key.
1342
+ * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-a-page-below-a-list-marks-one-menu-entry
1343
+ */
1344
+ entryKey(item) {
1345
+ return String(item.id ?? item.label ?? '')
1346
+ },
1347
+
1348
+ /**
1349
+ * Whether the address carries every key of an entry's `query`.
1350
+ * Values are compared as strings, since an address has no numbers.
1351
+ *
1352
+ * @param {object} item Menu entry with a `query`.
1353
+ * @return {boolean} True when every declared key matches.
1354
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-menu-entries-that-differ-in-query
1355
+ */
1356
+ queryMatches(item) {
1357
+ const current = this.$route?.query ?? {}
1358
+ const keys = Object.keys(item.query)
1359
+ return keys.length > 0 && keys.every((key) => String(current[key] ?? '') === String(item.query[key]))
1192
1360
  },
1193
1361
 
1194
1362
  /**