@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
package/l10n/nl.json CHANGED
@@ -1,5 +1,10 @@
1
1
  {
2
2
  "translations": {
3
+ "Linked items": "Gekoppelde items",
4
+ "Still loading: {sections}": "Nog aan het laden: {sections}",
5
+ "Could not load {section}.": "{section} kon niet worden geladen.",
6
+ "Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.": "Met inloggegevens van de organisatie handelen apps namens je organisatie. Keepiq, de kluis voor inloggegevens van Nextcloud, bewaart het geheim. De app krijgt het nooit in handen. Je kiest welke apps elk inloggegeven mogen gebruiken.",
7
+ "Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.": "Apps handelen soms namens jou bij een externe dienst. Je geeft het geheim één keer aan Nextcloud, en Keepiq bewaart het in een versleutelde kluis. Apps doen de aanvraag via Keepiq en zien het geheim nooit. Je kiest welke apps elk inloggegeven mogen gebruiken. Deel er één tussen apps, of houd er één per app.",
3
8
  "Show as": "Toon als",
4
9
  "Board": "Bord",
5
10
  "Calendar": "Kalender",
@@ -171,7 +176,7 @@
171
176
  "This integration is not set up yet. Configure a connection to start using it.": "Deze integratie is nog niet ingesteld. Configureer een verbinding om hem te gebruiken.",
172
177
  "This integration is not set up yet. Install and enable the {app} app to start using it.": "Deze integratie is nog niet ingesteld. Installeer en activeer de app {app} om hem te gebruiken.",
173
178
  "Set up {app}": "{app} instellen",
174
- "Add {type}": "Nieuwe {type}",
179
+ "Add {type}": "{type} toevoegen",
175
180
  "Add new contact": "Nieuw contact toevoegen",
176
181
  "Link contact": "Contact koppelen",
177
182
  "Search contacts": "Contacten zoeken",
@@ -1335,7 +1340,20 @@
1335
1340
  "Development environment": "Ontwikkelomgeving",
1336
1341
  "Test environment": "Testomgeving",
1337
1342
  "Acceptance environment": "Acceptatieomgeving",
1338
- "About {label}": "Over {label}"
1343
+ "About {label}": "Over {label}",
1344
+ "Could not load the form for this item.": "Kon het formulier voor dit item niet laden.",
1345
+ "Drop to add": "Laat los om toe te voegen",
1346
+ "Or drag files onto this list.": "Of sleep bestanden op deze lijst.",
1347
+ "This server does not keep track of what you open.": "Deze server houdt niet bij welke items je opent.",
1348
+ "Log in to see what you opened recently.": "Log in om te zien wat je onlangs opende.",
1349
+ "Your recent items are not available right now.": "Je recente items zijn nu niet beschikbaar.",
1350
+ "yesterday": "gisteren",
1351
+ "tomorrow": "morgen",
1352
+ "Filter by {column}": "Filteren op {column}",
1353
+ "Filter by {column}, active": "Filteren op {column}, actief",
1354
+ "Turn notifications off": "Meldingen uitzetten",
1355
+ "Turn notifications on": "Meldingen aanzetten",
1356
+ "Could not change notifications.": "Meldingen konden niet worden gewijzigd."
1339
1357
  },
1340
1358
  "plurals": {
1341
1359
  "%n year": [
@@ -1389,6 +1407,18 @@
1389
1407
  "You see {count} capability of {total}.": [
1390
1408
  "Je ziet {count} functionaliteit van {total}.",
1391
1409
  "Je ziet {count} functionaliteiten van {total}."
1410
+ ],
1411
+ "%n file added": [
1412
+ "%n bestand toegevoegd",
1413
+ "%n bestanden toegevoegd"
1414
+ ],
1415
+ "{count} day ago": [
1416
+ "{count} dag geleden",
1417
+ "{count} dagen geleden"
1418
+ ],
1419
+ "in {count} day": [
1420
+ "over {count} dag",
1421
+ "over {count} dagen"
1392
1422
  ]
1393
1423
  }
1394
1424
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.75.0",
3
+ "version": "2.77.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -183,7 +183,19 @@
183
183
  v-if="isCaption(item)"
184
184
  :key="item.id"
185
185
  :name="resolveLabel(item)"
186
- :data-testid="`cn-nav-caption-${item.id}`" />
186
+ :data-testid="`cn-nav-caption-${item.id}`">
187
+ <template v-if="item.href" #actions>
188
+ <NcActionLink
189
+ :href="item.href"
190
+ :aria-label="captionEditLabel(item)"
191
+ :data-testid="`cn-nav-caption-edit-${item.id}`">
192
+ <template #icon>
193
+ <Pencil :size="20" />
194
+ </template>
195
+ {{ captionEditLabel(item) }}
196
+ </NcActionLink>
197
+ </template>
198
+ </NcAppNavigationCaption>
187
199
  <NcAppNavigationItem
188
200
  v-else
189
201
  :key="item.id"
@@ -424,7 +436,19 @@
424
436
  v-if="isCaption(item)"
425
437
  :key="item.id"
426
438
  :name="resolveLabel(item)"
427
- :data-testid="`cn-nav-caption-${item.id}`" />
439
+ :data-testid="`cn-nav-caption-${item.id}`">
440
+ <template v-if="item.href" #actions>
441
+ <NcActionLink
442
+ :href="item.href"
443
+ :aria-label="captionEditLabel(item)"
444
+ :data-testid="`cn-nav-caption-edit-${item.id}`">
445
+ <template #icon>
446
+ <Pencil :size="20" />
447
+ </template>
448
+ {{ captionEditLabel(item) }}
449
+ </NcActionLink>
450
+ </template>
451
+ </NcAppNavigationCaption>
428
452
  <NcAppNavigationItem
429
453
  v-else
430
454
  :key="item.id"
@@ -466,7 +490,7 @@
466
490
  <script>
467
491
  import { translate as t } from '@nextcloud/l10n'
468
492
  import { generateUrl } from '@nextcloud/router'
469
- import { NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'
493
+ import { NcActionLink, NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'
470
494
  import { toRaw } from 'vue'
471
495
  import BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'
472
496
  import Cog from 'vue-material-design-icons/Cog.vue'
@@ -474,6 +498,7 @@ import Cog from 'vue-material-design-icons/Cog.vue'
474
498
  import HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'
475
499
  import MapMarkerPath from 'vue-material-design-icons/MapMarkerPath.vue'
476
500
  import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
501
+ import Pencil from 'vue-material-design-icons/Pencil.vue'
477
502
  import Plus from 'vue-material-design-icons/Plus.vue'
478
503
  import ShieldAccountOutline from 'vue-material-design-icons/ShieldAccountOutline.vue'
479
504
  import { ICON_MAP } from '../CnIcon/CnIcon.vue'
@@ -560,6 +585,8 @@ export default {
560
585
  name: 'CnAppNav',
561
586
 
562
587
  components: {
588
+ NcActionLink,
589
+ Pencil,
563
590
  NcAppNavigation,
564
591
  NcAppNavigationCaption,
565
592
  NcAppNavigationItem,
@@ -1921,8 +1948,10 @@ export default {
1921
1948
  /**
1922
1949
  * Whether a menu entry renders as a `NcAppNavigationCaption`
1923
1950
  * (`type: "caption"`) rather than a clickable
1924
- * `NcAppNavigationItem`. Caption entries ignore `route`, `href`,
1925
- * `action`, `icon`, `count`, `children`, and `pinned`.
1951
+ * `NcAppNavigationItem`. Caption entries ignore `route`, `action`,
1952
+ * `icon`, `count`, `children`, and `pinned`. A caption's `href` is
1953
+ * drawn as a pencil link beside the heading: the place where what
1954
+ * stands under it is changed (a user's own choice of entries, say).
1926
1955
  *
1927
1956
  * @param {{ type?: string }} item Menu entry descriptor.
1928
1957
  * @return {boolean}
@@ -1931,6 +1960,16 @@ export default {
1931
1960
  return item?.type === 'caption'
1932
1961
  },
1933
1962
 
1963
+ /**
1964
+ * The accessible name of a caption's pencil link.
1965
+ *
1966
+ * @param {object} item The caption entry.
1967
+ * @return {string} "Change {caption}", translated.
1968
+ */
1969
+ captionEditLabel(item) {
1970
+ return t('nextcloud-vue', 'Change {name}', { name: this.resolveLabel(item) })
1971
+ },
1972
+
1934
1973
  /**
1935
1974
  * Whether the host has registered a scoped slot named
1936
1975
  * `item-${id}-actions` for this menu item. Used to gate template
@@ -106,6 +106,7 @@ Custom card slot — replace the default CnObjectCard with your own rendering:
106
106
  |---|---|---|---|
107
107
  | `selectable` | `Boolean` | `false` | When `true`, each card shows a selection checkbox and the grid emits `select` events |
108
108
  | `selectedIds` | `Array` | `[]` | Array of currently selected object IDs (matched against `object[rowKey]`) |
109
+ | `accentOf` | `Function` | `null` | `(object) => { variant, icon?, label? } \| null`, each card's status accent (see CnObjectCard `accent`) |
109
110
 
110
111
  ## Board look props
111
112
 
@@ -40,6 +40,7 @@
40
40
  :status="statusOf ? statusOf(object) : null"
41
41
  :leading="leadingOf ? leadingOf(object) : null"
42
42
  :footerAction="footerActionOf ? footerActionOf(object) : null"
43
+ :accent="accentOf ? accentOf(object) : null"
43
44
  v-on="cardListeners(object)">
44
45
  <template v-if="$slots['card-actions']" #actions="{ object: obj }">
45
46
  <slot name="card-actions" :object="obj" />
@@ -182,6 +183,12 @@ export default {
182
183
  default: null,
183
184
  },
184
185
 
186
+ /** `(object) => { variant, icon?, label? }` for each card's status accent. See CnObjectCard `accent`. */
187
+ accentOf: {
188
+ type: Function,
189
+ default: null,
190
+ },
191
+
185
192
  /** Text shown when there are no objects */
186
193
  emptyText: {
187
194
  type: String,
@@ -10,6 +10,20 @@
10
10
  :formatted="formattedValue"
11
11
  v-bind="widgetProps" />
12
12
 
13
+ <!-- Built-in "group" widget: a Nextcloud group id (or a list of them)
14
+ shown as the group's display name, cached per id. Used on its own
15
+ for a property marked `referenceType: nextcloud-group` /
16
+ `format: nc-group` unless the column has a formatter. -->
17
+ <template v-else-if="isGroupCell">
18
+ <span v-if="!groupIds.length" class="cn-cell-renderer__dash">—</span>
19
+ <span v-else class="cn-cell-renderer__groups">
20
+ <template v-for="(gid, i) in groupIds" :key="gid">
21
+ <span v-if="i > 0">, </span>
22
+ <CnGroupNameCell :gid="gid" />
23
+ </template>
24
+ </span>
25
+ </template>
26
+
13
27
  <!-- Built-in "badge" widget — renders the (possibly formatter-shaped) value as a status pill -->
14
28
  <template v-else-if="widget === 'badge'">
15
29
  <CnStatusBadge v-if="hasValue"
@@ -192,11 +206,12 @@ import { getCanonicalLocale } from '@nextcloud/l10n'
192
206
  import { NcAvatar, NcDateTime } from '@nextcloud/vue'
193
207
  import CheckBold from 'vue-material-design-icons/CheckBold.vue'
194
208
  import CnFkResolveCell from '../CnFkResolveCell/CnFkResolveCell.vue'
209
+ import CnGroupNameCell from './CnGroupNameCell.vue'
195
210
  import { parseDateValue, resolveDateVariant } from '../../utils/dateVariant.js'
196
211
  import { safeCurrencyCode } from '../../utils/formatMetric.js'
197
212
  import { objectFieldValue } from '../../utils/objectName.js'
198
213
  import { safeHref } from '../../utils/safeHref.js'
199
- import { formatValue } from '../../utils/schema.js'
214
+ import { formatValue, isGroupProp } from '../../utils/schema.js'
200
215
  import { CnStatusBadge } from '../CnStatusBadge/index.js'
201
216
 
202
217
  /** A uuid in any version, the shape OpenRegister ids take. */
@@ -226,6 +241,7 @@ export default {
226
241
  components: {
227
242
  CnStatusBadge,
228
243
  CnFkResolveCell,
244
+ CnGroupNameCell,
229
245
  CheckBold,
230
246
  NcAvatar,
231
247
  NcDateTime,
@@ -307,6 +323,9 @@ export default {
307
323
  * a date whose colour follows `widgetProps.variantWhen`, rules on the
308
324
  * number of days until the date (`[{ op: "lt", value: 0, variant:
309
325
  * "error" }, { op: "lte", value: 5, variant: "warning" }]`).
326
+ * The built-in id `"group"` shows a Nextcloud group id (or a list of
327
+ * them) as the group's display name; a column whose property is
328
+ * marked `referenceType: "nextcloud-group"` gets it without asking.
310
329
  * Takes precedence over `formatter`/the type-aware rendering, but the
311
330
  * value handed to the widget is the formatter-shaped `formatted` when
312
331
  * `formatter` is also set.
@@ -577,6 +596,42 @@ export default {
577
596
  return c || null
578
597
  },
579
598
 
599
+ /**
600
+ * Whether this cell shows Nextcloud group names: the column asks for
601
+ * `widget: "group"`, or it names no widget and no formatter and its
602
+ * property is a group (or a list of groups).
603
+ *
604
+ * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name
605
+ * @return {boolean}
606
+ */
607
+ isGroupCell() {
608
+ if (this.widget === 'group') {
609
+ return true
610
+ }
611
+ if (this.widget || this.hasFormatter) {
612
+ return false
613
+ }
614
+ const p = this.property
615
+ if (!p || typeof p !== 'object') {
616
+ return false
617
+ }
618
+ return isGroupProp(p) || (p.type === 'array' && isGroupProp(p.items))
619
+ },
620
+
621
+ /**
622
+ * The group ids in this cell, empty ones dropped.
623
+ *
624
+ * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name
625
+ * @return {string[]}
626
+ */
627
+ groupIds() {
628
+ const raw = this.value
629
+ const list = Array.isArray(raw) ? raw : [raw]
630
+ return list
631
+ .filter((gid) => (typeof gid === 'string' && gid !== '') || typeof gid === 'number')
632
+ .map((gid) => String(gid))
633
+ },
634
+
580
635
  /** Variant for the built-in `badge` widget — `widgetProps.variant` or `'default'`. */
581
636
  badgeVariant() {
582
637
  return (this.widgetProps && this.widgetProps.variant) || 'default'
@@ -0,0 +1,61 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ SPDX-License-Identifier: EUPL-1.2
4
+
5
+ CnGroupNameCell: one Nextcloud group id shown as the group's display name.
6
+ The name is looked up once per page (utils/groupAutocomplete.js); until it
7
+ arrives, and when it cannot be found, the cell shows the id.
8
+ -->
9
+ <template>
10
+ <span class="cn-group-name-cell" :title="gid">{{ label }}</span>
11
+ </template>
12
+
13
+ <script>
14
+ import { groupDisplayName, loadGroupDisplayName } from '../../utils/groupAutocomplete.js'
15
+
16
+ /**
17
+ * CnGroupNameCell renders a Nextcloud group id as its display name.
18
+ *
19
+ * ```vue
20
+ * <CnGroupNameCell gid="toezicht" />
21
+ * ```
22
+ *
23
+ * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name
24
+ */
25
+ export default {
26
+ name: 'CnGroupNameCell',
27
+
28
+ props: {
29
+ /** The group id to show. */
30
+ gid: {
31
+ type: String,
32
+ required: true,
33
+ },
34
+ },
35
+
36
+ computed: {
37
+ /**
38
+ * The cached display name, or the id while it loads or when unknown.
39
+ *
40
+ * @return {string}
41
+ */
42
+ label() {
43
+ return groupDisplayName(this.gid) || this.gid
44
+ },
45
+ },
46
+
47
+ watch: {
48
+ gid: {
49
+ immediate: true,
50
+ /**
51
+ * Ask for the name of a new id (cached, so once per id per page).
52
+ *
53
+ * @param {string} gid The group id.
54
+ */
55
+ handler(gid) {
56
+ loadGroupDisplayName(gid)
57
+ },
58
+ },
59
+ },
60
+ }
61
+ </script>
@@ -2,25 +2,25 @@
2
2
  - SPDX-License-Identifier: EUPL-1.2
3
3
  - SPDX-FileCopyrightText: 2026 Conduction B.V.
4
4
  -
5
- - CnCredentials — the settings surface for the OpenRegister credential
5
+ - CnCredentials: the settings surface for the OpenRegister credential
6
6
  - broker. Apps occasionally need to act on your behalf against an external
7
7
  - service (GitHub, GitLab, …). Rather than hand the secret to the app, you
8
- - give it once to OpenRegister, which stores it in Keepiq — Nextcloud's
8
+ - give it once to OpenRegister, which stores it in Keepiq, Nextcloud's
9
9
  - native, encrypted credential vault. Apps then make the outbound call
10
10
  - THROUGH OpenRegister and never see the secret itself. You decide which
11
11
  - apps may use each credential, so one credential can be shared across apps
12
12
  - or kept dedicated to one.
13
13
  -
14
14
  - Two scopes render from this one component:
15
- - • scope="personal" (default) — the signed-in user's own
15
+ - • scope="personal" (default), the signed-in user's own
16
16
  - credentials, shown in an app's *personal* settings. From here you may
17
17
  - only add/remove *the app you are currently in* to/from a credential,
18
18
  - or add a new credential (the current app is auto-added). You never
19
19
  - manage the full app list of another app from here.
20
- - • scope="organisation" — organisation-wide credentials, shown in an
20
+ - • scope="organisation" , organisation-wide credentials, shown in an
21
21
  - app's *admin* settings. An admin manages the full allowed-app list.
22
22
  -
23
- - Talks to OpenRegister's credential endpoints (metadata only — a secret is
23
+ - Talks to OpenRegister's credential endpoints (metadata only, a secret is
24
24
  - write-only and is NEVER returned or displayed):
25
25
  - GET /apps/openregister/api/credentials?scope={scope}
26
26
  - GET /apps/openregister/api/credentials/providers
@@ -148,7 +148,7 @@
148
148
  </ul>
149
149
  </section>
150
150
 
151
- <!-- Add credential — inline wizard: pick a provider, then fill it in. -->
151
+ <!-- Add credential: inline wizard: pick a provider, then fill it in. -->
152
152
  <section class="cn-credentials__section">
153
153
  <NcButton v-if="!adding"
154
154
  variant="secondary"
@@ -269,7 +269,7 @@ const PROVIDERS_PATH = '/apps/openregister/api/credentials/providers'
269
269
  * Hardcoded per-provider presentation. Keyed by the OpenRegister catalogue
270
270
  * identifier. `colour` drives the tile; `setupHelp`/`setupUrl` explain how to
271
271
  * obtain the secret; `secretLabel` names the field. Providers not listed here
272
- * still work — they render a neutral tile from their identifier.
272
+ * still work, they render a neutral tile from their identifier.
273
273
  */
274
274
  const PROVIDER_META = {
275
275
  github: {
@@ -339,7 +339,7 @@ export default {
339
339
  },
340
340
 
341
341
  /**
342
- * The current app's manifest `credentials[]` declarations — the
342
+ * The current app's manifest `credentials[]` declarations: the
343
343
  * providers this app can reach through the broker. Shape:
344
344
  * `[{ provider, reason, scopes }]`. Rendered read-only under the intro.
345
345
  *
@@ -404,15 +404,16 @@ export default {
404
404
  },
405
405
 
406
406
  /**
407
- * Reframed intro copy per scope.
407
+ * Reframed intro copy per scope. No em-dashes, sentences under 16 words.
408
408
  *
409
+ * @spec openspec/changes/r4-object-lock-url-and-credentials-copy/specs/credentials-settings/spec.md
409
410
  * @return {string} Intro text.
410
411
  */
411
412
  introText() {
412
413
  if (this.scope === 'organisation') {
413
- return t('nextcloud-vue', 'Organisation credentials let apps act on behalf of your organisation. The secret is stored in Keepiq — Nextcloud\'s native credential vault — never in the app. You choose which apps may use each credential.')
414
+ return t('nextcloud-vue', 'Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.')
414
415
  }
415
- return t('nextcloud-vue', 'Apps sometimes need to act on your behalf against an external service. So they never hold your secrets, you give a secret to Nextcloud once and it is kept in Keepiq — a native, encrypted credential vault. Apps then make the call through Keepiq and never see the secret. You decide which apps may use each credential — share one across apps, or keep one per app.')
416
+ return t('nextcloud-vue', 'Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.')
416
417
  },
417
418
 
418
419
  /**
@@ -438,7 +439,7 @@ export default {
438
439
  const serverIds = this.providers.map((p) => p.identifier)
439
440
  let ids = serverIds.length ? serverIds : Object.keys(PROVIDER_META)
440
441
  // When the app declares which providers it uses (appCredentials),
441
- // only offer those — an app should not let you add a credential for
442
+ // only offer those, an app should not let you add a credential for
442
443
  // a provider it has no code path to use.
443
444
  if (this.supportedProviders.size > 0) {
444
445
  ids = ids.filter((id) => this.supportedProviders.has(id))
@@ -448,7 +449,7 @@ export default {
448
449
 
449
450
  /**
450
451
  * Provider identifiers the app declares it uses, derived from
451
- * `appCredentials`. Empty when the app declares nothing — filtering is
452
+ * `appCredentials`. Empty when the app declares nothing, filtering is
452
453
  * then a no-op, so consumers that don't declare providers are unchanged.
453
454
  *
454
455
  * @return {Set<string>} Supported provider identifiers.
@@ -460,7 +461,7 @@ export default {
460
461
  },
461
462
 
462
463
  /**
463
- * Stored credentials to display — filtered to the app's supported
464
+ * Stored credentials to display: filtered to the app's supported
464
465
  * providers when it declares any, so you can only see and authorise
465
466
  * credentials the app can actually use; otherwise all of them.
466
467
  *
@@ -503,7 +504,7 @@ export default {
503
504
  },
504
505
 
505
506
  /**
506
- * Whether the add form can be submitted — name, provider and a secret
507
+ * Whether the add form can be submitted: name, provider and a secret
507
508
  * are required (the whole point is to store a secret).
508
509
  *
509
510
  * @return {boolean} True when submittable.
@@ -727,7 +728,7 @@ export default {
727
728
  },
728
729
 
729
730
  /**
730
- * Human-readable provider title — hardcoded catalogue first, then the
731
+ * Human-readable provider title: hardcoded catalogue first, then the
731
732
  * server title, then the raw identifier.
732
733
  *
733
734
  * @param {string} identifier The provider identifier.
@@ -205,6 +205,7 @@ export default {
205
205
  | `filterable` | Boolean | `false` | Show a filter button in every header whose column can filter: enum, boolean, text, number, date and reference columns backed by a schema property. A column opts out with `filterable: false`. Applying a filter emits `column-filter`; the host owns the state and passes it back as `activeFilters`. `CnIndexPage` turns it on by default. |
206
206
  | `activeFilters` | Object | `{}` | The active filter map, `{ paramKey: values[] }`: the same map the facet sidebar writes, so a header filter and a sidebar filter on one field show the same state. |
207
207
  | `filterRegister` | String | `''` | Register slug a reference column's filter searches in when the column names none. |
208
+ | `lensReasonTexts` | Object \| null | `null` | App wording for an unavailable personal lens in self-fetch mode, keyed `<lens>.<reason>` or `<reason>`. Without it the library's text is used (see below). |
208
209
 
209
210
  ## Slots
210
211
 
@@ -233,6 +234,14 @@ features are folded in here as opt-in props (bare-table usage is unchanged):
233
234
  - `fetchParams` — extra query params for the self-fetch (a resolved filter map,
234
235
  `_order[field]` ordering, `_limit`); changing it re-triggers the fetch. Used by
235
236
  `CnWidgetObjectTable`'s declarative `source`.
237
+ - When the self-fetch asked for a personal lens (a "Recently opened" tile with
238
+ `source.filter._recent: true`) and the response reports it unavailable
239
+ (`@self.lenses.recent.available: false`, openregister#4514), the empty row
240
+ says why instead of `emptyText`: "This server does not keep track of what you
241
+ open." (`audit-trail-disabled`), "Log in to see what you opened recently."
242
+ (`anonymous`) or "Your recent items are not available right now."
243
+ (`read-history-unavailable`). `lensReasonTexts` replaces the wording; no
244
+ report, an available lens or an unknown reason keeps `emptyText`.
236
245
  - `rowClickRoute` — a function mapping a row to a vue-router route. Each such row renders a real `<a href>` in its first cell, stretched over the row, so hovering shows the URL, a plain or alt click routes in place, a ctrl/cmd/shift or middle click opens a new tab, and the row is reachable with Tab. The link is named by the first cell's text ("Open row" when that is empty or not plain text). There is no link on a `selectable` table without `rowClickToView`, where a row click selects. `CnWidgetObjectTable` rows inherit it. Trade-offs: cell text cannot be selected by dragging on a linked row, and a right-click on it shows the browser's link menu only when no host listens to `row-context-menu`.
237
246
  - `hideHeader` — drop the column-label row for a compact list widget.
238
247
  - `#footer` slot — supply a custom footer link (e.g. "+ New" or an always-shown