@conduction/nextcloud-vue 2.76.0 → 2.77.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 (119) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +18 -3
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +49 -3
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +42 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +228 -203
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  9. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js +9 -0
  10. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js.map +1 -0
  11. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js +50 -0
  12. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js.map +1 -0
  13. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js +13 -0
  14. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js.map +1 -0
  15. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +41 -4
  16. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +90 -51
  18. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +16 -28
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +12 -24
  22. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  23. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js +7 -2
  24. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js.map +1 -1
  25. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue3.js.map +1 -1
  26. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +114 -1
  27. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  28. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +115 -62
  29. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  30. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +89 -7
  31. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
  32. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js +62 -30
  33. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +98 -13
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +13 -9
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  38. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +199 -7
  39. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +155 -119
  41. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  42. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +6 -0
  43. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  44. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -1
  45. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  46. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
  47. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  48. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  49. package/dist/esm/composables/useListView.js +4 -0
  50. package/dist/esm/composables/useListView.js.map +1 -1
  51. package/dist/esm/index.js +2 -1
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/l10n/en.json.js +25 -1
  54. package/dist/esm/l10n/en.json.js.map +1 -1
  55. package/dist/esm/l10n/nl.json.js +25 -1
  56. package/dist/esm/l10n/nl.json.js.map +1 -1
  57. package/dist/esm/nextcloud-vue.css +288 -74
  58. package/dist/esm/store/plugins/interactions.js +20 -10
  59. package/dist/esm/store/plugins/interactions.js.map +1 -1
  60. package/dist/esm/store/useObjectStore.js +17 -0
  61. package/dist/esm/store/useObjectStore.js.map +1 -1
  62. package/dist/esm/utils/builtInFormatters.js +15 -7
  63. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  64. package/dist/esm/utils/groupAutocomplete.js +63 -1
  65. package/dist/esm/utils/groupAutocomplete.js.map +1 -1
  66. package/dist/esm/utils/lensAvailability.js +90 -0
  67. package/dist/esm/utils/lensAvailability.js.map +1 -0
  68. package/dist/esm/utils/personalLenses.js +13 -6
  69. package/dist/esm/utils/personalLenses.js.map +1 -1
  70. package/dist/esm/utils/recordInteractions.js +31 -13
  71. package/dist/esm/utils/recordInteractions.js.map +1 -1
  72. package/dist/esm/utils/resolveFilterTokens.js +46 -3
  73. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  74. package/dist/esm/utils/schema.js +1 -1
  75. package/dist/esm/utils/schema.js.map +1 -1
  76. package/dist/esm/utils/sentinelTokens.js +4 -4
  77. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  78. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  79. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  80. package/dist/esm/utils/widgetVisibility.js +46 -1
  81. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  82. package/dist/nextcloud-vue.cjs.js +3873 -2674
  83. package/dist/nextcloud-vue.cjs.js.map +2 -2
  84. package/dist/nextcloud-vue.css +288 -74
  85. package/l10n/en.json +25 -1
  86. package/l10n/nl.json +25 -1
  87. package/package.json +1 -1
  88. package/src/components/CnAppNav/CnAppNav.vue +44 -5
  89. package/src/components/CnCellRenderer/CnCellRenderer.vue +56 -1
  90. package/src/components/CnCellRenderer/CnGroupNameCell.vue +61 -0
  91. package/src/components/CnDataTable/CnDataTable.md +9 -0
  92. package/src/components/CnDataTable/CnDataTable.vue +80 -20
  93. package/src/components/CnDetailPage/CnDetailPage.vue +10 -27
  94. package/src/components/CnFavouriteToggle/CnFavouriteToggle.vue +7 -2
  95. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +204 -5
  96. package/src/components/CnFollowToggle/CnFollowToggle.vue +118 -13
  97. package/src/components/CnIndexPage/CnIndexPage.md +36 -0
  98. package/src/components/CnIndexPage/CnIndexPage.vue +106 -19
  99. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +226 -2
  100. package/src/components/CnObjectSidebar/CnFilesTab.vue +9 -0
  101. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
  102. package/src/composables/useListView.js +4 -0
  103. package/src/css/look-board-detail.css +40 -2
  104. package/src/css/table.css +82 -17
  105. package/src/index.js +2 -1
  106. package/src/schemas/app-manifest-v2.schema.json +11 -7
  107. package/src/store/plugins/interactions.js +20 -10
  108. package/src/store/useObjectStore.js +17 -0
  109. package/src/utils/builtInFormatters.js +15 -7
  110. package/src/utils/groupAutocomplete.js +75 -0
  111. package/src/utils/index.js +1 -1
  112. package/src/utils/lensAvailability.js +86 -0
  113. package/src/utils/personalLenses.js +13 -6
  114. package/src/utils/recordInteractions.js +31 -13
  115. package/src/utils/resolveFilterTokens.js +45 -2
  116. package/src/utils/schema.js +1 -1
  117. package/src/utils/sentinelTokens.js +4 -4
  118. package/src/utils/validateManifestV2.compiled.js +2 -2
  119. package/src/utils/widgetVisibility.js +45 -0
@@ -2198,9 +2198,64 @@
2198
2198
  background-color: var(--color-background-hover);
2199
2199
  }
2200
2200
 
2201
- .cn-table-sort-indicator {
2202
- margin-inline-start: var(--default-grid-baseline);
2203
- opacity: 0.6;
2201
+ /* The sort control: a real button that looks like the header text, the label
2202
+ followed by a 14px chevron 4px after it (the "Wettelijke termijn" header on
2203
+ the DqWooVerzoeken board). Selector scoped under the table so it outranks
2204
+ Nextcloud's own `button` rule, which would otherwise give it a border, a
2205
+ fill, a 34px min-height and a margin. */
2206
+ .cn-data-table th .cn-table-header__sort {
2207
+ display: inline-flex;
2208
+ align-items: center;
2209
+ gap: 4px;
2210
+ max-width: 100%;
2211
+ min-width: 0;
2212
+ min-height: 0;
2213
+ margin: 0;
2214
+ padding: 0;
2215
+ border: 0;
2216
+ border-radius: var(--border-radius, 4px);
2217
+ background: transparent;
2218
+ color: inherit;
2219
+ font: inherit;
2220
+ font-weight: inherit;
2221
+ text-align: start;
2222
+ vertical-align: middle;
2223
+ cursor: pointer;
2224
+ }
2225
+
2226
+ .cn-data-table th .cn-table-header__sort:hover,
2227
+ .cn-data-table th .cn-table-header__sort:focus {
2228
+ background: transparent;
2229
+ }
2230
+
2231
+ .cn-data-table th .cn-table-header__sort:focus-visible {
2232
+ outline: 2px solid var(--color-main-text);
2233
+ outline-offset: 2px;
2234
+ }
2235
+
2236
+ .cn-data-table th .cn-table-header__sort > :first-child {
2237
+ min-width: 0;
2238
+ overflow: hidden;
2239
+ text-overflow: ellipsis;
2240
+ }
2241
+
2242
+ /* The chevron. Unsorted columns show a pale down chevron, so every sortable
2243
+ column says it sorts; the sorted column shows the direction at full text
2244
+ colour. The direction is also in the header's `aria-sort`. */
2245
+ .cn-table-header__chevron {
2246
+ display: inline-flex;
2247
+ flex: none;
2248
+ color: var(--color-main-text);
2249
+ }
2250
+
2251
+ .cn-table-header__chevron--idle {
2252
+ color: var(--color-text-maxcontrast);
2253
+ opacity: 0.5;
2254
+ }
2255
+
2256
+ .cn-data-table th.cn-table-header--sortable:hover .cn-table-header__chevron--idle,
2257
+ .cn-data-table th .cn-table-header__sort:focus-visible .cn-table-header__chevron--idle {
2258
+ opacity: 1;
2204
2259
  }
2205
2260
 
2206
2261
  /* A column carrying a `description` gets a dotted underline — the long-standing
@@ -2216,15 +2271,12 @@
2216
2271
  cursor: help;
2217
2272
  }
2218
2273
 
2219
- .cn-data-table th.cn-table-header--sortable:focus-visible {
2220
- outline: 2px solid var(--color-main-text);
2221
- outline-offset: -2px;
2222
- }
2223
-
2224
- /* Header filter button (CnDataTable `filterable`). 24px hit area with the
2225
- 16px icon; the filled icon plus the primary colour marks an active filter,
2226
- so the state never rests on colour alone. */
2227
- .cn-table-header__filter {
2274
+ /* Header filter button (CnDataTable `filterable`): a 14px funnel directly
2275
+ after the sort chevron, in a 24px hit area. Muted with no filter set; with
2276
+ a filter set it takes the dark primary and a dot, so the state never rests
2277
+ on colour alone. */
2278
+ .cn-data-table th .cn-table-header__filter {
2279
+ position: relative;
2228
2280
  display: inline-flex;
2229
2281
  align-items: center;
2230
2282
  justify-content: center;
@@ -2232,7 +2284,7 @@
2232
2284
  min-width: 24px;
2233
2285
  height: 24px;
2234
2286
  min-height: 24px;
2235
- margin: 0 0 0 4px;
2287
+ margin: 0 0 0 2px;
2236
2288
  padding: 0;
2237
2289
  border: none;
2238
2290
  border-radius: var(--border-radius, 4px);
@@ -2242,18 +2294,31 @@
2242
2294
  cursor: pointer;
2243
2295
  }
2244
2296
 
2245
- .cn-table-header__filter:hover {
2297
+ .cn-data-table th .cn-table-header__filter:hover {
2246
2298
  background-color: var(--color-background-hover);
2247
2299
  color: var(--color-main-text);
2248
2300
  }
2249
2301
 
2250
- .cn-table-header__filter:focus-visible {
2302
+ .cn-data-table th .cn-table-header__filter:focus-visible {
2251
2303
  outline: 2px solid var(--color-main-text);
2252
2304
  outline-offset: 0;
2253
2305
  }
2254
2306
 
2255
- .cn-table-header__filter--active {
2256
- color: var(--color-primary-element);
2307
+ .cn-data-table th .cn-table-header__filter--active,
2308
+ .cn-data-table th .cn-table-header__filter--active:hover {
2309
+ color: var(--color-primary-element-light-text, var(--color-primary-element));
2310
+ }
2311
+
2312
+ /* The active-filter dot, on the funnel's top right. */
2313
+ .cn-table-header__filter-dot {
2314
+ position: absolute;
2315
+ inset-block-start: 3px;
2316
+ inset-inline-end: 3px;
2317
+ width: 6px;
2318
+ height: 6px;
2319
+ border-radius: 50%;
2320
+ background-color: var(--color-primary-element);
2321
+ pointer-events: none;
2257
2322
  }
2258
2323
 
2259
2324
  /* Active header filters above the table, one removable chip each. */
@@ -5503,12 +5568,26 @@ a.cn-kpi-card--stacked:focus-visible {
5503
5568
 
5504
5569
  /* ---- Folder tabs ------------------------------------------------------- */
5505
5570
 
5571
+ /* The strip draws no line of its own (design-system pull request 147). The card under it
5572
+ carries its full border, top included, so the top-right corner is the
5573
+ card's own rounded corner instead of a strip rule running straight past it.
5574
+ The open tab overlaps that top border by 1px (the nav's -1px margin) and
5575
+ paints over it, so it flows into the card as a folder tab. */
5506
5576
  .cn-look-board .cn-tabs--board .cn-tabs__bar {
5507
- border-bottom: 1px solid var(--color-border-dark);
5577
+ border-bottom: none;
5508
5578
  gap: 4px;
5579
+ position: relative;
5509
5580
  }
5510
5581
 
5582
+ .cn-look-board .cn-tabs.cn-tabs--board .cn-tabs__bar::after {
5583
+ content: none;
5584
+ }
5585
+
5586
+ /* Tabs sit on the card's top edge; only the open tab reaches 1px further
5587
+ down (min-height 35px below), over the card's top border. Top-aligned so
5588
+ the inactive tabs do not stretch to the open tab's height. */
5511
5589
  .cn-look-board .cn-tabs--board .cn-tabs__nav {
5590
+ align-items: flex-start;
5512
5591
  gap: 4px;
5513
5592
  }
5514
5593
 
@@ -5529,7 +5608,11 @@ a.cn-kpi-card--stacked:focus-visible {
5529
5608
  border-bottom: 1px solid var(--color-main-background);
5530
5609
  border-top: 1px solid var(--color-border-dark);
5531
5610
  font-weight: 700;
5611
+ min-height: 35px;
5612
+ padding-bottom: 9px;
5532
5613
  padding-top: 8px;
5614
+ position: relative;
5615
+ z-index: 1;
5533
5616
  }
5534
5617
 
5535
5618
  /* The count: a grey, borderless 20px badge 8px after the label. */
@@ -5566,10 +5649,30 @@ a.cn-kpi-card--stacked:focus-visible {
5566
5649
  .cn-look-board .cn-tabs--board .cn-tabs__content > .cn-tab > :first-child .cn-widget-wrapper,
5567
5650
  .cn-look-board .cn-tabs--board .cn-tabs__content > .cn-tab > .cn-widget-wrapper,
5568
5651
  .cn-look-board .cn-tabs--board .cn-tabs__content > .cn-tab > .cn-detail-card {
5569
- border-top: none;
5652
+ border-top: 1px solid var(--color-border);
5653
+ border-radius: 0 12px 12px 12px;
5654
+ }
5655
+
5656
+ /* A CnTabsWidget panel is the card itself: the same full border, so it joins
5657
+ the open tab the same way. The top-left stays square under the first tab. */
5658
+ .cn-look-board .cn-tabs--board.cn-tabs-widget__tabs .cn-tabs__content {
5659
+ border: 1px solid var(--color-border);
5570
5660
  border-radius: 0 12px 12px 12px;
5571
5661
  }
5572
5662
 
5663
+ /* A panel of CnTabsWidget is the content block itself: the open tab flows
5664
+ straight into it. The widget in the panel renders chromeless, but the board
5665
+ card rule below (and the first-card rule above) used to give its wrapper a
5666
+ border and a 12px radius again, so a second card sat inside the panel. Its
5667
+ left edge ran down from the open tab and its rounded top-right drew a line
5668
+ across the top right of the content block, where DqZaak shows one sheet.
5669
+ Same specificity as the first-card rule and later, so it wins on order. */
5670
+ .cn-look-board .cn-tabs--board.cn-tabs-widget__tabs .cn-tabs__content .cn-widget-wrapper.cn-widget-wrapper--chromeless {
5671
+ background: transparent;
5672
+ border: 0;
5673
+ border-radius: 0;
5674
+ }
5675
+
5573
5676
  /* ---- Body cards and side cards ----------------------------------------- */
5574
5677
 
5575
5678
  .cn-look-board .cn-detail-page .cn-widget-wrapper,
@@ -9356,9 +9459,64 @@ svg.leaflet-image-layer.leaflet-interactive path {
9356
9459
  background-color: var(--color-background-hover);
9357
9460
  }
9358
9461
 
9359
- .cn-table-sort-indicator {
9360
- margin-inline-start: var(--default-grid-baseline);
9361
- opacity: 0.6;
9462
+ /* The sort control: a real button that looks like the header text, the label
9463
+ followed by a 14px chevron 4px after it (the "Wettelijke termijn" header on
9464
+ the DqWooVerzoeken board). Selector scoped under the table so it outranks
9465
+ Nextcloud's own `button` rule, which would otherwise give it a border, a
9466
+ fill, a 34px min-height and a margin. */
9467
+ .cn-data-table th .cn-table-header__sort {
9468
+ display: inline-flex;
9469
+ align-items: center;
9470
+ gap: 4px;
9471
+ max-width: 100%;
9472
+ min-width: 0;
9473
+ min-height: 0;
9474
+ margin: 0;
9475
+ padding: 0;
9476
+ border: 0;
9477
+ border-radius: var(--border-radius, 4px);
9478
+ background: transparent;
9479
+ color: inherit;
9480
+ font: inherit;
9481
+ font-weight: inherit;
9482
+ text-align: start;
9483
+ vertical-align: middle;
9484
+ cursor: pointer;
9485
+ }
9486
+
9487
+ .cn-data-table th .cn-table-header__sort:hover,
9488
+ .cn-data-table th .cn-table-header__sort:focus {
9489
+ background: transparent;
9490
+ }
9491
+
9492
+ .cn-data-table th .cn-table-header__sort:focus-visible {
9493
+ outline: 2px solid var(--color-main-text);
9494
+ outline-offset: 2px;
9495
+ }
9496
+
9497
+ .cn-data-table th .cn-table-header__sort > :first-child {
9498
+ min-width: 0;
9499
+ overflow: hidden;
9500
+ text-overflow: ellipsis;
9501
+ }
9502
+
9503
+ /* The chevron. Unsorted columns show a pale down chevron, so every sortable
9504
+ column says it sorts; the sorted column shows the direction at full text
9505
+ colour. The direction is also in the header's `aria-sort`. */
9506
+ .cn-table-header__chevron {
9507
+ display: inline-flex;
9508
+ flex: none;
9509
+ color: var(--color-main-text);
9510
+ }
9511
+
9512
+ .cn-table-header__chevron--idle {
9513
+ color: var(--color-text-maxcontrast);
9514
+ opacity: 0.5;
9515
+ }
9516
+
9517
+ .cn-data-table th.cn-table-header--sortable:hover .cn-table-header__chevron--idle,
9518
+ .cn-data-table th .cn-table-header__sort:focus-visible .cn-table-header__chevron--idle {
9519
+ opacity: 1;
9362
9520
  }
9363
9521
 
9364
9522
  /* A column carrying a `description` gets a dotted underline — the long-standing
@@ -9374,15 +9532,12 @@ svg.leaflet-image-layer.leaflet-interactive path {
9374
9532
  cursor: help;
9375
9533
  }
9376
9534
 
9377
- .cn-data-table th.cn-table-header--sortable:focus-visible {
9378
- outline: 2px solid var(--color-main-text);
9379
- outline-offset: -2px;
9380
- }
9381
-
9382
- /* Header filter button (CnDataTable `filterable`). 24px hit area with the
9383
- 16px icon; the filled icon plus the primary colour marks an active filter,
9384
- so the state never rests on colour alone. */
9385
- .cn-table-header__filter {
9535
+ /* Header filter button (CnDataTable `filterable`): a 14px funnel directly
9536
+ after the sort chevron, in a 24px hit area. Muted with no filter set; with
9537
+ a filter set it takes the dark primary and a dot, so the state never rests
9538
+ on colour alone. */
9539
+ .cn-data-table th .cn-table-header__filter {
9540
+ position: relative;
9386
9541
  display: inline-flex;
9387
9542
  align-items: center;
9388
9543
  justify-content: center;
@@ -9390,7 +9545,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
9390
9545
  min-width: 24px;
9391
9546
  height: 24px;
9392
9547
  min-height: 24px;
9393
- margin: 0 0 0 4px;
9548
+ margin: 0 0 0 2px;
9394
9549
  padding: 0;
9395
9550
  border: none;
9396
9551
  border-radius: var(--border-radius, 4px);
@@ -9400,18 +9555,31 @@ svg.leaflet-image-layer.leaflet-interactive path {
9400
9555
  cursor: pointer;
9401
9556
  }
9402
9557
 
9403
- .cn-table-header__filter:hover {
9558
+ .cn-data-table th .cn-table-header__filter:hover {
9404
9559
  background-color: var(--color-background-hover);
9405
9560
  color: var(--color-main-text);
9406
9561
  }
9407
9562
 
9408
- .cn-table-header__filter:focus-visible {
9563
+ .cn-data-table th .cn-table-header__filter:focus-visible {
9409
9564
  outline: 2px solid var(--color-main-text);
9410
9565
  outline-offset: 0;
9411
9566
  }
9412
9567
 
9413
- .cn-table-header__filter--active {
9414
- color: var(--color-primary-element);
9568
+ .cn-data-table th .cn-table-header__filter--active,
9569
+ .cn-data-table th .cn-table-header__filter--active:hover {
9570
+ color: var(--color-primary-element-light-text, var(--color-primary-element));
9571
+ }
9572
+
9573
+ /* The active-filter dot, on the funnel's top right. */
9574
+ .cn-table-header__filter-dot {
9575
+ position: absolute;
9576
+ inset-block-start: 3px;
9577
+ inset-inline-end: 3px;
9578
+ width: 6px;
9579
+ height: 6px;
9580
+ border-radius: 50%;
9581
+ background-color: var(--color-primary-element);
9582
+ pointer-events: none;
9415
9583
  }
9416
9584
 
9417
9585
  /* Active header filters above the table, one removable chip each. */
@@ -14188,12 +14356,55 @@ button.cn-files-card__open[data-v-46f7e3c9]:hover {
14188
14356
  flex-direction: column;
14189
14357
  gap: 8px;
14190
14358
  min-width: 0;
14359
+ position: relative;
14191
14360
  border-radius: var(--border-radius-large, 8px);
14192
14361
  }
14193
14362
  .cn-files-browser--dragging[data-v-f5bdd9e2] {
14194
14363
  outline: 2px dashed var(--color-primary-element);
14195
14364
  outline-offset: 4px;
14196
14365
  }
14366
+ .cn-files-browser__drop-overlay[data-v-f5bdd9e2] {
14367
+ position: absolute;
14368
+ inset: 0;
14369
+ z-index: 2;
14370
+ display: flex;
14371
+ flex-direction: column;
14372
+ align-items: center;
14373
+ justify-content: center;
14374
+ gap: 10px;
14375
+ padding: 24px;
14376
+ box-sizing: border-box;
14377
+ border: 2px dashed var(--color-primary-element);
14378
+ border-radius: var(--border-radius-large, 8px);
14379
+ background: var(--color-primary-element-light);
14380
+ color: var(--color-primary-element-light-text, var(--color-main-text));
14381
+ text-align: center;
14382
+ pointer-events: none;
14383
+ }
14384
+ .cn-files-browser__drop-icon[data-v-f5bdd9e2] {
14385
+ display: flex;
14386
+ align-items: center;
14387
+ justify-content: center;
14388
+ width: 56px;
14389
+ height: 56px;
14390
+ border-radius: 50%;
14391
+ background: var(--color-main-background);
14392
+ color: var(--color-primary-element);
14393
+ }
14394
+ .cn-files-browser__drop-label[data-v-f5bdd9e2] {
14395
+ font-size: 20px;
14396
+ font-weight: 700;
14397
+ }
14398
+ .cn-files-browser__drop-hint[data-v-f5bdd9e2] {
14399
+ display: flex;
14400
+ align-items: center;
14401
+ gap: 8px;
14402
+ margin: 0;
14403
+ padding-top: 12px;
14404
+ border-top: 1px solid var(--color-border);
14405
+ font-size: 13px;
14406
+ color: var(--color-text-maxcontrast);
14407
+ }
14197
14408
  .cn-files-browser__bar[data-v-f5bdd9e2] {
14198
14409
  display: flex;
14199
14410
  align-items: center;
@@ -32093,8 +32304,42 @@ button.cn-object-calendar__overflow[data-v-bea31862] {
32093
32304
  }
32094
32305
 
32095
32306
 
32096
- .cn-favourite-toggle__star[data-v-836dad16] {
32097
- color: var(--color-warning);
32307
+ .cn-follow-toggle[data-v-eec896e2] {
32308
+ display: inline-flex;
32309
+ align-items: center;
32310
+ gap: 4px;
32311
+ }
32312
+ .cn-follow-toggle__on[data-v-eec896e2] {
32313
+ color: var(--color-primary-element);
32314
+ }
32315
+ .cn-follow-toggle__popover[data-v-eec896e2] {
32316
+ min-width: 260px;
32317
+ padding: 12px;
32318
+ }
32319
+ .cn-follow-toggle__list[data-v-eec896e2] {
32320
+ list-style: none;
32321
+ margin: 0 0 8px;
32322
+ padding: 0;
32323
+ }
32324
+ .cn-follow-toggle__row[data-v-eec896e2] {
32325
+ display: flex;
32326
+ align-items: center;
32327
+ gap: 8px;
32328
+ padding: 4px 0;
32329
+ }
32330
+ .cn-follow-toggle__who[data-v-eec896e2] {
32331
+ display: flex;
32332
+ flex-direction: column;
32333
+ flex: 1 1 auto;
32334
+ min-width: 0;
32335
+ }
32336
+ .cn-follow-toggle__since[data-v-eec896e2] {
32337
+ color: var(--color-text-maxcontrast);
32338
+ font-size: var(--font-size-small, 13px);
32339
+ }
32340
+ .cn-follow-toggle__error[data-v-eec896e2] {
32341
+ margin: 8px 0 0;
32342
+ color: var(--color-error-text);
32098
32343
  }
32099
32344
 
32100
32345
 
@@ -34239,42 +34484,6 @@ button.cn-object-calendar__overflow[data-v-bea31862] {
34239
34484
  }
34240
34485
 
34241
34486
 
34242
- .cn-follow-toggle[data-v-eec896e2] {
34243
- display: inline-flex;
34244
- align-items: center;
34245
- gap: 4px;
34246
- }
34247
- .cn-follow-toggle__popover[data-v-eec896e2] {
34248
- min-width: 260px;
34249
- padding: 12px;
34250
- }
34251
- .cn-follow-toggle__list[data-v-eec896e2] {
34252
- list-style: none;
34253
- margin: 0 0 8px;
34254
- padding: 0;
34255
- }
34256
- .cn-follow-toggle__row[data-v-eec896e2] {
34257
- display: flex;
34258
- align-items: center;
34259
- gap: 8px;
34260
- padding: 4px 0;
34261
- }
34262
- .cn-follow-toggle__who[data-v-eec896e2] {
34263
- display: flex;
34264
- flex-direction: column;
34265
- flex: 1 1 auto;
34266
- min-width: 0;
34267
- }
34268
- .cn-follow-toggle__since[data-v-eec896e2] {
34269
- color: var(--color-text-maxcontrast);
34270
- font-size: var(--font-size-small, 13px);
34271
- }
34272
- .cn-follow-toggle__error[data-v-eec896e2] {
34273
- margin: 8px 0 0;
34274
- color: var(--color-error-text);
34275
- }
34276
-
34277
-
34278
34487
  .cn-lifecycle-actions[data-v-a62ad362] {
34279
34488
  display: flex;
34280
34489
  flex-wrap: wrap;
@@ -35948,6 +36157,11 @@ button.cn-object-calendar__overflow[data-v-bea31862] {
35948
36157
  }
35949
36158
 
35950
36159
 
36160
+ .cn-favourite-toggle__star[data-v-836dad16] {
36161
+ color: var(--color-warning);
36162
+ }
36163
+
36164
+
35951
36165
  .cn-journey-review-list__items[data-v-d3c04832] {
35952
36166
  list-style: none;
35953
36167
  margin: 8px 0 0;
@@ -3,7 +3,8 @@ import { setFavourite, setReadState, setWatching, listWatchers, setWatcher } fro
3
3
  /**
4
4
  * Interactions plugin for the object store.
5
5
  *
6
- * Per-user interaction calls on a record: favourite, follow (watch) and the
6
+ * Per-user interaction calls on a record: follow (watch, with its
7
+ * notifications switch), the deprecated favourite (a quiet follow), and the
7
8
  * followers list. Each write puts the server's answer into the stored object's
8
9
  * `@self`, so a list showing the same object agrees with the toggle.
9
10
  *
@@ -55,7 +56,10 @@ function interactionsPlugin() {
55
56
  },
56
57
 
57
58
  /**
58
- * Star a record.
59
+ * Star a record: a quiet follow on the server.
60
+ *
61
+ * @deprecated A favourite is a follow with notifications off since
62
+ * OpenRegister's `merge-follow-and-favourites`; use `watch(type, id, { notify: false })`.
59
63
  *
60
64
  * @param {string} type The registered object type slug
61
65
  * @param {string} objectId The object ID
@@ -65,13 +69,15 @@ function interactionsPlugin() {
65
69
  const { register, schema } = this._interactionTarget(type);
66
70
  const result = await setFavourite(register, schema, objectId, true);
67
71
  if (result.ok) {
68
- this._patchSelf(type, objectId, { favourite: true });
72
+ this._patchSelf(type, objectId, { favourite: true, watching: true });
69
73
  }
70
74
  return result
71
75
  },
72
76
 
73
77
  /**
74
- * Unstar a record.
78
+ * Unstar a record: unfollow it.
79
+ *
80
+ * @deprecated Use `unwatch`.
75
81
  *
76
82
  * @param {string} type The registered object type slug
77
83
  * @param {string} objectId The object ID
@@ -81,7 +87,7 @@ function interactionsPlugin() {
81
87
  const { register, schema } = this._interactionTarget(type);
82
88
  const result = await setFavourite(register, schema, objectId, false);
83
89
  if (result.ok) {
84
- this._patchSelf(type, objectId, { favourite: false });
90
+ this._patchSelf(type, objectId, { favourite: false, watching: false, watchNotify: undefined });
85
91
  }
86
92
  return result
87
93
  },
@@ -119,17 +125,21 @@ function interactionsPlugin() {
119
125
  },
120
126
 
121
127
  /**
122
- * Follow a record.
128
+ * Follow a record, optionally setting its notifications switch.
123
129
  *
124
130
  * @param {string} type The registered object type slug
125
131
  * @param {string} objectId The object ID
132
+ * @param {object} [options] Options
133
+ * @param {boolean} [options.notify] The notifications switch; left out, a new follow notifies
126
134
  * @return {Promise<object>} The outcome
135
+ * @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-one-follow-control-with-a-notifications-switch
127
136
  */
128
- async watch(type, objectId) {
137
+ async watch(type, objectId, options = {}) {
129
138
  const { register, schema } = this._interactionTarget(type);
130
- const result = await setWatching(register, schema, objectId, true);
139
+ const result = await setWatching(register, schema, objectId, true, options);
131
140
  if (result.ok) {
132
- this._patchSelf(type, objectId, { watching: true });
141
+ const notify = result.data && typeof result.data.notify === 'boolean' ? result.data.notify : true;
142
+ this._patchSelf(type, objectId, { watching: true, favourite: true, watchNotify: notify });
133
143
  }
134
144
  return result
135
145
  },
@@ -145,7 +155,7 @@ function interactionsPlugin() {
145
155
  const { register, schema } = this._interactionTarget(type);
146
156
  const result = await setWatching(register, schema, objectId, false);
147
157
  if (result.ok) {
148
- this._patchSelf(type, objectId, { watching: false });
158
+ this._patchSelf(type, objectId, { watching: false, favourite: false, watchNotify: undefined });
149
159
  }
150
160
  return result
151
161
  },
@@ -1 +1 @@
1
- {"version":3,"file":"interactions.js","sources":["../../../../src/store/plugins/interactions.js"],"sourcesContent":["import { listWatchers, setFavourite, setReadState, setWatcher, setWatching } from '../../utils/recordInteractions.js'\n\n/**\n * Interactions plugin for the object store.\n *\n * Per-user interaction calls on a record: favourite, follow (watch) and the\n * followers list. Each write puts the server's answer into the stored object's\n * `@self`, so a list showing the same object agrees with the toggle.\n *\n * Actions: favourite, unfavourite, watch, unwatch, fetchWatchers, addWatcher, removeWatcher, markRead, markUnread.\n * Each returns the outcome `{ ok, status, data, message }` and never throws.\n *\n * @return {object} The plugin definition (name, state, getters, actions)\n *\n * @example\n * const useStore = createObjectStore('object', { plugins: [interactionsPlugin()] })\n * const store = useStore()\n * await store.watch('case', caseId)\n * const { data } = await store.fetchWatchers('case', caseId)\n */\nexport function interactionsPlugin() {\n\treturn {\n\t\tname: 'Interactions',\n\n\t\tstate: () => ({}),\n\n\t\tgetters: {},\n\n\t\tactions: {\n\t\t\t/**\n\t\t\t * Merge a patch into the stored object's `@self`.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @param {object} patch Keys to merge into `@self`\n\t\t\t */\n\t\t\t_patchSelf(type, objectId, patch) {\n\t\t\t\tconst stored = this.objects && this.objects[type] && this.objects[type][objectId]\n\t\t\t\tif (!stored) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = { ...stored, '@self': { ...(stored['@self'] || {}), ...patch } }\n\t\t\t\tthis.objects = { ...this.objects, [type]: { ...this.objects[type], [objectId]: next } }\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Resolve a type slug to its register and schema.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @return {{register: string, schema: string}} The path segments\n\t\t\t */\n\t\t\t_interactionTarget(type) {\n\t\t\t\tconst config = this._getTypeConfig(type)\n\t\t\t\treturn { register: config.register, schema: config.schema }\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Star a record.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync favourite(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setFavourite(register, schema, objectId, true)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { favourite: true })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Unstar a record.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync unfavourite(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setFavourite(register, schema, objectId, false)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { favourite: false })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Mark a record read (`PUT .../read-state`).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync markRead(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setReadState(register, schema, objectId, true)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { unread: false })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Mark a record unread (`DELETE .../read-state`).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync markUnread(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setReadState(register, schema, objectId, false)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { unread: true })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Follow a record.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync watch(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setWatching(register, schema, objectId, true)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { watching: true })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Unfollow a record.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync unwatch(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setWatching(register, schema, objectId, false)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { watching: false })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * List the followers of a record (needs `update` on it).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome; `data` is `{ results, total }`\n\t\t\t */\n\t\t\tasync fetchWatchers(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\treturn listWatchers(register, schema, objectId)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Add a colleague as a follower (needs `manage`).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @param {string} userId The colleague's user id\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync addWatcher(type, objectId, userId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\treturn setWatcher(register, schema, objectId, userId, true)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Remove a follower (needs `manage`, or be the user yourself).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @param {string} userId The follower's user id\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync removeWatcher(type, objectId, userId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\treturn setWatcher(register, schema, objectId, userId, false)\n\t\t\t},\n\t\t},\n\t}\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interactions.js","sources":["../../../../src/store/plugins/interactions.js"],"sourcesContent":["import { listWatchers, setFavourite, setReadState, setWatcher, setWatching } from '../../utils/recordInteractions.js'\n\n/**\n * Interactions plugin for the object store.\n *\n * Per-user interaction calls on a record: follow (watch, with its\n * notifications switch), the deprecated favourite (a quiet follow), and the\n * followers list. Each write puts the server's answer into the stored object's\n * `@self`, so a list showing the same object agrees with the toggle.\n *\n * Actions: favourite, unfavourite, watch, unwatch, fetchWatchers, addWatcher, removeWatcher, markRead, markUnread.\n * Each returns the outcome `{ ok, status, data, message }` and never throws.\n *\n * @return {object} The plugin definition (name, state, getters, actions)\n *\n * @example\n * const useStore = createObjectStore('object', { plugins: [interactionsPlugin()] })\n * const store = useStore()\n * await store.watch('case', caseId)\n * const { data } = await store.fetchWatchers('case', caseId)\n */\nexport function interactionsPlugin() {\n\treturn {\n\t\tname: 'Interactions',\n\n\t\tstate: () => ({}),\n\n\t\tgetters: {},\n\n\t\tactions: {\n\t\t\t/**\n\t\t\t * Merge a patch into the stored object's `@self`.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @param {object} patch Keys to merge into `@self`\n\t\t\t */\n\t\t\t_patchSelf(type, objectId, patch) {\n\t\t\t\tconst stored = this.objects && this.objects[type] && this.objects[type][objectId]\n\t\t\t\tif (!stored) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = { ...stored, '@self': { ...(stored['@self'] || {}), ...patch } }\n\t\t\t\tthis.objects = { ...this.objects, [type]: { ...this.objects[type], [objectId]: next } }\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Resolve a type slug to its register and schema.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @return {{register: string, schema: string}} The path segments\n\t\t\t */\n\t\t\t_interactionTarget(type) {\n\t\t\t\tconst config = this._getTypeConfig(type)\n\t\t\t\treturn { register: config.register, schema: config.schema }\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Star a record: a quiet follow on the server.\n\t\t\t *\n\t\t\t * @deprecated A favourite is a follow with notifications off since\n\t\t\t * OpenRegister's `merge-follow-and-favourites`; use `watch(type, id, { notify: false })`.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync favourite(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setFavourite(register, schema, objectId, true)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { favourite: true, watching: true })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Unstar a record: unfollow it.\n\t\t\t *\n\t\t\t * @deprecated Use `unwatch`.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync unfavourite(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setFavourite(register, schema, objectId, false)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { favourite: false, watching: false, watchNotify: undefined })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Mark a record read (`PUT .../read-state`).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync markRead(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setReadState(register, schema, objectId, true)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { unread: false })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Mark a record unread (`DELETE .../read-state`).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync markUnread(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setReadState(register, schema, objectId, false)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { unread: true })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Follow a record, optionally setting its notifications switch.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @param {object} [options] Options\n\t\t\t * @param {boolean} [options.notify] The notifications switch; left out, a new follow notifies\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t * @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-one-follow-control-with-a-notifications-switch\n\t\t\t */\n\t\t\tasync watch(type, objectId, options = {}) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setWatching(register, schema, objectId, true, options)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tconst notify = result.data && typeof result.data.notify === 'boolean' ? result.data.notify : true\n\t\t\t\t\tthis._patchSelf(type, objectId, { watching: true, favourite: true, watchNotify: notify })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Unfollow a record.\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync unwatch(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\tconst result = await setWatching(register, schema, objectId, false)\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis._patchSelf(type, objectId, { watching: false, favourite: false, watchNotify: undefined })\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * List the followers of a record (needs `update` on it).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @return {Promise<object>} The outcome; `data` is `{ results, total }`\n\t\t\t */\n\t\t\tasync fetchWatchers(type, objectId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\treturn listWatchers(register, schema, objectId)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Add a colleague as a follower (needs `manage`).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @param {string} userId The colleague's user id\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync addWatcher(type, objectId, userId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\treturn setWatcher(register, schema, objectId, userId, true)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * Remove a follower (needs `manage`, or be the user yourself).\n\t\t\t *\n\t\t\t * @param {string} type The registered object type slug\n\t\t\t * @param {string} objectId The object ID\n\t\t\t * @param {string} userId The follower's user id\n\t\t\t * @return {Promise<object>} The outcome\n\t\t\t */\n\t\t\tasync removeWatcher(type, objectId, userId) {\n\t\t\t\tconst { register, schema } = this._interactionTarget(type)\n\t\t\t\treturn setWatcher(register, schema, objectId, userId, false)\n\t\t\t},\n\t\t},\n\t}\n}\n"],"names":[],"mappings":"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"}