@conduction/nextcloud-vue 2.61.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.
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +128 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +79 -2
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +38 -25
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +21 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +4 -1
- package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +30 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +40 -15
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +2 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +53 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +22 -5
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStorePage/CnStorePage.vue2.js.map +1 -1
- package/dist/esm/components/CnStorePage/CnStorePage.vue3.js.map +1 -1
- package/dist/esm/components/CnWikiPage/CnWikiPage.vue2.js.map +1 -1
- package/dist/esm/components/CnWikiPage/CnWikiPage.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +5 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +5 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +108 -9
- package/dist/esm/utils/validateManifestV2.compiled.js +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +857 -490
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +108 -9
- package/l10n/en.json +5 -0
- package/l10n/nl.json +5 -0
- package/package.json +3 -2
- package/scripts/manifest-schema-hash.js +41 -0
- package/scripts/record-manifest-schema-hash.js +52 -0
- package/src/components/CnAppNav/CnAppNav.vue +128 -0
- package/src/components/CnBannerWidget/CnBannerWidget.vue +113 -2
- package/src/components/CnBrandStripe/CnBrandStripe.md +16 -0
- package/src/components/CnBrandStripe/CnBrandStripe.vue +58 -5
- package/src/components/CnDashboardPage/CnDashboardPage.vue +30 -6
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +42 -17
- package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +35 -2
- package/src/components/CnObjectSidebar/CnFilesTab.vue +65 -0
- package/src/components/CnReportsPage/CnReportsPage.vue +7 -0
- package/src/components/CnStorePage/CnStorePage.vue +7 -0
- package/src/components/CnWikiPage/CnWikiPage.vue +7 -0
- package/src/css/table.css +1 -2
- package/src/public/index.js +5 -0
- package/src/schemas/app-manifest-v2.schema.json +1 -1
- package/src/utils/validateManifestV2.compiled.js +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/nextcloud-vue.cjs.js.map
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34658160 bytes
|
|
4
|
+
sha256: 39b2808fa6e91c4a4c4e4dce5008a2efbcbb57754f60422fbac1771331bc070e
|
package/dist/nextcloud-vue.css
CHANGED
|
@@ -325,8 +325,7 @@
|
|
|
325
325
|
outline-offset: -2px;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
/* Multi-column sort priority badge (1, 2, 3)
|
|
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;
|
|
@@ -6360,8 +6359,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
|
|
6360
6359
|
outline-offset: -2px;
|
|
6361
6360
|
}
|
|
6362
6361
|
|
|
6363
|
-
/* Multi-column sort priority badge (1, 2, 3)
|
|
6364
|
-
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. */
|
|
6365
6363
|
.cn-table-sort-badge {
|
|
6366
6364
|
display: inline-flex;
|
|
6367
6365
|
align-items: center;
|
|
@@ -17513,6 +17511,30 @@ to { background-position: -200% 0;
|
|
|
17513
17511
|
color: inherit;
|
|
17514
17512
|
}
|
|
17515
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
|
+
|
|
17516
17538
|
/* Attention card. The severity edge is an inset shadow, so it takes no width. */
|
|
17517
17539
|
.cn-banner-widget--attention[data-v-3531692f] {
|
|
17518
17540
|
--cn-banner-accent: var(--color-primary-element);
|
|
@@ -31815,6 +31837,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
31815
31837
|
.cn-wiki-page__header[data-v-25a3d3cf] {
|
|
31816
31838
|
margin-bottom: 16px;
|
|
31817
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
|
+
}
|
|
31818
31847
|
.cn-wiki-page__title[data-v-25a3d3cf] {
|
|
31819
31848
|
font-size: 1.6em;
|
|
31820
31849
|
margin: 0;
|
|
@@ -31874,6 +31903,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
31874
31903
|
.cn-reports-page[data-v-41525752] {
|
|
31875
31904
|
padding: 16px;
|
|
31876
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
|
+
}
|
|
31877
31913
|
.cn-reports-page__title[data-v-41525752] {
|
|
31878
31914
|
margin: 0 0 4px;
|
|
31879
31915
|
}
|
|
@@ -31930,6 +31966,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
31930
31966
|
padding: 16px;
|
|
31931
31967
|
max-width: 1200px;
|
|
31932
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
|
+
}
|
|
31933
31976
|
.cn-store-page__heading[data-v-25c8171b] {
|
|
31934
31977
|
display: flex;
|
|
31935
31978
|
flex-wrap: wrap;
|
|
@@ -32019,6 +32062,13 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
32019
32062
|
gap: calc(6 * var(--default-grid-baseline));
|
|
32020
32063
|
padding: calc(5 * var(--default-grid-baseline));
|
|
32021
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
|
+
}
|
|
32022
32072
|
.cn-link-cards-page__title[data-v-2f9a546b] {
|
|
32023
32073
|
margin: 0 0 var(--default-grid-baseline);
|
|
32024
32074
|
}
|
|
@@ -32063,11 +32113,31 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
32063
32113
|
gap: calc(3 * var(--default-grid-baseline));
|
|
32064
32114
|
min-width: 0;
|
|
32065
32115
|
padding: calc(4 * var(--default-grid-baseline));
|
|
32066
|
-
|
|
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;
|
|
32067
32129
|
}
|
|
32130
|
+
|
|
32131
|
+
/* Hover moves the whole card: border, background and a soft lift. */
|
|
32068
32132
|
.cn-link-cards-page__card[data-v-2f9a546b]:hover {
|
|
32069
32133
|
background: var(--color-background-hover);
|
|
32070
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
|
+
}
|
|
32071
32141
|
}
|
|
32072
32142
|
.cn-link-cards-page__card[data-v-2f9a546b]:focus-visible {
|
|
32073
32143
|
border-color: var(--color-primary-element);
|
|
@@ -32084,8 +32154,15 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
32084
32154
|
gap: var(--default-grid-baseline);
|
|
32085
32155
|
min-width: 0;
|
|
32086
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. */
|
|
32087
32161
|
.cn-link-cards-page__label[data-v-2f9a546b] {
|
|
32088
|
-
|
|
32162
|
+
color: var(--color-main-text);
|
|
32163
|
+
font-size: 1.05em;
|
|
32164
|
+
font-weight: 700;
|
|
32165
|
+
line-height: 1.3;
|
|
32089
32166
|
overflow-wrap: anywhere;
|
|
32090
32167
|
}
|
|
32091
32168
|
.cn-link-cards-page__card-description[data-v-2f9a546b] {
|
|
@@ -34308,15 +34385,40 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
34308
34385
|
|
|
34309
34386
|
|
|
34310
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
|
+
);
|
|
34311
34404
|
display: flex;
|
|
34312
34405
|
flex: none;
|
|
34313
34406
|
width: 100%;
|
|
34314
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)));
|
|
34315
34414
|
}
|
|
34316
34415
|
.cn-brand-stripe--vertical[data-v-6fdfa763] {
|
|
34416
|
+
--cn-brand-stripe-direction: to bottom;
|
|
34317
34417
|
flex-direction: column;
|
|
34318
34418
|
width: var(--cn-brand-stripe-height, 4px);
|
|
34319
34419
|
height: 100%;
|
|
34420
|
+
background-repeat: repeat-y;
|
|
34421
|
+
background-size: 100% auto;
|
|
34320
34422
|
}
|
|
34321
34423
|
.cn-brand-stripe__band[data-v-6fdfa763] {
|
|
34322
34424
|
flex-basis: 0;
|
|
@@ -34324,15 +34426,12 @@ a.cn-integration-card__title[data-v-f7e9bbfa]:hover {
|
|
|
34324
34426
|
}
|
|
34325
34427
|
.cn-brand-stripe__band--1[data-v-6fdfa763] {
|
|
34326
34428
|
flex-grow: var(--cn-brand-stripe-ratio-1, 1);
|
|
34327
|
-
background-color: var(--cn-brand-stripe-color-1, var(--color-primary-element));
|
|
34328
34429
|
}
|
|
34329
34430
|
.cn-brand-stripe__band--2[data-v-6fdfa763] {
|
|
34330
34431
|
flex-grow: var(--cn-brand-stripe-ratio-2, 1);
|
|
34331
|
-
background-color: var(--cn-brand-stripe-color-2, var(--color-primary-element));
|
|
34332
34432
|
}
|
|
34333
34433
|
.cn-brand-stripe__band--3[data-v-6fdfa763] {
|
|
34334
34434
|
flex-grow: var(--cn-brand-stripe-ratio-3, 1);
|
|
34335
|
-
background-color: var(--cn-brand-stripe-color-3, var(--color-primary-element));
|
|
34336
34435
|
}
|
|
34337
34436
|
@media print {
|
|
34338
34437
|
.cn-brand-stripe[data-v-6fdfa763] {
|
package/l10n/en.json
CHANGED
|
@@ -295,6 +295,10 @@
|
|
|
295
295
|
"Last": "Last",
|
|
296
296
|
"Load more": "Load more",
|
|
297
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}",
|
|
298
302
|
"To do": "To do",
|
|
299
303
|
"Next step": "Next step",
|
|
300
304
|
"Review": "Review",
|
|
@@ -622,6 +626,7 @@
|
|
|
622
626
|
"No linked emails yet": "No linked emails yet",
|
|
623
627
|
"Could not load emails": "Could not load emails",
|
|
624
628
|
"Open in Mail": "Open in Mail",
|
|
629
|
+
"Open in Office": "Open in Office",
|
|
625
630
|
"(no subject)": "(no subject)",
|
|
626
631
|
"Unknown sender": "Unknown sender",
|
|
627
632
|
"No conversations linked yet": "No conversations linked yet",
|
package/l10n/nl.json
CHANGED
|
@@ -295,6 +295,10 @@
|
|
|
295
295
|
"Last": "Laatste",
|
|
296
296
|
"Load more": "Meer laden",
|
|
297
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}",
|
|
298
302
|
"To do": "Nog doen",
|
|
299
303
|
"Next step": "Volgende stap",
|
|
300
304
|
"Review": "Beoordelen",
|
|
@@ -614,6 +618,7 @@
|
|
|
614
618
|
"No linked emails yet": "Nog geen gekoppelde e-mails",
|
|
615
619
|
"Could not load emails": "Kon e-mails niet laden",
|
|
616
620
|
"Open in Mail": "Openen in Mail",
|
|
621
|
+
"Open in Office": "Openen in Office",
|
|
617
622
|
"(no subject)": "(geen onderwerp)",
|
|
618
623
|
"Unknown sender": "Onbekende afzender",
|
|
619
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.
|
|
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": {
|
|
@@ -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() {
|
|
@@ -1193,11 +1288,33 @@ export default {
|
|
|
1193
1288
|
* query; an entry without one steps back when a sibling's query
|
|
1194
1289
|
* matches, so exactly one of them is marked.
|
|
1195
1290
|
*
|
|
1291
|
+
* On a page below a list where that rule marks nothing, the one
|
|
1292
|
+
* entry `subRouteParent` picks is active instead.
|
|
1293
|
+
*
|
|
1196
1294
|
* @param {object} item Menu entry.
|
|
1197
1295
|
* @return {boolean} True when the entry is the current page.
|
|
1198
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
|
|
1199
1298
|
*/
|
|
1200
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) {
|
|
1201
1318
|
if (item.href || !item.route) {
|
|
1202
1319
|
return false
|
|
1203
1320
|
}
|
|
@@ -1217,6 +1334,17 @@ export default {
|
|
|
1217
1334
|
&& this.queryMatches(entry))
|
|
1218
1335
|
},
|
|
1219
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
|
+
|
|
1220
1348
|
/**
|
|
1221
1349
|
* Whether the address carries every key of an entry's `query`.
|
|
1222
1350
|
* Values are compared as strings, since an address has no numbers.
|