@conduction/nextcloud-vue 2.110.0 → 2.111.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 (33) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +2 -1
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  4. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +20 -0
  5. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  6. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +68 -61
  7. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  8. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +37 -12
  9. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  10. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  11. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  12. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +1 -1
  13. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  14. package/dist/esm/library-style.js +1 -1
  15. package/dist/esm/nextcloud-vue.css +98 -31
  16. package/dist/esm/utils/savedViewHelpers.js +40 -3
  17. package/dist/esm/utils/savedViewHelpers.js.map +1 -1
  18. package/dist/nextcloud-vue.cjs.js +198 -109
  19. package/dist/nextcloud-vue.cjs.js.map +2 -2
  20. package/dist/nextcloud-vue.css +98 -31
  21. package/package.json +1 -1
  22. package/src/components/CnAppRoot/CnAppRoot.vue +2 -1
  23. package/src/components/CnDetailPage/CnDetailPage.vue +35 -11
  24. package/src/components/CnIndexPage/CnIndexPage.md +1 -1
  25. package/src/components/CnIndexPage/CnIndexPage.vue +37 -12
  26. package/src/components/CnIndexSidebar/CnIndexSidebar.md +1 -1
  27. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +2 -2
  28. package/src/css/board.css +5 -2
  29. package/src/css/look-board-detail.css +19 -7
  30. package/src/css/look-board-index.css +14 -10
  31. package/src/css/look-board-reports.css +1 -1
  32. package/src/css/look-board.css +54 -9
  33. package/src/utils/savedViewHelpers.js +39 -2
@@ -91,14 +91,19 @@
91
91
  /* Buttons and inputs. */
92
92
  --cn-board-control-height: 40px;
93
93
  --cn-board-control-radius: 8px;
94
+ /* A control's edge: 3:1 against every ground a control sits on (WCAG
95
+ 1.4.11). Nextcloud's border-dark is a hairline (#dbdbdb, 1.4:1); its
96
+ max-contrast border is the control step, and a theme sets both. */
97
+ --cn-board-control-border: var(--color-border-maxcontrast);
94
98
  /* Cards. */
95
99
  --cn-board-card-radius: 12px;
96
100
  --cn-board-card-padding: 20px 22px;
97
101
  /* The side inset of a dashboard card: its title and its rows. */
98
102
  --cn-board-dashboard-card-inset: 24px;
99
- /* Hairline inside a card, and the soft secondary text. */
103
+ /* Hairline inside a card, and the soft secondary text: the boards' #3d4047,
104
+ between the main text and the muted text, so it follows a theme's pair. */
100
105
  --cn-board-hairline: var(--color-border);
101
- --cn-board-text-soft: var(--color-main-text);
106
+ --cn-board-text-soft: color-mix(in srgb, var(--color-main-text) 45%, var(--color-text-maxcontrast));
102
107
  }
103
108
 
104
109
  /*
@@ -250,6 +255,37 @@
250
255
  --font-weight-element: 600;
251
256
  }
252
257
 
258
+ /* ---- A button with an icon and a label --------------------------------- */
259
+ /* The boards draw it 14px in at both sides with 14px text and a 16px icon
260
+ 8px from its label (DqZaakEmail "Notitie toevoegen", DqVthInstellingen
261
+ "Regel toevoegen"). NcButton pads its start 4px and its end 12px, and puts
262
+ a 20px icon in a 30px slot. The rules hold the condition in `:where()`, so
263
+ they weigh (0,3,0): enough to outrank NcButton's own (0,2,0), and less than
264
+ any surface rule (header, toolbar, follow pill, Meer chevron, tab strip),
265
+ which keeps its own value. The padding goes through NcButton's
266
+ `--button-padding`, so a surface that sets `padding` is never contested.
267
+ @spec openspec/changes/board-icon-label-button/specs/board-icon-label-button/spec.md#requirement-a-board-button-with-an-icon-and-a-label-is-padded-as-the-boards-draw-it */
268
+ .cn-look-board .button-vue.button-vue:where(:has(> .button-vue__wrapper > .button-vue__icon:not(:empty)):has(> .button-vue__wrapper > .button-vue__text:not(:empty)):not(.cn-look-nextcloud *):not(.vue-crumb *)) {
269
+ --button-padding: 14px;
270
+ font-size: 14px;
271
+ }
272
+
273
+ .cn-look-board :where(.button-vue:has(> .button-vue__wrapper > .button-vue__icon:not(:empty)):has(> .button-vue__wrapper > .button-vue__text:not(:empty)):not(.cn-look-nextcloud *):not(.vue-crumb *)) .button-vue__wrapper.button-vue__wrapper {
274
+ gap: 8px;
275
+ }
276
+
277
+ .cn-look-board :where(.button-vue:has(> .button-vue__wrapper > .button-vue__icon:not(:empty)):has(> .button-vue__wrapper > .button-vue__text:not(:empty)):not(.cn-look-nextcloud *):not(.vue-crumb *)) .button-vue__icon.button-vue__icon {
278
+ width: 16px;
279
+ min-width: 16px;
280
+ height: 16px;
281
+ min-height: 16px;
282
+ }
283
+
284
+ .cn-look-board :where(.button-vue:has(> .button-vue__wrapper > .button-vue__icon:not(:empty)):has(> .button-vue__wrapper > .button-vue__text:not(:empty)):not(.cn-look-nextcloud *):not(.vue-crumb *)) .button-vue__icon svg {
285
+ width: 16px;
286
+ height: 16px;
287
+ }
288
+
253
289
  /* ---- The header button ------------------------------------------------- */
254
290
  .cn-look-board .cn-index-page__header-buttons:not(.cn-look-nextcloud *),
255
291
  .cn-look-board .cn-detail-page__header-actions:not(.cn-look-nextcloud *),
@@ -279,7 +315,7 @@
279
315
  --nldesign-component-button-primary-action-font-weight: 600;
280
316
  --nldesign-component-button-secondary-action-color: var(--color-main-text);
281
317
  --nldesign-component-button-secondary-action-background-color: var(--color-main-background);
282
- --nldesign-component-button-secondary-action-border-color: var(--color-border-dark);
318
+ --nldesign-component-button-secondary-action-border-color: var(--cn-board-control-border);
283
319
  --nldesign-component-button-secondary-action-hover-background-color: var(--color-background-hover);
284
320
  }
285
321
 
@@ -293,7 +329,7 @@
293
329
  min-height: var(--cn-board-control-height);
294
330
  padding: 0 14px;
295
331
  gap: 8px;
296
- border: 1px solid var(--color-border-dark);
332
+ border: 1px solid var(--cn-board-control-border);
297
333
  border-radius: var(--cn-board-control-radius);
298
334
  background: var(--color-main-background);
299
335
  color: var(--color-main-text);
@@ -325,7 +361,7 @@
325
361
  height: var(--cn-board-control-height);
326
362
  min-height: var(--cn-board-control-height);
327
363
  padding: 0 14px;
328
- border: 1px solid var(--color-border-dark);
364
+ border: 1px solid var(--cn-board-control-border);
329
365
  border-radius: var(--cn-board-control-radius);
330
366
  background: var(--color-main-background);
331
367
  color: var(--color-main-text);
@@ -333,8 +369,9 @@
333
369
  font-weight: 600;
334
370
  }
335
371
 
336
- /* Buttons 10px apart, each icon 16px with 8px to its label (DqZaak's header,
337
- DqZaken's "Downloaden"); NcButton draws a 20px icon in a 30px slot. */
372
+ /* Buttons 10px apart, each icon 16px (DqZaak's header, DqZaken's
373
+ "Downloaden"); NcButton draws a 20px icon in a 30px slot. The 8px to the
374
+ label is the board's icon-and-label gap above. */
338
375
  .cn-look-board .cn-action-buttons:not(.cn-look-nextcloud *) {
339
376
  gap: 10px;
340
377
  }
@@ -344,7 +381,6 @@
344
381
  min-width: 16px;
345
382
  height: 16px;
346
383
  min-height: 16px;
347
- margin-inline-end: 8px;
348
384
  }
349
385
 
350
386
  .cn-look-board .cn-action-buttons .button-vue .button-vue__icon:not(.cn-look-nextcloud *) svg {
@@ -379,7 +415,7 @@
379
415
  body ink onto every span (thematiq). */
380
416
  .cn-look-board .action-item.action-item--secondary .action-item__menutoggle.button-vue--secondary:not(.cn-look-nextcloud *) {
381
417
  --nldesign-color-on-surface: var(--color-main-text);
382
- border: 1px solid var(--color-border-dark);
418
+ border: 1px solid var(--cn-board-control-border);
383
419
  border-radius: var(--cn-board-control-radius);
384
420
  background: var(--color-main-background);
385
421
  color: var(--color-main-text);
@@ -1176,6 +1212,15 @@
1176
1212
  line-height: normal;
1177
1213
  }
1178
1214
 
1215
+ /* A neutral board tag ("Normaal", "Uit") is the soft text on the chip ground,
1216
+ not the main text on the darker background a Nextcloud tag takes.
1217
+ @spec openspec/changes/board-pill-tones/specs/board-pill-tones/spec.md#requirement-a-neutral-board-tag-is-soft-text-on-the-chip-ground */
1218
+ .cn-look-board .cn-status-badge--default:not(.cn-status-badge--solid):not(.cn-look-nextcloud *) {
1219
+ --cn-status-badge-ink: var(--cn-board-text-soft);
1220
+ background-color: var(--color-background-hover);
1221
+ color: var(--cn-status-badge-ink);
1222
+ }
1223
+
1179
1224
  /* A pill paints its own pairing of ink and fill. A theme that forces the body
1180
1225
  ink onto every span (thematiq's text rule, `!important` at id level) reads
1181
1226
  that ink from --nldesign-color-on-surface when the surface sets it, so the
@@ -2099,10 +2144,13 @@
2099
2144
  margin-inline-start: auto;
2100
2145
  padding: 1px 8px;
2101
2146
  border-radius: 10px;
2147
+ --nldesign-color-on-surface: var(--cn-board-text-soft, var(--color-main-text));
2102
2148
  background-color: var(--color-main-background);
2103
- color: var(--color-main-text);
2149
+ color: var(--cn-board-text-soft, var(--color-main-text));
2104
2150
  font-size: 12px;
2105
- font-weight: 400;
2151
+ font-weight: 700;
2152
+ /* 16px tall on its 1px pad (DqWerkbord's column counts). */
2153
+ line-height: 14px;
2106
2154
  }
2107
2155
 
2108
2156
  /* A column at its limit: the count in the error colour; "full" is said in words too. */
@@ -2474,7 +2522,7 @@
2474
2522
  height: var(--cn-board-control-height) !important;
2475
2523
  min-height: var(--cn-board-control-height) !important;
2476
2524
  padding: 0 14px !important;
2477
- border: 1px solid var(--color-border-dark) !important;
2525
+ border: 1px solid var(--cn-board-control-border) !important;
2478
2526
  border-radius: var(--cn-board-control-radius) !important;
2479
2527
  background: var(--color-main-background) !important;
2480
2528
  color: var(--color-main-text) !important;
@@ -2598,7 +2646,7 @@
2598
2646
  min-height: 38px;
2599
2647
  margin: 0;
2600
2648
  padding: 0 14px;
2601
- border: 1px solid var(--color-border-dark);
2649
+ border: 1px solid var(--cn-board-control-border);
2602
2650
  border-radius: 19px;
2603
2651
  background: var(--color-main-background);
2604
2652
  color: var(--color-main-text);
@@ -2640,20 +2688,22 @@
2640
2688
  padding: 1px 7px;
2641
2689
  border: 0;
2642
2690
  border-radius: 10px;
2691
+ --nldesign-color-on-surface: var(--cn-board-text-soft);
2643
2692
  background: var(--color-border);
2644
2693
  color: var(--cn-board-text-soft);
2645
2694
  font-size: 12px;
2646
2695
  font-weight: 600;
2647
- line-height: 1.4;
2696
+ /* 16px tall on its 1px pad, as the boards draw it (DqTaken, DqWerkbord).
2697
+ @spec openspec/changes/board-pill-tones/specs/board-pill-tones/spec.md#requirement-a-board-count-is-16px-tall */
2698
+ line-height: 14px;
2648
2699
  }
2649
2700
 
2650
2701
  .cn-look-board .cn-saved-views__chip--selected .cn-saved-views__chip-count,
2651
- /* On the selected chip the count is a lighter bubble on the dark chip
2652
- (DqZaken: #3d4047 on #1b1c1d); --cn-board-text-soft resolves to the main
2653
- text, the chip's own colour, which hid the bubble. */
2702
+ /* On the selected chip the count is the soft ink as a lighter bubble on the
2703
+ dark chip (DqZaken: #3d4047 on #1b1c1d). */
2654
2704
  .cn-look-board .cn-quick-filter-bar .cn-quick-filter-bar__tab--active .cn-quick-filter-bar__count {
2655
2705
  --nldesign-color-on-surface: var(--color-primary-element-text);
2656
- background: color-mix(in srgb, var(--color-main-background) 15%, var(--color-main-text));
2706
+ background: var(--cn-board-text-soft);
2657
2707
  color: var(--color-primary-element-text);
2658
2708
  }
2659
2709
 
@@ -2702,7 +2752,7 @@
2702
2752
  --nldesign-component-button-font-weight: 600;
2703
2753
  --nldesign-component-button-secondary-action-color: var(--color-main-text);
2704
2754
  --nldesign-component-button-secondary-action-background-color: var(--color-main-background);
2705
- --nldesign-component-button-secondary-action-border-color: var(--color-border-dark);
2755
+ --nldesign-component-button-secondary-action-border-color: var(--cn-board-control-border);
2706
2756
  --nldesign-component-button-secondary-action-hover-background-color: var(--color-background-hover);
2707
2757
  }
2708
2758
 
@@ -2729,6 +2779,8 @@
2729
2779
  margin-inline-start: 6px;
2730
2780
  padding: 0 6px;
2731
2781
  border-radius: 10px;
2782
+ /* A theme that forces the body ink onto every span reads it from here. */
2783
+ --nldesign-color-on-surface: var(--color-primary-element-text);
2732
2784
  background: var(--color-primary-element);
2733
2785
  color: var(--color-primary-element-text);
2734
2786
  font-size: 12px;
@@ -2837,7 +2889,7 @@
2837
2889
  height: 40px;
2838
2890
  padding-block: 0;
2839
2891
  padding-inline: 38px 12px !important;
2840
- border: 1px solid var(--color-border-dark);
2892
+ border: 1px solid var(--cn-board-control-border);
2841
2893
  border-radius: 8px;
2842
2894
  font-size: 14px;
2843
2895
  }
@@ -3214,7 +3266,7 @@
3214
3266
  height: 34px !important;
3215
3267
  min-height: 34px !important;
3216
3268
  padding: 0 !important;
3217
- border: 1px solid var(--color-border-dark) !important;
3269
+ border: 1px solid var(--cn-board-control-border) !important;
3218
3270
  border-radius: 8px !important;
3219
3271
  color: var(--cn-board-text-soft) !important;
3220
3272
  }
@@ -8000,7 +8052,7 @@ a.cn-kpi-card--board * {
8000
8052
  height: var(--cn-board-control-height) !important;
8001
8053
  min-height: var(--cn-board-control-height) !important;
8002
8054
  padding: 0 14px !important;
8003
- border: 1px solid var(--color-border-dark) !important;
8055
+ border: 1px solid var(--cn-board-control-border) !important;
8004
8056
  border-radius: var(--cn-board-control-radius) !important;
8005
8057
  background: var(--color-main-background) !important;
8006
8058
  color: var(--color-main-text) !important;
@@ -8013,13 +8065,13 @@ a.cn-kpi-card--board * {
8013
8065
  background: var(--color-background-hover) !important;
8014
8066
  }
8015
8067
 
8016
- /* Each header icon is 16px with 8px to its label; NcButton draws 20px in a 30px slot. */
8068
+ /* Each header icon is 16px; NcButton draws 20px in a 30px slot. The 8px to
8069
+ the label is the board's icon-and-label gap (look-board.css). */
8017
8070
  .cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary .button-vue__icon {
8018
8071
  width: 16px;
8019
8072
  min-width: 16px;
8020
8073
  height: 16px;
8021
8074
  min-height: 16px;
8022
- margin-inline-end: 8px;
8023
8075
  }
8024
8076
 
8025
8077
  .cn-look-board .cn-detail-page .cn-detail-page__header-actions > .button-vue--secondary .button-vue__icon svg {
@@ -8027,6 +8079,17 @@ a.cn-kpi-card--board * {
8027
8079
  height: 16px;
8028
8080
  }
8029
8081
 
8082
+ /* Row 3: the Follow control under the breadcrumb, on the full width.
8083
+ @spec openspec/changes/board-follow-row/specs/board-follow-row/spec.md#requirement-the-board-follow-control-sits-under-the-breadcrumb */
8084
+ .cn-look-board .cn-detail-page__header-row3 {
8085
+ align-items: center;
8086
+ display: flex;
8087
+ flex: 1 0 100%;
8088
+ flex-wrap: wrap;
8089
+ gap: 8px;
8090
+ min-width: 0;
8091
+ }
8092
+
8030
8093
  /* Row 2: pills, breadcrumb, dot, meta, chips. */
8031
8094
  .cn-look-board .cn-detail-page__header-row2 {
8032
8095
  align-items: center;
@@ -8184,14 +8247,14 @@ a.cn-kpi-card--board * {
8184
8247
  --nldesign-component-button-border-radius: 14px;
8185
8248
  --nldesign-component-button-secondary-action-color: var(--color-main-text);
8186
8249
  --nldesign-component-button-secondary-action-background-color: var(--color-main-background);
8187
- --nldesign-component-button-secondary-action-border-color: var(--color-border-dark);
8250
+ --nldesign-component-button-secondary-action-border-color: var(--cn-board-control-border);
8188
8251
  --nldesign-component-button-secondary-action-hover-background-color: var(--color-background-hover);
8189
8252
  box-sizing: border-box;
8190
8253
  height: 28px;
8191
8254
  min-height: 28px;
8192
8255
  min-width: 0;
8193
8256
  padding: 0 10px;
8194
- border: 1px solid var(--color-border-dark);
8257
+ border: 1px solid var(--cn-board-control-border);
8195
8258
  border-radius: 14px;
8196
8259
  background: var(--color-main-background);
8197
8260
  color: var(--color-main-text);
@@ -8329,13 +8392,14 @@ a.cn-kpi-card--board * {
8329
8392
 
8330
8393
  /* The Actions button at the end of the strip: at the top, 8px in from the
8331
8394
  end and 8px clear of the card, so the tabs, aligned to the bottom, sit 6px
8332
- lower than the button (DqZaak "Acties"). */
8395
+ lower than the button, 12px in at both sides (DqZaak "Acties"). */
8333
8396
  .cn-look-board .cn-tabs--board .cn-tabs__nav-end {
8334
8397
  align-self: flex-start;
8335
8398
  margin: 0 8px 8px 0;
8336
8399
  }
8337
8400
 
8338
8401
  .cn-look-board .cn-tabs--board .cn-tabs__nav-end .button-vue {
8402
+ --button-padding: 12px;
8339
8403
  min-height: 34px;
8340
8404
  block-size: 34px;
8341
8405
  }
@@ -8659,7 +8723,7 @@ a.cn-kpi-card--board * {
8659
8723
  .cn-look-board .cn-activity-tab--board .cn-activity-tab__kind {
8660
8724
  align-items: center;
8661
8725
  background: var(--color-main-background);
8662
- border: 1px solid var(--color-border-dark);
8726
+ border: 1px solid var(--cn-board-control-border);
8663
8727
  border-radius: 18px;
8664
8728
  color: var(--color-main-text);
8665
8729
  cursor: pointer;
@@ -8934,7 +8998,7 @@ a.cn-kpi-card--board * {
8934
8998
  height: var(--cn-board-control-height) !important;
8935
8999
  min-height: var(--cn-board-control-height) !important;
8936
9000
  padding: 0 14px !important;
8937
- border: 1px solid var(--color-border-dark) !important;
9001
+ border: 1px solid var(--cn-board-control-border) !important;
8938
9002
  border-radius: var(--cn-board-control-radius) !important;
8939
9003
  background: var(--color-main-background) !important;
8940
9004
  color: var(--color-main-text) !important;
@@ -39027,10 +39091,13 @@ to {
39027
39091
  margin-inline-start: auto;
39028
39092
  padding: 1px 8px;
39029
39093
  border-radius: 10px;
39094
+ --nldesign-color-on-surface: var(--cn-board-text-soft, var(--color-main-text));
39030
39095
  background-color: var(--color-main-background);
39031
- color: var(--color-main-text);
39096
+ color: var(--cn-board-text-soft, var(--color-main-text));
39032
39097
  font-size: 12px;
39033
- font-weight: 400;
39098
+ font-weight: 700;
39099
+ /* 16px tall on its 1px pad (DqWerkbord's column counts). */
39100
+ line-height: 14px;
39034
39101
  }
39035
39102
 
39036
39103
  /* A column at its limit: the count in the error colour; "full" is said in words too. */
@@ -258,15 +258,18 @@ function schemaSlug(schema) {
258
258
  * it: three case indexes share their views, the organisations index does
259
259
  * not. `savedViewsScope` on a page overrides that, for pages that should
260
260
  * share views although they read different sources, or keep them apart
261
- * although they read the same one.
261
+ * although they read the same one. A page over a named source
262
+ * (`entitySource`) and no schema shares its views with the pages over that
263
+ * source.
262
264
  *
263
265
  * @param {object} page The page.
264
266
  * @param {string} [page.scope] The explicit `savedViewsScope`, when set.
265
267
  * @param {string} [page.register] The page's register.
266
268
  * @param {object|string} [page.schema] The page's schema, slug or object.
269
+ * @param {string} [page.source] The page's `entitySource`, when it reads one.
267
270
  * @return {string} The scope key, or the empty string for a page with no source.
268
271
  */
269
- function savedViewScope({ scope, register, schema } = {}) {
272
+ function savedViewScope({ scope, register, schema, source } = {}) {
270
273
  if (typeof scope === 'string' && scope !== '') {
271
274
  return scope
272
275
  }
@@ -274,9 +277,43 @@ function savedViewScope({ scope, register, schema } = {}) {
274
277
  if (typeof register === 'string' && register !== '' && slug !== '') {
275
278
  return `${register}/${slug}`
276
279
  }
280
+ if (typeof source === 'string' && source !== '') {
281
+ return `source:${source}`
282
+ }
277
283
  return ''
278
284
  }
279
285
 
286
+ /**
287
+ * Whether an index page offers saved views in its sidebar.
288
+ *
289
+ * `allowSavedViews` decides when it is set. Unset, every index page that has
290
+ * a sidebar and a source a view can be scoped to (a register and schema, a
291
+ * named source, or `savedViewsScope`) offers them, so managing views is
292
+ * there on every list without each page opting in.
293
+ *
294
+ * @param {object} config The page's index config, or CnIndexPage's props.
295
+ * @param {boolean|null} [config.allowSavedViews] The explicit choice, when made.
296
+ * @param {object} [config.sidebar] The sidebar config.
297
+ * @param {string} [config.savedViewsScope] The explicit scope.
298
+ * @param {string} [config.register] The register.
299
+ * @param {object|string} [config.schema] The schema.
300
+ * @param {string} [config.entitySource] The named source.
301
+ * @return {boolean}
302
+ */
303
+ function pageOffersSavedViews(config) {
304
+ if (!config || typeof config !== 'object') {
305
+ return false
306
+ }
307
+ if (typeof config.allowSavedViews === 'boolean') {
308
+ return config.allowSavedViews
309
+ }
310
+ const sidebar = config.sidebar;
311
+ if (!sidebar || typeof sidebar !== 'object' || sidebar.enabled !== true || sidebar.show === false) {
312
+ return false
313
+ }
314
+ return savedViewScope({ scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource }) !== ''
315
+ }
316
+
280
317
  /**
281
318
  * Whether a saved view belongs on a page.
282
319
  *
@@ -410,5 +447,5 @@ function defaultFilterViews(defaults, page = {}) {
410
447
  })
411
448
  }
412
449
 
413
- export { buildRouteQueryFromViewState, buildViewCreatePayload, defaultFilterViews, extractViewState, extractViewStateFromRouteQuery, isOwnView, normalizeSharedWith, savedViewScope, schemaSlug, viewAccess, viewMatchesScope };
450
+ export { buildRouteQueryFromViewState, buildViewCreatePayload, defaultFilterViews, extractViewState, extractViewStateFromRouteQuery, isOwnView, normalizeSharedWith, pageOffersSavedViews, savedViewScope, schemaSlug, viewAccess, viewMatchesScope };
414
451
  //# sourceMappingURL=savedViewHelpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"savedViewHelpers.js","sources":["../../../src/utils/savedViewHelpers.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\n/**\n * Pure helpers for CnIndexPage's saved-views feature (saved-views-ui).\n *\n * OpenRegister ships a saved-search Views backend\n * (`/apps/openregister/api/views`, ViewsController + ViewService); these\n * helpers translate between the three state shapes involved:\n *\n * 1. **Route query** — CnIndexPage's deep-link contract: every `$route.query`\n * key NOT starting with `_` is a fetch filter (see\n * `useSelfFetchList.resolveQueryFilters`); `_`-prefixed keys are reserved\n * list params. Search is `_search` and sort is `_order`, the JSON-encoded\n * ordered array `[{ key, order }, …]` — the one spelling CnIndexPage,\n * listNavigation, CnPageRenderer and OpenRegister's object API all agree\n * on. This module writes NO spelling of its own.\n * 2. **View state** — the normalized internal object\n * `{ filters, search, sortKeys, sortKey, sortOrder }`, where `sortKeys` is\n * the truth and the last two mirror its first entry, exactly as\n * `useListView` holds the same state.\n * 3. **OR View payload** — the `POST/PUT /api/views` body whose opaque\n * `query` field round-trips through OpenRegister unchanged.\n *\n * All functions are pure and defensive: malformed input degrades to the\n * empty/default state, never throws.\n *\n * @module utils/savedViewHelpers\n */\n\nimport { parseSortKeys } from './routeFilters.js'\n\n/** The default (empty) view state. @return {object} A fresh empty state. */\nfunction emptyState() {\n\treturn { filters: {}, search: '', sortKeys: [], sortKey: null, sortOrder: 'asc' }\n}\n\n/**\n * Coerce anything into an ordered sort-key list.\n *\n * The single defensive entry point, and the whole of the stored-shape\n * compatibility: a view saved before sorts could be chained holds `sort` as one\n * `{ key, order }` object, and that must keep opening exactly as it does today.\n *\n * @param {unknown} value An array, a single `{ key, order }`, or anything else.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The normalized list.\n */\nfunction normalizeSortKeys(value) {\n\tconst list = Array.isArray(value) ? value : [value]\n\tconst seen = new Set()\n\tconst keys = []\n\tfor (const entry of list) {\n\t\tif (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {\n\t\t\tcontinue\n\t\t}\n\t\tif (seen.has(entry.key)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(entry.key)\n\t\tkeys.push({ key: entry.key, order: entry.order === 'desc' ? 'desc' : 'asc' })\n\t}\n\treturn keys\n}\n\n/**\n * Write a sort onto a state, keeping the single-key mirror in step.\n *\n * @param {object} state The state to write onto (mutated and returned).\n * @param {Array<{key: string, order: 'asc'|'desc'}>} keys The normalized sort.\n * @return {object} The same state.\n */\nfunction withSort(state, keys) {\n\tstate.sortKeys = keys\n\tstate.sortKey = keys.length > 0 ? keys[0].key : null\n\tstate.sortOrder = keys.length > 0 ? keys[0].order : 'asc'\n\treturn state\n}\n\n/**\n * The sort a caller handed in, whichever of the two shapes they hold.\n *\n * @param {object} src A view state, possibly carrying only `sortKey`/`sortOrder`.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The normalized sort.\n */\nfunction sortKeysOf(src) {\n\tif (src.sortKeys !== undefined && src.sortKeys !== null) {\n\t\treturn normalizeSortKeys(src.sortKeys)\n\t}\n\treturn src.sortKey ? normalizeSortKeys({ key: src.sortKey, order: src.sortOrder }) : []\n}\n\n/**\n * Extract the current view state from a `$route.query`-shaped object.\n *\n * Non-underscore-prefixed keys become `filters` entries (values passed\n * through as-is, including arrays from `?status[]=a&status[]=b` deep\n * links). Reserved keys map onto the dedicated state fields: `_search` and\n * `_order`. Other reserved keys (`_page`, `_limit`, …) are ignored —\n * pagination is never part of a saved view.\n *\n * @param {object|null|undefined} query The `$route.query` object.\n * @return {{ filters: object, search: string, sortKeys: Array<{key: string, order: string}>, sortKey: ?string, sortOrder: string }} The normalized view state.\n */\nexport function extractViewStateFromRouteQuery(query) {\n\tconst state = emptyState()\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn state\n\t}\n\tfor (const [key, value] of Object.entries(query)) {\n\t\tif (key.startsWith('_')) {\n\t\t\tcontinue\n\t\t}\n\t\tif (value === undefined || value === null || value === '') {\n\t\t\tcontinue\n\t\t}\n\t\tstate.filters[key] = value\n\t}\n\tif (typeof query._search === 'string' && query._search !== '') {\n\t\tstate.search = query._search\n\t}\n\treturn withSort(state, parseSortKeys(query._order))\n}\n\n/**\n * Build a fresh route-query object that APPLIES a view state.\n *\n * Spreads `state.filters` (skipping null/undefined/empty-string/empty-array\n * values, matching what `CnIndexPage.persistViewStateToRoute` skips — the two\n * write the same query and must agree on what an absent filter looks like),\n * then sets the reserved keys only when meaningful: `_search` for a non-empty\n * search term, `_order` for an active sort. Deliberately omits `_page` —\n * applying a view is a full state replace that implicitly resets pagination.\n *\n * @param {{ filters?: object, search?: string, sortKeys?: Array<{key: string, order: string}>, sortKey?: ?string, sortOrder?: string }} state The view state to serialize.\n * @return {object} A plain object suitable for `$router.replace({ query })`.\n */\nexport function buildRouteQueryFromViewState(state) {\n\tconst query = {}\n\tconst src = (state && typeof state === 'object') ? state : {}\n\tconst filters = (src.filters && typeof src.filters === 'object' && !Array.isArray(src.filters)) ? src.filters : {}\n\tfor (const [key, value] of Object.entries(filters)) {\n\t\tif (value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0)) {\n\t\t\tcontinue\n\t\t}\n\t\tquery[key] = value\n\t}\n\tif (typeof src.search === 'string' && src.search !== '') {\n\t\tquery._search = src.search\n\t}\n\tconst keys = sortKeysOf(src)\n\tif (keys.length > 0) {\n\t\tquery._order = JSON.stringify(keys)\n\t}\n\treturn query\n}\n\n/**\n * Build the exact `POST /apps/openregister/api/views` (and PUT) request\n * body for persisting a view. The `query` field is OpenRegister's opaque\n * JSON blob — ViewsController stores `$data['query']` verbatim when it is\n * an array/object, so the shape below round-trips unchanged and\n * {@link extractViewState} is its inverse.\n *\n * @param {object} options Payload options.\n * @param {string} options.name Required view name.\n * @param {string} [options.description] Optional description.\n * @param {boolean} [options.isPublic] Share the view with other users.\n * @param {boolean} [options.isDefault] Mark as the user's default view.\n * @param {{ filters?: object, search?: string, sortKeys?: Array<{key: string, order: string}>, sortKey?: ?string, sortOrder?: string }} options.state The view state to persist.\n * @param {string} [options.scope] The pages this view belongs to, from {@link savedViewScope}; omitted when empty.\n * @param {string} [options.register] The page's register, written as OpenRegister's own `registers` list.\n * @param {object|string} [options.schema] The page's schema (slug or object), written as OpenRegister's own `schemas` list.\n * @param {Array<{group: string, mode: string}>} [options.sharedWith] Groups to share with; omitted from the body when empty.\n * @param {object} [options.presentation] How the view shows (OpenRegister's shape); omitted from the body for a table, which is the default.\n * @return {object} The request body for the OR views API.\n */\nexport function buildViewCreatePayload({ name, description, isPublic, isDefault, state, scope, register, schema, sharedWith, presentation } = {}) {\n\tconst src = (state && typeof state === 'object') ? state : {}\n\tconst keys = sortKeysOf(src)\n\tconst query = {\n\t\tfilters: (src.filters && typeof src.filters === 'object' && !Array.isArray(src.filters)) ? src.filters : {},\n\t\tsearch: (typeof src.search === 'string') ? src.search : '',\n\t\t// `null` rather than `[]` for no sort: every view stored before sorts\n\t\t// could be chained says `null`, and one spelling for \"no sort\" is worth\n\t\t// more than the symmetry.\n\t\tsort: keys.length > 0 ? keys : null,\n\t}\n\tif (typeof scope === 'string' && scope !== '') {\n\t\tquery.scope = scope\n\t}\n\tif (typeof register === 'string' && register !== '') {\n\t\tquery.registers = [register]\n\t}\n\tconst slug = schemaSlug(schema)\n\tif (slug !== '') {\n\t\tquery.schemas = [slug]\n\t}\n\tconst body = {\n\t\tname,\n\t\tdescription: description || '',\n\t\tisPublic: !!isPublic,\n\t\tisDefault: !!isDefault,\n\t\tquery,\n\t}\n\tif (presentation && typeof presentation === 'object' && (presentation.viewType === 'kanban' || presentation.viewType === 'calendar')) {\n\t\tbody.presentation = presentation\n\t}\n\tconst audience = normalizeSharedWith(sharedWith)\n\tif (audience.length > 0) {\n\t\tbody.sharedWith = audience\n\t}\n\treturn body\n}\n\n/**\n * Clean an audience list: keep `{ group, mode }` and `{ user, mode }` entries\n * that name exactly one target, drop repeats, and coerce any mode but `write`\n * to `read`.\n *\n * @param {unknown} sharedWith The audience as given.\n * @return {Array<{group?: string, user?: string, mode: 'read'|'write'}>} The cleaned list.\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-5\n */\nexport function normalizeSharedWith(sharedWith) {\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const entry of Array.isArray(sharedWith) ? sharedWith : []) {\n\t\tconst group = entry && typeof entry.group === 'string' ? entry.group : ''\n\t\tconst user = entry && typeof entry.user === 'string' ? entry.user : ''\n\t\tif ((group === '') === (user === '')) {\n\t\t\tcontinue\n\t\t}\n\t\tconst key = user !== '' ? `user:${user}` : `group:${group}`\n\t\tif (seen.has(key)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(key)\n\t\tconst mode = entry.mode === 'write' ? 'write' : 'read'\n\t\tout.push(user !== '' ? { user, mode } : { group, mode })\n\t}\n\treturn out\n}\n\n/**\n * What the current user may do with a view: `owner`, `write` or `read`.\n *\n * The server decides: `@self.access` wins. A view without it (an older\n * OpenRegister) counts as `owner` when its `owner` is the current user and as\n * `read` otherwise.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {string|null|undefined} currentUserId The signed-in NC user id.\n * @return {'owner'|'write'|'read'} The access.\n */\nexport function viewAccess(view, currentUserId) {\n\tconst access = view && view['@self'] && view['@self'].access\n\tif (access === 'owner' || access === 'write' || access === 'read') {\n\t\treturn access\n\t}\n\treturn isOwnView(view, currentUserId) ? 'owner' : 'read'\n}\n\n/**\n * The slug of a schema given as a slug or as a schema object.\n *\n * @param {object|string|null|undefined} schema The page's `schema` prop.\n * @return {string} The slug, or the empty string.\n */\nexport function schemaSlug(schema) {\n\tif (typeof schema === 'string') {\n\t\treturn schema\n\t}\n\tif (schema && typeof schema === 'object' && typeof schema.slug === 'string') {\n\t\treturn schema.slug\n\t}\n\treturn ''\n}\n\n/**\n * The key naming which pages share a view.\n *\n * A view is a set of filters over one schema's fields, so by default every\n * page over the same register and schema shares it and no other page sees\n * it: three case indexes share their views, the organisations index does\n * not. `savedViewsScope` on a page overrides that, for pages that should\n * share views although they read different sources, or keep them apart\n * although they read the same one.\n *\n * @param {object} page The page.\n * @param {string} [page.scope] The explicit `savedViewsScope`, when set.\n * @param {string} [page.register] The page's register.\n * @param {object|string} [page.schema] The page's schema, slug or object.\n * @return {string} The scope key, or the empty string for a page with no source.\n */\nexport function savedViewScope({ scope, register, schema } = {}) {\n\tif (typeof scope === 'string' && scope !== '') {\n\t\treturn scope\n\t}\n\tconst slug = schemaSlug(schema)\n\tif (typeof register === 'string' && register !== '' && slug !== '') {\n\t\treturn `${register}/${slug}`\n\t}\n\treturn ''\n}\n\n/**\n * Whether a saved view belongs on a page.\n *\n * A view saved with a `scope` is shown where the scope matches. A view that\n * names `schemas` (OpenRegister's own search UI writes those) is shown on a\n * page over one of them. A view that says neither, saved before views were\n * scoped, is shown everywhere as it always was: hiding it would lose the\n * view, and nothing can tell which page it was made on.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {object} page The page, as {@link savedViewScope} takes it.\n * @return {boolean} True when the view is shown on this page.\n */\nexport function viewMatchesScope(view, page = {}) {\n\tconst query = view && typeof view === 'object' ? view.query : null\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn true\n\t}\n\tif (typeof query.scope === 'string' && query.scope !== '') {\n\t\treturn query.scope === savedViewScope(page)\n\t}\n\tif (Array.isArray(query.schemas) && query.schemas.length > 0) {\n\t\tconst slug = schemaSlug(page.schema)\n\t\tif (slug === '' || !query.schemas.map(String).includes(slug)) {\n\t\t\treturn false\n\t\t}\n\t\tif (Array.isArray(query.registers) && query.registers.length > 0 && page.register) {\n\t\t\treturn query.registers.map(String).includes(String(page.register))\n\t\t}\n\t\treturn true\n\t}\n\treturn true\n}\n\n/**\n * Normalize a View API object (as returned by `GET /api/views`) back into\n * the internal view state. Accepts either a full view object (reads its\n * `query` property) or a raw query blob directly. Every field degrades\n * defensively: missing/null/malformed `query`, non-object `filters`,\n * malformed `sort` all yield the empty/default state — never throws.\n *\n * @param {object|null|undefined} view The View API object (or its raw `query` blob).\n * @return {{ filters: object, search: string, sortKeys: Array<{key: string, order: string}>, sortKey: ?string, sortOrder: string }} The normalized view state.\n */\nexport function extractViewState(view) {\n\tconst state = emptyState()\n\tif (!view || typeof view !== 'object' || Array.isArray(view)) {\n\t\treturn state\n\t}\n\t// Full view object (has a `query` key) or a raw query blob itself.\n\tconst query = (Object.hasOwn(view, 'query')) ? view.query : view\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn state\n\t}\n\tif (query.filters && typeof query.filters === 'object' && !Array.isArray(query.filters)) {\n\t\tfor (const [key, value] of Object.entries(query.filters)) {\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tstate.filters[key] = value\n\t\t}\n\t}\n\tif (typeof query.search === 'string') {\n\t\tstate.search = query.search\n\t}\n\treturn withSort(state, normalizeSortKeys(query.sort))\n}\n\n/**\n * Whether the given view is owned by the given user. Drives the delete\n * affordance in CnSavedViewsControl — OpenRegister scopes destroy() to the\n * owner server-side (a foreign id 404s), so this only hides the button for\n * views the API would refuse to delete anyway.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {string|null|undefined} currentUserId The signed-in NC user id.\n * @return {boolean} True when the view belongs to the current user.\n */\nexport function isOwnView(view, currentUserId) {\n\treturn !!view && !!currentUserId && view.owner === currentUserId\n}\n\n/**\n * The default views an index page declares (`config.defaultSavedViews`), as\n * read-only views the saved-views control lists. They are never stored: the\n * manifest installs them, so every user has the same set and none can delete\n * one. `seeded: true` puts them in the control's default group; the query\n * keeps its `@`-tokens, which resolve when a default is applied.\n *\n * A default's `overrides` (page config laid over the page while it is\n * active, see `utils/savedViewOverrides.js`) ride in `query.overrides`, so a\n * copy of the default keeps them.\n *\n * @param {Array<object>|null|undefined} defaults The declared entries `{ slug, name, filters, search?, sort?, label?, overrides? }`.\n * @param {{ scope?: string, register?: string, schema?: object|string|null }} [page] The page, as `savedViewScope` takes it.\n * @return {Array<object>} The views, in declaration order; entries without a slug or name are left out.\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-4\n */\nexport function defaultFilterViews(defaults, page = {}) {\n\tif (!Array.isArray(defaults)) {\n\t\treturn []\n\t}\n\treturn defaults\n\t\t.filter((entry) => entry && typeof entry.slug === 'string' && entry.slug !== '' && typeof entry.name === 'string' && entry.name !== '')\n\t\t.map((entry) => {\n\t\t\tconst { query } = buildViewCreatePayload({\n\t\t\t\tname: entry.name,\n\t\t\t\tstate: {\n\t\t\t\t\tfilters: (entry.filters && typeof entry.filters === 'object' && !Array.isArray(entry.filters)) ? entry.filters : {},\n\t\t\t\t\tsearch: typeof entry.search === 'string' ? entry.search : '',\n\t\t\t\t\tsortKeys: Array.isArray(entry.sort) ? entry.sort : [],\n\t\t\t\t},\n\t\t\t\tscope: savedViewScope(page),\n\t\t\t\tregister: page.register,\n\t\t\t\tschema: page.schema,\n\t\t\t})\n\t\t\tif (entry.overrides && typeof entry.overrides === 'object' && !Array.isArray(entry.overrides)) {\n\t\t\t\tquery.overrides = entry.overrides\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: `default:${entry.slug}`,\n\t\t\t\tslug: entry.slug,\n\t\t\t\tname: entry.name,\n\t\t\t\towner: '',\n\t\t\t\tseeded: true,\n\t\t\t\tisPublic: false,\n\t\t\t\tlabels: typeof entry.label === 'string' && entry.label !== '' ? [entry.label] : [],\n\t\t\t\t'@self': { access: 'read' },\n\t\t\t\tquery,\n\t\t\t}\n\t\t})\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"savedViewHelpers.js","sources":["../../../src/utils/savedViewHelpers.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 Conduction B.V.\n// SPDX-License-Identifier: EUPL-1.2\n\n/**\n * Pure helpers for CnIndexPage's saved-views feature (saved-views-ui).\n *\n * OpenRegister ships a saved-search Views backend\n * (`/apps/openregister/api/views`, ViewsController + ViewService); these\n * helpers translate between the three state shapes involved:\n *\n * 1. **Route query** — CnIndexPage's deep-link contract: every `$route.query`\n * key NOT starting with `_` is a fetch filter (see\n * `useSelfFetchList.resolveQueryFilters`); `_`-prefixed keys are reserved\n * list params. Search is `_search` and sort is `_order`, the JSON-encoded\n * ordered array `[{ key, order }, …]` — the one spelling CnIndexPage,\n * listNavigation, CnPageRenderer and OpenRegister's object API all agree\n * on. This module writes NO spelling of its own.\n * 2. **View state** — the normalized internal object\n * `{ filters, search, sortKeys, sortKey, sortOrder }`, where `sortKeys` is\n * the truth and the last two mirror its first entry, exactly as\n * `useListView` holds the same state.\n * 3. **OR View payload** — the `POST/PUT /api/views` body whose opaque\n * `query` field round-trips through OpenRegister unchanged.\n *\n * All functions are pure and defensive: malformed input degrades to the\n * empty/default state, never throws.\n *\n * @module utils/savedViewHelpers\n */\n\nimport { parseSortKeys } from './routeFilters.js'\n\n/** The default (empty) view state. @return {object} A fresh empty state. */\nfunction emptyState() {\n\treturn { filters: {}, search: '', sortKeys: [], sortKey: null, sortOrder: 'asc' }\n}\n\n/**\n * Coerce anything into an ordered sort-key list.\n *\n * The single defensive entry point, and the whole of the stored-shape\n * compatibility: a view saved before sorts could be chained holds `sort` as one\n * `{ key, order }` object, and that must keep opening exactly as it does today.\n *\n * @param {unknown} value An array, a single `{ key, order }`, or anything else.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The normalized list.\n */\nfunction normalizeSortKeys(value) {\n\tconst list = Array.isArray(value) ? value : [value]\n\tconst seen = new Set()\n\tconst keys = []\n\tfor (const entry of list) {\n\t\tif (!entry || typeof entry !== 'object' || typeof entry.key !== 'string' || entry.key === '') {\n\t\t\tcontinue\n\t\t}\n\t\tif (seen.has(entry.key)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(entry.key)\n\t\tkeys.push({ key: entry.key, order: entry.order === 'desc' ? 'desc' : 'asc' })\n\t}\n\treturn keys\n}\n\n/**\n * Write a sort onto a state, keeping the single-key mirror in step.\n *\n * @param {object} state The state to write onto (mutated and returned).\n * @param {Array<{key: string, order: 'asc'|'desc'}>} keys The normalized sort.\n * @return {object} The same state.\n */\nfunction withSort(state, keys) {\n\tstate.sortKeys = keys\n\tstate.sortKey = keys.length > 0 ? keys[0].key : null\n\tstate.sortOrder = keys.length > 0 ? keys[0].order : 'asc'\n\treturn state\n}\n\n/**\n * The sort a caller handed in, whichever of the two shapes they hold.\n *\n * @param {object} src A view state, possibly carrying only `sortKey`/`sortOrder`.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The normalized sort.\n */\nfunction sortKeysOf(src) {\n\tif (src.sortKeys !== undefined && src.sortKeys !== null) {\n\t\treturn normalizeSortKeys(src.sortKeys)\n\t}\n\treturn src.sortKey ? normalizeSortKeys({ key: src.sortKey, order: src.sortOrder }) : []\n}\n\n/**\n * Extract the current view state from a `$route.query`-shaped object.\n *\n * Non-underscore-prefixed keys become `filters` entries (values passed\n * through as-is, including arrays from `?status[]=a&status[]=b` deep\n * links). Reserved keys map onto the dedicated state fields: `_search` and\n * `_order`. Other reserved keys (`_page`, `_limit`, …) are ignored —\n * pagination is never part of a saved view.\n *\n * @param {object|null|undefined} query The `$route.query` object.\n * @return {{ filters: object, search: string, sortKeys: Array<{key: string, order: string}>, sortKey: ?string, sortOrder: string }} The normalized view state.\n */\nexport function extractViewStateFromRouteQuery(query) {\n\tconst state = emptyState()\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn state\n\t}\n\tfor (const [key, value] of Object.entries(query)) {\n\t\tif (key.startsWith('_')) {\n\t\t\tcontinue\n\t\t}\n\t\tif (value === undefined || value === null || value === '') {\n\t\t\tcontinue\n\t\t}\n\t\tstate.filters[key] = value\n\t}\n\tif (typeof query._search === 'string' && query._search !== '') {\n\t\tstate.search = query._search\n\t}\n\treturn withSort(state, parseSortKeys(query._order))\n}\n\n/**\n * Build a fresh route-query object that APPLIES a view state.\n *\n * Spreads `state.filters` (skipping null/undefined/empty-string/empty-array\n * values, matching what `CnIndexPage.persistViewStateToRoute` skips — the two\n * write the same query and must agree on what an absent filter looks like),\n * then sets the reserved keys only when meaningful: `_search` for a non-empty\n * search term, `_order` for an active sort. Deliberately omits `_page` —\n * applying a view is a full state replace that implicitly resets pagination.\n *\n * @param {{ filters?: object, search?: string, sortKeys?: Array<{key: string, order: string}>, sortKey?: ?string, sortOrder?: string }} state The view state to serialize.\n * @return {object} A plain object suitable for `$router.replace({ query })`.\n */\nexport function buildRouteQueryFromViewState(state) {\n\tconst query = {}\n\tconst src = (state && typeof state === 'object') ? state : {}\n\tconst filters = (src.filters && typeof src.filters === 'object' && !Array.isArray(src.filters)) ? src.filters : {}\n\tfor (const [key, value] of Object.entries(filters)) {\n\t\tif (value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0)) {\n\t\t\tcontinue\n\t\t}\n\t\tquery[key] = value\n\t}\n\tif (typeof src.search === 'string' && src.search !== '') {\n\t\tquery._search = src.search\n\t}\n\tconst keys = sortKeysOf(src)\n\tif (keys.length > 0) {\n\t\tquery._order = JSON.stringify(keys)\n\t}\n\treturn query\n}\n\n/**\n * Build the exact `POST /apps/openregister/api/views` (and PUT) request\n * body for persisting a view. The `query` field is OpenRegister's opaque\n * JSON blob — ViewsController stores `$data['query']` verbatim when it is\n * an array/object, so the shape below round-trips unchanged and\n * {@link extractViewState} is its inverse.\n *\n * @param {object} options Payload options.\n * @param {string} options.name Required view name.\n * @param {string} [options.description] Optional description.\n * @param {boolean} [options.isPublic] Share the view with other users.\n * @param {boolean} [options.isDefault] Mark as the user's default view.\n * @param {{ filters?: object, search?: string, sortKeys?: Array<{key: string, order: string}>, sortKey?: ?string, sortOrder?: string }} options.state The view state to persist.\n * @param {string} [options.scope] The pages this view belongs to, from {@link savedViewScope}; omitted when empty.\n * @param {string} [options.register] The page's register, written as OpenRegister's own `registers` list.\n * @param {object|string} [options.schema] The page's schema (slug or object), written as OpenRegister's own `schemas` list.\n * @param {Array<{group: string, mode: string}>} [options.sharedWith] Groups to share with; omitted from the body when empty.\n * @param {object} [options.presentation] How the view shows (OpenRegister's shape); omitted from the body for a table, which is the default.\n * @return {object} The request body for the OR views API.\n */\nexport function buildViewCreatePayload({ name, description, isPublic, isDefault, state, scope, register, schema, sharedWith, presentation } = {}) {\n\tconst src = (state && typeof state === 'object') ? state : {}\n\tconst keys = sortKeysOf(src)\n\tconst query = {\n\t\tfilters: (src.filters && typeof src.filters === 'object' && !Array.isArray(src.filters)) ? src.filters : {},\n\t\tsearch: (typeof src.search === 'string') ? src.search : '',\n\t\t// `null` rather than `[]` for no sort: every view stored before sorts\n\t\t// could be chained says `null`, and one spelling for \"no sort\" is worth\n\t\t// more than the symmetry.\n\t\tsort: keys.length > 0 ? keys : null,\n\t}\n\tif (typeof scope === 'string' && scope !== '') {\n\t\tquery.scope = scope\n\t}\n\tif (typeof register === 'string' && register !== '') {\n\t\tquery.registers = [register]\n\t}\n\tconst slug = schemaSlug(schema)\n\tif (slug !== '') {\n\t\tquery.schemas = [slug]\n\t}\n\tconst body = {\n\t\tname,\n\t\tdescription: description || '',\n\t\tisPublic: !!isPublic,\n\t\tisDefault: !!isDefault,\n\t\tquery,\n\t}\n\tif (presentation && typeof presentation === 'object' && (presentation.viewType === 'kanban' || presentation.viewType === 'calendar')) {\n\t\tbody.presentation = presentation\n\t}\n\tconst audience = normalizeSharedWith(sharedWith)\n\tif (audience.length > 0) {\n\t\tbody.sharedWith = audience\n\t}\n\treturn body\n}\n\n/**\n * Clean an audience list: keep `{ group, mode }` and `{ user, mode }` entries\n * that name exactly one target, drop repeats, and coerce any mode but `write`\n * to `read`.\n *\n * @param {unknown} sharedWith The audience as given.\n * @return {Array<{group?: string, user?: string, mode: 'read'|'write'}>} The cleaned list.\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-5\n */\nexport function normalizeSharedWith(sharedWith) {\n\tconst seen = new Set()\n\tconst out = []\n\tfor (const entry of Array.isArray(sharedWith) ? sharedWith : []) {\n\t\tconst group = entry && typeof entry.group === 'string' ? entry.group : ''\n\t\tconst user = entry && typeof entry.user === 'string' ? entry.user : ''\n\t\tif ((group === '') === (user === '')) {\n\t\t\tcontinue\n\t\t}\n\t\tconst key = user !== '' ? `user:${user}` : `group:${group}`\n\t\tif (seen.has(key)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(key)\n\t\tconst mode = entry.mode === 'write' ? 'write' : 'read'\n\t\tout.push(user !== '' ? { user, mode } : { group, mode })\n\t}\n\treturn out\n}\n\n/**\n * What the current user may do with a view: `owner`, `write` or `read`.\n *\n * The server decides: `@self.access` wins. A view without it (an older\n * OpenRegister) counts as `owner` when its `owner` is the current user and as\n * `read` otherwise.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {string|null|undefined} currentUserId The signed-in NC user id.\n * @return {'owner'|'write'|'read'} The access.\n */\nexport function viewAccess(view, currentUserId) {\n\tconst access = view && view['@self'] && view['@self'].access\n\tif (access === 'owner' || access === 'write' || access === 'read') {\n\t\treturn access\n\t}\n\treturn isOwnView(view, currentUserId) ? 'owner' : 'read'\n}\n\n/**\n * The slug of a schema given as a slug or as a schema object.\n *\n * @param {object|string|null|undefined} schema The page's `schema` prop.\n * @return {string} The slug, or the empty string.\n */\nexport function schemaSlug(schema) {\n\tif (typeof schema === 'string') {\n\t\treturn schema\n\t}\n\tif (schema && typeof schema === 'object' && typeof schema.slug === 'string') {\n\t\treturn schema.slug\n\t}\n\treturn ''\n}\n\n/**\n * The key naming which pages share a view.\n *\n * A view is a set of filters over one schema's fields, so by default every\n * page over the same register and schema shares it and no other page sees\n * it: three case indexes share their views, the organisations index does\n * not. `savedViewsScope` on a page overrides that, for pages that should\n * share views although they read different sources, or keep them apart\n * although they read the same one. A page over a named source\n * (`entitySource`) and no schema shares its views with the pages over that\n * source.\n *\n * @param {object} page The page.\n * @param {string} [page.scope] The explicit `savedViewsScope`, when set.\n * @param {string} [page.register] The page's register.\n * @param {object|string} [page.schema] The page's schema, slug or object.\n * @param {string} [page.source] The page's `entitySource`, when it reads one.\n * @return {string} The scope key, or the empty string for a page with no source.\n */\nexport function savedViewScope({ scope, register, schema, source } = {}) {\n\tif (typeof scope === 'string' && scope !== '') {\n\t\treturn scope\n\t}\n\tconst slug = schemaSlug(schema)\n\tif (typeof register === 'string' && register !== '' && slug !== '') {\n\t\treturn `${register}/${slug}`\n\t}\n\tif (typeof source === 'string' && source !== '') {\n\t\treturn `source:${source}`\n\t}\n\treturn ''\n}\n\n/**\n * Whether an index page offers saved views in its sidebar.\n *\n * `allowSavedViews` decides when it is set. Unset, every index page that has\n * a sidebar and a source a view can be scoped to (a register and schema, a\n * named source, or `savedViewsScope`) offers them, so managing views is\n * there on every list without each page opting in.\n *\n * @param {object} config The page's index config, or CnIndexPage's props.\n * @param {boolean|null} [config.allowSavedViews] The explicit choice, when made.\n * @param {object} [config.sidebar] The sidebar config.\n * @param {string} [config.savedViewsScope] The explicit scope.\n * @param {string} [config.register] The register.\n * @param {object|string} [config.schema] The schema.\n * @param {string} [config.entitySource] The named source.\n * @return {boolean}\n */\nexport function pageOffersSavedViews(config) {\n\tif (!config || typeof config !== 'object') {\n\t\treturn false\n\t}\n\tif (typeof config.allowSavedViews === 'boolean') {\n\t\treturn config.allowSavedViews\n\t}\n\tconst sidebar = config.sidebar\n\tif (!sidebar || typeof sidebar !== 'object' || sidebar.enabled !== true || sidebar.show === false) {\n\t\treturn false\n\t}\n\treturn savedViewScope({ scope: config.savedViewsScope, register: config.register, schema: config.schema, source: config.entitySource }) !== ''\n}\n\n/**\n * Whether a saved view belongs on a page.\n *\n * A view saved with a `scope` is shown where the scope matches. A view that\n * names `schemas` (OpenRegister's own search UI writes those) is shown on a\n * page over one of them. A view that says neither, saved before views were\n * scoped, is shown everywhere as it always was: hiding it would lose the\n * view, and nothing can tell which page it was made on.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {object} page The page, as {@link savedViewScope} takes it.\n * @return {boolean} True when the view is shown on this page.\n */\nexport function viewMatchesScope(view, page = {}) {\n\tconst query = view && typeof view === 'object' ? view.query : null\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn true\n\t}\n\tif (typeof query.scope === 'string' && query.scope !== '') {\n\t\treturn query.scope === savedViewScope(page)\n\t}\n\tif (Array.isArray(query.schemas) && query.schemas.length > 0) {\n\t\tconst slug = schemaSlug(page.schema)\n\t\tif (slug === '' || !query.schemas.map(String).includes(slug)) {\n\t\t\treturn false\n\t\t}\n\t\tif (Array.isArray(query.registers) && query.registers.length > 0 && page.register) {\n\t\t\treturn query.registers.map(String).includes(String(page.register))\n\t\t}\n\t\treturn true\n\t}\n\treturn true\n}\n\n/**\n * Normalize a View API object (as returned by `GET /api/views`) back into\n * the internal view state. Accepts either a full view object (reads its\n * `query` property) or a raw query blob directly. Every field degrades\n * defensively: missing/null/malformed `query`, non-object `filters`,\n * malformed `sort` all yield the empty/default state — never throws.\n *\n * @param {object|null|undefined} view The View API object (or its raw `query` blob).\n * @return {{ filters: object, search: string, sortKeys: Array<{key: string, order: string}>, sortKey: ?string, sortOrder: string }} The normalized view state.\n */\nexport function extractViewState(view) {\n\tconst state = emptyState()\n\tif (!view || typeof view !== 'object' || Array.isArray(view)) {\n\t\treturn state\n\t}\n\t// Full view object (has a `query` key) or a raw query blob itself.\n\tconst query = (Object.hasOwn(view, 'query')) ? view.query : view\n\tif (!query || typeof query !== 'object' || Array.isArray(query)) {\n\t\treturn state\n\t}\n\tif (query.filters && typeof query.filters === 'object' && !Array.isArray(query.filters)) {\n\t\tfor (const [key, value] of Object.entries(query.filters)) {\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tstate.filters[key] = value\n\t\t}\n\t}\n\tif (typeof query.search === 'string') {\n\t\tstate.search = query.search\n\t}\n\treturn withSort(state, normalizeSortKeys(query.sort))\n}\n\n/**\n * Whether the given view is owned by the given user. Drives the delete\n * affordance in CnSavedViewsControl — OpenRegister scopes destroy() to the\n * owner server-side (a foreign id 404s), so this only hides the button for\n * views the API would refuse to delete anyway.\n *\n * @param {object|null|undefined} view The View API object.\n * @param {string|null|undefined} currentUserId The signed-in NC user id.\n * @return {boolean} True when the view belongs to the current user.\n */\nexport function isOwnView(view, currentUserId) {\n\treturn !!view && !!currentUserId && view.owner === currentUserId\n}\n\n/**\n * The default views an index page declares (`config.defaultSavedViews`), as\n * read-only views the saved-views control lists. They are never stored: the\n * manifest installs them, so every user has the same set and none can delete\n * one. `seeded: true` puts them in the control's default group; the query\n * keeps its `@`-tokens, which resolve when a default is applied.\n *\n * A default's `overrides` (page config laid over the page while it is\n * active, see `utils/savedViewOverrides.js`) ride in `query.overrides`, so a\n * copy of the default keeps them.\n *\n * @param {Array<object>|null|undefined} defaults The declared entries `{ slug, name, filters, search?, sort?, label?, overrides? }`.\n * @param {{ scope?: string, register?: string, schema?: object|string|null }} [page] The page, as `savedViewScope` takes it.\n * @return {Array<object>} The views, in declaration order; entries without a slug or name are left out.\n * @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-4\n */\nexport function defaultFilterViews(defaults, page = {}) {\n\tif (!Array.isArray(defaults)) {\n\t\treturn []\n\t}\n\treturn defaults\n\t\t.filter((entry) => entry && typeof entry.slug === 'string' && entry.slug !== '' && typeof entry.name === 'string' && entry.name !== '')\n\t\t.map((entry) => {\n\t\t\tconst { query } = buildViewCreatePayload({\n\t\t\t\tname: entry.name,\n\t\t\t\tstate: {\n\t\t\t\t\tfilters: (entry.filters && typeof entry.filters === 'object' && !Array.isArray(entry.filters)) ? entry.filters : {},\n\t\t\t\t\tsearch: typeof entry.search === 'string' ? entry.search : '',\n\t\t\t\t\tsortKeys: Array.isArray(entry.sort) ? entry.sort : [],\n\t\t\t\t},\n\t\t\t\tscope: savedViewScope(page),\n\t\t\t\tregister: page.register,\n\t\t\t\tschema: page.schema,\n\t\t\t})\n\t\t\tif (entry.overrides && typeof entry.overrides === 'object' && !Array.isArray(entry.overrides)) {\n\t\t\t\tquery.overrides = entry.overrides\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: `default:${entry.slug}`,\n\t\t\t\tslug: entry.slug,\n\t\t\t\tname: entry.name,\n\t\t\t\towner: '',\n\t\t\t\tseeded: true,\n\t\t\t\tisPublic: false,\n\t\t\t\tlabels: typeof entry.label === 'string' && entry.label !== '' ? [entry.label] : [],\n\t\t\t\t'@self': { access: 'read' },\n\t\t\t\tquery,\n\t\t\t}\n\t\t})\n}\n"],"names":[],"mappings":"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"}