@conduction/nextcloud-vue 2.92.0 → 2.94.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 (199) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +1 -1
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +2 -1
  4. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  6. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +28 -8
  7. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +37 -2
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +57 -4
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +44 -17
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  14. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  15. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +44 -2
  16. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  17. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +140 -59
  18. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  20. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +139 -3
  21. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  22. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js +60 -12
  23. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +111 -42
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +117 -73
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +34 -39
  30. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +7 -7
  32. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +5 -3
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  37. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +6 -4
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  40. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +46 -0
  41. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
  42. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js +6 -2
  43. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue3.js.map +1 -1
  44. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  45. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +82 -9
  46. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +104 -90
  48. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  49. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +9 -0
  50. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js.map +1 -0
  51. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +355 -0
  52. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -0
  53. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +462 -0
  54. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -0
  55. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  56. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +21 -0
  57. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +402 -391
  59. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  60. package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
  61. package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +141 -7
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -2
  65. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  66. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  67. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +22 -1
  68. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +20 -14
  70. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  71. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue.js +1 -1
  72. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js +2 -0
  73. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue2.js.map +1 -1
  74. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js +15 -5
  75. package/dist/esm/components/CnNotificationPreferences/CnNotificationPreferences.vue3.js.map +1 -1
  76. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js +8 -0
  77. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js.map +1 -0
  78. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +114 -0
  79. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -0
  80. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js +45 -0
  81. package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -0
  82. package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js +38 -2
  83. package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js.map +1 -1
  84. package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js +12 -2
  85. package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js.map +1 -1
  86. package/dist/esm/components/CnPagination/CnPagination.vue2.js.map +1 -1
  87. package/dist/esm/components/CnPagination/CnPagination.vue3.js +56 -51
  88. package/dist/esm/components/CnPagination/CnPagination.vue3.js.map +1 -1
  89. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  90. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +3 -3
  91. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  92. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  93. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue.js +1 -1
  94. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js +8 -30
  95. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue2.js.map +1 -1
  96. package/dist/esm/components/CnRoadmapTab/CnRoadmapTab.vue3.js.map +1 -1
  97. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  98. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +24 -0
  99. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  100. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +3 -1
  101. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  102. package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
  103. package/dist/esm/components/CnTabs/CnTabs.vue2.js +39 -2
  104. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  105. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  106. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  107. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +85 -8
  108. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  109. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +37 -9
  110. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  111. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +9 -0
  112. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js.map +1 -0
  113. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +164 -0
  114. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -0
  115. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +93 -0
  116. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -0
  117. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  118. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  119. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -3
  120. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  121. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  122. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +32 -0
  123. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  124. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +31 -20
  125. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  126. package/dist/esm/composables/indexSources.js +9 -0
  127. package/dist/esm/composables/indexSources.js.map +1 -1
  128. package/dist/esm/l10n/en.json.js +24 -1
  129. package/dist/esm/l10n/en.json.js.map +1 -1
  130. package/dist/esm/l10n/nl.json.js +24 -1
  131. package/dist/esm/l10n/nl.json.js.map +1 -1
  132. package/dist/esm/library-style.js +1 -1
  133. package/dist/esm/nextcloud-vue.css +1190 -410
  134. package/dist/esm/utils/dueText.js +49 -8
  135. package/dist/esm/utils/dueText.js.map +1 -1
  136. package/dist/esm/utils/featureCatalog.js +135 -0
  137. package/dist/esm/utils/featureCatalog.js.map +1 -0
  138. package/dist/esm/utils/fillTemplate.js +74 -3
  139. package/dist/esm/utils/fillTemplate.js.map +1 -1
  140. package/dist/esm/utils/headerMeta.js +62 -12
  141. package/dist/esm/utils/headerMeta.js.map +1 -1
  142. package/dist/esm/utils/roadmapIssues.js +86 -0
  143. package/dist/esm/utils/roadmapIssues.js.map +1 -0
  144. package/dist/esm/utils/userDisplayName.js +47 -0
  145. package/dist/esm/utils/userDisplayName.js.map +1 -0
  146. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  147. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  148. package/dist/nextcloud-vue.cjs.js +16229 -13423
  149. package/dist/nextcloud-vue.cjs.js.map +2 -2
  150. package/dist/nextcloud-vue.css +1190 -410
  151. package/l10n/en.json +24 -1
  152. package/l10n/nl.json +24 -1
  153. package/package.json +1 -1
  154. package/src/components/CnActionsMenu/CnActionsMenu.vue +3 -1
  155. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +39 -1
  156. package/src/components/CnAppRoot/CnAppRoot.md +2 -0
  157. package/src/components/CnAppRoot/CnAppRoot.vue +66 -5
  158. package/src/components/CnChoiceCards/CnChoiceCards.md +27 -0
  159. package/src/components/CnChoiceCards/CnChoiceCards.vue +168 -3
  160. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +222 -6
  161. package/src/components/CnDashboardPage/CnDashboardPage.vue +141 -8
  162. package/src/components/CnDataTable/CnDataTable.md +1 -0
  163. package/src/components/CnDataTable/CnDataTable.vue +41 -46
  164. package/src/components/CnDetailPage/CnDetailPage.vue +5 -3
  165. package/src/components/CnFactsWidget/CnFactsWidget.vue +6 -4
  166. package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +51 -1
  167. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +98 -13
  168. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +1053 -0
  169. package/src/components/CnFormDialog/CnFormDialog.vue +606 -562
  170. package/src/components/CnIndexPage/CnIndexPage.vue +144 -7
  171. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +23 -2
  172. package/src/components/CnNotificationPreferences/CnNotificationPreferences.vue +5 -0
  173. package/src/components/CnPageHeader/CnHeaderTrail.vue +140 -0
  174. package/src/components/CnPageHeader/CnPageHeader.md +11 -0
  175. package/src/components/CnPageHeader/CnPageHeader.vue +42 -0
  176. package/src/components/CnPagination/CnPagination.vue +10 -2
  177. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +3 -3
  178. package/src/components/CnRoadmapTab/CnRoadmapTab.vue +6 -28
  179. package/src/components/CnSettingsPage/CnSettingsPage.vue +26 -0
  180. package/src/components/CnTabs/CnTabs.vue +39 -2
  181. package/src/components/CnTabsWidget/CnTabsWidget.vue +106 -13
  182. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +305 -0
  183. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
  184. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +8 -0
  185. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +43 -2
  186. package/src/composables/indexSources.js +9 -0
  187. package/src/css/dialog.css +61 -0
  188. package/src/css/look-board-detail.css +8 -0
  189. package/src/css/look-board-index.css +27 -1
  190. package/src/css/look-board.css +60 -0
  191. package/src/css/page-header.css +16 -0
  192. package/src/schemas/app-manifest-v2.schema.json +30 -17
  193. package/src/utils/dueText.js +49 -8
  194. package/src/utils/featureCatalog.js +132 -0
  195. package/src/utils/fillTemplate.js +73 -2
  196. package/src/utils/headerMeta.js +62 -12
  197. package/src/utils/roadmapIssues.js +82 -0
  198. package/src/utils/userDisplayName.js +54 -0
  199. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -21,6 +21,8 @@
21
21
  :title="title"
22
22
  :description="headerDescription"
23
23
  :icon="showTitleIcon && !isBoardLook ? resolvedIcon : ''"
24
+ :breadcrumb="breadcrumb"
25
+ :meta="headerMetaText"
24
26
  :visuallyHidden="!showTitle">
25
27
  <!-- The board's header buttons (`headerButtons`), beside the
26
28
  title. Declaring them takes the Views and Actions menus
@@ -734,6 +736,7 @@
734
736
  :columnOverrides="columnOverrides"
735
737
  :rowClass="rowClass"
736
738
  :rowTitle="rowTitle"
739
+ :showUnread="showUnreadRows"
737
740
  :filterable="tableHeaderFilters"
738
741
  :activeFilters="effectiveActiveFilters"
739
742
  :filterRegister="typeof register === 'string' ? register : ''"
@@ -1609,6 +1612,31 @@ export default {
1609
1612
  required: true,
1610
1613
  },
1611
1614
 
1615
+ /**
1616
+ * A breadcrumb under the title (manifest `config.breadcrumb`): one
1617
+ * crumb `{ label, route?, params?, href?, icon?, separator? }` or a
1618
+ * list; the title follows as the current crumb. Drawn as the detail
1619
+ * header's breadcrumb row. Null (the default) draws none.
1620
+ *
1621
+ * @type {object|Array<object>|null}
1622
+ * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
1623
+ */
1624
+ breadcrumb: {
1625
+ type: [Object, Array],
1626
+ default: null,
1627
+ },
1628
+
1629
+ /**
1630
+ * A note after the breadcrumb and a middle dot (manifest `config.headerMeta`),
1631
+ * such as "{total} open": `{total}` and `{shown}` are the list's counts.
1632
+ *
1633
+ * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
1634
+ */
1635
+ headerMeta: {
1636
+ type: String,
1637
+ default: '',
1638
+ },
1639
+
1612
1640
  /** Optional description shown below the title */
1613
1641
  description: {
1614
1642
  type: String,
@@ -2232,7 +2260,7 @@ export default {
2232
2260
  * can open is an ordinary segment; one it cannot (no board columns, no
2233
2261
  * map configured) is drawn disabled, named "<mode>: not available on
2234
2262
  * this list". Fed from `pages[].config.viewSwitch`. Ignored without the
2235
- * board look, and absent the switch is as before.
2263
+ * board look; absent, the board look draws all four.
2236
2264
  *
2237
2265
  * @type {Array<'table' | 'cards' | 'board' | 'map'>}
2238
2266
  * @spec openspec/changes/screens-view-switch-parity/specs/view-switch-board-look/spec.md#requirement-the-manifest-can-draw-segments-the-page-cannot-open
@@ -3555,6 +3583,8 @@ export default {
3555
3583
  data() {
3556
3584
  return {
3557
3585
  currentViewMode: this.viewMode,
3586
+ /** Whether `?_view=` names a mode the page did not offer yet, to open once it does. */
3587
+ routeViewPending: false,
3558
3588
  /** The column field the reader picked in the board's column switch; '' = the default. */
3559
3589
  boardColumnFieldChoice: '',
3560
3590
  /** The rows a reference column field is built from, by field. */
@@ -4118,11 +4148,14 @@ export default {
4118
4148
  * @spec openspec/changes/screens-view-switch-parity/specs/view-switch-board-look/spec.md#requirement-the-manifest-can-draw-segments-the-page-cannot-open
4119
4149
  */
4120
4150
  viewSwitchDisabledModes() {
4121
- if (!this.isBoardLook || !Array.isArray(this.viewSwitch)) {
4151
+ if (!this.isBoardLook) {
4122
4152
  return []
4123
4153
  }
4154
+ // The index boards draw all four segments, so the board look does
4155
+ // too unless the manifest names its own list.
4156
+ const listed = Array.isArray(this.viewSwitch) ? this.viewSwitch : ['table', 'cards', 'board', 'map']
4124
4157
  const offered = [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])]
4125
- return this.viewSwitch.filter((mode) => ['table', 'cards', 'board', 'map'].includes(mode) && !offered.includes(mode))
4158
+ return listed.filter((mode) => ['table', 'cards', 'board', 'map'].includes(mode) && !offered.includes(mode))
4126
4159
  },
4127
4160
 
4128
4161
  /**
@@ -4160,6 +4193,17 @@ export default {
4160
4193
  })
4161
4194
  },
4162
4195
 
4196
+ /**
4197
+ * The modes `?_view=` can open: the toggle's modes plus the map when
4198
+ * the page offers it.
4199
+ *
4200
+ * @return {Array<string>} The modes.
4201
+ * @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-the-address-opens-a-view-mode-that-arrives-later
4202
+ */
4203
+ routeViewModes() {
4204
+ return [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])]
4205
+ },
4206
+
4163
4207
  /**
4164
4208
  * The column fields the board offers (`board.columnFields`), for the
4165
4209
  * column switch (screens-board-reference-columns).
@@ -5380,6 +5424,21 @@ export default {
5380
5424
  return this.isSelfFetchMode ? this.list.pagination.value : this.pagination
5381
5425
  },
5382
5426
 
5427
+ /**
5428
+ * Whether the table marks unread rows. The boards draw every row alike,
5429
+ * so under the board look only a page that offers the Unread lens
5430
+ * (`personalLenses`) marks them; the Nextcloud look always does.
5431
+ *
5432
+ * @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-unread-rows-are-marked-only-where-the-reader-asked-for-them
5433
+ * @return {boolean} True when unread rows read in bold with the dot.
5434
+ */
5435
+ showUnreadRows() {
5436
+ if (!this.isBoardLook) {
5437
+ return true
5438
+ }
5439
+ return Array.isArray(this.personalLenses) && this.personalLenses.includes('unread')
5440
+ },
5441
+
5383
5442
  /** Resolved schema OBJECT (for column generation / icons / labels). */
5384
5443
  /**
5385
5444
  * Whether the table shows its header filters. A self-fetching page
@@ -6002,8 +6061,9 @@ export default {
6002
6061
  }
6003
6062
  // The board's header: Export reads "Download" with its icon and
6004
6063
  // the Actions menu reads "Actions", without the manifest saying so.
6005
- if (this.isBoardLook && label === '' && button.action === 'export') {
6006
- label = t('nextcloud-vue', 'Download')
6064
+ // A labelled Export keeps the icon too, unless it names its own.
6065
+ if (this.isBoardLook && button.action === 'export') {
6066
+ label = label || t('nextcloud-vue', 'Download')
6007
6067
  icon = icon || 'Download'
6008
6068
  }
6009
6069
  if (label === '' && button.action === 'actions-menu') {
@@ -6286,6 +6346,22 @@ export default {
6286
6346
  * @spec openspec/changes/screens-table-footer-and-system-dates-parity/specs/index-list-board-look/spec.md#requirement-the-count-reads-in-the-users-language
6287
6347
  * @return {string}
6288
6348
  */
6349
+ /**
6350
+ * The header meta note with the list's counts filled in.
6351
+ *
6352
+ * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
6353
+ * @return {string}
6354
+ */
6355
+ headerMetaText() {
6356
+ if (!this.headerMeta) {
6357
+ return ''
6358
+ }
6359
+ const total = this.effectivePagination?.total
6360
+ return this.cnTranslate(this.headerMeta)
6361
+ .replace('{shown}', String(this.effectiveObjects.length))
6362
+ .replace('{total}', typeof total === 'number' ? String(total) : '')
6363
+ },
6364
+
6289
6365
  headerDescription() {
6290
6366
  const total = this.effectivePagination?.total
6291
6367
  // Board look: the count line is a template over the rows on this page
@@ -6476,6 +6552,12 @@ export default {
6476
6552
  this.applyViewModeFromRoute()
6477
6553
  },
6478
6554
 
6555
+ routeViewModes() {
6556
+ if (this.routeViewPending) {
6557
+ this.applyViewModeFromRoute()
6558
+ }
6559
+ },
6560
+
6479
6561
  boardFetchKey: {
6480
6562
  immediate: true,
6481
6563
  handler(key) {
@@ -6908,6 +6990,10 @@ export default {
6908
6990
  */
6909
6991
  async loadFilterCounts() {
6910
6992
  const requestKey = this.countRequestKey
6993
+ if (this.isNamedSource) {
6994
+ await this.loadNamedSourceCounts(requestKey)
6995
+ return
6996
+ }
6911
6997
  const schema = this.exportSchemaSlug
6912
6998
  if (requestKey === '' || !this.register || !schema) {
6913
6999
  this.tabCounts = null
@@ -6939,6 +7025,46 @@ export default {
6939
7025
  this.savedViewCounts = viewCounts
6940
7026
  },
6941
7027
 
7028
+ /**
7029
+ * The quick-filter counts of a named source (`entitySource`): each tab
7030
+ * that asks is counted by the source's own `count(config)`, with the
7031
+ * tab's filter over `sourceConfig`. A source without `count` shows
7032
+ * none; a failed count leaves that tab without a number.
7033
+ *
7034
+ * @param {string} requestKey The `countRequestKey` this load answers.
7035
+ * @return {Promise<void>}
7036
+ * @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-a-named-source-counts-its-quick-filters
7037
+ */
7038
+ async loadNamedSourceCounts(requestKey) {
7039
+ const count = this.namedSource ? this.namedSource.count : null
7040
+ if (requestKey === '' || typeof count !== 'function') {
7041
+ this.tabCounts = null
7042
+ this.savedViewCounts = null
7043
+ return
7044
+ }
7045
+ const params = (this.$route && this.$route.params) || {}
7046
+ const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {}
7047
+ const tabCounts = {}
7048
+ await Promise.all(this.countEntries
7049
+ .filter((entry) => entry.key.startsWith('tab:'))
7050
+ .map(async (entry) => {
7051
+ const config = resolveFilterMap({ ...(this.sourceConfig || {}), ...(entry.filter || {}) }, params, ctx)
7052
+ try {
7053
+ const total = await count(config)
7054
+ if (Number.isFinite(total)) {
7055
+ tabCounts[Number(entry.key.slice(4))] = total
7056
+ }
7057
+ } catch {
7058
+ // No number for this tab.
7059
+ }
7060
+ }))
7061
+ if (requestKey !== this.countRequestKey) {
7062
+ return
7063
+ }
7064
+ this.tabCounts = tabCounts
7065
+ this.savedViewCounts = null
7066
+ },
7067
+
6942
7068
  // ── Split view (case-page-and-list-as-a-place) ──────────────────
6943
7069
 
6944
7070
  /**
@@ -9203,6 +9329,7 @@ export default {
9203
9329
  * @param {string} mode 'table' or 'cards'
9204
9330
  */
9205
9331
  onViewModeChange(mode) {
9332
+ this.routeViewPending = false
9206
9333
  this.currentViewMode = mode
9207
9334
  this.$emit('view-mode-change', mode)
9208
9335
  },
@@ -9212,16 +9339,26 @@ export default {
9212
9339
  * offers it. The key starts with `_`, like `_search` and `_page`, so it
9213
9340
  * is never read as a list filter. A menu entry's `query` can so open a
9214
9341
  * list as its board, on the one page that lists the schema
9215
- * (screens-board-reference-columns).
9342
+ * (screens-board-reference-columns). A mode the page offers only later
9343
+ * (the map, once rows with a geometry are in) opens when it arrives,
9344
+ * unless the reader picked a view first.
9216
9345
  *
9217
9346
  * @return {void}
9218
9347
  * @spec openspec/changes/screens-board-reference-columns/specs/board-view/spec.md#requirement-the-address-can-open-a-list-in-a-view-mode
9348
+ * @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-the-address-opens-a-view-mode-that-arrives-later
9219
9349
  */
9220
9350
  applyViewModeFromRoute() {
9221
9351
  const wanted = this.$route?.query?._view
9222
- if (typeof wanted === 'string' && wanted !== this.currentViewMode && this.effectiveToggleModes.includes(wanted)) {
9352
+ if (typeof wanted !== 'string' || wanted === '' || wanted === this.currentViewMode) {
9353
+ this.routeViewPending = false
9354
+ return
9355
+ }
9356
+ if (this.routeViewModes.includes(wanted)) {
9223
9357
  this.currentViewMode = wanted
9358
+ this.routeViewPending = false
9359
+ return
9224
9360
  }
9361
+ this.routeViewPending = true
9225
9362
  },
9226
9363
 
9227
9364
  /**
@@ -5,7 +5,7 @@
5
5
 
6
6
  <template>
7
7
  <div class="cn-nc-widget-widget" :class="{ 'cn-nc-widget-widget--horizontal': displayMode === 'horizontal' }">
8
- <header class="cn-nc-widget-widget__header">
8
+ <header v-if="showsHeader" class="cn-nc-widget-widget__header" data-testid="cn-nc-widget-header">
9
9
  <img
10
10
  v-if="widgetIconUrl"
11
11
  class="cn-nc-widget-widget__header-icon"
@@ -130,9 +130,16 @@ function getDashboardGlobal() {
130
130
  export default {
131
131
  name: 'CnNcWidgetWidget',
132
132
 
133
+ inject: {
134
+ /** Whether the hosting card draws a title row (CnWidgetWrapper). */
135
+ cnWidgetTitled: { default: null },
136
+ },
137
+
133
138
  props: {
134
139
  /**
135
- * Persisted widget content `{widgetId, displayMode, limit?, showAllUrl?, showAllLabel?}`.
140
+ * Persisted widget content `{widgetId, displayMode, limit?, showAllUrl?, showAllLabel?, showHeader?}`.
141
+ * `showHeader: false` drops the widget's own header (icon and title);
142
+ * it is dropped too when the hosting card draws a title row.
136
143
  * `limit` is the most items shown (the list then comes from the items
137
144
  * API, so it fits its cell); `showAllUrl` / `showAllLabel` the link to
138
145
  * the rest (default the widget's own page and "Show all").
@@ -207,6 +214,20 @@ export default {
207
214
  return null
208
215
  },
209
216
 
217
+ /**
218
+ * Whether the widget draws its own header: not with
219
+ * `content.showHeader: false`, nor inside a card that draws a title.
220
+ *
221
+ * @spec openspec/changes/nc-widget-header/specs/nc-widget-header/spec.md#requirement-a-nextcloud-widget-draws-no-second-heading
222
+ * @return {boolean}
223
+ */
224
+ showsHeader() {
225
+ if (this.content && this.content.showHeader === false) {
226
+ return false
227
+ }
228
+ return !(typeof this.cnWidgetTitled === 'function' && this.cnWidgetTitled())
229
+ },
230
+
210
231
  /**
211
232
  * The header title.
212
233
  *
@@ -1,5 +1,8 @@
1
1
  <template>
2
2
  <NcAppSettingsSection id="notifications" :name="t('nextcloud-vue', 'Notifications')">
3
+ <template v-if="cnLook === 'board'" #icon>
4
+ <BellOutline :size="18" />
5
+ </template>
3
6
  <NcLoadingIcon v-if="loading" :size="24" />
4
7
 
5
8
  <NcEmptyContent v-else-if="error"
@@ -107,6 +110,8 @@ export default {
107
110
  * than an always-empty pane.
108
111
  */
109
112
  cnAppId: { default: () => '' },
113
+ /** The app's look; `board` gives the section its navigation icon. */
114
+ cnLook: { default: 'nextcloud' },
110
115
  /**
111
116
  * App-supplied labels for its notification rules, provided by the
112
117
  * CnAppRoot ancestor from its `notificationLabels` prop. Keyed
@@ -0,0 +1,140 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <div
7
+ v-if="crumbs.length > 0 || meta"
8
+ class="cn-header-trail cn-detail-page__header-row2"
9
+ data-testid="cn-header-trail">
10
+ <CnBreadcrumbs
11
+ v-if="crumbs.length > 0"
12
+ class="cn-detail-page__breadcrumbs cn-detail-page__breadcrumbs--row2"
13
+ :crumbs="crumbs"
14
+ :rootText="crumbs[0].icon === undefined"
15
+ :separator="separator"
16
+ data-testid="cn-header-trail-breadcrumbs" />
17
+ <span
18
+ v-if="crumbs.length > 0 && meta"
19
+ class="cn-detail-page__header-dot"
20
+ aria-hidden="true">&middot;</span>
21
+ <span
22
+ v-if="meta"
23
+ class="cn-detail-page__header-meta"
24
+ data-testid="cn-header-trail-meta">{{ meta }}</span>
25
+ </div>
26
+ </template>
27
+
28
+ <script>
29
+ import { CnBreadcrumbs } from '../CnBreadcrumbs/index.js'
30
+
31
+ /**
32
+ * The breadcrumb and meta line under a page title: "Instellingen / Zaaktypen
33
+ * · 12 zaaktypen". It draws the same markup and classes as the board look's
34
+ * detail header row, so a list, dashboard or settings page carries the
35
+ * detail page's breadcrumb style rather than one of its own.
36
+ *
37
+ * `breadcrumb` is one crumb or a list, each `{ label, route?, params?, href?,
38
+ * icon?, separator? }`. The page title follows as the current crumb.
39
+ */
40
+ export default {
41
+ name: 'CnHeaderTrail',
42
+
43
+ components: { CnBreadcrumbs },
44
+
45
+ inject: {
46
+ cnTranslate: { default: () => (key) => key },
47
+ },
48
+
49
+ props: {
50
+ /**
51
+ * The crumbs before the current page, one object or a list.
52
+ *
53
+ * @type {object|Array<object>|null}
54
+ * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
55
+ */
56
+ breadcrumb: {
57
+ type: [Object, Array],
58
+ default: null,
59
+ },
60
+
61
+ /** The current crumb: the page title, already translated. */
62
+ current: {
63
+ type: String,
64
+ default: '',
65
+ },
66
+
67
+ /**
68
+ * The note after the crumbs and a middle dot, already resolved.
69
+ *
70
+ * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
71
+ */
72
+ meta: {
73
+ type: String,
74
+ default: '',
75
+ },
76
+
77
+ /**
78
+ * Translate function for the crumb labels; falls back to the injected
79
+ * `cnTranslate`.
80
+ *
81
+ * @type {((key: string) => string)|null}
82
+ */
83
+ translate: {
84
+ type: Function,
85
+ default: null,
86
+ },
87
+ },
88
+
89
+ computed: {
90
+ /**
91
+ * The declared crumbs that carry a label.
92
+ *
93
+ * @return {Array<object>}
94
+ */
95
+ declared() {
96
+ const list = Array.isArray(this.breadcrumb) ? this.breadcrumb : (this.breadcrumb ? [this.breadcrumb] : [])
97
+ return list.filter((crumb) => crumb && typeof crumb.label === 'string' && crumb.label !== '')
98
+ },
99
+
100
+ /**
101
+ * The crumbs for CnBreadcrumbs: each declared crumb as a link, then the
102
+ * current page. Empty without a declared crumb.
103
+ *
104
+ * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
105
+ * @return {Array<object>}
106
+ */
107
+ crumbs() {
108
+ if (this.declared.length === 0) {
109
+ return []
110
+ }
111
+ const out = this.declared.map((crumb) => {
112
+ const entry = { label: (this.translate ?? this.cnTranslate)(crumb.label) }
113
+ if (typeof crumb.icon === 'string' && crumb.icon !== '') {
114
+ entry.icon = crumb.icon
115
+ }
116
+ if (crumb.route) {
117
+ entry.to = { name: crumb.route, ...(crumb.params ? { params: crumb.params } : {}) }
118
+ } else if (crumb.href) {
119
+ entry.href = crumb.href
120
+ }
121
+ return entry
122
+ })
123
+ if (this.current) {
124
+ out.push({ label: this.current })
125
+ }
126
+ return out
127
+ },
128
+
129
+ /**
130
+ * The text between the crumbs: the first crumb's `separator`, else `/`.
131
+ *
132
+ * @return {string}
133
+ */
134
+ separator() {
135
+ const declared = this.declared[0] && this.declared[0].separator
136
+ return typeof declared === 'string' && declared !== '' ? declared : '/'
137
+ },
138
+ },
139
+ }
140
+ </script>
@@ -81,3 +81,14 @@ With extra content (e.g., a badge or status indicator):
81
81
  </template>
82
82
  </CnPageHeader>
83
83
  ```
84
+
85
+ Breadcrumb and meta: `breadcrumb` (one crumb `{ label, route?, params?, href?, icon?, separator? }` or a list) draws a
86
+ line under the title in the detail header's breadcrumb style, with the title as the current crumb; `meta` follows after a
87
+ middle dot:
88
+
89
+ ```vue
90
+ <CnPageHeader
91
+ title="Zaaktypen"
92
+ :breadcrumb="{ label: 'Instellingen', href: '#instellingen' }"
93
+ meta="14 zaaktypen" />
94
+ ```
@@ -13,6 +13,12 @@
13
13
  <h1 class="cn-page-header__title" data-testid="cn-page-title">
14
14
  {{ resolvedTitle }}
15
15
  </h1>
16
+ <CnHeaderTrail
17
+ v-if="!visuallyHidden"
18
+ :breadcrumb="breadcrumb"
19
+ :current="resolvedTitle"
20
+ :meta="resolvedMeta"
21
+ :translate="effectiveTranslate" />
16
22
  <p v-if="description && !visuallyHidden" class="cn-page-header__description" data-testid="cn-page-description">
17
23
  {{ resolvedDescription }}
18
24
  </p>
@@ -22,6 +28,7 @@
22
28
  </template>
23
29
 
24
30
  <script>
31
+ import CnHeaderTrail from './CnHeaderTrail.vue'
25
32
  import { CnIcon } from '../CnIcon/index.js'
26
33
 
27
34
  /**
@@ -64,6 +71,7 @@ export default {
64
71
  name: 'CnPageHeader',
65
72
 
66
73
  components: {
74
+ CnHeaderTrail,
67
75
  CnIcon,
68
76
  },
69
77
 
@@ -90,6 +98,31 @@ export default {
90
98
  default: '',
91
99
  },
92
100
 
101
+ /**
102
+ * A breadcrumb under the title: one crumb or a list, each
103
+ * `{ label, route?, params?, href?, icon?, separator? }`. The title
104
+ * follows as the current crumb. Drawn as the board look's detail
105
+ * header row.
106
+ *
107
+ * @type {object|Array<object>|null}
108
+ * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
109
+ */
110
+ breadcrumb: {
111
+ type: [Object, Array],
112
+ default: null,
113
+ },
114
+
115
+ /**
116
+ * A note after the breadcrumb and a middle dot, such as "12 open". Run
117
+ * through the host translate function.
118
+ *
119
+ * @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
120
+ */
121
+ meta: {
122
+ type: String,
123
+ default: '',
124
+ },
125
+
93
126
  /** Optional MDI icon name (rendered via CnIcon) */
94
127
  icon: {
95
128
  type: String,
@@ -153,6 +186,15 @@ export default {
153
186
  resolvedDescription() {
154
187
  return this.description ? this.effectiveTranslate(this.description) : this.description
155
188
  },
189
+
190
+ /**
191
+ * The meta note run through the host translate function.
192
+ *
193
+ * @return {string}
194
+ */
195
+ resolvedMeta() {
196
+ return this.meta ? this.effectiveTranslate(this.meta) : ''
197
+ },
156
198
  },
157
199
  }
158
200
  </script>
@@ -8,13 +8,13 @@
8
8
  Board variant (`variant: "board"`): the footer of the table card as the
9
9
  screens draw it. The count text at the start, numbered 34px links at
10
10
  the end, "Previous" only after the first page and "Next" only before
11
- the last. No First, no Last, no page-size select.
11
+ the last (inert on a single page). No First, no Last, no page-size select.
12
12
  -->
13
13
  <template v-if="isBoardVariant">
14
14
  <span class="cn-pagination__page-info cn-pagination__board-count" data-testid="cn-pagination-board-count">
15
15
  {{ boardCountText }}
16
16
  </span>
17
- <nav v-if="totalPages > 1" class="cn-pagination__board-pages" :aria-label="t('nextcloud-vue', 'Pagination')">
17
+ <nav class="cn-pagination__board-pages" :aria-label="t('nextcloud-vue', 'Pagination')">
18
18
  <button
19
19
  v-if="currentPage > 1"
20
20
  type="button"
@@ -43,6 +43,14 @@
43
43
  @click="changePage(currentPage + 1)">
44
44
  {{ nextLabel }}
45
45
  </button>
46
+ <!-- A single page still draws its "1" and an inert Next, as the boards do. -->
47
+ <span
48
+ v-else-if="totalPages <= 1"
49
+ class="cn-pagination__board-link cn-pagination__board-link--wide cn-pagination__board-link--disabled"
50
+ aria-disabled="true"
51
+ data-testid="cn-pagination-next">
52
+ {{ nextLabel }}
53
+ </span>
46
54
  </nav>
47
55
  </template>
48
56
 
@@ -727,9 +727,9 @@ export default {
727
727
  },
728
728
 
729
729
  /**
730
- * The row's due text (`due`: `{ field, todayLabel?, overdueLabel?, emptyText? }`):
731
- * "3 dagen te laat" in the danger tone, "Vandaag", the date otherwise,
732
- * `emptyText` when the field is empty.
730
+ * The row's due text (`due`: `{ field, todayLabel?, overdueLabel?, upcomingLabel?, upcomingWeeksLabel?, upcomingWeeksFrom?, emptyText? }`):
731
+ * "3 dagen te laat" in the danger tone, "Vandaag", the date or the time
732
+ * left ("nog 4 dagen") otherwise, `emptyText` when the field is empty.
733
733
  *
734
734
  * @param {object} row The row.
735
735
  * @return {{text: string, tone: string}|null} The text and tone, or null.
@@ -69,9 +69,7 @@
69
69
  *
70
70
  * Spec: features-roadmap-component — Requirement "RoadmapTab".
71
71
  */
72
- import axios from '@nextcloud/axios'
73
72
  import { translate as t } from '@nextcloud/l10n'
74
- import { generateUrl } from '@nextcloud/router'
75
73
  import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
76
74
  import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
77
75
  import ClockOutline from 'vue-material-design-icons/ClockOutline.vue'
@@ -79,6 +77,7 @@ import InformationOutline from 'vue-material-design-icons/InformationOutline.vue
79
77
  import KeyOutline from 'vue-material-design-icons/KeyOutline.vue'
80
78
  import RoadVariant from 'vue-material-design-icons/RoadVariant.vue'
81
79
  import CnRoadmapItem from '../CnRoadmapItem/CnRoadmapItem.vue'
80
+ import { fetchRoadmapIssues, sortByReactions } from '../../utils/roadmapIssues.js'
82
81
 
83
82
  export default {
84
83
  name: 'CnRoadmapTab',
@@ -124,7 +123,7 @@ export default {
124
123
 
125
124
  computed: {
126
125
  sortedItems() {
127
- return [...this.items].sort((a, b) => (b.reactions?.total_count || 0) - (a.reactions?.total_count || 0))
126
+ return sortByReactions(this.items)
128
127
  },
129
128
 
130
129
  disabledLabel() {
@@ -175,31 +174,10 @@ export default {
175
174
  this.loading = true
176
175
  this.degraded = null
177
176
  this.items = []
178
- try {
179
- const url = generateUrl('/apps/openregister/api/github/issues')
180
- const response = await axios.get(url, {
181
- params: {
182
- repo: this.repo,
183
- labels: 'enhancement,feature',
184
- },
185
- })
186
- const data = response.data || {}
187
- if (data.hint === 'github_pat_not_configured') {
188
- this.degraded = 'pat_not_configured'
189
- } else if (Array.isArray(data.items)) {
190
- this.items = data.items
191
- }
192
- } catch (e) {
193
- const status = e?.response?.status
194
- const code = e?.response?.data?.error
195
- if (status === 429 || code === 'github_rate_limited' || code === 'rate_limited') {
196
- this.degraded = 'rate_limited'
197
- } else {
198
- this.degraded = 'error'
199
- }
200
- } finally {
201
- this.loading = false
202
- }
177
+ const { items, degraded } = await fetchRoadmapIssues(this.repo)
178
+ this.items = items
179
+ this.degraded = degraded
180
+ this.loading = false
203
181
  },
204
182
  },
205
183
  }