@conduction/nextcloud-vue 2.82.0 → 2.84.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 (178) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +2 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +24 -0
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +9 -4
  7. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
  10. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBodySections/CnBodySections.vue.js +1 -1
  13. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
  14. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
  15. package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  17. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
  18. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  19. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  21. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +125 -3
  22. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
  24. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +14 -0
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
  30. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
  31. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
  32. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +203 -4
  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/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  37. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
  38. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
  40. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
  42. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
  43. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
  44. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +96 -12
  46. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +95 -50
  48. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
  50. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  51. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  52. package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js +2 -2
  53. package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js.map +1 -1
  54. package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js +2 -2
  55. package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js.map +1 -1
  56. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  57. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +31 -2
  58. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  60. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +1 -1
  61. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +16 -2
  62. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +3 -3
  64. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  66. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +37 -2
  67. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  68. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  69. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  70. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +176 -0
  71. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  72. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +181 -52
  73. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  74. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +1 -1
  75. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +70 -5
  76. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +61 -35
  78. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -1
  79. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue.js +1 -1
  80. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
  81. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  82. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
  83. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  84. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  85. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +4 -2
  86. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  87. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  88. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
  89. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +72 -5
  90. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  91. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +88 -71
  92. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  93. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +1 -1
  94. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +18 -4
  95. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -1
  96. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -1
  97. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  98. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +33 -3
  99. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  100. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  101. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue.js +1 -1
  102. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
  103. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  104. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  105. package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
  106. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
  107. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
  108. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
  109. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
  110. package/dist/esm/l10n/en.json.js +16 -1
  111. package/dist/esm/l10n/en.json.js.map +1 -1
  112. package/dist/esm/l10n/nl.json.js +41 -4
  113. package/dist/esm/l10n/nl.json.js.map +1 -1
  114. package/dist/esm/library-style.js +1 -1
  115. package/dist/esm/nextcloud-vue.css +900 -537
  116. package/dist/esm/utils/buildManifest.js +37 -3
  117. package/dist/esm/utils/buildManifest.js.map +1 -1
  118. package/dist/esm/utils/detailWidgetVisibility.js +149 -0
  119. package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
  120. package/dist/esm/utils/fetchAggregate.js +25 -2
  121. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  122. package/dist/esm/utils/formatMetric.js +39 -3
  123. package/dist/esm/utils/formatMetric.js.map +1 -1
  124. package/dist/esm/utils/resolveManifestTokens.js +1 -0
  125. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  126. package/dist/esm/utils/sentinelTokens.js +9 -0
  127. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  128. package/dist/esm/utils/switchValues.js +62 -0
  129. package/dist/esm/utils/switchValues.js.map +1 -0
  130. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  131. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  132. package/dist/nextcloud-vue.cjs.js +36711 -34909
  133. package/dist/nextcloud-vue.cjs.js.map +2 -2
  134. package/dist/nextcloud-vue.css +900 -537
  135. package/l10n/en.json +16 -1
  136. package/l10n/nl.json +41 -4
  137. package/package.json +1 -1
  138. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +31 -2
  139. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
  140. package/src/components/CnBodySections/CnBodySections.vue +22 -1
  141. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
  142. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +128 -6
  143. package/src/components/CnDashboardPage/CnDashboardPage.vue +14 -0
  144. package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
  145. package/src/components/CnDetailPage/CnDetailPage.md +2 -0
  146. package/src/components/CnDetailPage/CnDetailPage.vue +203 -4
  147. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
  148. package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
  149. package/src/components/CnIndexPage/CnIndexPage.vue +125 -15
  150. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
  151. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
  152. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +31 -2
  153. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +54 -3
  154. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +37 -2
  155. package/src/components/CnReportsPage/CnReportsPage.vue +263 -5
  156. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +110 -18
  157. package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
  158. package/src/components/CnStatWidget/CnStatWidget.vue +4 -2
  159. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +125 -7
  160. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +18 -4
  161. package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -3
  162. package/src/components/CnWalkthrough/CnWalkthrough.vue +16 -0
  163. package/src/css/form-field.css +14 -0
  164. package/src/css/form-widget.css +11 -0
  165. package/src/css/index-page.css +11 -0
  166. package/src/css/index.css +1 -0
  167. package/src/css/look-board-reports.css +175 -0
  168. package/src/css/look-board.css +44 -1
  169. package/src/dialogs/CnTransitionInputDialog.vue +87 -7
  170. package/src/schemas/app-manifest-v2.schema.json +24 -3
  171. package/src/utils/buildManifest.js +36 -2
  172. package/src/utils/detailWidgetVisibility.js +145 -0
  173. package/src/utils/fetchAggregate.js +24 -1
  174. package/src/utils/formatMetric.js +38 -2
  175. package/src/utils/resolveManifestTokens.js +1 -0
  176. package/src/utils/sentinelTokens.js +9 -0
  177. package/src/utils/switchValues.js +59 -0
  178. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -119,9 +119,12 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
119
119
  createVNode(_component_CnBuildiqEditButton)
120
120
  ])
121
121
  ])),
122
+ ($options.versionCardAtEnd)
123
+ ? renderSlot(_ctx.$slots, "default", {}, undefined, undefined, 3)
124
+ : createCommentVNode("v-if", true),
122
125
  ($props.showVersionCard)
123
126
  ? (openBlock(), createBlock(_component_CnVersionInfoCard, {
124
- key: 3,
127
+ key: 4,
125
128
  appName: $props.appName,
126
129
  appVersion: $options.resolvedVersion,
127
130
  configuredVersion: $options.resolvedConfiguredVersion,
@@ -220,7 +223,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
220
223
  : createCommentVNode("v-if", true),
221
224
  ($props.showOrganisationCredentials)
222
225
  ? (openBlock(), createBlock(_component_CnCredentials, {
223
- key: 4,
226
+ key: 5,
224
227
  scope: "organisation",
225
228
  appId: $props.appId,
226
229
  appName: $props.appName,
@@ -228,10 +231,12 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
228
231
  "data-testid": "cn-admin-organisation-credentials"
229
232
  }, null, 8 /* PROPS */, ["appId", "appName", "appCredentials"]))
230
233
  : createCommentVNode("v-if", true),
231
- renderSlot(_ctx.$slots, "default"),
234
+ (!$options.versionCardAtEnd)
235
+ ? renderSlot(_ctx.$slots, "default", {}, undefined, undefined, 6)
236
+ : createCommentVNode("v-if", true),
232
237
  ($props.showSetup && $data.setupWizardOpen)
233
238
  ? (openBlock(), createBlock(_component_CnSetupWizard, {
234
- key: 5,
239
+ key: 7,
235
240
  appId: $props.appId,
236
241
  steps: $props.setupSteps,
237
242
  onComplete: _cache[1] || (_cache[1] = $event => ($data.setupWizardOpen = false)),
@@ -1 +1 @@
1
- {"version":3,"file":"CnAdminSettingsShell.vue3.js","sources":["../../../../src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue?vue&type=template&id=533044f8&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-admin-settings-shell\" :class=\"ownLookClass\">\n\t\t<!-- Names a development, test or acceptance environment; cannot be dismissed. Production shows nothing. -->\n\t\t<CnEnvironmentBanner v-if=\"environmentName\" :environment=\"environmentName\" />\n\n\t\t<!-- Page title header — the uniform \"<App> Settings / Configure your <App> installation\" block.\n\t\t Uses NcSettingsSection directly (not CnSettingsSection) so the description subtitle and\n\t\t doc-url icon render — CnSettingsSection repurposes those props and does not forward them. -->\n\t\t<NcSettingsSection\n\t\t\tv-if=\"!isBoard\"\n\t\t\t:name=\"resolvedTitle\"\n\t\t\t:description=\"resolvedDescription\"\n\t\t\t:docUrl=\"docUrl\" />\n\n\t\t<!-- The board header: an h1 and its description, then the labelled\n\t\t Documentation button and the buildiq square. No documentation icon\n\t\t beside the title. -->\n\t\t<div v-else class=\"cn-admin-settings-shell__header\" data-testid=\"cn-admin-settings-header\">\n\t\t\t<div class=\"cn-admin-settings-shell__heading\">\n\t\t\t\t<h1 class=\"cn-admin-settings-shell__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h1>\n\t\t\t\t<!-- `breadcrumbs`: the board's path line in place of the\n\t\t\t\t description (\"Beheerinstellingen / Launchpad / Algemeen ·\n\t\t\t\t geldt voor de hele organisatie\"). -->\n\t\t\t\t<div v-if=\"crumbs.length > 0\" class=\"cn-admin-settings-shell__crumbs\" data-testid=\"cn-admin-settings-breadcrumbs\">\n\t\t\t\t\t<nav class=\"cn-admin-settings-shell__crumb-list\" :aria-label=\"breadcrumbLabel\">\n\t\t\t\t\t\t<template v-for=\"(crumb, index) in crumbs\" :key=\"index\">\n\t\t\t\t\t\t\t<span v-if=\"index > 0\" aria-hidden=\"true\">/</span>\n\t\t\t\t\t\t\t<a v-if=\"crumb.href\" :href=\"crumb.href\" class=\"cn-admin-settings-shell__crumb-link\">{{ crumb.label }}</a>\n\t\t\t\t\t\t\t<span v-else :aria-current=\"index === crumbs.length - 1 ? 'page' : null\">{{ crumb.label }}</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</nav>\n\t\t\t\t\t<template v-if=\"breadcrumbNote\">\n\t\t\t\t\t\t<span aria-hidden=\"true\">·</span>\n\t\t\t\t\t\t<span>{{ breadcrumbNote }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t\t<p v-else class=\"cn-admin-settings-shell__description\">\n\t\t\t\t\t{{ resolvedDescription }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-admin-settings-shell__header-buttons\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"docUrl\"\n\t\t\t\t\t:href=\"docUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\tdata-testid=\"cn-admin-settings-documentation\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<BookOpenVariant :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ documentationLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<CnBuildiqEditButton />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Version information card (version, up-to-date check, re-import, support footer) -->\n\t\t<CnVersionInfoCard\n\t\t\tv-if=\"showVersionCard\"\n\t\t\t:appName=\"appName\"\n\t\t\t:appVersion=\"resolvedVersion\"\n\t\t\t:configuredVersion=\"resolvedConfiguredVersion\"\n\t\t\t:isUpToDate=\"resolvedIsUpToDate\"\n\t\t\t:showUpdateButton=\"showUpdateButton\"\n\t\t\t:updating=\"updating\"\n\t\t\t:title=\"versionTitle\"\n\t\t\t:description=\"resolvedVersionDescription\"\n\t\t\t@update=\"onUpdate\">\n\t\t\t<template #actions>\n\t\t\t\t<!-- Canonical \"Re-import configuration\" action (POST /apps/<appId>/api/settings/load) -->\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showReimport\"\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"reimporting\"\n\t\t\t\t\t@click=\"reimport\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"reimporting\" :size=\"20\" />\n\t\t\t\t\t\t<Refresh v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ reimporting ? importingLabel : reimportLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- Run the first-time setup wizard on demand (ADR-042). -->\n\t\t\t\t<NcButton v-if=\"showSetup\" data-testid=\"cn-admin-run-setup\" @click=\"setupWizardOpen = true\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<AutoFix :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ setupLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- \"Help us / suggest a feature\" — a direct link to the forge's\n\t\t\t\t feature-request issue form (team decision 2026-09-04: the\n\t\t\t\t in-product suggestion modal is gone; the forge is where\n\t\t\t\t the conversation happens, in English). -->\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showHelp && helpUrl\"\n\t\t\t\t\t:href=\"helpUrl\"\n\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<HelpCircleOutline :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ helpLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- @slot actions Extra action buttons rendered next to the built-in update / re-import buttons in the version card header. -->\n\t\t\t\t<slot name=\"actions\" />\n\t\t\t</template>\n\n\t\t\t<template #footer>\n\t\t\t\t<!-- @slot footer Replaces the default support/SLA footer inside the version card. -->\n\t\t\t\t<slot name=\"footer\">\n\t\t\t\t\t<div v-if=\"showSupport\" class=\"cn-support-info\">\n\t\t\t\t\t\t<h4>{{ supportTitle }}</h4>\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t{{ supportLine }}\n\t\t\t\t\t\t\t<a :href=\"`mailto:${supportEmail}`\">{{ supportEmail }}</a>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"slaEmail\">\n\t\t\t\t\t\t\t{{ slaLine }}\n\t\t\t\t\t\t\t<a :href=\"`mailto:${slaEmail}`\">{{ slaEmail }}</a>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</slot>\n\t\t\t</template>\n\n\t\t\t<template #additional-items>\n\t\t\t\t<!-- @slot version-items Extra key/value rows appended inside the version card details. -->\n\t\t\t\t<slot name=\"version-items\" />\n\t\t\t</template>\n\t\t</CnVersionInfoCard>\n\n\t\t<!--\n\t\t\tOrganisation credential broker (ADR-079 Step 2).\n\n\t\t\tThis is the ONE home for org-wide credentials. It used to live in a\n\t\t\tgeneric modal on CnAppRoot, rendered from `manifest.adminSettings[]`\n\t\t\tand gated on app ownership — but no app ever declared that array, so\n\t\t\tthe surface was unreachable in every app in the fleet. It belongs\n\t\t\there: an org-wide secret store is app-level configuration, and\n\t\t\t/settings/admin/<app> is authorized by Nextcloud server-side rather\n\t\t\tthan by a client-side flag.\n\n\t\t\tOpt-in, because only an app that actually brokers credentials should\n\t\t\tshow it — the manifest `credentials[]` declarations drive the\n\t\t\tinformational \"apps requesting credentials\" list inside.\n\t\t-->\n\t\t<CnCredentials\n\t\t\tv-if=\"showOrganisationCredentials\"\n\t\t\tscope=\"organisation\"\n\t\t\t:appId=\"appId\"\n\t\t\t:appName=\"appName\"\n\t\t\t:appCredentials=\"appCredentials\"\n\t\t\tdata-testid=\"cn-admin-organisation-credentials\" />\n\n\t\t<!-- @slot default The app's own settings sections, rendered below the version card. -->\n\t\t<slot />\n\n\t\t<!-- First-time setup wizard, opened from the admin page (ADR-042). -->\n\t\t<CnSetupWizard\n\t\t\tv-if=\"showSetup && setupWizardOpen\"\n\t\t\t:appId=\"appId\"\n\t\t\t:steps=\"setupSteps\"\n\t\t\t@complete=\"setupWizardOpen = false\"\n\t\t\t@close=\"setupWizardOpen = false\" />\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { showError, showSuccess } from '@nextcloud/dialogs'\nimport { loadState } from '@nextcloud/initial-state'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcLoadingIcon, NcSettingsSection } from '@nextcloud/vue'\nimport { computed } from 'vue'\nimport AutoFix from 'vue-material-design-icons/AutoFix.vue'\nimport BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'\nimport HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'\nimport Refresh from 'vue-material-design-icons/Refresh.vue'\nimport CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'\nimport CnCredentials from '../CnCredentials/CnCredentials.vue'\nimport CnEnvironmentBanner from '../CnEnvironmentBanner/CnEnvironmentBanner.vue'\nimport CnSetupWizard from '../CnSetupWizard/CnSetupWizard.vue'\nimport { useEnvironment } from '../../composables/useEnvironment.js'\nimport { normalizeLook, useLook } from '../../composables/useLook.js'\nimport { buildFeatureRequestUrl } from '../../utils/forge.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { CnVersionInfoCard } from '../CnVersionInfoCard/index.js'\n\n/**\n * CnAdminSettingsShell — the canonical chrome for a Conduction app's Nextcloud\n * admin settings page.\n *\n * Renders, in fleet-standard order:\n * 1. a page title header (\"<App> Settings\" / \"Configure your <App> installation\" + doc-url),\n * 2. a CnVersionInfoCard (version from `loadState`, up-to-date badge, a\n * \"Re-import configuration\" button wired to the AppHost endpoint\n * `POST /apps/<appId>/api/settings/load`, and a support/SLA footer),\n * 3. the app's own settings sections (default slot).\n *\n * Every app's `AdminRoot.vue` collapses to a thin wrapper so the chrome can\n * never drift between apps again.\n *\n * Basic usage\n * ```vue\n * <CnAdminSettingsShell\n * app-id=\"shillinq\"\n * app-name=\"Shillinq\"\n * doc-url=\"https://shillinq.conduction.nl/docs/intro\">\n * <MyRegisterMapping />\n * <MyFeatureSettings />\n * </CnAdminSettingsShell>\n * ```\n *\n * Real up-to-date check (when the backend exposes it)\n * ```vue\n * <CnAdminSettingsShell\n * app-id=\"opencatalogi\" app-name=\"OpenCatalogi\"\n * :app-version=\"running\" :configured-version=\"configured\"\n * :is-up-to-date=\"running === configured\" />\n * ```\n */\nexport default {\n\tname: 'CnAdminSettingsShell',\n\n\tcomponents: {\n\t\tCnEnvironmentBanner,\n\t\tCnBuildiqEditButton,\n\t\tBookOpenVariant,\n\t\tNcSettingsSection,\n\t\tCnCredentials,\n\t\tCnVersionInfoCard,\n\t\tCnSetupWizard,\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tRefresh,\n\t\tAutoFix,\n\t\tHelpCircleOutline,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The environment this instance is: `development`, `test`, `acceptance` or\n\t\t * `production`. Wins over the active organisation's `environment` field.\n\t\t * A non-production environment is named in a banner at the top and the tab\n\t\t * title gets a prefix. Production, an unknown value or no value shows nothing.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tenvironment: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Render the organisation credential broker on this admin page\n\t\t * (ADR-079 Step 2). Opt-in: only an app that actually brokers org-wide\n\t\t * secrets should surface it.\n\t\t *\n\t\t * This replaces the generic `manifest.adminSettings[]` modal that used\n\t\t * to live on CnAppRoot. That modal was gated on app OWNERSHIP and on an\n\t\t * array no app in the fleet ever declared, so the broker was\n\t\t * unreachable everywhere. Here it is reachable and the access decision\n\t\t * is Nextcloud's, made server-side for /settings/admin/<app>.\n\t\t */\n\t\tshowOrganisationCredentials: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * The app's manifest `credentials[]` declarations, forwarded to the\n\t\t * broker to drive its informational \"apps requesting credentials\"\n\t\t * list. Only read when `showOrganisationCredentials` is true.\n\t\t */\n\t\tappCredentials: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The look the shell is drawn in: `board` or `nextcloud`. Empty follows\n\t\t * the `cnLook` that CnAppRoot provides.\n\t\t *\n\t\t * @spec openspec/changes/screens-chrome-parity/specs/settings-board-look/spec.md#requirement-the-admin-settings-shell-takes-the-board-header\n\t\t */\n\t\tlook: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The Nextcloud app id (used for the default re-import endpoint + version loadState). */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Human-readable application name shown in the header and version card. */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * Running app version. When empty, read from\n\t\t * `loadState(appId, 'version', 'Unknown')` (provided by AppHost's\n\t\t * GenericAdminSettings::getForm()).\n\t\t */\n\t\tappVersion: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Page title. Defaults to \"<appName> Settings\". */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Page description. Defaults to \"Configure your <appName> installation\". */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the path under the title, in place of the description,\n\t\t * as `[{ label, href? }]` (\"Beheerinstellingen\" linking to the admin\n\t\t * settings, then the app and the section). A crumb without `href` is\n\t\t * text; the last one is the current page.\n\t\t *\n\t\t * @spec openspec/changes/screens-admin-settings-board-header/specs/settings-board-look/spec.md\n\t\t */\n\t\tbreadcrumbs: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Board look: a note after the path, behind a \"·\" (\"geldt voor de\n\t\t * hele organisatie\").\n\t\t *\n\t\t * @spec openspec/changes/screens-admin-settings-board-header/specs/settings-board-look/spec.md\n\t\t */\n\t\tbreadcrumbNote: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Documentation URL (renders the info icon next to the title). */\n\t\tdocUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Whether to render the version card. */\n\t\tshowVersionCard: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Version card heading. */\n\t\tversionTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Version information'),\n\t\t},\n\n\t\t/** Version card description. Defaults to \"Information about the current <appName> installation\". */\n\t\tversionDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Configured version (for apps that track a separate config version).\n\t\t * When omitted, read from `loadState(appId, 'configuredVersion', '')`\n\t\t * (AppHost stamps it on each config import).\n\t\t */\n\t\tconfiguredVersion: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether the installation is up to date. Drives the badge/button in the\n\t\t * version card. When left null, the shell reads the REAL value from\n\t\t * `loadState(appId, 'isUpToDate', true)` — AppHost's GenericAdminSettings\n\t\t * provides it by comparing the running version to the imported config\n\t\t * version. Pass an explicit boolean to override; do NOT hardcode true.\n\t\t */\n\t\tisUpToDate: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether to show the version card's up-to-date / update button. */\n\t\tshowUpdateButton: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Whether an app update is in progress (drives the update button spinner). */\n\t\tupdating: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to show the \"Re-import configuration\" action. */\n\t\tshowReimport: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Override the re-import endpoint. Defaults to the canonical AppHost\n\t\t * route `/apps/<appId>/api/settings/load`.\n\t\t */\n\t\treimportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Whether to render the default support footer. */\n\t\tshowSupport: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Support contact email. */\n\t\tsupportEmail: {\n\t\t\ttype: String,\n\t\t\tdefault: 'support@conduction.nl',\n\t\t},\n\n\t\t/** SLA contact email (set to '' to hide the SLA line). */\n\t\tslaEmail: {\n\t\t\ttype: String,\n\t\t\tdefault: 'sales@conduction.nl',\n\t\t},\n\n\t\t/** Show a \"Run setup wizard\" action that opens CnSetupWizard (ADR-042). */\n\t\tshowSetup: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** The `manifest.setup.steps` array passed to CnSetupWizard. */\n\t\tsetupSteps: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Show a \"Help us / suggest a feature\" link to the forge's feature-request form. */\n\t\tshowHelp: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** `<owner>/<repo>` GitHub slug the feature-request link targets. */\n\t\thelpRepo: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['update', 'reimported', 'reimport-error'],\n\n\tsetup(props) {\n\t\t// The app's own `environment` setting, else the active organisation's.\n\t\tconst { environment: environmentName } = useEnvironment({ environment: () => props.environment })\n\t\tconst { isBoard } = useLook(props)\n\t\t// A shell told `look: \"board\"` itself draws the board look also on an\n\t\t// admin page that no CnAppRoot wraps, as Nextcloud's settings pages are.\n\t\tconst ownLookClass = computed(() => (normalizeLook(props.look) === 'board' ? 'cn-look-board' : ''))\n\t\treturn { environmentName, isBoard, ownLookClass }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treimporting: false,\n\t\t\tsetupWizardOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The crumbs that have a label.\n\t\t *\n\t\t * @return {Array<{label: string, href?: string}>}\n\t\t * @spec openspec/changes/screens-admin-settings-board-header/specs/settings-board-look/spec.md\n\t\t */\n\t\tcrumbs() {\n\t\t\treturn (Array.isArray(this.breadcrumbs) ? this.breadcrumbs : [])\n\t\t\t\t.filter((crumb) => crumb && typeof crumb.label === 'string' && crumb.label !== '')\n\t\t},\n\n\t\t/** @return {string} The accessible name of the breadcrumb navigation. */\n\t\tbreadcrumbLabel() {\n\t\t\treturn t('nextcloud-vue', 'Breadcrumb')\n\t\t},\n\n\t\t/**\n\t\t * The \"Help us\" link target: the forge's feature-request issue form\n\t\t * for `helpRepo`, on the fleet-default forge. Empty (hiding the\n\t\t * button) when no repo is configured.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\thelpUrl() {\n\t\t\tconst repo = String(this.helpRepo || '').trim()\n\t\t\tif (!repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn buildFeatureRequestUrl(null, repo)\n\t\t},\n\n\t\t/** @return {string} The running version, falling back to AppHost initial state. */\n\t\tresolvedVersion() {\n\t\t\tif (this.appVersion) {\n\t\t\t\treturn this.appVersion\n\t\t\t}\n\t\t\treturn loadState(this.appId, 'version', 'Unknown')\n\t\t},\n\n\t\t/** @return {string} Configured version, falling back to AppHost initial state. */\n\t\tresolvedConfiguredVersion() {\n\t\t\treturn this.configuredVersion || loadState(this.appId, 'configuredVersion', '')\n\t\t},\n\n\t\t/** @return {boolean} Real up-to-date signal: explicit prop wins, else AppHost initial state. */\n\t\tresolvedIsUpToDate() {\n\t\t\tif (this.isUpToDate !== null) {\n\t\t\t\treturn this.isUpToDate\n\t\t\t}\n\t\t\treturn loadState(this.appId, 'isUpToDate', true)\n\t\t},\n\n\t\t/** @return {string} Page title, defaulting to \"<appName> Settings\". */\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', '{app} Settings', { app: this.appName })\n\t\t},\n\n\t\t/** @return {string} Page description. */\n\t\tresolvedDescription() {\n\t\t\treturn this.description || t('nextcloud-vue', 'Configure your {app} installation', { app: this.appName })\n\t\t},\n\n\t\t/** @return {string} Version card description. */\n\t\tresolvedVersionDescription() {\n\t\t\treturn this.versionDescription || t('nextcloud-vue', 'Information about the current {app} installation', { app: this.appName })\n\t\t},\n\n\t\t/** @return {string} Re-import endpoint. */\n\t\tresolvedReimportUrl() {\n\t\t\treturn prefixUrl(this.reimportUrl) || generateUrl('/apps/{appId}/api/settings/load', { appId: this.appId })\n\t\t},\n\n\t\t/** @return {string} Re-import button label. */\n\t\treimportLabel() {\n\t\t\treturn t('nextcloud-vue', 'Re-import configuration')\n\t\t},\n\n\t\t/** @return {string} Re-import in-progress label. */\n\t\timportingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Importing…')\n\t\t},\n\n\t\t/** @return {string} Support footer heading. */\n\t\tsupportTitle() {\n\t\t\treturn t('nextcloud-vue', 'Support')\n\t\t},\n\n\t\t/** @return {string} Support footer line. */\n\t\tsupportLine() {\n\t\t\treturn t('nextcloud-vue', 'For support, contact us at')\n\t\t},\n\n\t\t/** @return {string} SLA footer line. */\n\t\tslaLine() {\n\t\t\treturn t('nextcloud-vue', 'For a Service Level Agreement (SLA), contact')\n\t\t},\n\n\t\t/** @return {string} Setup-wizard button label. */\n\t\tsetupLabel() {\n\t\t\treturn t('nextcloud-vue', 'Run setup wizard')\n\t\t},\n\n\t\t/** @return {string} Documentation button label (board header). */\n\t\tdocumentationLabel() {\n\t\t\treturn t('nextcloud-vue', 'Documentation')\n\t\t},\n\n\t\t/** @return {string} Help / suggest-feature button label. */\n\t\thelpLabel() {\n\t\t\treturn t('nextcloud-vue', 'Help us')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/** Forward the version card's update-button click to the parent. */\n\t\tonUpdate() {\n\t\t\t/**\n\t\t\t * @event update Emitted when the version card's update button is clicked.\n\t\t\t */\n\t\t\tthis.$emit('update')\n\t\t},\n\n\t\t/**\n\t\t * Re-import the app's OpenRegister configuration via the canonical\n\t\t * AppHost endpoint, then emit the result so the parent can refresh.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync reimport() {\n\t\t\tif (this.reimporting) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.reimporting = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.post(this.resolvedReimportUrl)\n\t\t\t\tconst data = response?.data ?? {}\n\t\t\t\t// AppHost returns { success, message, ... }; a bare 2xx also counts.\n\t\t\t\tif (data.success === false) {\n\t\t\t\t\tthrow new Error(data.message || t('nextcloud-vue', 'Re-import failed'))\n\t\t\t\t}\n\t\t\t\tshowSuccess(data.message || t('nextcloud-vue', 'Configuration re-imported successfully'))\n\t\t\t\t/**\n\t\t\t\t * @event reimported Emitted after a successful configuration re-import.\n\t\t\t\t * @type {object} The AppHost response payload.\n\t\t\t\t */\n\t\t\t\tthis.$emit('reimported', data)\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error?.response?.data?.message || error?.message || t('nextcloud-vue', 'Re-import failed')\n\t\t\t\tshowError(message)\n\t\t\t\t/**\n\t\t\t\t * @event reimport-error Emitted when a configuration re-import fails.\n\t\t\t\t * @type {Error} The error that caused the failure.\n\t\t\t\t */\n\t\t\t\tthis.$emit('reimport-error', error)\n\t\t\t} finally {\n\t\t\t\tthis.reimporting = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-admin-settings-shell {\n\tmax-width: 900px;\n}\n\n/* The board look lifts the width and lays the header and cards on a grid (look-board.css). */\n\n.cn-support-info {\n\tmargin-top: 16px;\n\tpadding-top: 16px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-support-info h4 {\n\tmargin: 0 0 8px 0;\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-support-info p {\n\tmargin: 4px 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n\n.cn-support-info a {\n\tcolor: var(--color-primary-element);\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createBlock","_openBlock","_createElementVNode","_toDisplayString","_Fragment","_renderList","_createVNode","_createTextVNode","_renderSlot"],"mappings":"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"}
1
+ {"version":3,"file":"CnAdminSettingsShell.vue3.js","sources":["../../../../src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue?vue&type=template&id=2f22ff08&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-admin-settings-shell\" :class=\"ownLookClass\">\n\t\t<!-- Names a development, test or acceptance environment; cannot be dismissed. Production shows nothing. -->\n\t\t<CnEnvironmentBanner v-if=\"environmentName\" :environment=\"environmentName\" />\n\n\t\t<!-- Page title header — the uniform \"<App> Settings / Configure your <App> installation\" block.\n\t\t Uses NcSettingsSection directly (not CnSettingsSection) so the description subtitle and\n\t\t doc-url icon render — CnSettingsSection repurposes those props and does not forward them. -->\n\t\t<NcSettingsSection\n\t\t\tv-if=\"!isBoard\"\n\t\t\t:name=\"resolvedTitle\"\n\t\t\t:description=\"resolvedDescription\"\n\t\t\t:docUrl=\"docUrl\" />\n\n\t\t<!-- The board header: an h1 and its description, then the labelled\n\t\t Documentation button and the buildiq square. No documentation icon\n\t\t beside the title. -->\n\t\t<div v-else class=\"cn-admin-settings-shell__header\" data-testid=\"cn-admin-settings-header\">\n\t\t\t<div class=\"cn-admin-settings-shell__heading\">\n\t\t\t\t<h1 class=\"cn-admin-settings-shell__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h1>\n\t\t\t\t<!-- `breadcrumbs`: the board's path line in place of the\n\t\t\t\t description (\"Beheerinstellingen / Launchpad / Algemeen ·\n\t\t\t\t geldt voor de hele organisatie\"). -->\n\t\t\t\t<div v-if=\"crumbs.length > 0\" class=\"cn-admin-settings-shell__crumbs\" data-testid=\"cn-admin-settings-breadcrumbs\">\n\t\t\t\t\t<nav class=\"cn-admin-settings-shell__crumb-list\" :aria-label=\"breadcrumbLabel\">\n\t\t\t\t\t\t<template v-for=\"(crumb, index) in crumbs\" :key=\"index\">\n\t\t\t\t\t\t\t<span v-if=\"index > 0\" aria-hidden=\"true\">/</span>\n\t\t\t\t\t\t\t<a v-if=\"crumb.href\" :href=\"crumb.href\" class=\"cn-admin-settings-shell__crumb-link\">{{ crumb.label }}</a>\n\t\t\t\t\t\t\t<span v-else :aria-current=\"index === crumbs.length - 1 ? 'page' : null\">{{ crumb.label }}</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</nav>\n\t\t\t\t\t<template v-if=\"breadcrumbNote\">\n\t\t\t\t\t\t<span aria-hidden=\"true\">·</span>\n\t\t\t\t\t\t<span>{{ breadcrumbNote }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t\t<p v-else class=\"cn-admin-settings-shell__description\">\n\t\t\t\t\t{{ resolvedDescription }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-admin-settings-shell__header-buttons\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"docUrl\"\n\t\t\t\t\t:href=\"docUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\tdata-testid=\"cn-admin-settings-documentation\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<BookOpenVariant :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ documentationLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<CnBuildiqEditButton />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- `versionCardPosition: \"end\"`: the app's sections come first, and the\n\t\t version card and the credential broker follow them (board DcBeheer\n\t\t starts with the app's own card). The same slot, rendered once. -->\n\t\t<slot v-if=\"versionCardAtEnd\" />\n\n\t\t<!-- Version information card (version, up-to-date check, re-import, support footer) -->\n\t\t<CnVersionInfoCard\n\t\t\tv-if=\"showVersionCard\"\n\t\t\t:appName=\"appName\"\n\t\t\t:appVersion=\"resolvedVersion\"\n\t\t\t:configuredVersion=\"resolvedConfiguredVersion\"\n\t\t\t:isUpToDate=\"resolvedIsUpToDate\"\n\t\t\t:showUpdateButton=\"showUpdateButton\"\n\t\t\t:updating=\"updating\"\n\t\t\t:title=\"versionTitle\"\n\t\t\t:description=\"resolvedVersionDescription\"\n\t\t\t@update=\"onUpdate\">\n\t\t\t<template #actions>\n\t\t\t\t<!-- Canonical \"Re-import configuration\" action (POST /apps/<appId>/api/settings/load) -->\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showReimport\"\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"reimporting\"\n\t\t\t\t\t@click=\"reimport\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"reimporting\" :size=\"20\" />\n\t\t\t\t\t\t<Refresh v-else :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ reimporting ? importingLabel : reimportLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- Run the first-time setup wizard on demand (ADR-042). -->\n\t\t\t\t<NcButton v-if=\"showSetup\" data-testid=\"cn-admin-run-setup\" @click=\"setupWizardOpen = true\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<AutoFix :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ setupLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- \"Help us / suggest a feature\" — a direct link to the forge's\n\t\t\t\t feature-request issue form (team decision 2026-09-04: the\n\t\t\t\t in-product suggestion modal is gone; the forge is where\n\t\t\t\t the conversation happens, in English). -->\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showHelp && helpUrl\"\n\t\t\t\t\t:href=\"helpUrl\"\n\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<HelpCircleOutline :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ helpLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- @slot actions Extra action buttons rendered next to the built-in update / re-import buttons in the version card header. -->\n\t\t\t\t<slot name=\"actions\" />\n\t\t\t</template>\n\n\t\t\t<template #footer>\n\t\t\t\t<!-- @slot footer Replaces the default support/SLA footer inside the version card. -->\n\t\t\t\t<slot name=\"footer\">\n\t\t\t\t\t<div v-if=\"showSupport\" class=\"cn-support-info\">\n\t\t\t\t\t\t<h4>{{ supportTitle }}</h4>\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t{{ supportLine }}\n\t\t\t\t\t\t\t<a :href=\"`mailto:${supportEmail}`\">{{ supportEmail }}</a>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"slaEmail\">\n\t\t\t\t\t\t\t{{ slaLine }}\n\t\t\t\t\t\t\t<a :href=\"`mailto:${slaEmail}`\">{{ slaEmail }}</a>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</slot>\n\t\t\t</template>\n\n\t\t\t<template #additional-items>\n\t\t\t\t<!-- @slot version-items Extra key/value rows appended inside the version card details. -->\n\t\t\t\t<slot name=\"version-items\" />\n\t\t\t</template>\n\t\t</CnVersionInfoCard>\n\n\t\t<!--\n\t\t\tOrganisation credential broker (ADR-079 Step 2).\n\n\t\t\tThis is the ONE home for org-wide credentials. It used to live in a\n\t\t\tgeneric modal on CnAppRoot, rendered from `manifest.adminSettings[]`\n\t\t\tand gated on app ownership — but no app ever declared that array, so\n\t\t\tthe surface was unreachable in every app in the fleet. It belongs\n\t\t\there: an org-wide secret store is app-level configuration, and\n\t\t\t/settings/admin/<app> is authorized by Nextcloud server-side rather\n\t\t\tthan by a client-side flag.\n\n\t\t\tOpt-in, because only an app that actually brokers credentials should\n\t\t\tshow it — the manifest `credentials[]` declarations drive the\n\t\t\tinformational \"apps requesting credentials\" list inside.\n\t\t-->\n\t\t<CnCredentials\n\t\t\tv-if=\"showOrganisationCredentials\"\n\t\t\tscope=\"organisation\"\n\t\t\t:appId=\"appId\"\n\t\t\t:appName=\"appName\"\n\t\t\t:appCredentials=\"appCredentials\"\n\t\t\tdata-testid=\"cn-admin-organisation-credentials\" />\n\n\t\t<!-- @slot default The app's own settings sections, rendered below the version card, or above it with `versionCardPosition: \"end\"`. -->\n\t\t<slot v-if=\"!versionCardAtEnd\" />\n\n\t\t<!-- First-time setup wizard, opened from the admin page (ADR-042). -->\n\t\t<CnSetupWizard\n\t\t\tv-if=\"showSetup && setupWizardOpen\"\n\t\t\t:appId=\"appId\"\n\t\t\t:steps=\"setupSteps\"\n\t\t\t@complete=\"setupWizardOpen = false\"\n\t\t\t@close=\"setupWizardOpen = false\" />\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { showError, showSuccess } from '@nextcloud/dialogs'\nimport { loadState } from '@nextcloud/initial-state'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcLoadingIcon, NcSettingsSection } from '@nextcloud/vue'\nimport { computed } from 'vue'\nimport AutoFix from 'vue-material-design-icons/AutoFix.vue'\nimport BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'\nimport HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'\nimport Refresh from 'vue-material-design-icons/Refresh.vue'\nimport CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'\nimport CnCredentials from '../CnCredentials/CnCredentials.vue'\nimport CnEnvironmentBanner from '../CnEnvironmentBanner/CnEnvironmentBanner.vue'\nimport CnSetupWizard from '../CnSetupWizard/CnSetupWizard.vue'\nimport { useEnvironment } from '../../composables/useEnvironment.js'\nimport { normalizeLook, useLook } from '../../composables/useLook.js'\nimport { buildFeatureRequestUrl } from '../../utils/forge.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { CnVersionInfoCard } from '../CnVersionInfoCard/index.js'\n\n/**\n * CnAdminSettingsShell — the canonical chrome for a Conduction app's Nextcloud\n * admin settings page.\n *\n * Renders, in fleet-standard order:\n * 1. a page title header (\"<App> Settings\" / \"Configure your <App> installation\" + doc-url),\n * 2. a CnVersionInfoCard (version from `loadState`, up-to-date badge, a\n * \"Re-import configuration\" button wired to the AppHost endpoint\n * `POST /apps/<appId>/api/settings/load`, and a support/SLA footer),\n * 3. the app's own settings sections (default slot).\n *\n * Every app's `AdminRoot.vue` collapses to a thin wrapper so the chrome can\n * never drift between apps again.\n *\n * Basic usage\n * ```vue\n * <CnAdminSettingsShell\n * app-id=\"shillinq\"\n * app-name=\"Shillinq\"\n * doc-url=\"https://shillinq.conduction.nl/docs/intro\">\n * <MyRegisterMapping />\n * <MyFeatureSettings />\n * </CnAdminSettingsShell>\n * ```\n *\n * Real up-to-date check (when the backend exposes it)\n * ```vue\n * <CnAdminSettingsShell\n * app-id=\"opencatalogi\" app-name=\"OpenCatalogi\"\n * :app-version=\"running\" :configured-version=\"configured\"\n * :is-up-to-date=\"running === configured\" />\n * ```\n */\nexport default {\n\tname: 'CnAdminSettingsShell',\n\n\tcomponents: {\n\t\tCnEnvironmentBanner,\n\t\tCnBuildiqEditButton,\n\t\tBookOpenVariant,\n\t\tNcSettingsSection,\n\t\tCnCredentials,\n\t\tCnVersionInfoCard,\n\t\tCnSetupWizard,\n\t\tNcButton,\n\t\tNcLoadingIcon,\n\t\tRefresh,\n\t\tAutoFix,\n\t\tHelpCircleOutline,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The environment this instance is: `development`, `test`, `acceptance` or\n\t\t * `production`. Wins over the active organisation's `environment` field.\n\t\t * A non-production environment is named in a banner at the top and the tab\n\t\t * title gets a prefix. Production, an unknown value or no value shows nothing.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tenvironment: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Render the organisation credential broker on this admin page\n\t\t * (ADR-079 Step 2). Opt-in: only an app that actually brokers org-wide\n\t\t * secrets should surface it.\n\t\t *\n\t\t * This replaces the generic `manifest.adminSettings[]` modal that used\n\t\t * to live on CnAppRoot. That modal was gated on app OWNERSHIP and on an\n\t\t * array no app in the fleet ever declared, so the broker was\n\t\t * unreachable everywhere. Here it is reachable and the access decision\n\t\t * is Nextcloud's, made server-side for /settings/admin/<app>.\n\t\t */\n\t\tshowOrganisationCredentials: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * The app's manifest `credentials[]` declarations, forwarded to the\n\t\t * broker to drive its informational \"apps requesting credentials\"\n\t\t * list. Only read when `showOrganisationCredentials` is true.\n\t\t */\n\t\tappCredentials: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The look the shell is drawn in: `board` or `nextcloud`. Empty follows\n\t\t * the `cnLook` that CnAppRoot provides.\n\t\t *\n\t\t * @spec openspec/changes/screens-chrome-parity/specs/settings-board-look/spec.md#requirement-the-admin-settings-shell-takes-the-board-header\n\t\t */\n\t\tlook: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The Nextcloud app id (used for the default re-import endpoint + version loadState). */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Human-readable application name shown in the header and version card. */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * Running app version. When empty, read from\n\t\t * `loadState(appId, 'version', 'Unknown')` (provided by AppHost's\n\t\t * GenericAdminSettings::getForm()).\n\t\t */\n\t\tappVersion: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Page title. Defaults to \"<appName> Settings\". */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Page description. Defaults to \"Configure your <appName> installation\". */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the path under the title, in place of the description,\n\t\t * as `[{ label, href? }]` (\"Beheerinstellingen\" linking to the admin\n\t\t * settings, then the app and the section). A crumb without `href` is\n\t\t * text; the last one is the current page.\n\t\t *\n\t\t * @spec openspec/changes/screens-admin-settings-board-header/specs/settings-board-look/spec.md\n\t\t */\n\t\tbreadcrumbs: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Board look: a note after the path, behind a \"·\" (\"geldt voor de\n\t\t * hele organisatie\").\n\t\t *\n\t\t * @spec openspec/changes/screens-admin-settings-board-header/specs/settings-board-look/spec.md\n\t\t */\n\t\tbreadcrumbNote: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Documentation URL (renders the info icon next to the title). */\n\t\tdocUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Whether to render the version card. */\n\t\tshowVersionCard: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Where the version card (and the organisation credential broker) sits:\n\t\t * `start`, above the app's sections (the default), or `end`, after them,\n\t\t * for a page whose board starts with the app's own card. The keyboard\n\t\t * order follows, because the slot moves in the markup.\n\t\t *\n\t\t * @spec openspec/changes/screens-admin-settings-version-card-end/specs/settings-board-look/spec.md#requirement-the-version-card-can-follow-the-apps-sections\n\t\t */\n\t\tversionCardPosition: {\n\t\t\ttype: String,\n\t\t\tdefault: 'start',\n\t\t\tvalidator: (value) => ['start', 'end'].includes(value),\n\t\t},\n\n\t\t/** Version card heading. */\n\t\tversionTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Version information'),\n\t\t},\n\n\t\t/** Version card description. Defaults to \"Information about the current <appName> installation\". */\n\t\tversionDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Configured version (for apps that track a separate config version).\n\t\t * When omitted, read from `loadState(appId, 'configuredVersion', '')`\n\t\t * (AppHost stamps it on each config import).\n\t\t */\n\t\tconfiguredVersion: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether the installation is up to date. Drives the badge/button in the\n\t\t * version card. When left null, the shell reads the REAL value from\n\t\t * `loadState(appId, 'isUpToDate', true)` — AppHost's GenericAdminSettings\n\t\t * provides it by comparing the running version to the imported config\n\t\t * version. Pass an explicit boolean to override; do NOT hardcode true.\n\t\t */\n\t\tisUpToDate: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether to show the version card's up-to-date / update button. */\n\t\tshowUpdateButton: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Whether an app update is in progress (drives the update button spinner). */\n\t\tupdating: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to show the \"Re-import configuration\" action. */\n\t\tshowReimport: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Override the re-import endpoint. Defaults to the canonical AppHost\n\t\t * route `/apps/<appId>/api/settings/load`.\n\t\t */\n\t\treimportUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Whether to render the default support footer. */\n\t\tshowSupport: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Support contact email. */\n\t\tsupportEmail: {\n\t\t\ttype: String,\n\t\t\tdefault: 'support@conduction.nl',\n\t\t},\n\n\t\t/** SLA contact email (set to '' to hide the SLA line). */\n\t\tslaEmail: {\n\t\t\ttype: String,\n\t\t\tdefault: 'sales@conduction.nl',\n\t\t},\n\n\t\t/** Show a \"Run setup wizard\" action that opens CnSetupWizard (ADR-042). */\n\t\tshowSetup: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** The `manifest.setup.steps` array passed to CnSetupWizard. */\n\t\tsetupSteps: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Show a \"Help us / suggest a feature\" link to the forge's feature-request form. */\n\t\tshowHelp: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** `<owner>/<repo>` GitHub slug the feature-request link targets. */\n\t\thelpRepo: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['update', 'reimported', 'reimport-error'],\n\n\tsetup(props) {\n\t\t// The app's own `environment` setting, else the active organisation's.\n\t\tconst { environment: environmentName } = useEnvironment({ environment: () => props.environment })\n\t\tconst { isBoard } = useLook(props)\n\t\t// A shell told `look: \"board\"` itself draws the board look also on an\n\t\t// admin page that no CnAppRoot wraps, as Nextcloud's settings pages are.\n\t\tconst ownLookClass = computed(() => (normalizeLook(props.look) === 'board' ? 'cn-look-board' : ''))\n\t\treturn { environmentName, isBoard, ownLookClass }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treimporting: false,\n\t\t\tsetupWizardOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app's sections come before the version card.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/screens-admin-settings-version-card-end/specs/settings-board-look/spec.md#requirement-the-version-card-can-follow-the-apps-sections\n\t\t */\n\t\tversionCardAtEnd() {\n\t\t\treturn this.versionCardPosition === 'end'\n\t\t},\n\n\t\t/**\n\t\t * The crumbs that have a label.\n\t\t *\n\t\t * @return {Array<{label: string, href?: string}>}\n\t\t * @spec openspec/changes/screens-admin-settings-board-header/specs/settings-board-look/spec.md\n\t\t */\n\t\tcrumbs() {\n\t\t\treturn (Array.isArray(this.breadcrumbs) ? this.breadcrumbs : [])\n\t\t\t\t.filter((crumb) => crumb && typeof crumb.label === 'string' && crumb.label !== '')\n\t\t},\n\n\t\t/** @return {string} The accessible name of the breadcrumb navigation. */\n\t\tbreadcrumbLabel() {\n\t\t\treturn t('nextcloud-vue', 'Breadcrumb')\n\t\t},\n\n\t\t/**\n\t\t * The \"Help us\" link target: the forge's feature-request issue form\n\t\t * for `helpRepo`, on the fleet-default forge. Empty (hiding the\n\t\t * button) when no repo is configured.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\thelpUrl() {\n\t\t\tconst repo = String(this.helpRepo || '').trim()\n\t\t\tif (!repo) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn buildFeatureRequestUrl(null, repo)\n\t\t},\n\n\t\t/** @return {string} The running version, falling back to AppHost initial state. */\n\t\tresolvedVersion() {\n\t\t\tif (this.appVersion) {\n\t\t\t\treturn this.appVersion\n\t\t\t}\n\t\t\treturn loadState(this.appId, 'version', 'Unknown')\n\t\t},\n\n\t\t/** @return {string} Configured version, falling back to AppHost initial state. */\n\t\tresolvedConfiguredVersion() {\n\t\t\treturn this.configuredVersion || loadState(this.appId, 'configuredVersion', '')\n\t\t},\n\n\t\t/** @return {boolean} Real up-to-date signal: explicit prop wins, else AppHost initial state. */\n\t\tresolvedIsUpToDate() {\n\t\t\tif (this.isUpToDate !== null) {\n\t\t\t\treturn this.isUpToDate\n\t\t\t}\n\t\t\treturn loadState(this.appId, 'isUpToDate', true)\n\t\t},\n\n\t\t/** @return {string} Page title, defaulting to \"<appName> Settings\". */\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', '{app} Settings', { app: this.appName })\n\t\t},\n\n\t\t/** @return {string} Page description. */\n\t\tresolvedDescription() {\n\t\t\treturn this.description || t('nextcloud-vue', 'Configure your {app} installation', { app: this.appName })\n\t\t},\n\n\t\t/** @return {string} Version card description. */\n\t\tresolvedVersionDescription() {\n\t\t\treturn this.versionDescription || t('nextcloud-vue', 'Information about the current {app} installation', { app: this.appName })\n\t\t},\n\n\t\t/** @return {string} Re-import endpoint. */\n\t\tresolvedReimportUrl() {\n\t\t\treturn prefixUrl(this.reimportUrl) || generateUrl('/apps/{appId}/api/settings/load', { appId: this.appId })\n\t\t},\n\n\t\t/** @return {string} Re-import button label. */\n\t\treimportLabel() {\n\t\t\treturn t('nextcloud-vue', 'Re-import configuration')\n\t\t},\n\n\t\t/** @return {string} Re-import in-progress label. */\n\t\timportingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Importing…')\n\t\t},\n\n\t\t/** @return {string} Support footer heading. */\n\t\tsupportTitle() {\n\t\t\treturn t('nextcloud-vue', 'Support')\n\t\t},\n\n\t\t/** @return {string} Support footer line. */\n\t\tsupportLine() {\n\t\t\treturn t('nextcloud-vue', 'For support, contact us at')\n\t\t},\n\n\t\t/** @return {string} SLA footer line. */\n\t\tslaLine() {\n\t\t\treturn t('nextcloud-vue', 'For a Service Level Agreement (SLA), contact')\n\t\t},\n\n\t\t/** @return {string} Setup-wizard button label. */\n\t\tsetupLabel() {\n\t\t\treturn t('nextcloud-vue', 'Run setup wizard')\n\t\t},\n\n\t\t/** @return {string} Documentation button label (board header). */\n\t\tdocumentationLabel() {\n\t\t\treturn t('nextcloud-vue', 'Documentation')\n\t\t},\n\n\t\t/** @return {string} Help / suggest-feature button label. */\n\t\thelpLabel() {\n\t\t\treturn t('nextcloud-vue', 'Help us')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/** Forward the version card's update-button click to the parent. */\n\t\tonUpdate() {\n\t\t\t/**\n\t\t\t * @event update Emitted when the version card's update button is clicked.\n\t\t\t */\n\t\t\tthis.$emit('update')\n\t\t},\n\n\t\t/**\n\t\t * Re-import the app's OpenRegister configuration via the canonical\n\t\t * AppHost endpoint, then emit the result so the parent can refresh.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync reimport() {\n\t\t\tif (this.reimporting) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.reimporting = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.post(this.resolvedReimportUrl)\n\t\t\t\tconst data = response?.data ?? {}\n\t\t\t\t// AppHost returns { success, message, ... }; a bare 2xx also counts.\n\t\t\t\tif (data.success === false) {\n\t\t\t\t\tthrow new Error(data.message || t('nextcloud-vue', 'Re-import failed'))\n\t\t\t\t}\n\t\t\t\tshowSuccess(data.message || t('nextcloud-vue', 'Configuration re-imported successfully'))\n\t\t\t\t/**\n\t\t\t\t * @event reimported Emitted after a successful configuration re-import.\n\t\t\t\t * @type {object} The AppHost response payload.\n\t\t\t\t */\n\t\t\t\tthis.$emit('reimported', data)\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error?.response?.data?.message || error?.message || t('nextcloud-vue', 'Re-import failed')\n\t\t\t\tshowError(message)\n\t\t\t\t/**\n\t\t\t\t * @event reimport-error Emitted when a configuration re-import fails.\n\t\t\t\t * @type {Error} The error that caused the failure.\n\t\t\t\t */\n\t\t\t\tthis.$emit('reimport-error', error)\n\t\t\t} finally {\n\t\t\t\tthis.reimporting = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-admin-settings-shell {\n\tmax-width: 900px;\n}\n\n/* The board look lifts the width and lays the header and cards on a grid (look-board.css). */\n\n.cn-support-info {\n\tmargin-top: 16px;\n\tpadding-top: 16px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-support-info h4 {\n\tmargin: 0 0 8px 0;\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-support-info p {\n\tmargin: 4px 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n\n.cn-support-info a {\n\tcolor: var(--color-primary-element);\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createBlock","_openBlock","_createElementVNode","_toDisplayString","_Fragment","_renderList","_createVNode","_createTextVNode","_renderSlot"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnAuditTrailCard.vue2.js';
2
2
  import { render } from './CnAuditTrailCard.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-b6134724";
5
+ script.__scopeId = "data-v-280a5ace";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -109,7 +109,7 @@ var script = {
109
109
  if (!this.register || !this.schema || !this.objectId) {
110
110
  return ''
111
111
  }
112
- return `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trail?${params.toString()}`
112
+ return `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trails?${params.toString()}`
113
113
  },
114
114
  },
115
115
 
@@ -1 +1 @@
1
- {"version":3,"file":"CnAuditTrailCard.vue2.js","sources":["../../../../src/components/CnAuditTrailCard/CnAuditTrailCard.vue"],"sourcesContent":["<!--\n CnAuditTrailCard — compact audit-trail widget for the integration\n registry.\n\n Surface-aware shell around the `audit-trail` integration: fetches\n the object's recent audit-trail entries (query-time storage strategy\n per AD-22) and renders them in a CnDetailCard.\n\n Per umbrella change `pluggable-integration-registry` AD-19 (surface\n fallback), one component handles all surfaces — `surface` is\n forwarded so the component can branch internally if desired.\n-->\n<template>\n\t<CnDetailCard\n\t\t:title=\"resolvedTitle\"\n\t\t:icon=\"History\"\n\t\t:collapsible=\"collapsible\"\n\t\t:data-surface=\"surface\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-audit-card__empty\">\n\t\t\t{{ noEntriesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-audit-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"entry in displayedEntries\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-audit-card__row\">\n\t\t\t\t<div class=\"cn-audit-card__row-head\">\n\t\t\t\t\t<strong class=\"cn-audit-card__action\">{{ entry.action || entry.event || actionLabel }}</strong>\n\t\t\t\t\t<span class=\"cn-audit-card__when\">{{ formatWhen(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-audit-card__row-body\">\n\t\t\t\t\t<span class=\"cn-audit-card__actor\">{{ formatActor(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"entries.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-audit-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ entries.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport History from 'vue-material-design-icons/History.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\n/**\n * CnAuditTrailCard — compact audit-trail widget rendered by the\n * integration registry on dashboard and detail surfaces.\n *\n * Basic usage\n * ```vue\n * <CnAuditTrailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n */\nexport default {\n\tname: 'CnAuditTrailCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Required for `scope: \"object\"` (the default); unused for `scope: \"app\"`. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/**\n\t\t * What the card lists. `object` (the default) is one object's trail\n\t\t * and needs `register`, `schema` and `objectId`. `app` is the\n\t\t * app-wide feed: the entries of every object the caller may read\n\t\t * (OpenRegister's `/audit-trails/readable`), narrowed to `register`\n\t\t * and `schema` when those are set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'object',\n\t\t\tvalidator: (value) => ['object', 'app'].includes(value),\n\t\t},\n\n\t\t/** Rendering surface — passed for AD-19 surface fallback consumers. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoEntriesLabel: { type: String, default: () => t('nextcloud-vue', 'No audit entries yet') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated fallback action label. */\n\t\tactionLabel: { type: String, default: () => t('nextcloud-vue', 'Change') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tHistory: markRaw(History),\n\t\t\tentries: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Audit trail')\n\t\t},\n\n\t\tdisplayedEntries() {\n\t\t\treturn this.entries.slice(0, this.maxDisplay)\n\t\t},\n\n\t\t/**\n\t\t * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the\n\t\t * object's own trail, or the app-wide readable feed narrowed to the\n\t\t * register and schema when set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {string}\n\t\t */\n\t\tfetchUrl() {\n\t\t\tconst params = new URLSearchParams({ limit: String(this.maxDisplay) })\n\t\t\tif (this.scope === 'app') {\n\t\t\t\tif (this.register) {\n\t\t\t\t\tparams.set('register', this.register)\n\t\t\t\t}\n\t\t\t\tif (this.schema) {\n\t\t\t\t\tparams.set('schema', this.schema)\n\t\t\t\t}\n\t\t\t\treturn `${this.apiBase}/audit-trails/readable?${params.toString()}`\n\t\t\t}\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trail?${params.toString()}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tfetchUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (url) {\n\t\t\t\t\tthis.fetchEntries()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the entries from `fetchUrl`. The readable feed answers\n\t\t * `{ rows, nextCursor }`, the object trail `{ results }`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEntries() {\n\t\t\tconst url = this.fetchUrl\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.results || data.rows || data\n\t\t\t\t\tthis.entries = Array.isArray(list) ? list : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAuditTrailCard] failed to fetch audit trail', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatActor(entry) {\n\t\t\treturn entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''\n\t\t},\n\n\t\tformatWhen(entry) {\n\t\t\tconst raw = entry.creationDateTime || entry.created || entry.timestamp\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleString()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-audit-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-audit-card__row {\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-audit-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-audit-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-audit-card__action {\n\tcolor: var(--color-main-text);\n}\n\n.cn-audit-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-audit-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-audit-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-audit-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-audit-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnAuditTrailCard.vue2.js","sources":["../../../../src/components/CnAuditTrailCard/CnAuditTrailCard.vue"],"sourcesContent":["<!--\n CnAuditTrailCard — compact audit-trail widget for the integration\n registry.\n\n Surface-aware shell around the `audit-trail` integration: fetches\n the object's recent audit-trail entries (query-time storage strategy\n per AD-22) and renders them in a CnDetailCard.\n\n Per umbrella change `pluggable-integration-registry` AD-19 (surface\n fallback), one component handles all surfaces — `surface` is\n forwarded so the component can branch internally if desired.\n-->\n<template>\n\t<CnDetailCard\n\t\t:title=\"resolvedTitle\"\n\t\t:icon=\"History\"\n\t\t:collapsible=\"collapsible\"\n\t\t:data-surface=\"surface\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-audit-card__empty\">\n\t\t\t{{ noEntriesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-audit-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"entry in displayedEntries\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-audit-card__row\">\n\t\t\t\t<div class=\"cn-audit-card__row-head\">\n\t\t\t\t\t<strong class=\"cn-audit-card__action\">{{ entry.action || entry.event || actionLabel }}</strong>\n\t\t\t\t\t<span class=\"cn-audit-card__when\">{{ formatWhen(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-audit-card__row-body\">\n\t\t\t\t\t<span class=\"cn-audit-card__actor\">{{ formatActor(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"entries.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-audit-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ entries.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport History from 'vue-material-design-icons/History.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\n/**\n * CnAuditTrailCard — compact audit-trail widget rendered by the\n * integration registry on dashboard and detail surfaces.\n *\n * Basic usage\n * ```vue\n * <CnAuditTrailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n */\nexport default {\n\tname: 'CnAuditTrailCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Required for `scope: \"object\"` (the default); unused for `scope: \"app\"`. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/**\n\t\t * What the card lists. `object` (the default) is one object's trail\n\t\t * and needs `register`, `schema` and `objectId`. `app` is the\n\t\t * app-wide feed: the entries of every object the caller may read\n\t\t * (OpenRegister's `/audit-trails/readable`), narrowed to `register`\n\t\t * and `schema` when those are set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'object',\n\t\t\tvalidator: (value) => ['object', 'app'].includes(value),\n\t\t},\n\n\t\t/** Rendering surface — passed for AD-19 surface fallback consumers. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoEntriesLabel: { type: String, default: () => t('nextcloud-vue', 'No audit entries yet') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated fallback action label. */\n\t\tactionLabel: { type: String, default: () => t('nextcloud-vue', 'Change') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tHistory: markRaw(History),\n\t\t\tentries: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Audit trail')\n\t\t},\n\n\t\tdisplayedEntries() {\n\t\t\treturn this.entries.slice(0, this.maxDisplay)\n\t\t},\n\n\t\t/**\n\t\t * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the\n\t\t * object's own trail, or the app-wide readable feed narrowed to the\n\t\t * register and schema when set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {string}\n\t\t */\n\t\tfetchUrl() {\n\t\t\tconst params = new URLSearchParams({ limit: String(this.maxDisplay) })\n\t\t\tif (this.scope === 'app') {\n\t\t\t\tif (this.register) {\n\t\t\t\t\tparams.set('register', this.register)\n\t\t\t\t}\n\t\t\t\tif (this.schema) {\n\t\t\t\t\tparams.set('schema', this.schema)\n\t\t\t\t}\n\t\t\t\treturn `${this.apiBase}/audit-trails/readable?${params.toString()}`\n\t\t\t}\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trails?${params.toString()}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tfetchUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (url) {\n\t\t\t\t\tthis.fetchEntries()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the entries from `fetchUrl`. The readable feed answers\n\t\t * `{ rows, nextCursor }`, the object trail `{ results }`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEntries() {\n\t\t\tconst url = this.fetchUrl\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.results || data.rows || data\n\t\t\t\t\tthis.entries = Array.isArray(list) ? list : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAuditTrailCard] failed to fetch audit trail', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatActor(entry) {\n\t\t\treturn entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''\n\t\t},\n\n\t\tformatWhen(entry) {\n\t\t\tconst raw = entry.creationDateTime || entry.created || entry.timestamp\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleString()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-audit-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-audit-card__row {\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-audit-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-audit-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-audit-card__action {\n\tcolor: var(--color-main-text);\n}\n\n.cn-audit-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-audit-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-audit-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-audit-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-audit-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnAuditTrailCard.vue3.js","sources":["../../../../src/components/CnAuditTrailCard/CnAuditTrailCard.vue?vue&type=template&id=b6134724&lang.js"],"sourcesContent":["<!--\n CnAuditTrailCard — compact audit-trail widget for the integration\n registry.\n\n Surface-aware shell around the `audit-trail` integration: fetches\n the object's recent audit-trail entries (query-time storage strategy\n per AD-22) and renders them in a CnDetailCard.\n\n Per umbrella change `pluggable-integration-registry` AD-19 (surface\n fallback), one component handles all surfaces — `surface` is\n forwarded so the component can branch internally if desired.\n-->\n<template>\n\t<CnDetailCard\n\t\t:title=\"resolvedTitle\"\n\t\t:icon=\"History\"\n\t\t:collapsible=\"collapsible\"\n\t\t:data-surface=\"surface\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-audit-card__empty\">\n\t\t\t{{ noEntriesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-audit-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"entry in displayedEntries\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-audit-card__row\">\n\t\t\t\t<div class=\"cn-audit-card__row-head\">\n\t\t\t\t\t<strong class=\"cn-audit-card__action\">{{ entry.action || entry.event || actionLabel }}</strong>\n\t\t\t\t\t<span class=\"cn-audit-card__when\">{{ formatWhen(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-audit-card__row-body\">\n\t\t\t\t\t<span class=\"cn-audit-card__actor\">{{ formatActor(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"entries.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-audit-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ entries.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport History from 'vue-material-design-icons/History.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\n/**\n * CnAuditTrailCard — compact audit-trail widget rendered by the\n * integration registry on dashboard and detail surfaces.\n *\n * Basic usage\n * ```vue\n * <CnAuditTrailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n */\nexport default {\n\tname: 'CnAuditTrailCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Required for `scope: \"object\"` (the default); unused for `scope: \"app\"`. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/**\n\t\t * What the card lists. `object` (the default) is one object's trail\n\t\t * and needs `register`, `schema` and `objectId`. `app` is the\n\t\t * app-wide feed: the entries of every object the caller may read\n\t\t * (OpenRegister's `/audit-trails/readable`), narrowed to `register`\n\t\t * and `schema` when those are set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'object',\n\t\t\tvalidator: (value) => ['object', 'app'].includes(value),\n\t\t},\n\n\t\t/** Rendering surface — passed for AD-19 surface fallback consumers. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoEntriesLabel: { type: String, default: () => t('nextcloud-vue', 'No audit entries yet') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated fallback action label. */\n\t\tactionLabel: { type: String, default: () => t('nextcloud-vue', 'Change') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tHistory: markRaw(History),\n\t\t\tentries: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Audit trail')\n\t\t},\n\n\t\tdisplayedEntries() {\n\t\t\treturn this.entries.slice(0, this.maxDisplay)\n\t\t},\n\n\t\t/**\n\t\t * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the\n\t\t * object's own trail, or the app-wide readable feed narrowed to the\n\t\t * register and schema when set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {string}\n\t\t */\n\t\tfetchUrl() {\n\t\t\tconst params = new URLSearchParams({ limit: String(this.maxDisplay) })\n\t\t\tif (this.scope === 'app') {\n\t\t\t\tif (this.register) {\n\t\t\t\t\tparams.set('register', this.register)\n\t\t\t\t}\n\t\t\t\tif (this.schema) {\n\t\t\t\t\tparams.set('schema', this.schema)\n\t\t\t\t}\n\t\t\t\treturn `${this.apiBase}/audit-trails/readable?${params.toString()}`\n\t\t\t}\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trail?${params.toString()}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tfetchUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (url) {\n\t\t\t\t\tthis.fetchEntries()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the entries from `fetchUrl`. The readable feed answers\n\t\t * `{ rows, nextCursor }`, the object trail `{ results }`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEntries() {\n\t\t\tconst url = this.fetchUrl\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.results || data.rows || data\n\t\t\t\t\tthis.entries = Array.isArray(list) ? list : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAuditTrailCard] failed to fetch audit trail', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatActor(entry) {\n\t\t\treturn entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''\n\t\t},\n\n\t\tformatWhen(entry) {\n\t\t\tconst raw = entry.creationDateTime || entry.created || entry.timestamp\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleString()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-audit-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-audit-card__row {\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-audit-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-audit-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-audit-card__action {\n\tcolor: var(--color-main-text);\n}\n\n.cn-audit-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-audit-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-audit-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-audit-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-audit-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_toDisplayString","_createElementVNode"],"mappings":";;;;AA+BwC,EAAA,KAAA,EAAA,sBAAA;;;;AAGjC,EAAA,KAAA,EAAA,qBAAA;;AAKE,MAAA,UAAA,GAAA,EAAA,KAAE,EAAA,yBAAA,GAAA;AACC,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,uBAAA,GAAA;AACD,MAAA,UAAA,GAAA,EAAA,KAAI,EAAA,qBAAA,GAAA;;;;;;;;uBAhBXA;SACI,EAAE,QAAa,CAAA,aAAA;AACnB,IAAA,IAAG,EAAE,KAAO,CAAA,OAAA;AACX,IAAA,WAAS,EAAE,MAAW,CAAA,WAAA;kBACZ,EAAE,MAAO,CAAA,OAAA;;2BACf;OAAA,KAAA,CAAA,OAAA;mCAAA,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;AACS,UAAA,CAAA,KAAA,CAAA,OAAO,CAAC,MAAM,KAAA,CAAA;0BAA5BC,kBAEI,CAAA,KAAA,EAFJ,UAEI,EAAAC,eAAA,CADF,MAAc,CAAA,cAAA,CAAA,EAAA,CAAA,YAAA;;gCAGjBD,8CACiB,QAAgB,CAAA,gBAAA,EAAA,MAApB,KAAA;qCADbA;kBAEE,GAAG,EAAC,KAAM,CAAC,EAAE;AACd,kBAAA,KAAK,EAAC,oBAAoB;;AAC1B,kBAAAE,kBAAA,QAAA;oBACCA,kBAAO,CAAA,QAAA,EAAP,UAAO,EAAAD,eAAA,CAAA,KAAA,CAAA,MAAA,IAAA,KAAA,CAAA,KAAA,IAAA,MAAA,CAAA,WAAA,CAAA,EAAA,CAAA,YAAA;AACP,oBAAAC,kBAAA,CAAU,MAAV,EAAA,UAAU,EAAAD,eAAA,CAAA,QAAA,CAAA,UAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnAuditTrailCard.vue3.js","sources":["../../../../src/components/CnAuditTrailCard/CnAuditTrailCard.vue?vue&type=template&id=280a5ace&lang.js"],"sourcesContent":["<!--\n CnAuditTrailCard — compact audit-trail widget for the integration\n registry.\n\n Surface-aware shell around the `audit-trail` integration: fetches\n the object's recent audit-trail entries (query-time storage strategy\n per AD-22) and renders them in a CnDetailCard.\n\n Per umbrella change `pluggable-integration-registry` AD-19 (surface\n fallback), one component handles all surfaces — `surface` is\n forwarded so the component can branch internally if desired.\n-->\n<template>\n\t<CnDetailCard\n\t\t:title=\"resolvedTitle\"\n\t\t:icon=\"History\"\n\t\t:collapsible=\"collapsible\"\n\t\t:data-surface=\"surface\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-audit-card__empty\">\n\t\t\t{{ noEntriesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-audit-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"entry in displayedEntries\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-audit-card__row\">\n\t\t\t\t<div class=\"cn-audit-card__row-head\">\n\t\t\t\t\t<strong class=\"cn-audit-card__action\">{{ entry.action || entry.event || actionLabel }}</strong>\n\t\t\t\t\t<span class=\"cn-audit-card__when\">{{ formatWhen(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-audit-card__row-body\">\n\t\t\t\t\t<span class=\"cn-audit-card__actor\">{{ formatActor(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"entries.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-audit-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ entries.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport History from 'vue-material-design-icons/History.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\n/**\n * CnAuditTrailCard — compact audit-trail widget rendered by the\n * integration registry on dashboard and detail surfaces.\n *\n * Basic usage\n * ```vue\n * <CnAuditTrailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n */\nexport default {\n\tname: 'CnAuditTrailCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Required for `scope: \"object\"` (the default); unused for `scope: \"app\"`. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/**\n\t\t * What the card lists. `object` (the default) is one object's trail\n\t\t * and needs `register`, `schema` and `objectId`. `app` is the\n\t\t * app-wide feed: the entries of every object the caller may read\n\t\t * (OpenRegister's `/audit-trails/readable`), narrowed to `register`\n\t\t * and `schema` when those are set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'object',\n\t\t\tvalidator: (value) => ['object', 'app'].includes(value),\n\t\t},\n\n\t\t/** Rendering surface — passed for AD-19 surface fallback consumers. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoEntriesLabel: { type: String, default: () => t('nextcloud-vue', 'No audit entries yet') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated fallback action label. */\n\t\tactionLabel: { type: String, default: () => t('nextcloud-vue', 'Change') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tHistory: markRaw(History),\n\t\t\tentries: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Audit trail')\n\t\t},\n\n\t\tdisplayedEntries() {\n\t\t\treturn this.entries.slice(0, this.maxDisplay)\n\t\t},\n\n\t\t/**\n\t\t * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the\n\t\t * object's own trail, or the app-wide readable feed narrowed to the\n\t\t * register and schema when set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {string}\n\t\t */\n\t\tfetchUrl() {\n\t\t\tconst params = new URLSearchParams({ limit: String(this.maxDisplay) })\n\t\t\tif (this.scope === 'app') {\n\t\t\t\tif (this.register) {\n\t\t\t\t\tparams.set('register', this.register)\n\t\t\t\t}\n\t\t\t\tif (this.schema) {\n\t\t\t\t\tparams.set('schema', this.schema)\n\t\t\t\t}\n\t\t\t\treturn `${this.apiBase}/audit-trails/readable?${params.toString()}`\n\t\t\t}\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trails?${params.toString()}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tfetchUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (url) {\n\t\t\t\t\tthis.fetchEntries()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the entries from `fetchUrl`. The readable feed answers\n\t\t * `{ rows, nextCursor }`, the object trail `{ results }`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEntries() {\n\t\t\tconst url = this.fetchUrl\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tconst list = data.results || data.rows || data\n\t\t\t\t\tthis.entries = Array.isArray(list) ? list : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAuditTrailCard] failed to fetch audit trail', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatActor(entry) {\n\t\t\treturn entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''\n\t\t},\n\n\t\tformatWhen(entry) {\n\t\t\tconst raw = entry.creationDateTime || entry.created || entry.timestamp\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleString()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-audit-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-audit-card__row {\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-audit-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-audit-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-audit-card__action {\n\tcolor: var(--color-main-text);\n}\n\n.cn-audit-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-audit-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-audit-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-audit-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-audit-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_toDisplayString","_createElementVNode"],"mappings":";;;;AA+BwC,EAAA,KAAA,EAAA,sBAAA;;;;AAGjC,EAAA,KAAA,EAAA,qBAAA;;AAKE,MAAA,UAAA,GAAA,EAAA,KAAE,EAAA,yBAAA,GAAA;AACC,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,uBAAA,GAAA;AACD,MAAA,UAAA,GAAA,EAAA,KAAI,EAAA,qBAAA,GAAA;;;;;;;;uBAhBXA;SACI,EAAE,QAAa,CAAA,aAAA;AACnB,IAAA,IAAG,EAAE,KAAO,CAAA,OAAA;AACX,IAAA,WAAS,EAAE,MAAW,CAAA,WAAA;kBACZ,EAAE,MAAO,CAAA,OAAA;;2BACf;OAAA,KAAA,CAAA,OAAA;mCAAA,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;AACS,UAAA,CAAA,KAAA,CAAA,OAAO,CAAC,MAAM,KAAA,CAAA;0BAA5BC,kBAEI,CAAA,KAAA,EAFJ,UAEI,EAAAC,eAAA,CADF,MAAc,CAAA,cAAA,CAAA,EAAA,CAAA,YAAA;;gCAGjBD,8CACiB,QAAgB,CAAA,gBAAA,EAAA,MAApB,KAAA;qCADbA;kBAEE,GAAG,EAAC,KAAM,CAAC,EAAE;AACd,kBAAA,KAAK,EAAC,oBAAoB;;AAC1B,kBAAAE,kBAAA,QAAA;oBACCA,kBAAO,CAAA,QAAA,EAAP,UAAO,EAAAD,eAAA,CAAA,KAAA,CAAA,MAAA,IAAA,KAAA,CAAA,KAAA,IAAA,MAAA,CAAA,WAAA,CAAA,EAAA,CAAA,YAAA;AACP,oBAAAC,kBAAA,CAAU,MAAV,EAAA,UAAU,EAAAD,eAAA,CAAA,QAAA,CAAA,UAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnBodySections.vue2.js';
2
2
  import { render } from './CnBodySections.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-19a0caa4";
5
+ script.__scopeId = "data-v-44324dca";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -15,6 +15,8 @@ var script = {
15
15
  cnRegistry: { default: () => ({}) },
16
16
  /** Legacy custom-component map provided by CnAppRoot. */
17
17
  cnCustomComponents: { default: () => ({}) },
18
+ /** The host's translate function for manifest-authored strings, provided by CnAppRoot. */
19
+ cnTranslate: { default: null },
18
20
  },
19
21
 
20
22
  props: {
@@ -134,7 +136,7 @@ var script = {
134
136
  return {
135
137
  id,
136
138
  name,
137
- title: (section && section.title) || '',
139
+ title: this.translateTitle((section && section.title) || ''),
138
140
  card: !!(section && section.card),
139
141
  colSpan: section && section.colSpan,
140
142
  placement: section && section.placement,
@@ -146,6 +148,25 @@ var script = {
146
148
  },
147
149
 
148
150
  methods: {
151
+ /**
152
+ * A manifest-authored section title in the reader's language.
153
+ *
154
+ * The title is written in the manifest, so it belongs to the host
155
+ * app's catalogue, the same as every other manifest title the detail
156
+ * page shows. A section heading printed raw read "Asked about" to a
157
+ * Dutch handler on a pipelinq ticket (Zuiddrecht live round 3).
158
+ *
159
+ * @param {string} title The title as written in the manifest.
160
+ * @return {string} The translated title, or '' when there is none.
161
+ * @spec openspec/changes/detail-page-strings-dutch/specs/detail-page-strings-dutch/spec.md#requirement-the-detail-page-speaks-the-readers-language
162
+ */
163
+ translateTitle(title) {
164
+ if (title === '' || typeof this.cnTranslate !== 'function') {
165
+ return title
166
+ }
167
+ return this.cnTranslate(title)
168
+ },
169
+
149
170
  /**
150
171
  * Resolve a section's host-app component by registry name. Precedence
151
172
  * matches CnPageRenderer.resolveCustomComponent: the v2 `cnRegistry`
@@ -1 +1 @@
1
- {"version":3,"file":"CnBodySections.vue2.js","sources":["../../../../src/components/CnBodySections/CnBodySections.vue"],"sourcesContent":["<!--\n CnBodySections — declarative in-body sections primitive.\n\n Renders an array of `sections` (manifest `config.bodyWidgets` /\n `config.sections`) as titled cards IN THE PAGE BODY, each hosting a\n registered host-app component. Unlike the integration registry (which\n forces a sidebar tab + widget parity pair), a body section resolves its\n component straight from the v2 component registry (`cnRegistry`) or the\n legacy `cnCustomComponents` map — the SAME resolver CnPageRenderer uses\n for `type:\"custom\"` page components. No sidebar tab is required.\n\n Each section's `props` are token-resolved via resolveFilterValue\n (`@objectId`, `@object.<field>`, `@workspace.<key>`, the time tokens,\n `@me`, …). Optional `?`-suffixed tokens are dropped when unset. The\n loaded object + objectId are also `provide`d on `cnSectionContext` so a\n host component written to inject context works without explicit props.\n\n A section whose component cannot be resolved, or whose render throws,\n degrades to an inline error card — it never breaks the page.\n\n Reusable across detail / dashboard / index pages: pass the page's\n `context` ({ objectId, object, workspace, register, schema }) and the\n list of sections.\n-->\n<template>\n\t<div\n\t\tv-if=\"resolvedSections.length > 0\"\n\t\tclass=\"cn-body-sections\"\n\t\t:class=\"{ 'cn-body-sections--grid': useGrid }\"\n\t\tdata-testid=\"cn-body-sections\">\n\t\t<section\n\t\t\tv-for=\"entry in resolvedSections\"\n\t\t\t:key=\"entry.id\"\n\t\t\tclass=\"cn-body-sections__section\"\n\t\t\t:class=\"{ 'cn-body-sections__section--card': entry.card }\"\n\t\t\t:style=\"sectionStyle(entry)\"\n\t\t\t:data-section-id=\"entry.id\"\n\t\t\tdata-testid=\"cn-body-section\">\n\t\t\t<h3\n\t\t\t\tv-if=\"entry.title\"\n\t\t\t\tclass=\"cn-body-sections__title\"\n\t\t\t\t:data-testid=\"`cn-body-section-title-${entry.id}`\">\n\t\t\t\t{{ entry.title }}\n\t\t\t</h3>\n\t\t\t<!-- Resolved + rendered host component, guarded by an error\n\t\t\t boundary so a throwing section degrades inline. -->\n\t\t\t<CnSectionBoundary\n\t\t\t\tv-if=\"entry.component\"\n\t\t\t\t:sectionId=\"entry.id\"\n\t\t\t\t:errorLabel=\"errorLabel\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"entry.component\"\n\t\t\t\t\tv-bind=\"entry.props\"\n\t\t\t\t\t:data-testid=\"`cn-body-section-component-${entry.id}`\" />\n\t\t\t</CnSectionBoundary>\n\t\t\t<!-- Unresolved component: inline error, page survives. -->\n\t\t\t<div\n\t\t\t\tv-else\n\t\t\t\tclass=\"cn-body-sections__error\"\n\t\t\t\t:data-testid=\"`cn-body-section-error-${entry.id}`\">\n\t\t\t\t{{ unresolvedLabel(entry) }}\n\t\t\t</div>\n\t\t</section>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { provide, ref, watch } from 'vue'\nimport { resolveFilterValue } from '../../utils/resolveFilterTokens.js'\nimport CnSectionBoundary from './CnSectionBoundary.js'\n\nexport default {\n\tname: 'CnBodySections',\n\n\tcomponents: {\n\t\tCnSectionBoundary,\n\t},\n\n\tinject: {\n\t\t/** V2 component registry (kind-tagged) provided by CnAppRoot. */\n\t\tcnRegistry: { default: () => ({}) },\n\t\t/** Legacy custom-component map provided by CnAppRoot. */\n\t\tcnCustomComponents: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Declarative in-body section descriptors. Each entry:\n\t\t * `{ id, component, title?, props?, placement?, colSpan?, card? }`.\n\t\t * `card: true` draws the section in the same card as the grid's\n\t\t * widgets, for a component that has no card of its own.\n\t\t * `component` is the registry name of a host-app component\n\t\t * (resolved from `cnRegistry` then `cnCustomComponents`).\n\t\t *\n\t\t * @type {Array<{id?: string, component: string, title?: string, props?: object, placement?: string, colSpan?: number, card?: boolean}>}\n\t\t */\n\t\tsections: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Page/object context used to resolve `@objectId` / `@object.<field>`\n\t\t * / `@workspace.<key>` / `@config.<key>` tokens in each section's `props`,\n\t\t * and `provide`d on `cnSectionContext` for host components that inject\n\t\t * instead of taking props. Shape:\n\t\t * `{ objectId?, object?, workspace?, config?, register?, schema? }`.\n\t\t *\n\t\t * @type {{objectId?: (string|number), object?: object, workspace?: object, config?: object, register?: string, schema?: string}}\n\t\t */\n\t\tcontext: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Optional placement filter — when set, only sections whose\n\t\t * `placement` matches this value render. CnDetailPage mounts one\n\t\t * CnBodySections per placement slot so sections land in the right\n\t\t * spot in the body. When unset, ALL sections render (no filter).\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tplacement: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether to lay sections out on a 12-column responsive grid (honouring\n\t\t * each section's `colSpan`). Defaults to true. Set false to stack\n\t\t * full-width.\n\t\t */\n\t\tgrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Inline label shown when a section's component throws while rendering. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This section failed to load.'),\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// Reactive holder published so a host section component can\n\t\t// `inject('cnSectionContext')` and read the loaded object / objectId\n\t\t// without the manifest having to spell out @object props. Mirrors the\n\t\t// dashboard's cnWorkspaceContext / detail page's cnObjectContext pattern.\n\t\tconst cnSectionContext = ref({ ...(props.context || {}) })\n\t\tprovide('cnSectionContext', cnSectionContext)\n\t\twatch(\n\t\t\t() => props.context,\n\t\t\t(next) => {\n\t\t\t\tcnSectionContext.value = { ...(next || {}) }\n\t\t\t},\n\t\t\t{ deep: true },\n\t\t)\n\t\treturn {}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the grid layout is active — only when `grid` is true AND at\n\t\t * least one rendered section sets a `colSpan`. A plain stack otherwise.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tuseGrid() {\n\t\t\treturn this.grid && this.filteredSections.some((s) => typeof s.colSpan === 'number')\n\t\t},\n\n\t\t/**\n\t\t * Sections filtered by the `placement` prop (when set). Order is\n\t\t * preserved from the input array.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tfilteredSections() {\n\t\t\tconst list = Array.isArray(this.sections) ? this.sections : []\n\t\t\tif (this.placement === null || this.placement === undefined) {\n\t\t\t\treturn list\n\t\t\t}\n\t\t\treturn list.filter((s) => (s && s.placement) === this.placement)\n\t\t},\n\n\t\t/**\n\t\t * Each filtered section augmented with its resolved Vue `component`\n\t\t * (or null) and its token-resolved `props`. Unresolved components keep\n\t\t * `component: null` so the template renders an inline error rather than\n\t\t * crashing.\n\t\t *\n\t\t * @return {Array<{id: string, title: string, component: object|null, props: object, name: string, colSpan: number|undefined, placement: string|undefined}>}\n\t\t */\n\t\tresolvedSections() {\n\t\t\treturn this.filteredSections.map((section, index) => {\n\t\t\t\tconst name = section && section.component\n\t\t\t\tconst id = (section && section.id) || name || `section-${index}`\n\t\t\t\treturn {\n\t\t\t\t\tid,\n\t\t\t\t\tname,\n\t\t\t\t\ttitle: (section && section.title) || '',\n\t\t\t\t\tcard: !!(section && section.card),\n\t\t\t\t\tcolSpan: section && section.colSpan,\n\t\t\t\t\tplacement: section && section.placement,\n\t\t\t\t\tcomponent: this.resolveSectionComponent(name),\n\t\t\t\t\tprops: this.resolveSectionProps(section),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve a section's host-app component by registry name. Precedence\n\t\t * matches CnPageRenderer.resolveCustomComponent: the v2 `cnRegistry`\n\t\t * (any kind exposing a `.component`) wins, else the legacy\n\t\t * `cnCustomComponents` map. Returns null when neither has the name —\n\t\t * the section then renders an inline error.\n\t\t *\n\t\t * @param {string} name The registry component name.\n\t\t * @return {object|null} The resolved Vue component, or null.\n\t\t */\n\t\tresolveSectionComponent(name) {\n\t\t\tif (typeof name !== 'string' || name === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst reg = (this.cnRegistry && this.cnRegistry[name]) || null\n\t\t\tif (reg && reg.component) {\n\t\t\t\treturn reg.component\n\t\t\t}\n\t\t\tconst legacy = this.cnCustomComponents && this.cnCustomComponents[name]\n\t\t\treturn legacy || null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a section's `props` map: every value is passed through\n\t\t * resolveFilterValue with this page's context so `@objectId` /\n\t\t * `@object.<field>` / `@workspace.<key>` / time tokens resolve. A value\n\t\t * that resolves to an unset OPTIONAL token (`@…?` left as-is, or a\n\t\t * required `@object.<field>` whose field is absent) is DROPPED from the\n\t\t * props so the child sees `undefined` rather than a raw `@…` string.\n\t\t *\n\t\t * @param {object} section The section descriptor.\n\t\t * @return {object} The token-resolved props for the host component.\n\t\t */\n\t\tresolveSectionProps(section) {\n\t\t\tconst raw = (section && section.props && typeof section.props === 'object') ? section.props : {}\n\t\t\tconst ctx = this.context || {}\n\t\t\tconst out = {}\n\t\t\tfor (const [key, value] of Object.entries(raw)) {\n\t\t\t\tconst resolved = resolveFilterValue(value, ctx)\n\t\t\t\t// Drop a value that stayed an unresolved `@`-token (optional\n\t\t\t\t// workspace token, or an @object/@objectId with no context yet)\n\t\t\t\t// so the child receives undefined instead of a literal token.\n\t\t\t\tif (typeof resolved === 'string' && resolved.charAt(0) === '@') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout[key] = resolved\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Layout style for a section — a grid `grid-column: span N` when the\n\t\t * grid is active and the section declares a `colSpan`; otherwise none.\n\t\t *\n\t\t * @param {object} entry A resolved section.\n\t\t * @return {object} An inline-style object.\n\t\t */\n\t\tsectionStyle(entry) {\n\t\t\tif (!this.useGrid || typeof entry.colSpan !== 'number') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst span = Math.max(1, Math.min(12, entry.colSpan))\n\t\t\treturn { gridColumn: `span ${span}` }\n\t\t},\n\n\t\t/**\n\t\t * Inline message for a section whose `component` name did not resolve.\n\t\t *\n\t\t * @param {object} entry A resolved section.\n\t\t * @return {string}\n\t\t */\n\t\tunresolvedLabel(entry) {\n\t\t\treturn t('nextcloud-vue', 'Section component \"{name}\" is not registered.', { name: entry.name || entry.id })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-body-sections {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\twidth: 100%;\n}\n\n.cn-body-sections--grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(12, 1fr);\n\tgap: 16px;\n}\n\n.cn-body-sections__section {\n\tmin-width: 0;\n}\n\n/* Same card as the grid's widgets, so a section reads as one of them. */\n.cn-body-sections__section--card {\n\tpadding: 12px 16px 16px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n}\n\n.cn-body-sections__section--card > .cn-body-sections__title {\n\tmargin: 0 -16px 12px;\n\tpadding: 0 16px 12px;\n\tfont-size: 14px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-body-sections__title {\n\tmargin: 0 0 8px;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-body-sections__error {\n\tpadding: 12px 16px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground-color: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnBodySections.vue2.js","sources":["../../../../src/components/CnBodySections/CnBodySections.vue"],"sourcesContent":["<!--\n CnBodySections — declarative in-body sections primitive.\n\n Renders an array of `sections` (manifest `config.bodyWidgets` /\n `config.sections`) as titled cards IN THE PAGE BODY, each hosting a\n registered host-app component. Unlike the integration registry (which\n forces a sidebar tab + widget parity pair), a body section resolves its\n component straight from the v2 component registry (`cnRegistry`) or the\n legacy `cnCustomComponents` map — the SAME resolver CnPageRenderer uses\n for `type:\"custom\"` page components. No sidebar tab is required.\n\n Each section's `props` are token-resolved via resolveFilterValue\n (`@objectId`, `@object.<field>`, `@workspace.<key>`, the time tokens,\n `@me`, …). Optional `?`-suffixed tokens are dropped when unset. The\n loaded object + objectId are also `provide`d on `cnSectionContext` so a\n host component written to inject context works without explicit props.\n\n A section whose component cannot be resolved, or whose render throws,\n degrades to an inline error card — it never breaks the page.\n\n Reusable across detail / dashboard / index pages: pass the page's\n `context` ({ objectId, object, workspace, register, schema }) and the\n list of sections.\n-->\n<template>\n\t<div\n\t\tv-if=\"resolvedSections.length > 0\"\n\t\tclass=\"cn-body-sections\"\n\t\t:class=\"{ 'cn-body-sections--grid': useGrid }\"\n\t\tdata-testid=\"cn-body-sections\">\n\t\t<section\n\t\t\tv-for=\"entry in resolvedSections\"\n\t\t\t:key=\"entry.id\"\n\t\t\tclass=\"cn-body-sections__section\"\n\t\t\t:class=\"{ 'cn-body-sections__section--card': entry.card }\"\n\t\t\t:style=\"sectionStyle(entry)\"\n\t\t\t:data-section-id=\"entry.id\"\n\t\t\tdata-testid=\"cn-body-section\">\n\t\t\t<h3\n\t\t\t\tv-if=\"entry.title\"\n\t\t\t\tclass=\"cn-body-sections__title\"\n\t\t\t\t:data-testid=\"`cn-body-section-title-${entry.id}`\">\n\t\t\t\t{{ entry.title }}\n\t\t\t</h3>\n\t\t\t<!-- Resolved + rendered host component, guarded by an error\n\t\t\t boundary so a throwing section degrades inline. -->\n\t\t\t<CnSectionBoundary\n\t\t\t\tv-if=\"entry.component\"\n\t\t\t\t:sectionId=\"entry.id\"\n\t\t\t\t:errorLabel=\"errorLabel\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"entry.component\"\n\t\t\t\t\tv-bind=\"entry.props\"\n\t\t\t\t\t:data-testid=\"`cn-body-section-component-${entry.id}`\" />\n\t\t\t</CnSectionBoundary>\n\t\t\t<!-- Unresolved component: inline error, page survives. -->\n\t\t\t<div\n\t\t\t\tv-else\n\t\t\t\tclass=\"cn-body-sections__error\"\n\t\t\t\t:data-testid=\"`cn-body-section-error-${entry.id}`\">\n\t\t\t\t{{ unresolvedLabel(entry) }}\n\t\t\t</div>\n\t\t</section>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { provide, ref, watch } from 'vue'\nimport { resolveFilterValue } from '../../utils/resolveFilterTokens.js'\nimport CnSectionBoundary from './CnSectionBoundary.js'\n\nexport default {\n\tname: 'CnBodySections',\n\n\tcomponents: {\n\t\tCnSectionBoundary,\n\t},\n\n\tinject: {\n\t\t/** V2 component registry (kind-tagged) provided by CnAppRoot. */\n\t\tcnRegistry: { default: () => ({}) },\n\t\t/** Legacy custom-component map provided by CnAppRoot. */\n\t\tcnCustomComponents: { default: () => ({}) },\n\t\t/** The host's translate function for manifest-authored strings, provided by CnAppRoot. */\n\t\tcnTranslate: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Declarative in-body section descriptors. Each entry:\n\t\t * `{ id, component, title?, props?, placement?, colSpan?, card? }`.\n\t\t * `card: true` draws the section in the same card as the grid's\n\t\t * widgets, for a component that has no card of its own.\n\t\t * `component` is the registry name of a host-app component\n\t\t * (resolved from `cnRegistry` then `cnCustomComponents`).\n\t\t *\n\t\t * @type {Array<{id?: string, component: string, title?: string, props?: object, placement?: string, colSpan?: number, card?: boolean}>}\n\t\t */\n\t\tsections: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Page/object context used to resolve `@objectId` / `@object.<field>`\n\t\t * / `@workspace.<key>` / `@config.<key>` tokens in each section's `props`,\n\t\t * and `provide`d on `cnSectionContext` for host components that inject\n\t\t * instead of taking props. Shape:\n\t\t * `{ objectId?, object?, workspace?, config?, register?, schema? }`.\n\t\t *\n\t\t * @type {{objectId?: (string|number), object?: object, workspace?: object, config?: object, register?: string, schema?: string}}\n\t\t */\n\t\tcontext: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Optional placement filter — when set, only sections whose\n\t\t * `placement` matches this value render. CnDetailPage mounts one\n\t\t * CnBodySections per placement slot so sections land in the right\n\t\t * spot in the body. When unset, ALL sections render (no filter).\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tplacement: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether to lay sections out on a 12-column responsive grid (honouring\n\t\t * each section's `colSpan`). Defaults to true. Set false to stack\n\t\t * full-width.\n\t\t */\n\t\tgrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Inline label shown when a section's component throws while rendering. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This section failed to load.'),\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// Reactive holder published so a host section component can\n\t\t// `inject('cnSectionContext')` and read the loaded object / objectId\n\t\t// without the manifest having to spell out @object props. Mirrors the\n\t\t// dashboard's cnWorkspaceContext / detail page's cnObjectContext pattern.\n\t\tconst cnSectionContext = ref({ ...(props.context || {}) })\n\t\tprovide('cnSectionContext', cnSectionContext)\n\t\twatch(\n\t\t\t() => props.context,\n\t\t\t(next) => {\n\t\t\t\tcnSectionContext.value = { ...(next || {}) }\n\t\t\t},\n\t\t\t{ deep: true },\n\t\t)\n\t\treturn {}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the grid layout is active — only when `grid` is true AND at\n\t\t * least one rendered section sets a `colSpan`. A plain stack otherwise.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tuseGrid() {\n\t\t\treturn this.grid && this.filteredSections.some((s) => typeof s.colSpan === 'number')\n\t\t},\n\n\t\t/**\n\t\t * Sections filtered by the `placement` prop (when set). Order is\n\t\t * preserved from the input array.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tfilteredSections() {\n\t\t\tconst list = Array.isArray(this.sections) ? this.sections : []\n\t\t\tif (this.placement === null || this.placement === undefined) {\n\t\t\t\treturn list\n\t\t\t}\n\t\t\treturn list.filter((s) => (s && s.placement) === this.placement)\n\t\t},\n\n\t\t/**\n\t\t * Each filtered section augmented with its resolved Vue `component`\n\t\t * (or null) and its token-resolved `props`. Unresolved components keep\n\t\t * `component: null` so the template renders an inline error rather than\n\t\t * crashing.\n\t\t *\n\t\t * @return {Array<{id: string, title: string, component: object|null, props: object, name: string, colSpan: number|undefined, placement: string|undefined}>}\n\t\t */\n\t\tresolvedSections() {\n\t\t\treturn this.filteredSections.map((section, index) => {\n\t\t\t\tconst name = section && section.component\n\t\t\t\tconst id = (section && section.id) || name || `section-${index}`\n\t\t\t\treturn {\n\t\t\t\t\tid,\n\t\t\t\t\tname,\n\t\t\t\t\ttitle: this.translateTitle((section && section.title) || ''),\n\t\t\t\t\tcard: !!(section && section.card),\n\t\t\t\t\tcolSpan: section && section.colSpan,\n\t\t\t\t\tplacement: section && section.placement,\n\t\t\t\t\tcomponent: this.resolveSectionComponent(name),\n\t\t\t\t\tprops: this.resolveSectionProps(section),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest-authored section title in the reader's language.\n\t\t *\n\t\t * The title is written in the manifest, so it belongs to the host\n\t\t * app's catalogue, the same as every other manifest title the detail\n\t\t * page shows. A section heading printed raw read \"Asked about\" to a\n\t\t * Dutch handler on a pipelinq ticket (Zuiddrecht live round 3).\n\t\t *\n\t\t * @param {string} title The title as written in the manifest.\n\t\t * @return {string} The translated title, or '' when there is none.\n\t\t * @spec openspec/changes/detail-page-strings-dutch/specs/detail-page-strings-dutch/spec.md#requirement-the-detail-page-speaks-the-readers-language\n\t\t */\n\t\ttranslateTitle(title) {\n\t\t\tif (title === '' || typeof this.cnTranslate !== 'function') {\n\t\t\t\treturn title\n\t\t\t}\n\t\t\treturn this.cnTranslate(title)\n\t\t},\n\n\t\t/**\n\t\t * Resolve a section's host-app component by registry name. Precedence\n\t\t * matches CnPageRenderer.resolveCustomComponent: the v2 `cnRegistry`\n\t\t * (any kind exposing a `.component`) wins, else the legacy\n\t\t * `cnCustomComponents` map. Returns null when neither has the name —\n\t\t * the section then renders an inline error.\n\t\t *\n\t\t * @param {string} name The registry component name.\n\t\t * @return {object|null} The resolved Vue component, or null.\n\t\t */\n\t\tresolveSectionComponent(name) {\n\t\t\tif (typeof name !== 'string' || name === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst reg = (this.cnRegistry && this.cnRegistry[name]) || null\n\t\t\tif (reg && reg.component) {\n\t\t\t\treturn reg.component\n\t\t\t}\n\t\t\tconst legacy = this.cnCustomComponents && this.cnCustomComponents[name]\n\t\t\treturn legacy || null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a section's `props` map: every value is passed through\n\t\t * resolveFilterValue with this page's context so `@objectId` /\n\t\t * `@object.<field>` / `@workspace.<key>` / time tokens resolve. A value\n\t\t * that resolves to an unset OPTIONAL token (`@…?` left as-is, or a\n\t\t * required `@object.<field>` whose field is absent) is DROPPED from the\n\t\t * props so the child sees `undefined` rather than a raw `@…` string.\n\t\t *\n\t\t * @param {object} section The section descriptor.\n\t\t * @return {object} The token-resolved props for the host component.\n\t\t */\n\t\tresolveSectionProps(section) {\n\t\t\tconst raw = (section && section.props && typeof section.props === 'object') ? section.props : {}\n\t\t\tconst ctx = this.context || {}\n\t\t\tconst out = {}\n\t\t\tfor (const [key, value] of Object.entries(raw)) {\n\t\t\t\tconst resolved = resolveFilterValue(value, ctx)\n\t\t\t\t// Drop a value that stayed an unresolved `@`-token (optional\n\t\t\t\t// workspace token, or an @object/@objectId with no context yet)\n\t\t\t\t// so the child receives undefined instead of a literal token.\n\t\t\t\tif (typeof resolved === 'string' && resolved.charAt(0) === '@') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout[key] = resolved\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Layout style for a section — a grid `grid-column: span N` when the\n\t\t * grid is active and the section declares a `colSpan`; otherwise none.\n\t\t *\n\t\t * @param {object} entry A resolved section.\n\t\t * @return {object} An inline-style object.\n\t\t */\n\t\tsectionStyle(entry) {\n\t\t\tif (!this.useGrid || typeof entry.colSpan !== 'number') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst span = Math.max(1, Math.min(12, entry.colSpan))\n\t\t\treturn { gridColumn: `span ${span}` }\n\t\t},\n\n\t\t/**\n\t\t * Inline message for a section whose `component` name did not resolve.\n\t\t *\n\t\t * @param {object} entry A resolved section.\n\t\t * @return {string}\n\t\t */\n\t\tunresolvedLabel(entry) {\n\t\t\treturn t('nextcloud-vue', 'Section component \"{name}\" is not registered.', { name: entry.name || entry.id })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-body-sections {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\twidth: 100%;\n}\n\n.cn-body-sections--grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(12, 1fr);\n\tgap: 16px;\n}\n\n.cn-body-sections__section {\n\tmin-width: 0;\n}\n\n/* Same card as the grid's widgets, so a section reads as one of them. */\n.cn-body-sections__section--card {\n\tpadding: 12px 16px 16px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n}\n\n.cn-body-sections__section--card > .cn-body-sections__title {\n\tmargin: 0 -16px 12px;\n\tpadding: 0 16px 12px;\n\tfont-size: 14px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-body-sections__title {\n\tmargin: 0 0 8px;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-body-sections__error {\n\tpadding: 12px 16px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground-color: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnBodySections.vue3.js","sources":["../../../../src/components/CnBodySections/CnBodySections.vue?vue&type=template&id=19a0caa4&lang.js"],"sourcesContent":["<!--\n CnBodySections — declarative in-body sections primitive.\n\n Renders an array of `sections` (manifest `config.bodyWidgets` /\n `config.sections`) as titled cards IN THE PAGE BODY, each hosting a\n registered host-app component. Unlike the integration registry (which\n forces a sidebar tab + widget parity pair), a body section resolves its\n component straight from the v2 component registry (`cnRegistry`) or the\n legacy `cnCustomComponents` map — the SAME resolver CnPageRenderer uses\n for `type:\"custom\"` page components. No sidebar tab is required.\n\n Each section's `props` are token-resolved via resolveFilterValue\n (`@objectId`, `@object.<field>`, `@workspace.<key>`, the time tokens,\n `@me`, …). Optional `?`-suffixed tokens are dropped when unset. The\n loaded object + objectId are also `provide`d on `cnSectionContext` so a\n host component written to inject context works without explicit props.\n\n A section whose component cannot be resolved, or whose render throws,\n degrades to an inline error card — it never breaks the page.\n\n Reusable across detail / dashboard / index pages: pass the page's\n `context` ({ objectId, object, workspace, register, schema }) and the\n list of sections.\n-->\n<template>\n\t<div\n\t\tv-if=\"resolvedSections.length > 0\"\n\t\tclass=\"cn-body-sections\"\n\t\t:class=\"{ 'cn-body-sections--grid': useGrid }\"\n\t\tdata-testid=\"cn-body-sections\">\n\t\t<section\n\t\t\tv-for=\"entry in resolvedSections\"\n\t\t\t:key=\"entry.id\"\n\t\t\tclass=\"cn-body-sections__section\"\n\t\t\t:class=\"{ 'cn-body-sections__section--card': entry.card }\"\n\t\t\t:style=\"sectionStyle(entry)\"\n\t\t\t:data-section-id=\"entry.id\"\n\t\t\tdata-testid=\"cn-body-section\">\n\t\t\t<h3\n\t\t\t\tv-if=\"entry.title\"\n\t\t\t\tclass=\"cn-body-sections__title\"\n\t\t\t\t:data-testid=\"`cn-body-section-title-${entry.id}`\">\n\t\t\t\t{{ entry.title }}\n\t\t\t</h3>\n\t\t\t<!-- Resolved + rendered host component, guarded by an error\n\t\t\t boundary so a throwing section degrades inline. -->\n\t\t\t<CnSectionBoundary\n\t\t\t\tv-if=\"entry.component\"\n\t\t\t\t:sectionId=\"entry.id\"\n\t\t\t\t:errorLabel=\"errorLabel\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"entry.component\"\n\t\t\t\t\tv-bind=\"entry.props\"\n\t\t\t\t\t:data-testid=\"`cn-body-section-component-${entry.id}`\" />\n\t\t\t</CnSectionBoundary>\n\t\t\t<!-- Unresolved component: inline error, page survives. -->\n\t\t\t<div\n\t\t\t\tv-else\n\t\t\t\tclass=\"cn-body-sections__error\"\n\t\t\t\t:data-testid=\"`cn-body-section-error-${entry.id}`\">\n\t\t\t\t{{ unresolvedLabel(entry) }}\n\t\t\t</div>\n\t\t</section>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { provide, ref, watch } from 'vue'\nimport { resolveFilterValue } from '../../utils/resolveFilterTokens.js'\nimport CnSectionBoundary from './CnSectionBoundary.js'\n\nexport default {\n\tname: 'CnBodySections',\n\n\tcomponents: {\n\t\tCnSectionBoundary,\n\t},\n\n\tinject: {\n\t\t/** V2 component registry (kind-tagged) provided by CnAppRoot. */\n\t\tcnRegistry: { default: () => ({}) },\n\t\t/** Legacy custom-component map provided by CnAppRoot. */\n\t\tcnCustomComponents: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Declarative in-body section descriptors. Each entry:\n\t\t * `{ id, component, title?, props?, placement?, colSpan?, card? }`.\n\t\t * `card: true` draws the section in the same card as the grid's\n\t\t * widgets, for a component that has no card of its own.\n\t\t * `component` is the registry name of a host-app component\n\t\t * (resolved from `cnRegistry` then `cnCustomComponents`).\n\t\t *\n\t\t * @type {Array<{id?: string, component: string, title?: string, props?: object, placement?: string, colSpan?: number, card?: boolean}>}\n\t\t */\n\t\tsections: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Page/object context used to resolve `@objectId` / `@object.<field>`\n\t\t * / `@workspace.<key>` / `@config.<key>` tokens in each section's `props`,\n\t\t * and `provide`d on `cnSectionContext` for host components that inject\n\t\t * instead of taking props. Shape:\n\t\t * `{ objectId?, object?, workspace?, config?, register?, schema? }`.\n\t\t *\n\t\t * @type {{objectId?: (string|number), object?: object, workspace?: object, config?: object, register?: string, schema?: string}}\n\t\t */\n\t\tcontext: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Optional placement filter — when set, only sections whose\n\t\t * `placement` matches this value render. CnDetailPage mounts one\n\t\t * CnBodySections per placement slot so sections land in the right\n\t\t * spot in the body. When unset, ALL sections render (no filter).\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tplacement: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether to lay sections out on a 12-column responsive grid (honouring\n\t\t * each section's `colSpan`). Defaults to true. Set false to stack\n\t\t * full-width.\n\t\t */\n\t\tgrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Inline label shown when a section's component throws while rendering. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This section failed to load.'),\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// Reactive holder published so a host section component can\n\t\t// `inject('cnSectionContext')` and read the loaded object / objectId\n\t\t// without the manifest having to spell out @object props. Mirrors the\n\t\t// dashboard's cnWorkspaceContext / detail page's cnObjectContext pattern.\n\t\tconst cnSectionContext = ref({ ...(props.context || {}) })\n\t\tprovide('cnSectionContext', cnSectionContext)\n\t\twatch(\n\t\t\t() => props.context,\n\t\t\t(next) => {\n\t\t\t\tcnSectionContext.value = { ...(next || {}) }\n\t\t\t},\n\t\t\t{ deep: true },\n\t\t)\n\t\treturn {}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the grid layout is active — only when `grid` is true AND at\n\t\t * least one rendered section sets a `colSpan`. A plain stack otherwise.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tuseGrid() {\n\t\t\treturn this.grid && this.filteredSections.some((s) => typeof s.colSpan === 'number')\n\t\t},\n\n\t\t/**\n\t\t * Sections filtered by the `placement` prop (when set). Order is\n\t\t * preserved from the input array.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tfilteredSections() {\n\t\t\tconst list = Array.isArray(this.sections) ? this.sections : []\n\t\t\tif (this.placement === null || this.placement === undefined) {\n\t\t\t\treturn list\n\t\t\t}\n\t\t\treturn list.filter((s) => (s && s.placement) === this.placement)\n\t\t},\n\n\t\t/**\n\t\t * Each filtered section augmented with its resolved Vue `component`\n\t\t * (or null) and its token-resolved `props`. Unresolved components keep\n\t\t * `component: null` so the template renders an inline error rather than\n\t\t * crashing.\n\t\t *\n\t\t * @return {Array<{id: string, title: string, component: object|null, props: object, name: string, colSpan: number|undefined, placement: string|undefined}>}\n\t\t */\n\t\tresolvedSections() {\n\t\t\treturn this.filteredSections.map((section, index) => {\n\t\t\t\tconst name = section && section.component\n\t\t\t\tconst id = (section && section.id) || name || `section-${index}`\n\t\t\t\treturn {\n\t\t\t\t\tid,\n\t\t\t\t\tname,\n\t\t\t\t\ttitle: (section && section.title) || '',\n\t\t\t\t\tcard: !!(section && section.card),\n\t\t\t\t\tcolSpan: section && section.colSpan,\n\t\t\t\t\tplacement: section && section.placement,\n\t\t\t\t\tcomponent: this.resolveSectionComponent(name),\n\t\t\t\t\tprops: this.resolveSectionProps(section),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve a section's host-app component by registry name. Precedence\n\t\t * matches CnPageRenderer.resolveCustomComponent: the v2 `cnRegistry`\n\t\t * (any kind exposing a `.component`) wins, else the legacy\n\t\t * `cnCustomComponents` map. Returns null when neither has the name —\n\t\t * the section then renders an inline error.\n\t\t *\n\t\t * @param {string} name The registry component name.\n\t\t * @return {object|null} The resolved Vue component, or null.\n\t\t */\n\t\tresolveSectionComponent(name) {\n\t\t\tif (typeof name !== 'string' || name === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst reg = (this.cnRegistry && this.cnRegistry[name]) || null\n\t\t\tif (reg && reg.component) {\n\t\t\t\treturn reg.component\n\t\t\t}\n\t\t\tconst legacy = this.cnCustomComponents && this.cnCustomComponents[name]\n\t\t\treturn legacy || null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a section's `props` map: every value is passed through\n\t\t * resolveFilterValue with this page's context so `@objectId` /\n\t\t * `@object.<field>` / `@workspace.<key>` / time tokens resolve. A value\n\t\t * that resolves to an unset OPTIONAL token (`@…?` left as-is, or a\n\t\t * required `@object.<field>` whose field is absent) is DROPPED from the\n\t\t * props so the child sees `undefined` rather than a raw `@…` string.\n\t\t *\n\t\t * @param {object} section The section descriptor.\n\t\t * @return {object} The token-resolved props for the host component.\n\t\t */\n\t\tresolveSectionProps(section) {\n\t\t\tconst raw = (section && section.props && typeof section.props === 'object') ? section.props : {}\n\t\t\tconst ctx = this.context || {}\n\t\t\tconst out = {}\n\t\t\tfor (const [key, value] of Object.entries(raw)) {\n\t\t\t\tconst resolved = resolveFilterValue(value, ctx)\n\t\t\t\t// Drop a value that stayed an unresolved `@`-token (optional\n\t\t\t\t// workspace token, or an @object/@objectId with no context yet)\n\t\t\t\t// so the child receives undefined instead of a literal token.\n\t\t\t\tif (typeof resolved === 'string' && resolved.charAt(0) === '@') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout[key] = resolved\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Layout style for a section — a grid `grid-column: span N` when the\n\t\t * grid is active and the section declares a `colSpan`; otherwise none.\n\t\t *\n\t\t * @param {object} entry A resolved section.\n\t\t * @return {object} An inline-style object.\n\t\t */\n\t\tsectionStyle(entry) {\n\t\t\tif (!this.useGrid || typeof entry.colSpan !== 'number') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst span = Math.max(1, Math.min(12, entry.colSpan))\n\t\t\treturn { gridColumn: `span ${span}` }\n\t\t},\n\n\t\t/**\n\t\t * Inline message for a section whose `component` name did not resolve.\n\t\t *\n\t\t * @param {object} entry A resolved section.\n\t\t * @return {string}\n\t\t */\n\t\tunresolvedLabel(entry) {\n\t\t\treturn t('nextcloud-vue', 'Section component \"{name}\" is not registered.', { name: entry.name || entry.id })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-body-sections {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\twidth: 100%;\n}\n\n.cn-body-sections--grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(12, 1fr);\n\tgap: 16px;\n}\n\n.cn-body-sections__section {\n\tmin-width: 0;\n}\n\n/* Same card as the grid's widgets, so a section reads as one of them. */\n.cn-body-sections__section--card {\n\tpadding: 12px 16px 16px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n}\n\n.cn-body-sections__section--card > .cn-body-sections__title {\n\tmargin: 0 -16px 12px;\n\tpadding: 0 16px 12px;\n\tfont-size: 14px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-body-sections__title {\n\tmargin: 0 0 8px;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-body-sections__error {\n\tpadding: 12px 16px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground-color: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n</style>\n"],"names":["_normalizeClass","_createElementBlock","_normalizeStyle"],"mappings":";;;;;;;;;AAkDQ,EAAA,OAAA,CAAA,QAAA,CAAA,gBAAK,CAAA,MAAA,GAAA,CAAA;;;QACR,KAAE,EAAAA,cAAA,CAAA,CAAC,kBAAiB,EAAA,EAAA,wBAAA,EACD,QAAA,CAAA,OAAA,EAAA,CAAA,CAAA;qBACX,EAAC,kBAAkB;;0BAC7BC,8CACgB,QAAgB,CAAA,gBAAA,EAAA,CAAzB,KAAK,KAAA;+BADZA;YAEC,GAAE,EAAA,KAAA,CAAA,EAAA;iBACE,EAACD,cAAA,CAAA,CAAA,2BAAA,EACyB,EAAA,iCAAA,EAAA,KAAA,CAAA,IAAA,EAAA,CAAA,CAAA;YAC9B,KAAK,EAAAE,cAAA,CAAE,QAAY,CAAA,YAAA,CAAC,KAAK,CAAA,CAAA;YACzB,iBAAe,EAAE,KAAK,CAAC,EAAC;AACzB,YAAA,aAAK,EAAA,iBAAA;;AAEF,YAAA,CAAA,KAAA,CAAA,KAAA;8BADHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnBodySections.vue3.js","sources":["../../../../src/components/CnBodySections/CnBodySections.vue?vue&type=template&id=44324dca&lang.js"],"sourcesContent":["<!--\n CnBodySections — declarative in-body sections primitive.\n\n Renders an array of `sections` (manifest `config.bodyWidgets` /\n `config.sections`) as titled cards IN THE PAGE BODY, each hosting a\n registered host-app component. Unlike the integration registry (which\n forces a sidebar tab + widget parity pair), a body section resolves its\n component straight from the v2 component registry (`cnRegistry`) or the\n legacy `cnCustomComponents` map — the SAME resolver CnPageRenderer uses\n for `type:\"custom\"` page components. No sidebar tab is required.\n\n Each section's `props` are token-resolved via resolveFilterValue\n (`@objectId`, `@object.<field>`, `@workspace.<key>`, the time tokens,\n `@me`, …). Optional `?`-suffixed tokens are dropped when unset. The\n loaded object + objectId are also `provide`d on `cnSectionContext` so a\n host component written to inject context works without explicit props.\n\n A section whose component cannot be resolved, or whose render throws,\n degrades to an inline error card — it never breaks the page.\n\n Reusable across detail / dashboard / index pages: pass the page's\n `context` ({ objectId, object, workspace, register, schema }) and the\n list of sections.\n-->\n<template>\n\t<div\n\t\tv-if=\"resolvedSections.length > 0\"\n\t\tclass=\"cn-body-sections\"\n\t\t:class=\"{ 'cn-body-sections--grid': useGrid }\"\n\t\tdata-testid=\"cn-body-sections\">\n\t\t<section\n\t\t\tv-for=\"entry in resolvedSections\"\n\t\t\t:key=\"entry.id\"\n\t\t\tclass=\"cn-body-sections__section\"\n\t\t\t:class=\"{ 'cn-body-sections__section--card': entry.card }\"\n\t\t\t:style=\"sectionStyle(entry)\"\n\t\t\t:data-section-id=\"entry.id\"\n\t\t\tdata-testid=\"cn-body-section\">\n\t\t\t<h3\n\t\t\t\tv-if=\"entry.title\"\n\t\t\t\tclass=\"cn-body-sections__title\"\n\t\t\t\t:data-testid=\"`cn-body-section-title-${entry.id}`\">\n\t\t\t\t{{ entry.title }}\n\t\t\t</h3>\n\t\t\t<!-- Resolved + rendered host component, guarded by an error\n\t\t\t boundary so a throwing section degrades inline. -->\n\t\t\t<CnSectionBoundary\n\t\t\t\tv-if=\"entry.component\"\n\t\t\t\t:sectionId=\"entry.id\"\n\t\t\t\t:errorLabel=\"errorLabel\">\n\t\t\t\t<component\n\t\t\t\t\t:is=\"entry.component\"\n\t\t\t\t\tv-bind=\"entry.props\"\n\t\t\t\t\t:data-testid=\"`cn-body-section-component-${entry.id}`\" />\n\t\t\t</CnSectionBoundary>\n\t\t\t<!-- Unresolved component: inline error, page survives. -->\n\t\t\t<div\n\t\t\t\tv-else\n\t\t\t\tclass=\"cn-body-sections__error\"\n\t\t\t\t:data-testid=\"`cn-body-section-error-${entry.id}`\">\n\t\t\t\t{{ unresolvedLabel(entry) }}\n\t\t\t</div>\n\t\t</section>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { provide, ref, watch } from 'vue'\nimport { resolveFilterValue } from '../../utils/resolveFilterTokens.js'\nimport CnSectionBoundary from './CnSectionBoundary.js'\n\nexport default {\n\tname: 'CnBodySections',\n\n\tcomponents: {\n\t\tCnSectionBoundary,\n\t},\n\n\tinject: {\n\t\t/** V2 component registry (kind-tagged) provided by CnAppRoot. */\n\t\tcnRegistry: { default: () => ({}) },\n\t\t/** Legacy custom-component map provided by CnAppRoot. */\n\t\tcnCustomComponents: { default: () => ({}) },\n\t\t/** The host's translate function for manifest-authored strings, provided by CnAppRoot. */\n\t\tcnTranslate: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Declarative in-body section descriptors. Each entry:\n\t\t * `{ id, component, title?, props?, placement?, colSpan?, card? }`.\n\t\t * `card: true` draws the section in the same card as the grid's\n\t\t * widgets, for a component that has no card of its own.\n\t\t * `component` is the registry name of a host-app component\n\t\t * (resolved from `cnRegistry` then `cnCustomComponents`).\n\t\t *\n\t\t * @type {Array<{id?: string, component: string, title?: string, props?: object, placement?: string, colSpan?: number, card?: boolean}>}\n\t\t */\n\t\tsections: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Page/object context used to resolve `@objectId` / `@object.<field>`\n\t\t * / `@workspace.<key>` / `@config.<key>` tokens in each section's `props`,\n\t\t * and `provide`d on `cnSectionContext` for host components that inject\n\t\t * instead of taking props. Shape:\n\t\t * `{ objectId?, object?, workspace?, config?, register?, schema? }`.\n\t\t *\n\t\t * @type {{objectId?: (string|number), object?: object, workspace?: object, config?: object, register?: string, schema?: string}}\n\t\t */\n\t\tcontext: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Optional placement filter — when set, only sections whose\n\t\t * `placement` matches this value render. CnDetailPage mounts one\n\t\t * CnBodySections per placement slot so sections land in the right\n\t\t * spot in the body. When unset, ALL sections render (no filter).\n\t\t *\n\t\t * @type {string|null}\n\t\t */\n\t\tplacement: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether to lay sections out on a 12-column responsive grid (honouring\n\t\t * each section's `colSpan`). Defaults to true. Set false to stack\n\t\t * full-width.\n\t\t */\n\t\tgrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Inline label shown when a section's component throws while rendering. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This section failed to load.'),\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// Reactive holder published so a host section component can\n\t\t// `inject('cnSectionContext')` and read the loaded object / objectId\n\t\t// without the manifest having to spell out @object props. Mirrors the\n\t\t// dashboard's cnWorkspaceContext / detail page's cnObjectContext pattern.\n\t\tconst cnSectionContext = ref({ ...(props.context || {}) })\n\t\tprovide('cnSectionContext', cnSectionContext)\n\t\twatch(\n\t\t\t() => props.context,\n\t\t\t(next) => {\n\t\t\t\tcnSectionContext.value = { ...(next || {}) }\n\t\t\t},\n\t\t\t{ deep: true },\n\t\t)\n\t\treturn {}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the grid layout is active — only when `grid` is true AND at\n\t\t * least one rendered section sets a `colSpan`. A plain stack otherwise.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tuseGrid() {\n\t\t\treturn this.grid && this.filteredSections.some((s) => typeof s.colSpan === 'number')\n\t\t},\n\n\t\t/**\n\t\t * Sections filtered by the `placement` prop (when set). Order is\n\t\t * preserved from the input array.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tfilteredSections() {\n\t\t\tconst list = Array.isArray(this.sections) ? this.sections : []\n\t\t\tif (this.placement === null || this.placement === undefined) {\n\t\t\t\treturn list\n\t\t\t}\n\t\t\treturn list.filter((s) => (s && s.placement) === this.placement)\n\t\t},\n\n\t\t/**\n\t\t * Each filtered section augmented with its resolved Vue `component`\n\t\t * (or null) and its token-resolved `props`. Unresolved components keep\n\t\t * `component: null` so the template renders an inline error rather than\n\t\t * crashing.\n\t\t *\n\t\t * @return {Array<{id: string, title: string, component: object|null, props: object, name: string, colSpan: number|undefined, placement: string|undefined}>}\n\t\t */\n\t\tresolvedSections() {\n\t\t\treturn this.filteredSections.map((section, index) => {\n\t\t\t\tconst name = section && section.component\n\t\t\t\tconst id = (section && section.id) || name || `section-${index}`\n\t\t\t\treturn {\n\t\t\t\t\tid,\n\t\t\t\t\tname,\n\t\t\t\t\ttitle: this.translateTitle((section && section.title) || ''),\n\t\t\t\t\tcard: !!(section && section.card),\n\t\t\t\t\tcolSpan: section && section.colSpan,\n\t\t\t\t\tplacement: section && section.placement,\n\t\t\t\t\tcomponent: this.resolveSectionComponent(name),\n\t\t\t\t\tprops: this.resolveSectionProps(section),\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest-authored section title in the reader's language.\n\t\t *\n\t\t * The title is written in the manifest, so it belongs to the host\n\t\t * app's catalogue, the same as every other manifest title the detail\n\t\t * page shows. A section heading printed raw read \"Asked about\" to a\n\t\t * Dutch handler on a pipelinq ticket (Zuiddrecht live round 3).\n\t\t *\n\t\t * @param {string} title The title as written in the manifest.\n\t\t * @return {string} The translated title, or '' when there is none.\n\t\t * @spec openspec/changes/detail-page-strings-dutch/specs/detail-page-strings-dutch/spec.md#requirement-the-detail-page-speaks-the-readers-language\n\t\t */\n\t\ttranslateTitle(title) {\n\t\t\tif (title === '' || typeof this.cnTranslate !== 'function') {\n\t\t\t\treturn title\n\t\t\t}\n\t\t\treturn this.cnTranslate(title)\n\t\t},\n\n\t\t/**\n\t\t * Resolve a section's host-app component by registry name. Precedence\n\t\t * matches CnPageRenderer.resolveCustomComponent: the v2 `cnRegistry`\n\t\t * (any kind exposing a `.component`) wins, else the legacy\n\t\t * `cnCustomComponents` map. Returns null when neither has the name —\n\t\t * the section then renders an inline error.\n\t\t *\n\t\t * @param {string} name The registry component name.\n\t\t * @return {object|null} The resolved Vue component, or null.\n\t\t */\n\t\tresolveSectionComponent(name) {\n\t\t\tif (typeof name !== 'string' || name === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst reg = (this.cnRegistry && this.cnRegistry[name]) || null\n\t\t\tif (reg && reg.component) {\n\t\t\t\treturn reg.component\n\t\t\t}\n\t\t\tconst legacy = this.cnCustomComponents && this.cnCustomComponents[name]\n\t\t\treturn legacy || null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a section's `props` map: every value is passed through\n\t\t * resolveFilterValue with this page's context so `@objectId` /\n\t\t * `@object.<field>` / `@workspace.<key>` / time tokens resolve. A value\n\t\t * that resolves to an unset OPTIONAL token (`@…?` left as-is, or a\n\t\t * required `@object.<field>` whose field is absent) is DROPPED from the\n\t\t * props so the child sees `undefined` rather than a raw `@…` string.\n\t\t *\n\t\t * @param {object} section The section descriptor.\n\t\t * @return {object} The token-resolved props for the host component.\n\t\t */\n\t\tresolveSectionProps(section) {\n\t\t\tconst raw = (section && section.props && typeof section.props === 'object') ? section.props : {}\n\t\t\tconst ctx = this.context || {}\n\t\t\tconst out = {}\n\t\t\tfor (const [key, value] of Object.entries(raw)) {\n\t\t\t\tconst resolved = resolveFilterValue(value, ctx)\n\t\t\t\t// Drop a value that stayed an unresolved `@`-token (optional\n\t\t\t\t// workspace token, or an @object/@objectId with no context yet)\n\t\t\t\t// so the child receives undefined instead of a literal token.\n\t\t\t\tif (typeof resolved === 'string' && resolved.charAt(0) === '@') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout[key] = resolved\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Layout style for a section — a grid `grid-column: span N` when the\n\t\t * grid is active and the section declares a `colSpan`; otherwise none.\n\t\t *\n\t\t * @param {object} entry A resolved section.\n\t\t * @return {object} An inline-style object.\n\t\t */\n\t\tsectionStyle(entry) {\n\t\t\tif (!this.useGrid || typeof entry.colSpan !== 'number') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst span = Math.max(1, Math.min(12, entry.colSpan))\n\t\t\treturn { gridColumn: `span ${span}` }\n\t\t},\n\n\t\t/**\n\t\t * Inline message for a section whose `component` name did not resolve.\n\t\t *\n\t\t * @param {object} entry A resolved section.\n\t\t * @return {string}\n\t\t */\n\t\tunresolvedLabel(entry) {\n\t\t\treturn t('nextcloud-vue', 'Section component \"{name}\" is not registered.', { name: entry.name || entry.id })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-body-sections {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\twidth: 100%;\n}\n\n.cn-body-sections--grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(12, 1fr);\n\tgap: 16px;\n}\n\n.cn-body-sections__section {\n\tmin-width: 0;\n}\n\n/* Same card as the grid's widgets, so a section reads as one of them. */\n.cn-body-sections__section--card {\n\tpadding: 12px 16px 16px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n}\n\n.cn-body-sections__section--card > .cn-body-sections__title {\n\tmargin: 0 -16px 12px;\n\tpadding: 0 16px 12px;\n\tfont-size: 14px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-body-sections__title {\n\tmargin: 0 0 8px;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-body-sections__error {\n\tpadding: 12px 16px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground-color: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n}\n</style>\n"],"names":["_normalizeClass","_createElementBlock","_normalizeStyle"],"mappings":";;;;;;;;;AAkDQ,EAAA,OAAA,CAAA,QAAA,CAAA,gBAAK,CAAA,MAAA,GAAA,CAAA;;;QACR,KAAE,EAAAA,cAAA,CAAA,CAAC,kBAAiB,EAAA,EAAA,wBAAA,EACD,QAAA,CAAA,OAAA,EAAA,CAAA,CAAA;qBACX,EAAC,kBAAkB;;0BAC7BC,8CACgB,QAAgB,CAAA,gBAAA,EAAA,CAAzB,KAAK,KAAA;+BADZA;YAEC,GAAE,EAAA,KAAA,CAAA,EAAA;iBACE,EAACD,cAAA,CAAA,CAAA,2BAAA,EACyB,EAAA,iCAAA,EAAA,KAAA,CAAA,IAAA,EAAA,CAAA,CAAA;YAC9B,KAAK,EAAAE,cAAA,CAAE,QAAY,CAAA,YAAA,CAAC,KAAK,CAAA,CAAA;YACzB,iBAAe,EAAE,KAAK,CAAC,EAAC;AACzB,YAAA,aAAK,EAAA,iBAAA;;AAEF,YAAA,CAAA,KAAA,CAAA,KAAA;8BADHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnCountdownWidget.vue2.js';
2
2
  import { render } from './CnCountdownWidget.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-17598258";
5
+ script.__scopeId = "data-v-5546e71c";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };