@conduction/nextcloud-vue 2.103.0 → 2.104.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 (74) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +23 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +94 -72
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +18 -1
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -3
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  9. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +39 -2
  10. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  11. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +14 -0
  12. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +40 -2
  14. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  15. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +159 -3
  17. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +105 -85
  19. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +7 -2
  21. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  22. package/dist/esm/components/CnObjectRow/CnObjectRow.vue.js +1 -1
  23. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +57 -5
  24. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  25. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +37 -4
  26. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  27. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
  28. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
  29. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
  30. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  31. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +18 -2
  32. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  33. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  34. package/dist/esm/composables/indexSources.js +7 -7
  35. package/dist/esm/composables/indexSources.js.map +1 -1
  36. package/dist/esm/l10n/en.json.js +4 -0
  37. package/dist/esm/l10n/en.json.js.map +1 -1
  38. package/dist/esm/l10n/nl.json.js +4 -0
  39. package/dist/esm/l10n/nl.json.js.map +1 -1
  40. package/dist/esm/library-style.js +1 -1
  41. package/dist/esm/nextcloud-vue.css +114 -33
  42. package/dist/esm/utils/nextcloudDirectory.js +18 -1
  43. package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
  44. package/dist/esm/utils/sortOrder.js +61 -0
  45. package/dist/esm/utils/sortOrder.js.map +1 -0
  46. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  47. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  48. package/dist/nextcloud-vue.cjs.js +4172 -3662
  49. package/dist/nextcloud-vue.cjs.js.map +2 -2
  50. package/dist/nextcloud-vue.css +114 -33
  51. package/l10n/en.json +4 -0
  52. package/l10n/nl.json +4 -0
  53. package/package.json +1 -1
  54. package/src/components/CnActionsBar/CnActionsBar.md +1 -0
  55. package/src/components/CnActionsBar/CnActionsBar.vue +39 -2
  56. package/src/components/CnAppRoot/CnAppRoot.md +1 -1
  57. package/src/components/CnAppRoot/CnAppRoot.vue +21 -4
  58. package/src/components/CnDataTable/CnDataTable.md +1 -0
  59. package/src/components/CnDataTable/CnDataTable.vue +49 -0
  60. package/src/components/CnDetailPage/CnDetailPage.md +1 -0
  61. package/src/components/CnDetailPage/CnDetailPage.vue +40 -2
  62. package/src/components/CnIndexPage/CnIndexPage.md +4 -2
  63. package/src/components/CnIndexPage/CnIndexPage.vue +174 -8
  64. package/src/components/CnIndexPage/useSelfFetchList.js +6 -1
  65. package/src/components/CnObjectRow/CnObjectRow.vue +95 -1
  66. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +7 -2
  67. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +18 -2
  68. package/src/composables/indexSources.js +7 -7
  69. package/src/css/index-page.css +19 -0
  70. package/src/css/look-board-index.css +48 -0
  71. package/src/schemas/app-manifest-v2.schema.json +23 -1
  72. package/src/utils/nextcloudDirectory.js +17 -0
  73. package/src/utils/sortOrder.js +58 -0
  74. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -2645,6 +2645,17 @@
2645
2645
  cursor: not-allowed;
2646
2646
  }
2647
2647
 
2648
+ /* `searchPlacement: "toolbar"`: the search field on row 1 takes the room
2649
+ before Filter and the view switch, which keep the end of the row. */
2650
+ .cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-actions-bar__search--toolbar {
2651
+ flex: 1 1 220px;
2652
+ min-width: 180px;
2653
+ }
2654
+
2655
+ .cn-look-board .cn-actions-bar--board > .cn-actions-bar__row--views > .cn-actions-bar__search--toolbar ~ .cn-actions-bar__view-controls {
2656
+ margin-inline-start: 0;
2657
+ }
2658
+
2648
2659
  /* The search field: 40px, radius 8, not a pill. */
2649
2660
  .cn-look-board .cn-actions-bar--board .cn-actions-bar__search {
2650
2661
  flex: 1 1 260px;
@@ -3381,6 +3392,43 @@
3381
3392
  .cn-look-board:has(.cn-index-page) > .app-sidebar__toggle {
3382
3393
  display: none;
3383
3394
  }
3395
+
3396
+ /* The folder pane as a card (DqAfdelingen): 280px, a hairline border, 12px
3397
+ corners, with each folder's count in a grey pill. */
3398
+ .cn-look-board .cn-index-page__folder-pane {
3399
+ flex: 0 0 280px;
3400
+ align-self: flex-start;
3401
+ margin-inline-end: 20px;
3402
+ padding: 14px;
3403
+ border: 1px solid var(--cn-board-hairline);
3404
+ border-radius: var(--cn-board-card-radius, 12px);
3405
+ background: var(--color-main-background);
3406
+ }
3407
+
3408
+ .cn-look-board .cn-index-page__folder-pane .cn-folder-tree__count {
3409
+ display: inline-flex;
3410
+ align-items: center;
3411
+ justify-content: center;
3412
+ min-width: 20px;
3413
+ height: 20px;
3414
+ padding: 0 6px;
3415
+ box-sizing: border-box;
3416
+ border-radius: 10px;
3417
+ background: var(--color-background-dark);
3418
+ color: var(--color-main-text);
3419
+ font-size: 12px;
3420
+ font-weight: 600;
3421
+ }
3422
+
3423
+ /* The selected folder's heading above the list: a 20px title over a soft line. */
3424
+ .cn-look-board .cn-index-page__scope-heading {
3425
+ margin-bottom: 16px;
3426
+ }
3427
+
3428
+ .cn-look-board .cn-index-page__scope-title {
3429
+ font-size: 20px;
3430
+ font-weight: 700;
3431
+ }
3384
3432
  /* ========================================
3385
3433
  @conduction/nextcloud-vue — Table Styles
3386
3434
  ======================================== */
@@ -6193,6 +6241,25 @@ a.cn-kpi-card--board * {
6193
6241
  font-size: 14px;
6194
6242
  }
6195
6243
 
6244
+ /* The selected folder's heading above the list. */
6245
+ .cn-index-page__scope-heading {
6246
+ display: flex;
6247
+ flex-direction: column;
6248
+ gap: 4px;
6249
+ margin-bottom: 12px;
6250
+ }
6251
+
6252
+ .cn-index-page__scope-title {
6253
+ margin: 0;
6254
+ font-size: 1.25em;
6255
+ font-weight: 700;
6256
+ }
6257
+
6258
+ .cn-index-page__scope-subtitle {
6259
+ font-size: 14px;
6260
+ color: var(--color-text-maxcontrast);
6261
+ }
6262
+
6196
6263
  /* Opt-in folder navigation pane (left of the list). */
6197
6264
  .cn-index-page__folder-pane {
6198
6265
  flex: 0 0 240px;
@@ -26771,16 +26838,16 @@ button.cn-stepper__step[data-v-776581d4] {
26771
26838
  }
26772
26839
 
26773
26840
 
26774
- .cn-widget-object-table[data-v-dfdcad2a] {
26841
+ .cn-widget-object-table[data-v-d628ab02] {
26775
26842
  width: 100%;
26776
26843
  }
26777
- .cn-widget-object-table__footer[data-v-dfdcad2a] {
26844
+ .cn-widget-object-table__footer[data-v-d628ab02] {
26778
26845
  display: flex;
26779
26846
  gap: 4px;
26780
26847
  padding: 4px 8px;
26781
26848
  border-top: 1px solid var(--color-border);
26782
26849
  }
26783
- .cn-widget-object-table__error[data-v-dfdcad2a] {
26850
+ .cn-widget-object-table__error[data-v-d628ab02] {
26784
26851
  color: var(--color-text-error, var(--color-error-text));
26785
26852
  font-size: 0.85em;
26786
26853
  margin: 4px 8px 0;
@@ -38455,7 +38522,7 @@ to {
38455
38522
  color: var(--color-text-maxcontrast);
38456
38523
  }
38457
38524
 
38458
- .cn-object-row[data-v-a4a78a68] {
38525
+ .cn-object-row[data-v-6e2b35c2] {
38459
38526
  display: flex;
38460
38527
  align-items: center;
38461
38528
  gap: 12px;
@@ -38463,18 +38530,26 @@ to {
38463
38530
  border-bottom: 1px solid var(--color-border);
38464
38531
  cursor: pointer;
38465
38532
  }
38466
- .cn-object-row[data-v-a4a78a68]:hover {
38533
+ .cn-object-row[data-v-6e2b35c2]:hover {
38467
38534
  background-color: var(--color-background-hover);
38468
38535
  }
38469
- .cn-object-row--selected[data-v-a4a78a68] {
38536
+ .cn-object-row--selected[data-v-6e2b35c2] {
38470
38537
  /* Inset accent — never border-left (would shift cell content). */
38471
38538
  box-shadow: inset 3px 0 0 0 var(--color-primary-element);
38472
38539
  background-color: var(--color-primary-element-light);
38473
38540
  }
38474
- .cn-object-row__checkbox[data-v-a4a78a68] {
38541
+ .cn-object-row__lead[data-v-6e2b35c2],
38542
+ .cn-object-row__trail[data-v-6e2b35c2] {
38543
+ flex: 0 0 auto;
38544
+ min-width: 0;
38545
+ }
38546
+ .cn-object-row__lead[data-v-6e2b35c2] {
38547
+ font-weight: 600;
38548
+ }
38549
+ .cn-object-row__checkbox[data-v-6e2b35c2] {
38475
38550
  flex-shrink: 0;
38476
38551
  }
38477
- .cn-object-row__icon[data-v-a4a78a68] {
38552
+ .cn-object-row__icon[data-v-6e2b35c2] {
38478
38553
  flex-shrink: 0;
38479
38554
  display: flex;
38480
38555
  align-items: center;
@@ -38483,38 +38558,38 @@ to {
38483
38558
  height: 24px;
38484
38559
  color: var(--color-text-maxcontrast);
38485
38560
  }
38486
- .cn-object-row__image[data-v-a4a78a68] {
38561
+ .cn-object-row__image[data-v-6e2b35c2] {
38487
38562
  width: 24px;
38488
38563
  height: 24px;
38489
38564
  object-fit: contain;
38490
38565
  border-radius: var(--border-radius);
38491
38566
  }
38492
- .cn-object-row__main[data-v-a4a78a68] {
38567
+ .cn-object-row__main[data-v-6e2b35c2] {
38493
38568
  display: flex;
38494
38569
  flex-direction: column;
38495
38570
  min-width: 0;
38496
38571
  flex: 1;
38497
38572
  }
38498
- .cn-object-row__title[data-v-a4a78a68] {
38573
+ .cn-object-row__title[data-v-6e2b35c2] {
38499
38574
  font-weight: 600;
38500
38575
  overflow: hidden;
38501
38576
  text-overflow: ellipsis;
38502
38577
  white-space: nowrap;
38503
38578
  }
38504
- .cn-object-row__subtitle[data-v-a4a78a68] {
38579
+ .cn-object-row__subtitle[data-v-6e2b35c2] {
38505
38580
  font-size: 13px;
38506
38581
  color: var(--color-text-maxcontrast);
38507
38582
  overflow: hidden;
38508
38583
  text-overflow: ellipsis;
38509
38584
  white-space: nowrap;
38510
38585
  }
38511
- .cn-object-row__badges[data-v-a4a78a68] {
38586
+ .cn-object-row__badges[data-v-6e2b35c2] {
38512
38587
  flex-shrink: 0;
38513
38588
  display: flex;
38514
38589
  align-items: center;
38515
38590
  gap: 6px;
38516
38591
  }
38517
- .cn-object-row__actions[data-v-a4a78a68] {
38592
+ .cn-object-row__actions[data-v-6e2b35c2] {
38518
38593
  flex-shrink: 0;
38519
38594
  display: flex;
38520
38595
  align-items: center;
@@ -46285,17 +46360,17 @@ a.cn-integration-card__title[data-v-093a874d]:hover {
46285
46360
  }
46286
46361
 
46287
46362
 
46288
- .cn-user-settings-dialog__body[data-v-e537e50a] {
46363
+ .cn-user-settings-dialog__body[data-v-6d3e749e] {
46289
46364
  display: flex;
46290
46365
  flex-wrap: wrap;
46291
46366
  gap: 0 24px;
46292
46367
  }
46293
- .cn-user-settings-dialog__nav[data-v-e537e50a] {
46368
+ .cn-user-settings-dialog__nav[data-v-6d3e749e] {
46294
46369
  flex: 0 0 200px;
46295
46370
  width: 200px;
46296
46371
  padding-top: 18px;
46297
46372
  }
46298
- .cn-user-settings-dialog__nav ul[data-v-e537e50a] {
46373
+ .cn-user-settings-dialog__nav ul[data-v-6d3e749e] {
46299
46374
  display: flex;
46300
46375
  flex-direction: column;
46301
46376
  gap: 2px;
@@ -46303,7 +46378,12 @@ a.cn-integration-card__title[data-v-093a874d]:hover {
46303
46378
  padding: 0;
46304
46379
  list-style: none;
46305
46380
  }
46306
- .cn-user-settings-dialog__nav-link[data-v-e537e50a] {
46381
+
46382
+ /* A navigation entry, not a link in running text: no underline, also not
46383
+ on hover. `!important` because a theme's `a { text-decoration }` carries
46384
+ it (thematiq), and a bare `a` with `!important` beats any class. */
46385
+ .cn-user-settings-dialog__nav-link[data-v-6d3e749e],
46386
+ .cn-user-settings-dialog__nav-link[data-v-6d3e749e]:visited {
46307
46387
  display: flex;
46308
46388
  align-items: center;
46309
46389
  gap: 10px;
@@ -46312,22 +46392,23 @@ a.cn-integration-card__title[data-v-093a874d]:hover {
46312
46392
  border-radius: var(--border-radius-element, 8px);
46313
46393
  font-size: 15px;
46314
46394
  color: var(--color-main-text);
46315
- text-decoration: none;
46395
+ text-decoration: none !important;
46316
46396
  }
46317
- .cn-user-settings-dialog__nav-link[data-v-e537e50a]:hover {
46397
+ .cn-user-settings-dialog__nav-link[data-v-6d3e749e]:hover {
46318
46398
  background: var(--color-background-hover);
46399
+ text-decoration: none !important;
46319
46400
  }
46320
- .cn-user-settings-dialog__nav-link[data-v-e537e50a]:focus-visible {
46401
+ .cn-user-settings-dialog__nav-link[data-v-6d3e749e]:focus-visible {
46321
46402
  outline: 2px solid var(--color-primary-element);
46322
46403
  outline-offset: 2px;
46323
46404
  }
46324
- .cn-user-settings-dialog__nav-link--active[data-v-e537e50a],
46325
- .cn-user-settings-dialog__nav-link--active[data-v-e537e50a]:hover {
46405
+ .cn-user-settings-dialog__nav-link--active[data-v-6d3e749e],
46406
+ .cn-user-settings-dialog__nav-link--active[data-v-6d3e749e]:hover {
46326
46407
  background: var(--color-primary-element-light);
46327
46408
  color: var(--color-primary-element-light-text);
46328
46409
  font-weight: 600;
46329
46410
  }
46330
- .cn-user-settings-dialog__nav-icon[data-v-e537e50a] {
46411
+ .cn-user-settings-dialog__nav-icon[data-v-6d3e749e] {
46331
46412
  display: inline-flex;
46332
46413
  flex: none;
46333
46414
  width: 18px;
@@ -46335,18 +46416,18 @@ a.cn-integration-card__title[data-v-093a874d]:hover {
46335
46416
  align-items: center;
46336
46417
  justify-content: center;
46337
46418
  }
46338
- .cn-user-settings-dialog__sections[data-v-e537e50a] {
46419
+ .cn-user-settings-dialog__sections[data-v-6d3e749e] {
46339
46420
  display: flex;
46340
46421
  flex: 1 1 380px;
46341
46422
  flex-direction: column;
46342
46423
  min-width: 0;
46343
46424
  }
46344
- .cn-user-settings-dialog__sections[data-v-e537e50a] .app-settings-section {
46425
+ .cn-user-settings-dialog__sections[data-v-6d3e749e] .app-settings-section {
46345
46426
  margin: 0;
46346
46427
  padding: 18px 0;
46347
46428
  border-top: 1px solid var(--color-border);
46348
46429
  }
46349
- .cn-user-settings-dialog__sections[data-v-e537e50a] .app-settings-section__name {
46430
+ .cn-user-settings-dialog__sections[data-v-6d3e749e] .app-settings-section__name {
46350
46431
  margin: 0 0 12px;
46351
46432
  font-size: 17px;
46352
46433
  font-weight: 700;
@@ -46354,33 +46435,33 @@ a.cn-integration-card__title[data-v-093a874d]:hover {
46354
46435
  align-items: center;
46355
46436
  gap: 10px;
46356
46437
  }
46357
- .cn-user-settings-dialog__note[data-v-e537e50a] {
46438
+ .cn-user-settings-dialog__note[data-v-6d3e749e] {
46358
46439
  flex: 1 1 auto;
46359
46440
  margin: 0;
46360
46441
  font-size: 14px;
46361
46442
  color: var(--color-text-maxcontrast);
46362
46443
  }
46363
- .cn-user-settings-dialog__nc-links[data-v-e537e50a] {
46444
+ .cn-user-settings-dialog__nc-links[data-v-6d3e749e] {
46364
46445
  margin-bottom: 2px;
46365
46446
  }
46366
- .cn-user-settings-dialog__group[data-v-e537e50a] {
46447
+ .cn-user-settings-dialog__group[data-v-6d3e749e] {
46367
46448
  margin: 10px 0 2px;
46368
46449
  padding: 0 12px;
46369
46450
  font-size: 15px;
46370
46451
  font-weight: 600;
46371
46452
  color: var(--color-main-text);
46372
46453
  }
46373
- .cn-user-settings-dialog__group-sections[data-v-e537e50a] {
46454
+ .cn-user-settings-dialog__group-sections[data-v-6d3e749e] {
46374
46455
  padding-inline-start: 12px;
46375
46456
  }
46376
- .cn-user-settings-dialog__heading-icon[data-v-e537e50a] {
46457
+ .cn-user-settings-dialog__heading-icon[data-v-6d3e749e] {
46377
46458
  display: inline-flex;
46378
46459
  order: -1;
46379
46460
  color: var(--color-text-maxcontrast);
46380
46461
  }
46381
46462
 
46382
46463
  /* One section at a time: the others are mounted but hidden. */
46383
- .cn-user-settings-dialog__sections[data-v-e537e50a] .app-settings-section[hidden] {
46464
+ .cn-user-settings-dialog__sections[data-v-6d3e749e] .app-settings-section[hidden] {
46384
46465
  display: none;
46385
46466
  }
46386
46467
 
@@ -65,6 +65,23 @@ async function fetchGroupMembers(axios, groupId) {
65
65
  return Array.isArray(users) ? users : Object.values(users)
66
66
  }
67
67
 
68
+ /**
69
+ * Every user the reader may list, with their details: all users for an
70
+ * admin, the users of their groups for a sub-admin. Refused for others.
71
+ *
72
+ * @param {object} axios The axios instance.
73
+ * @param {{limit?: number}} [options] How many to read.
74
+ * @return {Promise<Array<object>>} Nextcloud's user details, one per user.
75
+ */
76
+ async function fetchUsers(axios, options = {}) {
77
+ const res = await axios.get(generateOcsUrl('cloud/users/details'), {
78
+ params: { limit: options.limit || 500, format: 'json' },
79
+ headers: OCS_HEADERS,
80
+ });
81
+ const users = (ocsData(res) && ocsData(res).users) || {};
82
+ return Array.isArray(users) ? users : Object.values(users)
83
+ }
84
+
68
85
  /**
69
86
  * Whether a user is in today: an out-of-office period that runs now (the
70
87
  * dav app's absence, Nextcloud 28 and later), else their user status.
@@ -117,5 +134,5 @@ async function fetchStatuses(axios) {
117
134
  }
118
135
  }
119
136
 
120
- export { fetchGroupMembers, fetchGroups, fetchPresence, fetchStatuses };
137
+ export { fetchGroupMembers, fetchGroups, fetchPresence, fetchStatuses, fetchUsers };
121
138
  //# sourceMappingURL=nextcloudDirectory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nextcloudDirectory.js","sources":["../../../src/utils/nextcloudDirectory.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { generateOcsUrl } from '@nextcloud/router'\n\n/**\n * Nextcloud's own directory: groups, the users in a group, and whether a\n * user is in today. Read through the OCS API with the reader's session, so\n * Nextcloud decides what the reader may see (listing a group's members needs\n * an admin or the group's sub-admin).\n *\n * @module utils/nextcloudDirectory\n */\n\nconst OCS_HEADERS = { 'OCS-APIRequest': 'true' }\n\n/**\n * The `ocs.data` of an OCS response, or null.\n *\n * @param {object} res An axios response.\n * @return {object|null}\n */\nfunction ocsData(res) {\n\treturn (res && res.data && res.data.ocs && res.data.ocs.data) || null\n}\n\n/**\n * The groups, with their member counts.\n *\n * @param {object} axios The axios instance.\n * @param {{search?: string, limit?: number}} [options] Narrowing.\n * @return {Promise<Array<{id: string, name: string, count: number}>>}\n * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members\n */\nexport async function fetchGroups(axios, options = {}) {\n\tconst res = await axios.get(generateOcsUrl('cloud/groups/details'), {\n\t\tparams: { search: options.search || '', limit: options.limit || 200, format: 'json' },\n\t\theaders: OCS_HEADERS,\n\t})\n\tconst groups = (ocsData(res) && ocsData(res).groups) || []\n\treturn groups.map((group) => ({\n\t\tid: String(group.id),\n\t\tname: group.displayname || String(group.id),\n\t\tcount: Number(group.usercount) || 0,\n\t}))\n}\n\n/**\n * The members of one group, with their details.\n *\n * @param {object} axios The axios instance.\n * @param {string} groupId The group id.\n * @return {Promise<Array<object>>} Nextcloud's user details, one per member.\n * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members\n */\nexport async function fetchGroupMembers(axios, groupId) {\n\tconst res = await axios.get(generateOcsUrl('cloud/groups/{groupId}/users/details', { groupId }), {\n\t\tparams: { format: 'json' },\n\t\theaders: OCS_HEADERS,\n\t})\n\tconst users = (ocsData(res) && ocsData(res).users) || {}\n\treturn Array.isArray(users) ? users : Object.values(users)\n}\n\n/**\n * Whether a user is in today: an out-of-office period that runs now (the\n * dav app's absence, Nextcloud 28 and later), else their user status.\n *\n * @param {object} axios The axios instance.\n * @param {string} userId The user id.\n * @param {object|null} status The user's entry from the user status list, or null.\n * @return {Promise<{state: string, until: (string|null), message: string}>}\n * `state` is `absent` (with `until`, an ISO date), `away`, `present` or `unknown`.\n * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members\n */\nexport async function fetchPresence(axios, userId, status) {\n\ttry {\n\t\tconst res = await axios.get(generateOcsUrl('apps/dav/api/v1/outOfOffice/{userId}/now', { userId }), { params: { format: 'json' }, headers: OCS_HEADERS })\n\t\tconst ooo = ocsData(res)\n\t\tif (ooo && ooo.endDate) {\n\t\t\tconst end = typeof ooo.endDate === 'number' ? new Date(ooo.endDate * 1000) : new Date(ooo.endDate)\n\t\t\treturn { state: 'absent', until: Number.isNaN(end.getTime()) ? null : end.toISOString().slice(0, 10), message: ooo.shortMessage || '' }\n\t\t}\n\t} catch {\n\t\t// 404: no absence now, or an instance without the absence API.\n\t}\n\tif (status && typeof status === 'object') {\n\t\tconst message = typeof status.message === 'string' ? status.message : ''\n\t\tif (status.status === 'online') {\n\t\t\treturn { state: 'present', until: null, message }\n\t\t}\n\t\tif (status.status === 'away' || status.status === 'dnd') {\n\t\t\treturn { state: 'away', until: null, message }\n\t\t}\n\t}\n\treturn { state: 'unknown', until: null, message: '' }\n}\n\n/**\n * The user statuses Nextcloud shares with the reader, by user id. Empty when\n * the user status app is off.\n *\n * @param {object} axios The axios instance.\n * @return {Promise<Record<string, object>>}\n * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members\n */\nexport async function fetchStatuses(axios) {\n\ttry {\n\t\tconst res = await axios.get(generateOcsUrl('apps/user_status/api/v1/statuses'), { params: { limit: 500, format: 'json' }, headers: OCS_HEADERS })\n\t\tconst list = ocsData(res) || []\n\t\treturn Object.fromEntries((Array.isArray(list) ? list : []).map((entry) => [entry.userId, entry]))\n\t} catch {\n\t\treturn {}\n\t}\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"nextcloudDirectory.js","sources":["../../../src/utils/nextcloudDirectory.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { generateOcsUrl } from '@nextcloud/router'\n\n/**\n * Nextcloud's own directory: groups, the users in a group, and whether a\n * user is in today. Read through the OCS API with the reader's session, so\n * Nextcloud decides what the reader may see (listing a group's members needs\n * an admin or the group's sub-admin).\n *\n * @module utils/nextcloudDirectory\n */\n\nconst OCS_HEADERS = { 'OCS-APIRequest': 'true' }\n\n/**\n * The `ocs.data` of an OCS response, or null.\n *\n * @param {object} res An axios response.\n * @return {object|null}\n */\nfunction ocsData(res) {\n\treturn (res && res.data && res.data.ocs && res.data.ocs.data) || null\n}\n\n/**\n * The groups, with their member counts.\n *\n * @param {object} axios The axios instance.\n * @param {{search?: string, limit?: number}} [options] Narrowing.\n * @return {Promise<Array<{id: string, name: string, count: number}>>}\n * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members\n */\nexport async function fetchGroups(axios, options = {}) {\n\tconst res = await axios.get(generateOcsUrl('cloud/groups/details'), {\n\t\tparams: { search: options.search || '', limit: options.limit || 200, format: 'json' },\n\t\theaders: OCS_HEADERS,\n\t})\n\tconst groups = (ocsData(res) && ocsData(res).groups) || []\n\treturn groups.map((group) => ({\n\t\tid: String(group.id),\n\t\tname: group.displayname || String(group.id),\n\t\tcount: Number(group.usercount) || 0,\n\t}))\n}\n\n/**\n * The members of one group, with their details.\n *\n * @param {object} axios The axios instance.\n * @param {string} groupId The group id.\n * @return {Promise<Array<object>>} Nextcloud's user details, one per member.\n * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members\n */\nexport async function fetchGroupMembers(axios, groupId) {\n\tconst res = await axios.get(generateOcsUrl('cloud/groups/{groupId}/users/details', { groupId }), {\n\t\tparams: { format: 'json' },\n\t\theaders: OCS_HEADERS,\n\t})\n\tconst users = (ocsData(res) && ocsData(res).users) || {}\n\treturn Array.isArray(users) ? users : Object.values(users)\n}\n\n/**\n * Every user the reader may list, with their details: all users for an\n * admin, the users of their groups for a sub-admin. Refused for others.\n *\n * @param {object} axios The axios instance.\n * @param {{limit?: number}} [options] How many to read.\n * @return {Promise<Array<object>>} Nextcloud's user details, one per user.\n */\nexport async function fetchUsers(axios, options = {}) {\n\tconst res = await axios.get(generateOcsUrl('cloud/users/details'), {\n\t\tparams: { limit: options.limit || 500, format: 'json' },\n\t\theaders: OCS_HEADERS,\n\t})\n\tconst users = (ocsData(res) && ocsData(res).users) || {}\n\treturn Array.isArray(users) ? users : Object.values(users)\n}\n\n/**\n * Whether a user is in today: an out-of-office period that runs now (the\n * dav app's absence, Nextcloud 28 and later), else their user status.\n *\n * @param {object} axios The axios instance.\n * @param {string} userId The user id.\n * @param {object|null} status The user's entry from the user status list, or null.\n * @return {Promise<{state: string, until: (string|null), message: string}>}\n * `state` is `absent` (with `until`, an ISO date), `away`, `present` or `unknown`.\n * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members\n */\nexport async function fetchPresence(axios, userId, status) {\n\ttry {\n\t\tconst res = await axios.get(generateOcsUrl('apps/dav/api/v1/outOfOffice/{userId}/now', { userId }), { params: { format: 'json' }, headers: OCS_HEADERS })\n\t\tconst ooo = ocsData(res)\n\t\tif (ooo && ooo.endDate) {\n\t\t\tconst end = typeof ooo.endDate === 'number' ? new Date(ooo.endDate * 1000) : new Date(ooo.endDate)\n\t\t\treturn { state: 'absent', until: Number.isNaN(end.getTime()) ? null : end.toISOString().slice(0, 10), message: ooo.shortMessage || '' }\n\t\t}\n\t} catch {\n\t\t// 404: no absence now, or an instance without the absence API.\n\t}\n\tif (status && typeof status === 'object') {\n\t\tconst message = typeof status.message === 'string' ? status.message : ''\n\t\tif (status.status === 'online') {\n\t\t\treturn { state: 'present', until: null, message }\n\t\t}\n\t\tif (status.status === 'away' || status.status === 'dnd') {\n\t\t\treturn { state: 'away', until: null, message }\n\t\t}\n\t}\n\treturn { state: 'unknown', until: null, message: '' }\n}\n\n/**\n * The user statuses Nextcloud shares with the reader, by user id. Empty when\n * the user status app is off.\n *\n * @param {object} axios The axios instance.\n * @return {Promise<Record<string, object>>}\n * @spec openspec/changes/index-groups-users-source/specs/index-page/spec.md#requirement-an-index-page-can-list-nextcloud-groups-and-their-members\n */\nexport async function fetchStatuses(axios) {\n\ttry {\n\t\tconst res = await axios.get(generateOcsUrl('apps/user_status/api/v1/statuses'), { params: { limit: 500, format: 'json' }, headers: OCS_HEADERS })\n\t\tconst list = ocsData(res) || []\n\t\treturn Object.fromEntries((Array.isArray(list) ? list : []).map((entry) => [entry.userId, entry]))\n\t} catch {\n\t\treturn {}\n\t}\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ */
5
+
6
+ /**
7
+ * Read an order a manifest writes into the `[{ key, order }]` list a self-fetch
8
+ * index page sorts by. Accepted forms:
9
+ *
10
+ * - `"title"` ascending, `"-createdAt"` descending, `"priority:desc"`;
11
+ * - several of those, comma separated, in priority order (`"-priority,title"`);
12
+ * - an OpenRegister `_order` map, `{ "priority": "desc", "title": "asc" }`;
13
+ * - a `[{ key, order? }]` list.
14
+ *
15
+ * @param {string|object|Array|null|undefined} value The order.
16
+ * @return {Array<{key: string, order: 'asc'|'desc'}>} The sort keys, possibly empty.
17
+ */
18
+ function orderToSortKeys(value) {
19
+ const direction = (order) => (String(order || '').toLowerCase() === 'desc' ? 'desc' : 'asc');
20
+ if (Array.isArray(value)) {
21
+ return value
22
+ .filter((entry) => entry && typeof entry.key === 'string' && entry.key !== '')
23
+ .map((entry) => ({ key: entry.key, order: direction(entry.order) }))
24
+ }
25
+ if (value && typeof value === 'object') {
26
+ return Object.entries(value)
27
+ .filter(([key]) => key !== '')
28
+ .map(([key, order]) => ({ key, order: direction(order) }))
29
+ }
30
+ if (typeof value !== 'string') {
31
+ return []
32
+ }
33
+ return value.split(',')
34
+ .map((part) => part.trim())
35
+ .filter(Boolean)
36
+ .map((part) => {
37
+ if (part.startsWith('-')) {
38
+ return { key: part.slice(1), order: 'desc' }
39
+ }
40
+ const [key, order] = part.split(':');
41
+ return { key, order: direction(order) }
42
+ })
43
+ .filter((entry) => entry.key !== '')
44
+ }
45
+
46
+ /**
47
+ * Whether two sort key lists order the same way.
48
+ *
49
+ * @param {Array<{key: string, order: string}>} a One list.
50
+ * @param {Array<{key: string, order: string}>} b The other.
51
+ * @return {boolean} True when they name the same keys, directions and priority.
52
+ */
53
+ function sameSortKeys(a, b) {
54
+ const left = Array.isArray(a) ? a : [];
55
+ const right = Array.isArray(b) ? b : [];
56
+ return left.length === right.length
57
+ && left.every((entry, index) => entry.key === right[index].key && (entry.order || 'asc') === (right[index].order || 'asc'))
58
+ }
59
+
60
+ export { orderToSortKeys, sameSortKeys };
61
+ //# sourceMappingURL=sortOrder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sortOrder.js","sources":["../../../src/utils/sortOrder.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\n/**\n * Read an order a manifest writes into the `[{ key, order }]` list a self-fetch\n * index page sorts by. Accepted forms:\n *\n * - `\"title\"` ascending, `\"-createdAt\"` descending, `\"priority:desc\"`;\n * - several of those, comma separated, in priority order (`\"-priority,title\"`);\n * - an OpenRegister `_order` map, `{ \"priority\": \"desc\", \"title\": \"asc\" }`;\n * - a `[{ key, order? }]` list.\n *\n * @param {string|object|Array|null|undefined} value The order.\n * @return {Array<{key: string, order: 'asc'|'desc'}>} The sort keys, possibly empty.\n */\nexport function orderToSortKeys(value) {\n\tconst direction = (order) => (String(order || '').toLowerCase() === 'desc' ? 'desc' : 'asc')\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.filter((entry) => entry && typeof entry.key === 'string' && entry.key !== '')\n\t\t\t.map((entry) => ({ key: entry.key, order: direction(entry.order) }))\n\t}\n\tif (value && typeof value === 'object') {\n\t\treturn Object.entries(value)\n\t\t\t.filter(([key]) => key !== '')\n\t\t\t.map(([key, order]) => ({ key, order: direction(order) }))\n\t}\n\tif (typeof value !== 'string') {\n\t\treturn []\n\t}\n\treturn value.split(',')\n\t\t.map((part) => part.trim())\n\t\t.filter(Boolean)\n\t\t.map((part) => {\n\t\t\tif (part.startsWith('-')) {\n\t\t\t\treturn { key: part.slice(1), order: 'desc' }\n\t\t\t}\n\t\t\tconst [key, order] = part.split(':')\n\t\t\treturn { key, order: direction(order) }\n\t\t})\n\t\t.filter((entry) => entry.key !== '')\n}\n\n/**\n * Whether two sort key lists order the same way.\n *\n * @param {Array<{key: string, order: string}>} a One list.\n * @param {Array<{key: string, order: string}>} b The other.\n * @return {boolean} True when they name the same keys, directions and priority.\n */\nexport function sameSortKeys(a, b) {\n\tconst left = Array.isArray(a) ? a : []\n\tconst right = Array.isArray(b) ? b : []\n\treturn left.length === right.length\n\t\t&& left.every((entry, index) => entry.key === right[index].key && (entry.order || 'asc') === (right[index].order || 'asc'))\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,MAAM,SAAS,GAAG,CAAC,KAAK,MAAM,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,KAAK,EAAC;AAC7F,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AAC3B,EAAE,OAAO,KAAK;AACd,IAAI,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC;AACjF,IAAI,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACvE,EAAE;AACF,CAAC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AACzC,EAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;AAC9B,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC;AACjC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAC7D,EAAE;AACF,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAChC,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;AACxB,GAAG,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;AAC7B,GAAG,MAAM,CAAC,OAAO,CAAC;AAClB,GAAG,GAAG,CAAC,CAAC,IAAI,KAAK;AACjB,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AAC7B,IAAI,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE;AAChD,IAAI;AACJ,GAAG,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAC;AACvC,GAAG,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE;AAC1C,GAAG,CAAC;AACJ,GAAG,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC;AACtC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE;AACnC,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAE;AACvC,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAE;AACxC,CAAC,OAAO,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;AACpC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC;AAC7H;;;;"}