@conduction/nextcloud-vue 2.75.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 (158) 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/CnCardGrid/CnCardGrid.vue2.js +6 -0
  6. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -2
  8. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  9. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +42 -1
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +228 -203
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  13. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js +9 -0
  14. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js.map +1 -0
  15. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js +50 -0
  16. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js.map +1 -0
  17. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js +13 -0
  18. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js.map +1 -0
  19. package/dist/esm/components/CnCredentials/CnCredentials.vue2.js +11 -10
  20. package/dist/esm/components/CnCredentials/CnCredentials.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCredentials/CnCredentials.vue3.js.map +1 -1
  22. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +41 -4
  23. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  24. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +90 -51
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  26. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +24 -33
  27. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +12 -24
  29. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  30. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js +7 -2
  31. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js.map +1 -1
  32. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue3.js.map +1 -1
  33. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +114 -1
  34. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  35. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +115 -62
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  37. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +89 -7
  38. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js +62 -30
  40. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
  41. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +242 -19
  42. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +25 -20
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/selfModeActions.js +90 -14
  46. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  47. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +15 -3
  48. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  49. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +26 -0
  50. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  51. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +26 -9
  52. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  53. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +199 -7
  54. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  55. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +155 -119
  56. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +19 -3
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  60. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +6 -0
  61. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -1
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  64. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +153 -38
  65. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  66. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +74 -56
  67. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
  69. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  70. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  71. package/dist/esm/composables/useListView.js +4 -0
  72. package/dist/esm/composables/useListView.js.map +1 -1
  73. package/dist/esm/composables/useObjectLock.js +41 -10
  74. package/dist/esm/composables/useObjectLock.js.map +1 -1
  75. package/dist/esm/index.js +3 -1
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/esm/l10n/en.json.js +31 -1
  78. package/dist/esm/l10n/en.json.js.map +1 -1
  79. package/dist/esm/l10n/nl.json.js +32 -2
  80. package/dist/esm/l10n/nl.json.js.map +1 -1
  81. package/dist/esm/nextcloud-vue.css +376 -75
  82. package/dist/esm/store/plugins/interactions.js +20 -10
  83. package/dist/esm/store/plugins/interactions.js.map +1 -1
  84. package/dist/esm/store/useObjectStore.js +54 -9
  85. package/dist/esm/store/useObjectStore.js.map +1 -1
  86. package/dist/esm/utils/builtInFormatters.js +15 -7
  87. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  88. package/dist/esm/utils/groupAutocomplete.js +63 -1
  89. package/dist/esm/utils/groupAutocomplete.js.map +1 -1
  90. package/dist/esm/utils/lensAvailability.js +90 -0
  91. package/dist/esm/utils/lensAvailability.js.map +1 -0
  92. package/dist/esm/utils/objectSignals.js +44 -0
  93. package/dist/esm/utils/objectSignals.js.map +1 -0
  94. package/dist/esm/utils/personalLenses.js +13 -6
  95. package/dist/esm/utils/personalLenses.js.map +1 -1
  96. package/dist/esm/utils/recordInteractions.js +31 -13
  97. package/dist/esm/utils/recordInteractions.js.map +1 -1
  98. package/dist/esm/utils/resolveFilterTokens.js +46 -3
  99. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  100. package/dist/esm/utils/schema.js +1 -1
  101. package/dist/esm/utils/schema.js.map +1 -1
  102. package/dist/esm/utils/sentinelTokens.js +4 -4
  103. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  104. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  105. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  106. package/dist/esm/utils/widgetVisibility.js +46 -1
  107. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  108. package/dist/nextcloud-vue.cjs.js +4732 -2990
  109. package/dist/nextcloud-vue.cjs.js.map +2 -2
  110. package/dist/nextcloud-vue.css +376 -75
  111. package/l10n/en.json +31 -1
  112. package/l10n/nl.json +32 -2
  113. package/package.json +1 -1
  114. package/src/components/CnAppNav/CnAppNav.vue +44 -5
  115. package/src/components/CnCardGrid/CnCardGrid.md +1 -0
  116. package/src/components/CnCardGrid/CnCardGrid.vue +7 -0
  117. package/src/components/CnCellRenderer/CnCellRenderer.vue +56 -1
  118. package/src/components/CnCellRenderer/CnGroupNameCell.vue +61 -0
  119. package/src/components/CnCredentials/CnCredentials.vue +17 -16
  120. package/src/components/CnDataTable/CnDataTable.md +9 -0
  121. package/src/components/CnDataTable/CnDataTable.vue +80 -20
  122. package/src/components/CnDetailPage/CnDetailPage.vue +18 -32
  123. package/src/components/CnFavouriteToggle/CnFavouriteToggle.vue +7 -2
  124. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +204 -5
  125. package/src/components/CnFollowToggle/CnFollowToggle.vue +118 -13
  126. package/src/components/CnIndexPage/CnIndexPage.md +51 -0
  127. package/src/components/CnIndexPage/CnIndexPage.vue +261 -35
  128. package/src/components/CnIndexPage/selfModeActions.js +89 -13
  129. package/src/components/CnIndexPage/useSelfFetchList.js +15 -3
  130. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  131. package/src/components/CnObjectCard/CnObjectCard.vue +89 -1
  132. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +226 -2
  133. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +19 -3
  134. package/src/components/CnObjectSidebar/CnFilesTab.vue +9 -0
  135. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +181 -38
  136. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
  137. package/src/composables/__tests__/useObjectLock.endpoints.spec.js +83 -0
  138. package/src/composables/useListView.js +4 -0
  139. package/src/composables/useObjectLock.js +41 -10
  140. package/src/css/look-board-detail.css +40 -2
  141. package/src/css/look-board-index.css +35 -1
  142. package/src/css/table.css +82 -17
  143. package/src/index.js +3 -1
  144. package/src/schemas/app-manifest-v2.schema.json +11 -7
  145. package/src/store/plugins/interactions.js +20 -10
  146. package/src/store/useObjectStore.js +54 -9
  147. package/src/utils/builtInFormatters.js +15 -7
  148. package/src/utils/groupAutocomplete.js +75 -0
  149. package/src/utils/index.js +2 -1
  150. package/src/utils/lensAvailability.js +86 -0
  151. package/src/utils/objectSignals.js +41 -0
  152. package/src/utils/personalLenses.js +13 -6
  153. package/src/utils/recordInteractions.js +31 -13
  154. package/src/utils/resolveFilterTokens.js +45 -2
  155. package/src/utils/schema.js +1 -1
  156. package/src/utils/sentinelTokens.js +4 -4
  157. package/src/utils/validateManifestV2.compiled.js +2 -2
  158. package/src/utils/widgetVisibility.js +45 -0
@@ -3,22 +3,41 @@
3
3
  - SPDX-License-Identifier: EUPL-1.2
4
4
  -->
5
5
  <template>
6
- <span class="cn-follow-toggle" data-testid="cn-follow-toggle">
6
+ <span class="cn-follow-toggle"
7
+ :class="{ 'cn-follow-toggle--compact': compact }"
8
+ data-testid="cn-follow-toggle">
7
9
  <NcButton
8
- :variant="state ? 'secondary' : 'tertiary'"
10
+ :variant="state && !compact ? 'secondary' : 'tertiary'"
9
11
  :aria-pressed="state ? 'true' : 'false'"
12
+ :aria-label="compact ? (state ? followingLabel : followLabel) : undefined"
10
13
  :title="tooltip"
11
14
  :disabled="busy"
12
15
  data-testid="cn-follow-toggle-button"
13
16
  @click.stop="toggle">
14
17
  <template #icon>
15
- <Bell v-if="state" :size="20" />
16
- <BellOutline v-else :size="20" />
18
+ <Eye v-if="state" :size="20" class="cn-follow-toggle__on" />
19
+ <EyeOutline v-else :size="20" />
20
+ </template>
21
+ <template v-if="!compact">
22
+ {{ state ? followingLabel : followLabel }}
23
+ </template>
24
+ </NcButton>
25
+ <NcButton
26
+ v-if="showNotifySwitch"
27
+ variant="tertiary"
28
+ :aria-pressed="notifyState ? 'true' : 'false'"
29
+ :aria-label="notifyLabel"
30
+ :title="notifyLabel"
31
+ :disabled="busy"
32
+ data-testid="cn-follow-toggle-notify"
33
+ @click.stop="toggleNotify">
34
+ <template #icon>
35
+ <Bell v-if="notifyState" :size="20" />
36
+ <BellOffOutline v-else :size="20" />
17
37
  </template>
18
- {{ state ? followingLabel : followLabel }}
19
38
  </NcButton>
20
39
  <NcPopover
21
- v-if="count !== null"
40
+ v-if="count !== null && !compact"
22
41
  v-model:shown="open"
23
42
  placement="bottom-start">
24
43
  <template #trigger>
@@ -82,15 +101,26 @@
82
101
  import { translate as t } from '@nextcloud/l10n'
83
102
  import { NcAvatar, NcButton, NcLoadingIcon, NcPopover, NcSelect } from '@nextcloud/vue'
84
103
  import Bell from 'vue-material-design-icons/Bell.vue'
85
- import BellOutline from 'vue-material-design-icons/BellOutline.vue'
104
+ import BellOffOutline from 'vue-material-design-icons/BellOffOutline.vue'
86
105
  import Close from 'vue-material-design-icons/Close.vue'
106
+ import Eye from 'vue-material-design-icons/Eye.vue'
107
+ import EyeOutline from 'vue-material-design-icons/EyeOutline.vue'
87
108
  import { patchStoredSelf } from '../../utils/patchStoredSelf.js'
88
109
  import { listWatchers, setWatcher, setWatching } from '../../utils/recordInteractions.js'
89
110
  import { searchNextcloudUsers } from '../../utils/userAutocomplete.js'
90
111
 
91
112
  /**
92
- * CnFollowToggle — a Follow toggle bound to a record's `@self.watching` marker,
93
- * with the follower count and a followers popover for users who may see them.
113
+ * CnFollowToggle — the one Follow control of a record: a Follow toggle bound to
114
+ * `@self.watching`, a notifications switch bound to `@self.watchNotify`, the
115
+ * follower count and a followers popover for users who may see them.
116
+ *
117
+ * Following and favourites are one feature (OpenRegister's
118
+ * `merge-follow-and-favourites`): a favourite is a follow with notifications
119
+ * off, so there is no separate star. While the user follows, a bell beside the
120
+ * toggle turns the follow's notifications on or off (`PUT .../watch` with
121
+ * `{"notify": bool}`). With `notifies` false (the register sends no change
122
+ * notifications) the bell is not offered. `compact` renders the toggle as an
123
+ * icon only, without the bell, count or popover, for a column in a list.
94
124
  *
95
125
  * A click flips the toggle at once and sends `PUT` or `DELETE .../watch`; on
96
126
  * failure it flips back and shows the server's message, and a 404 says the
@@ -105,13 +135,14 @@ import { searchNextcloudUsers } from '../../utils/userAutocomplete.js'
105
135
  * Example:
106
136
  * ```vue
107
137
  * <CnFollowToggle register="pipelinq" schema="ticket" :object-id="id"
108
- * :watching="self.watching" :watcher-count="self.watcherCount" :can-manage="self.can && self.can.manage" />
138
+ * :watching="self.watching" :notify="self.watchNotify"
139
+ * :watcher-count="self.watcherCount" :can-manage="self.can && self.can.manage" />
109
140
  * ```
110
141
  */
111
142
  export default {
112
143
  name: 'CnFollowToggle',
113
144
 
114
- components: { Bell, BellOutline, Close, NcAvatar, NcButton, NcLoadingIcon, NcPopover, NcSelect },
145
+ components: { Bell, BellOffOutline, Close, Eye, EyeOutline, NcAvatar, NcButton, NcLoadingIcon, NcPopover, NcSelect },
115
146
 
116
147
  props: {
117
148
  /** Register slug of the record. */
@@ -138,6 +169,21 @@ export default {
138
169
  default: false,
139
170
  },
140
171
 
172
+ /**
173
+ * Whether the user's follow notifies them (`@self.watchNotify`). Null
174
+ * (an older server, or not following) reads as on.
175
+ */
176
+ notify: {
177
+ type: Boolean,
178
+ default: null,
179
+ },
180
+
181
+ /** Icon only, without the bell, count or popover: for a column in a list. */
182
+ compact: {
183
+ type: Boolean,
184
+ default: false,
185
+ },
186
+
141
187
  /** Number of followers (`@self.watcherCount`). Null hides the count and the popover. */
142
188
  watcherCount: {
143
189
  type: Number,
@@ -168,6 +214,7 @@ export default {
168
214
  data() {
169
215
  return {
170
216
  state: this.watching,
217
+ notifyState: this.notify !== false,
171
218
  count: this.watcherCount,
172
219
  busy: false,
173
220
  open: false,
@@ -196,6 +243,21 @@ export default {
196
243
  return t('nextcloud-vue', 'Add a colleague')
197
244
  },
198
245
 
246
+ notifyLabel() {
247
+ return this.notifyState
248
+ ? t('nextcloud-vue', 'Turn notifications off')
249
+ : t('nextcloud-vue', 'Turn notifications on')
250
+ },
251
+
252
+ /**
253
+ * Whether the bell renders: following, not compact, and the register notifies.
254
+ *
255
+ * @return {boolean}
256
+ */
257
+ showNotifySwitch() {
258
+ return this.state && !this.compact && this.notifies
259
+ },
260
+
199
261
  followersLabel() {
200
262
  return t('nextcloud-vue', '{count} followers', { count: this.count })
201
263
  },
@@ -216,6 +278,10 @@ export default {
216
278
  this.state = value
217
279
  },
218
280
 
281
+ notify(value) {
282
+ this.notifyState = value !== false
283
+ },
284
+
219
285
  watcherCount(value) {
220
286
  this.count = value
221
287
  },
@@ -272,12 +338,47 @@ export default {
272
338
  }
273
339
  return
274
340
  }
341
+ // A new follow notifies (the server's default); an unfollow has no switch.
342
+ this.notifyState = this.state
275
343
  patchStoredSelf(this.register, this.schema, this.objectId, {
276
344
  watching: this.state,
345
+ favourite: this.state,
346
+ watchNotify: this.state ? true : undefined,
277
347
  ...(this.count !== null ? { watcherCount: this.count } : {}),
278
348
  })
279
- /** @event change Emitted after a successful call. Payload: `{ watching, count }`. */
280
- this.$emit('change', { watching: this.state, count: this.count })
349
+ /** @event change Emitted after a successful call. Payload: `{ watching, notify, count }`. */
350
+ this.$emit('change', { watching: this.state, notify: this.state ? this.notifyState : null, count: this.count })
351
+ },
352
+
353
+ /**
354
+ * Turn the follow's notifications on or off, optimistically.
355
+ *
356
+ * @return {Promise<void>}
357
+ * @spec openspec/changes/one-follow-control/specs/record-follow/spec.md#requirement-one-follow-control-with-a-notifications-switch
358
+ */
359
+ async toggleNotify() {
360
+ if (this.busy || !this.state) {
361
+ return
362
+ }
363
+ const previous = this.notifyState
364
+ this.notifyState = !previous
365
+ this.busy = true
366
+ const result = await setWatching(this.register, this.schema, this.objectId, true, { notify: this.notifyState })
367
+ this.busy = false
368
+ if (!result.ok) {
369
+ this.notifyState = previous
370
+ const message = result.status === 404
371
+ ? t('nextcloud-vue', 'You can no longer see this record.')
372
+ : (result.message || t('nextcloud-vue', 'Could not change notifications.'))
373
+ import('@nextcloud/dialogs').then(({ showError }) => showError(message)).catch(() => {})
374
+ this.$emit('error', message)
375
+ if (result.status === 404) {
376
+ this.$emit('not-found')
377
+ }
378
+ return
379
+ }
380
+ patchStoredSelf(this.register, this.schema, this.objectId, { watchNotify: this.notifyState })
381
+ this.$emit('change', { watching: true, notify: this.notifyState, count: this.count })
281
382
  },
282
383
 
283
384
  async loadWatchers() {
@@ -340,6 +441,10 @@ export default {
340
441
  gap: 4px;
341
442
  }
342
443
 
444
+ .cn-follow-toggle__on {
445
+ color: var(--color-primary-element);
446
+ }
447
+
343
448
  .cn-follow-toggle__popover {
344
449
  min-width: 260px;
345
450
  padding: 12px;
@@ -358,6 +358,8 @@ export default {
358
358
  | `includeColumns` | Array | `null` | Column keys to show (whitelist); `null` means all |
359
359
  | `columnOverrides` | Object | `{}` | Per-column config overrides in schema mode |
360
360
  | `emptyText` | String | `'No items found'` | Text shown in the empty state |
361
+ | `lenses` | Object \| null | `null` | The personal-lens report of the list on screen (`@self.lenses` of the response) for a host that fetches itself. In self-fetch mode the page reads it from the store. See [When a lens cannot answer](#when-a-lens-cannot-answer). |
362
+ | `lensReasonTexts` | Object \| null | `null` | App wording for an unavailable lens, keyed `<lens>.<reason>` or `<reason>`. |
361
363
  | `loadingText` | String | `'Loading…'` | Accessible label for the loading spinner (NcLoadingIcon aria-label) |
362
364
  | `rowIcon` | String \| Function | `null` | Optional leading icon for every table row — a static MDI name or `(row) => iconName`. Forwarded to CnDataTable; fed from the manifest as `pages[].config.rowIcon`. Unset = no icon column. |
363
365
  | `rowClass` | Function | `null` | Callback returning CSS class(es) for a row |
@@ -434,6 +436,21 @@ list nested under a parent route (`/forms/:id/submissions`) is a fully
434
436
  declarative page. When `objects` **is** supplied (every existing consumer),
435
437
  nothing changes — no store is touched and `filter` has no effect.
436
438
 
439
+ ### Mixed-schema lists (`collectionUrl`)
440
+
441
+ Set `collectionUrl` to list from an endpoint that searches several
442
+ register/schema pairs at once. `register` and `schema` stay the page's own
443
+ pair (columns, Add, export, import); a row of another pair edits, saves and
444
+ deletes through its own `@self.register` / `@self.schema`.
445
+
446
+ ```js static
447
+ <CnIndexPage
448
+ title="Publications"
449
+ register="19"
450
+ schema="24"
451
+ :collection-url="generateUrl('/apps/opencatalogi/api/{slug}', { slug })" />
452
+ ```
453
+
437
454
  ## Named entity sources (`config.entitySource`) — lists that are not OpenRegister objects
438
455
 
439
456
  Self-fetch needs a `register` + `schema` pair. Some lists have neither: a flow
@@ -979,6 +996,40 @@ A person who has claimed no teams gets an empty list, and the tab says so
979
996
  through `narrowsToNothing`. A lens labelled "my teams" that quietly showed
980
997
  every team would be a label stating one rule while the fetch ran another.
981
998
 
999
+ ### When a lens cannot answer
1000
+
1001
+ OpenRegister reports, for every personal lens a list asked for, whether it
1002
+ could answer (openregister#4514, needed for any effect):
1003
+
1004
+ ```json
1005
+ { "results": [], "@self": { "lenses": { "recent": { "available": false, "reason": "audit-trail-disabled" } } } }
1006
+ ```
1007
+
1008
+ When a lens reports `available: false`, the empty state says why instead of
1009
+ showing `emptyText`:
1010
+
1011
+ | Reason | Text |
1012
+ |---|---|
1013
+ | `audit-trail-disabled` | This server does not keep track of what you open. |
1014
+ | `anonymous` | Log in to see what you opened recently. |
1015
+ | `read-history-unavailable` | Your recent items are not available right now. |
1016
+
1017
+ The texts are object-neutral and translated (Dutch: "Deze server houdt niet
1018
+ bij welke items je opent.", "Log in om te zien wat je onlangs opende.", "Je
1019
+ recente items zijn nu niet beschikbaar."). Say it in the app's own words with
1020
+ `lensReasonTexts`:
1021
+
1022
+ ```html
1023
+ <CnIndexPage :lens-reason-texts="{ 'recent.audit-trail-disabled': t('dossiq', 'This server does not keep track of which cases you open.') }" />
1024
+ ```
1025
+
1026
+ A response without `@self.lenses`, a lens that is available, or a reason with
1027
+ no text keeps `emptyText`, and an `#empty` slot still wins. The lens name is a
1028
+ key, so another lens (`watching`) that starts reporting needs only text. The
1029
+ store keeps the report as `lenses[type]` and `useListView` exposes it as
1030
+ `lenses`; `readLensReports` and `lensUnavailableText` are in
1031
+ `src/utils/lensAvailability.js`.
1032
+
982
1033
  ### Row actions and indicators
983
1034
 
984
1035
  See the two sections below. Row actions are the intersection of what the page