@conduction/nextcloud-vue 2.56.0-beta.53 → 2.56.0-beta.55

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 (180) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +6 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +2 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  7. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
  8. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  9. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
  10. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +51 -7
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +104 -73
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +25 -8
  17. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +5 -3
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -8
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +13 -4
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  25. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
  26. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
  27. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
  28. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
  29. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
  30. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
  31. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  32. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +54 -3
  33. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +16 -2
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  37. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
  38. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
  42. package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
  43. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  44. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +42 -7
  45. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  46. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +121 -57
  47. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
  49. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
  53. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
  54. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
  55. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
  56. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
  57. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
  58. package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
  59. package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +387 -78
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +317 -209
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/useNamedSource.js +4 -1
  65. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  66. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  67. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
  68. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  70. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  71. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
  72. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  73. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
  74. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  76. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
  77. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
  79. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  80. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  81. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
  82. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  83. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
  84. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  85. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  86. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +68 -7
  87. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  88. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +256 -240
  89. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  90. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
  91. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
  92. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
  93. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
  94. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
  95. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
  96. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +1 -1
  97. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +38 -9
  98. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -1
  99. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  101. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +51 -4
  102. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  103. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +40 -25
  104. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  105. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
  106. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
  110. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +7 -7
  111. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  112. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  113. package/dist/esm/composables/indexSources.js +167 -20
  114. package/dist/esm/composables/indexSources.js.map +1 -1
  115. package/dist/esm/composables/useEndpointSource.js +28 -6
  116. package/dist/esm/composables/useEndpointSource.js.map +1 -1
  117. package/dist/esm/index.js +1 -0
  118. package/dist/esm/index.js.map +1 -1
  119. package/dist/esm/l10n/en.json.js +43 -1
  120. package/dist/esm/l10n/en.json.js.map +1 -1
  121. package/dist/esm/l10n/nl.json.js +41 -1
  122. package/dist/esm/l10n/nl.json.js.map +1 -1
  123. package/dist/esm/library-style.js +1 -1
  124. package/dist/esm/nextcloud-vue.css +1101 -396
  125. package/dist/esm/utils/languageMapText.js +50 -0
  126. package/dist/esm/utils/languageMapText.js.map +1 -0
  127. package/dist/esm/utils/libraryWidgetKeys.js +3 -0
  128. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  129. package/dist/esm/utils/mapList.js +71 -0
  130. package/dist/esm/utils/mapList.js.map +1 -0
  131. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  132. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  133. package/dist/nextcloud-vue.cjs.js +9996 -7378
  134. package/dist/nextcloud-vue.cjs.js.map +2 -2
  135. package/dist/nextcloud-vue.css +1101 -396
  136. package/l10n/en.json +43 -1
  137. package/l10n/nl.json +41 -1
  138. package/package.json +1 -1
  139. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +7 -1
  140. package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
  141. package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
  142. package/src/components/CnDashboardPage/CnDashboardPage.vue +89 -20
  143. package/src/components/CnDetailPage/CnDetailPage.vue +28 -9
  144. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +13 -4
  145. package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
  146. package/src/components/CnExportsWidget/index.js +7 -0
  147. package/src/components/CnFactsWidget/CnFactsWidget.vue +65 -3
  148. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
  149. package/src/components/CnFilesBrowser/filesReview.js +57 -0
  150. package/src/components/CnFlowsPage/CnFlowBoard.vue +77 -2
  151. package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
  152. package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
  153. package/src/components/CnGroupedCardsWidget/index.js +28 -0
  154. package/src/components/CnIndexPage/CnIndexPage.vue +376 -11
  155. package/src/components/CnIndexPage/useNamedSource.js +4 -1
  156. package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
  157. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
  158. package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
  159. package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
  160. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +114 -7
  161. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
  162. package/src/components/CnSavedViewAlertWidget/index.js +7 -0
  163. package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +38 -9
  164. package/src/components/CnSetupWizard/CnSetupWizard.vue +72 -5
  165. package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  167. package/src/components/CnWizardDialog/CnWizardDialog.vue +7 -7
  168. package/src/components/index.js +3 -0
  169. package/src/composables/indexSources.js +166 -19
  170. package/src/composables/useEndpointSource.js +28 -6
  171. package/src/css/detail-page.css +15 -0
  172. package/src/css/dialog.css +48 -0
  173. package/src/css/index-page.css +117 -0
  174. package/src/css/look-board.css +127 -1
  175. package/src/index.js +1 -0
  176. package/src/schemas/app-manifest-v2.schema.json +55 -1
  177. package/src/utils/languageMapText.js +46 -0
  178. package/src/utils/libraryWidgetKeys.js +3 -0
  179. package/src/utils/mapList.js +67 -0
  180. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnSavedViewAlertWidget.vue3.js","sources":["../../../../src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue?vue&type=template&id=4439a3f3&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-saved-view-alert\" data-testid=\"cn-saved-view-alert\">\n\t\t<p v-if=\"!view\" class=\"cn-saved-view-alert__empty\" data-testid=\"cn-saved-view-alert-empty\">\n\t\t\t{{ emptyText }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<dl class=\"cn-saved-view-alert__facts\">\n\t\t\t\t<dt>{{ t('nextcloud-vue', 'Name') }}</dt>\n\t\t\t\t<dd data-testid=\"cn-saved-view-alert-name\">\n\t\t\t\t\t{{ view.name }}\n\t\t\t\t</dd>\n\t\t\t\t<template v-if=\"countText\">\n\t\t\t\t\t<dt>{{ t('nextcloud-vue', 'Now') }}</dt>\n\t\t\t\t\t<dd data-testid=\"cn-saved-view-alert-count\">\n\t\t\t\t\t\t{{ countText }}\n\t\t\t\t\t</dd>\n\t\t\t\t</template>\n\t\t\t</dl>\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\ttype=\"switch\"\n\t\t\t\t:modelValue=\"enabled\"\n\t\t\t\t:disabled=\"saving || !canEdit\"\n\t\t\t\tdata-testid=\"cn-saved-view-alert-switch\"\n\t\t\t\t@update:modelValue=\"onToggle\">\n\t\t\t\t<span class=\"cn-saved-view-alert__switch-text\">\n\t\t\t\t\t<span>{{ t('nextcloud-vue', 'Tell me above a number') }}</span>\n\t\t\t\t\t<span class=\"cn-saved-view-alert__hint\">{{ hintText }}</span>\n\t\t\t\t</span>\n\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t<NcTextField v-if=\"enabled\"\n\t\t\t\t:modelValue=\"String(threshold)\"\n\t\t\t\ttype=\"number\"\n\t\t\t\tmin=\"0\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Threshold')\"\n\t\t\t\t:disabled=\"saving || !canEdit\"\n\t\t\t\tdata-testid=\"cn-saved-view-alert-threshold\"\n\t\t\t\t@update:modelValue=\"onThreshold\" />\n\t\t\t<p v-if=\"error\"\n\t\t\t\tclass=\"cn-saved-view-alert__error\"\n\t\t\t\trole=\"alert\"\n\t\t\t\tdata-testid=\"cn-saved-view-alert-error\">\n\t\t\t\t{{ error }}\n\t\t\t</p>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcCheckboxRadioSwitch, NcTextField } from '@nextcloud/vue'\nimport { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'\n\n/**\n * CnSavedViewAlertWidget — the saved view an index page has applied, with its\n * count alert (DqZoekresultaten \"Opgeslagen zoekopdracht\"): its name, how many\n * rows it holds now, and a switch that asks OpenRegister to tell the reader\n * when the count reaches a threshold.\n *\n * The alert is the view's own `alert` (`{ operator: 'gte', threshold,\n * recipients }`), saved with a PATCH on the view; OpenRegister evaluates it on\n * its sweep and fires on the crossing, not on the state. Switching it off\n * clears it. The widget reads the page through the `cnIndexList` CnIndexPage\n * provides, so it belongs in an index page's `sidePanel`\n * (`type: \"saved-view-alert\"`).\n *\n * Example (manifest):\n * ```json\n * { \"id\": \"alert\", \"type\": \"saved-view-alert\", \"title\": \"Saved search\", \"content\": { \"noun\": { \"one\": \"case\", \"other\": \"cases\" } } }\n * ```\n */\nexport default {\n\tname: 'CnSavedViewAlertWidget',\n\n\tcomponents: { NcCheckboxRadioSwitch, NcTextField },\n\n\tinject: {\n\t\t/** The index page's list: its applied saved view and its total. */\n\t\tcnIndexList: { default: null },\n\t\t/** Host translate function provided by CnAppRoot. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget content: `{ noun?: { one, other }, emptyText? }`. `noun`\n\t\t * names what is counted (\"9 zaken\"); `emptyText` shows while no saved\n\t\t * view is applied. Both are translated with the app's catalogue.\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn { saving: false, error: '', draft: null }\n\t},\n\n\tcomputed: {\n\t\t/** @return {object|null} The applied saved view, or null. */\n\t\tview() {\n\t\t\treturn (this.cnIndexList && this.cnIndexList.appliedView) || null\n\t\t},\n\n\t\t/** @return {boolean} Whether the view carries an alert. */\n\t\tenabled() {\n\t\t\tconst alert = this.view && this.view.alert\n\t\t\treturn Boolean(alert && typeof alert === 'object' && Number.isFinite(Number(alert.threshold)))\n\t\t},\n\n\t\t/** @return {number} The threshold being edited, else the view's, else 10. */\n\t\tthreshold() {\n\t\t\tif (this.draft !== null) {\n\t\t\t\treturn this.draft\n\t\t\t}\n\t\t\tconst value = this.enabled ? Number(this.view.alert.threshold) : 10\n\t\t\treturn Number.isFinite(value) ? value : 10\n\t\t},\n\n\t\t/** @return {boolean} Whether the reader may change the view: its owner, or when the view says nothing. */\n\t\tcanEdit() {\n\t\t\tconst owner = this.view && this.view.owner\n\t\t\tif (!owner) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst user = getCurrentUser()\n\t\t\treturn Boolean(user && user.uid === owner)\n\t\t},\n\n\t\t/** @return {string} \"9 zaken\", or '' while the total is unknown. */\n\t\tcountText() {\n\t\t\tconst total = this.cnIndexList ? this.cnIndexList.total : undefined\n\t\t\tif (typeof total !== 'number') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst noun = this.content && this.content.noun\n\t\t\tif (noun && typeof noun === 'object' && noun.one && noun.other) {\n\t\t\t\treturn `${total} ${this.cnTranslate(total === 1 ? noun.one : noun.other)}`\n\t\t\t}\n\t\t\treturn n('nextcloud-vue', '%n result', '%n results', total)\n\t\t},\n\n\t\t/** @return {string} The line under the switch. */\n\t\thintText() {\n\t\t\treturn t('nextcloud-vue', 'A message to you when there are {threshold} or more', { threshold: this.threshold })\n\t\t},\n\n\t\t/** @return {string} What shows while no saved view is applied. */\n\t\temptyText() {\n\t\t\tconst own = this.content && this.content.emptyText\n\t\t\treturn own ? this.cnTranslate(own) : t('nextcloud-vue', 'Save this search or open a saved one to set an alert on it.')\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Switch the alert on (at the shown threshold) or off.\n\t\t *\n\t\t * @param {boolean} on The new state.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onToggle(on) {\n\t\t\tawait this.save(on ? this.alertFor(this.threshold) : null)\n\t\t},\n\n\t\t/**\n\t\t * Save a new threshold, when the alert is on.\n\t\t *\n\t\t * @param {string} value The typed value.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onThreshold(value) {\n\t\t\tconst number = Math.max(0, Math.floor(Number(value)))\n\t\t\tif (!Number.isFinite(number)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.draft = number\n\t\t\tawait this.save(this.alertFor(number))\n\t\t\tthis.draft = null\n\t\t},\n\n\t\t/**\n\t\t * The alert the view stores: reaching the threshold tells the reader.\n\t\t *\n\t\t * @param {number} threshold The threshold.\n\t\t * @return {{operator: string, threshold: number, recipients: Array<string>}}\n\t\t */\n\t\talertFor(threshold) {\n\t\t\tconst user = getCurrentUser()\n\t\t\treturn { operator: 'gte', threshold, recipients: user && user.uid ? [user.uid] : [] }\n\t\t},\n\n\t\t/**\n\t\t * PATCH the view's alert and hand the saved view back to the page.\n\t\t *\n\t\t * @param {object|null} alert The alert, or null to clear it.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync save(alert) {\n\t\t\tif (!this.view) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst saved = await useSavedViewsApi().patchView(this.view.uuid || this.view.id, { alert })\n\t\t\t\tif (saved && this.cnIndexList && typeof this.cnIndexList.replaceSavedView === 'function') {\n\t\t\t\t\tthis.cnIndexList.replaceSavedView(saved)\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthis.error = (error && error.response && error.response.data && error.response.data.error) || t('nextcloud-vue', 'The alert could not be saved.')\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-saved-view-alert {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tfont-size: 14px;\n}\n\n.cn-saved-view-alert__facts {\n\tdisplay: grid;\n\tgrid-template-columns: max-content 1fr;\n\tgap: 6px 16px;\n\tmargin: 0;\n}\n\n.cn-saved-view-alert__facts dt {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-saved-view-alert__facts dd {\n\tmargin: 0;\n\tfont-weight: 500;\n}\n\n.cn-saved-view-alert__switch-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-saved-view-alert__hint,\n.cn-saved-view-alert__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-saved-view-alert__empty,\n.cn-saved-view-alert__error {\n\tmargin: 0;\n}\n\n.cn-saved-view-alert__error {\n\tcolor: var(--color-error-text, var(--color-error));\n\tfont-size: 13px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createElementVNode","_Fragment","_createVNode","_createBlock"],"mappings":";;;AASM,EAAA,KAAI,EAAE,qBAAM;AAAA,EAAA,aAAA,EAAA,qBAAA;;;;AACA,EAAA,KAAK,EAAC,4BAAiB;AAAA,EAAA,aAAA,EAAA,2BAAA;;AAIlC,MAAA,UAAA,GAAA,EAAA,KAAC,EAAA,4BAAA,GAAA;AAEA,MAAA,UAAA,GAAA,EAAA,aAAW,EAAC,0BAAwB,GAAA;AAKrC,MAAA,UAAA,GAAA,EAAA,aAAA,EAAA,2BAAA,GAAA;AAWG,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,kCAAU,GAAA;AAEf,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,2BAAiB,GAAA;;;AAY9B,EAAA,KAAE,EAAA,4BAAA;AACF,EAAA,IAAI,EAAC,OAAG;AACR,EAAA,aAAE,EAAA,2BAAA;;;;;;;AAvCJ,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;MACU,QAAI,CAAA,IAAA;sBAAbA,kBAEG,CAAA,GAAA,EAFH,UAEG,EAAAC,eAAA,CADA,QAAS,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;6BAcP,gBAAA,EAAA;AAVJ,YAAAC,kBAAA,CAA0B,4BAAnB,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,MAAA,CAAA,CAAA,EAAA,CAAA,YAAA;AACP,YAAAA,kBAAA,CAEI,IAFJ,EAAA,UAEI,EADAD,eAAA,CAAA,QAAA,CAAA,IAAG,CAAC,IAAI,CAAA,EAAA,CAAA,YAAA;aAER,QAAA,CAAA,SAAA;gDAKM,CAAAE,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;AAJV,kBAAAD,kBAAA,CAAS,4BAAF,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;kBACRA,kBAEK,CAAA,IAAA,EAFL,UAEK,EAAAD,eAAA,CADD,QAAS,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;;;UAIdG,WAUM,CAAA,gCAAA,EAAA;AATN,YAAA,IAAI,EAAC,QAAQ;AACZ,YAAA,UAAU,EAAE,QAAO,CAAA,OAAA;YACnB,QAAS,EAAC,KAAM,CAAA,MAAA,IAAA,CAAK,QAAO,CAAA,OAAA;yBAClB,EAAC,4BAA4B;AACvC,YAAA,qBAAK,EAAA,QAAA,CAAA,QAAA;;6BACN,MAGO;AAHP,cAAAF,kBAAA,CAGO,QAHP,UAGO,EAAA;AAFN,gBAAAA,kBAAA,CAAwB,8BAAf,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,wBAAA,CAAA,CAAA,EAAA,CAAA,YAAA;gBACTA,kBAA6B,CAAA,MAAA,EAA7B,UAA6B,EAAAD,eAAA,CAAA,QAAA,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;;;;;WAGZ,QAAO,CAAA,OAAA;4BAAzBI,WAOmC,CAAA,sBAAA,EAAA;;gBANlC,UAAU,EAAE,MAAM,CAAE,QAAQ,CAAA,SAAA,CAAA;AAC7B,gBAAA,IAAI,EAAC,QAAQ;AACb,gBAAA,GAAG,EAAC,GAAG;AACP,gBAAA,KAAM,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,WAAA,CAAA;gBACR,QAAQ,EAAE,KAAM,CAAA,MAAA,IAAA,CAAK,QAAO,CAAA,OAAA;AAC7B,gBAAA,aAAW,EAAA,+BAAA;AACV,gBAAA,qBAAiB,EAAE,QAAW,CAAA,WAAA;;;gBACrB,CAAA,KAAA;4BAAVL,wBAAA;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnSettingsFieldsSection.vue2.js';
2
2
  import { render } from './CnSettingsFieldsSection.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-65c151a8";
5
+ script.__scopeId = "data-v-090828e4";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -7,6 +7,32 @@ import { serverErrorMessage } from '../../utils/serverErrorMessage.js';
7
7
  import __sfc_main from '../CnSettingsField/CnSettingsField.vue.js';
8
8
  import __sfc_main$1 from '../CnSettingsSection/CnSettingsSection.vue.js';
9
9
 
10
+ /**
11
+ * A value by key: the key as written when the object holds it (a flat map
12
+ * with dotted keys such as `integration.brk.mode`), else as a dotted path.
13
+ *
14
+ * @param {object} data The object.
15
+ * @param {string} key The key or dotted path.
16
+ * @return {unknown}
17
+ */
18
+ function literalFirst(data, key) {
19
+ if (data && typeof data === 'object' && Object.hasOwn(data, key)) {
20
+ return data[key]
21
+ }
22
+ return readPath(data, key)
23
+ }
24
+
25
+ /**
26
+ * The request options for a target: an OCS endpoint (`/ocs/...`) needs the
27
+ * `OCS-APIRequest` header; `prefixUrl` already serves it outside index.php.
28
+ *
29
+ * @param {string} url The target.
30
+ * @return {object}
31
+ */
32
+ function ocsConfig(url) {
33
+ return /^\/ocs\//.test(String(url)) ? { headers: { 'OCS-APIRequest': 'true' } } : {}
34
+ }
35
+
10
36
  /**
11
37
  * Up to two initials of a name, for an image field without an image.
12
38
  *
@@ -85,15 +111,16 @@ var script = {
85
111
  * The fields: `[{ key, label, type?, hint?, placeholder?, required?,
86
112
  * pattern?, patternMessage?, min?, max?, rows?, options?, optionsUrl?,
87
113
  * optionsPath?, optionLabel?, optionValue?, default?, readOnly?, wide?,
88
- * url?, valuePath?, saveUrl?, saveMethod?, saveKey?, uploadUrl?,
114
+ * url?, valuePath?, saveUrl?, saveMethod?, saveKey?, saveParams?, uploadUrl?,
89
115
  * uploadField?, uploadParams?, uploadValuePath?, initials?, initialsFrom? }]`.
90
116
  * `type` is `text` (default), `textarea`, `number`, `switch`, `select`,
91
117
  * `secret` or `image`. `key` names the setting; a dotted key reads a
92
118
  * nested value and is written as written. A field with its own `url`
93
119
  * is read there (at `valuePath`, default its key) and saved to its
94
120
  * `saveUrl` (default that `url`) with `saveMethod` (default POST) under
95
- * `saveKey` (default its key), so one section can hold the settings of
96
- * several apps. An `image` uploads at once to `uploadUrl`. Labels,
121
+ * `saveKey` (default its key), with `saveParams` merged into the body,
122
+ * so one section can hold the settings of several apps. A target under
123
+ * `/ocs/` is sent the `OCS-APIRequest` header. An `image` uploads at once to `uploadUrl`. Labels,
97
124
  * hints, placeholders and messages go through the app's translate
98
125
  * function.
99
126
  *
@@ -282,7 +309,7 @@ var script = {
282
309
  const failures = {};
283
310
  await Promise.all(targets.map(async (target) => {
284
311
  try {
285
- const response = await axios__default.get(prefixUrl(target));
312
+ const response = await axios__default.get(prefixUrl(target), ocsConfig(target));
286
313
  answers[target] = response?.data ?? {};
287
314
  } catch (error) {
288
315
  failures[target] = error;
@@ -335,7 +362,8 @@ var script = {
335
362
 
336
363
  /**
337
364
  * A field's value in its read answer: at its `valuePath` (a field with its
338
- * own `url`), else at the section's `valuesPath`, then its `key`.
365
+ * own `url`), else at the section's `valuesPath`, then its `key`. A key
366
+ * the answer holds as written wins over reading it as a dotted path.
339
367
  *
340
368
  * @param {object} field The field.
341
369
  * @param {object|null} data The answer of the field's read target.
@@ -346,10 +374,10 @@ var script = {
346
374
  return undefined
347
375
  }
348
376
  if (field.url) {
349
- return readPath(data, field.valuePath || field.key)
377
+ return literalFirst(data, field.valuePath || field.key)
350
378
  }
351
- const values = (this.valuesPath ? readPath(data, this.valuesPath) : data) || {};
352
- return Object.hasOwn(values, field.key) ? values[field.key] : readPath(values, field.key)
379
+ const values = (this.valuesPath ? literalFirst(data, this.valuesPath) : data) || {};
380
+ return literalFirst(values, field.key)
353
381
  },
354
382
 
355
383
  /**
@@ -433,6 +461,7 @@ var script = {
433
461
  }
434
462
  const group = groups.get(id);
435
463
  group.fields.push(field);
464
+ Object.assign(group.data, field.saveParams || {});
436
465
  group.data[field.saveKey || field.key] = value;
437
466
  }
438
467
  this.saving = true;
@@ -441,7 +470,7 @@ var script = {
441
470
  const sent = {};
442
471
  for (const group of groups.values()) {
443
472
  try {
444
- await axios__default.request({ url: prefixUrl(group.url), method: group.method, data: group.data });
473
+ await axios__default.request({ url: prefixUrl(group.url), method: group.method, data: group.data, ...ocsConfig(group.url) });
445
474
  Object.assign(sent, group.data);
446
475
  this.acceptSaved(group.fields);
447
476
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"CnSettingsFieldsSection.vue2.js","sources":["../../../../src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<CnSettingsSection\n\t\t:name=\"name\"\n\t\t:description=\"description\"\n\t\t:docUrl=\"docUrl\"\n\t\t:wide=\"wide\"\n\t\t:loading=\"loading\"\n\t\t:error=\"loadError !== ''\"\n\t\t:errorMessage=\"loadError\"\n\t\t:onRetry=\"load\"\n\t\tdata-testid=\"cn-settings-fields-section\">\n\t\t<form v-if=\"!loading && loadError === ''\"\n\t\t\tclass=\"cn-settings-fields\"\n\t\t\tnovalidate\n\t\t\t@submit.prevent=\"save\">\n\t\t\t<div class=\"cn-settings-fields__grid\" :class=\"`cn-settings-fields__grid--${columnCount}`\">\n\t\t\t\t<CnSettingsField\n\t\t\t\t\tv-for=\"field in resolvedFields\"\n\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\tclass=\"cn-settings-fields__field\"\n\t\t\t\t\t:class=\"{ 'cn-settings-fields__field--wide': field.wide }\"\n\t\t\t\t\t:field=\"field\"\n\t\t\t\t\t:modelValue=\"form[field.key]\"\n\t\t\t\t\t:error=\"errors[field.key] || unreadable[field.key] || ''\"\n\t\t\t\t\t:hint=\"field.hint\"\n\t\t\t\t\t@update:modelValue=\"setValue(field, $event)\"\n\t\t\t\t\t@uploaded=\"onUploaded(field, $event)\" />\n\t\t\t</div>\n\n\t\t\t<NcNoteCard v-if=\"notice\"\n\t\t\t\t:type=\"notice.type\"\n\t\t\t\t:role=\"notice.type === 'error' ? 'alert' : 'status'\"\n\t\t\t\tdata-testid=\"cn-settings-fields-notice\">\n\t\t\t\t{{ notice.text }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<div class=\"cn-settings-section__save cn-settings-fields__save\">\n\t\t\t\t<span v-if=\"dirty\" class=\"cn-settings-fields__dirty\" data-testid=\"cn-settings-fields-dirty\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Unsaved changes') }}\n\t\t\t\t</span>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t:disabled=\"saving || !dirty\"\n\t\t\t\t\tdata-testid=\"cn-settings-fields-save\">\n\t\t\t\t\t<template v-if=\"saving\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ resolvedSaveLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</form>\n\t</CnSettingsSection>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\nimport { CnSettingsField } from '../CnSettingsField/index.js'\nimport { CnSettingsSection } from '../CnSettingsSection/index.js'\n\n/**\n * Up to two initials of a name, for an image field without an image.\n *\n * @param {unknown} name The name.\n * @return {string}\n */\nfunction initialsOf(name) {\n\treturn String(name || '').trim().split(/\\s+/).filter(Boolean).slice(0, 2).map((word) => word[0].toUpperCase()).join('')\n}\n\n/**\n * CnSettingsFieldsSection - a settings section declared as fields: it reads\n * the app's settings, draws the fields in one to three columns, and saves\n * them with ONE Save button for the section.\n *\n * It reads `GET url` (default `/apps/<appId>/api/settings`) and writes the\n * section's fields with `PUT saveUrl` (default the same url). The Save button\n * is on while something changed (\"Unsaved changes\"); a save checks the\n * fields first (required, pattern, min, max) and marks each failing field,\n * then says it saved or why it did not. A `secret` field never shows the\n * stored value: it says a value is stored, and is sent only when something\n * was typed.\n *\n * It renders inside CnAdminSettingsShell and CnSettingsPage, in the default\n * and the board look.\n *\n * ```vue\n * <CnSettingsFieldsSection\n * app-id=\"myapp\"\n * name=\"Connection\"\n * :columns=\"2\"\n * :fields=\"[\n * { key: 'baseUrl', label: 'Server address', required: true, pattern: '^https://', patternMessage: 'Starts with https://' },\n * { key: 'apiKey', label: 'API key', type: 'secret' },\n * { key: 'mode', label: 'Mode', type: 'select', options: [{ value: 'live', label: 'Live' }, { value: 'test', label: 'Test' }] },\n * { key: 'enabled', label: 'Enabled', type: 'switch', hint: 'Off stops every call.' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnSettingsFieldsSection',\n\n\tcomponents: { CnSettingsField, CnSettingsSection, NcButton, NcLoadingIcon, NcNoteCard },\n\n\tinject: {\n\t\t/** The app's translate function; identity outside CnAppRoot. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The section title. */\n\t\tname: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The line under the title. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** A documentation link beside the title. */\n\t\tdocUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Span the whole settings grid in the board look. */\n\t\twide: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * The fields: `[{ key, label, type?, hint?, placeholder?, required?,\n\t\t * pattern?, patternMessage?, min?, max?, rows?, options?, optionsUrl?,\n\t\t * optionsPath?, optionLabel?, optionValue?, default?, readOnly?, wide?,\n\t\t * url?, valuePath?, saveUrl?, saveMethod?, saveKey?, uploadUrl?,\n\t\t * uploadField?, uploadParams?, uploadValuePath?, initials?, initialsFrom? }]`.\n\t\t * `type` is `text` (default), `textarea`, `number`, `switch`, `select`,\n\t\t * `secret` or `image`. `key` names the setting; a dotted key reads a\n\t\t * nested value and is written as written. A field with its own `url`\n\t\t * is read there (at `valuePath`, default its key) and saved to its\n\t\t * `saveUrl` (default that `url`) with `saveMethod` (default POST) under\n\t\t * `saveKey` (default its key), so one section can hold the settings of\n\t\t * several apps. An `image` uploads at once to `uploadUrl`. Labels,\n\t\t * hints, placeholders and messages go through the app's translate\n\t\t * function.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-section-declared-as-fields-reads-and-saves-the-apps-settings\n\t\t */\n\t\tfields: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Columns of the field grid: 1, 2 or 3. A narrow card stacks them. */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 1,\n\t\t\tvalidator: (value) => [1, 2, 3].includes(value),\n\t\t},\n\n\t\t/** The app id, for the default url `/apps/<appId>/api/settings`. */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Where the settings are read (`GET`); app-absolute or a full URL. */\n\t\turl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Where the section saves; defaults to `url`. */\n\t\tsaveUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The method of the save request. */\n\t\tsaveMethod: {\n\t\t\ttype: String,\n\t\t\tdefault: 'PUT',\n\t\t\tvalidator: (value) => ['PUT', 'POST', 'PATCH'].includes(String(value).toUpperCase()),\n\t\t},\n\n\t\t/** A dotted path to the values in the GET answer; empty reads the answer itself. */\n\t\tvaluesPath: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The Save button's label. Defaults to \"Save\". */\n\t\tsaveLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved', 'save-error'],\n\n\tdata() {\n\t\treturn {\n\t\t\tform: {},\n\t\t\tbaseline: {},\n\t\t\tstoredSecrets: {},\n\t\t\t// Field key to the message of a read target that failed.\n\t\t\tunreadable: {},\n\t\t\terrors: {},\n\t\t\tloading: true,\n\t\t\tloadError: '',\n\t\t\tsaving: false,\n\t\t\tnotice: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {number} The column count, 1 to 3. */\n\t\tcolumnCount() {\n\t\t\treturn [1, 2, 3].includes(this.columns) ? this.columns : 1\n\t\t},\n\n\t\t/** @return {string} Where the settings are read: `url`, else the app's settings endpoint. */\n\t\treadTarget() {\n\t\t\treturn this.url || (this.appId ? `/apps/${this.appId}/api/settings` : '')\n\t\t},\n\n\t\t/** @return {string} Where the section saves: `saveUrl`, else the read target. */\n\t\twriteTarget() {\n\t\t\treturn this.saveUrl || this.readTarget\n\t\t},\n\n\t\t/**\n\t\t * The declared fields with their text translated and a secret's stored state.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tresolvedFields() {\n\t\t\treturn (Array.isArray(this.fields) ? this.fields : [])\n\t\t\t\t.filter((field) => field && typeof field.key === 'string' && field.key !== '')\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\thint: this.tr(field.hint || ''),\n\t\t\t\t\tplaceholder: this.tr(field.placeholder || ''),\n\t\t\t\t\tsecretSet: field.type === 'secret' ? !!this.storedSecrets[field.key] : undefined,\n\t\t\t\t\tinitials: field.type === 'image' ? initialsOf(field.initialsFrom ? this.form[field.initialsFrom] : field.initials) : undefined,\n\t\t\t\t\treadOnly: field.readOnly || !!this.unreadable[field.key],\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Whether something changed since the last load or save.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-one-save-per-section-with-a-dirty-state\n\t\t */\n\t\tdirty() {\n\t\t\treturn this.resolvedFields.some((field) => field.type !== 'image' && !this.same(this.form[field.key], this.baseline[field.key]))\n\t\t},\n\n\t\t/** @return {string} The Save button's label. */\n\t\tresolvedSaveLabel() {\n\t\t\treturn this.saveLabel ? this.tr(this.saveLabel) : t('nextcloud-vue', 'Save')\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} value A declared string.\n\t\t * @return {string} It through the app's translate function.\n\t\t */\n\t\ttr(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : (value || '')\n\t\t},\n\n\t\t/**\n\t\t * @param {unknown} a A value.\n\t\t * @param {unknown} b Another.\n\t\t * @return {boolean} Whether they count as the same setting.\n\t\t */\n\t\tsame(a, b) {\n\t\t\tconst empty = (v) => v === null || v === undefined || v === ''\n\t\t\tif (empty(a) && empty(b)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn JSON.stringify(a) === JSON.stringify(b)\n\t\t},\n\n\t\t/**\n\t\t * Change a field's value; its error goes until the next save.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {unknown} value The value.\n\t\t * @return {void}\n\t\t */\n\t\tsetValue(field, value) {\n\t\t\tthis.form = { ...this.form, [field.key]: value }\n\t\t\tif (this.errors[field.key]) {\n\t\t\t\tconst { [field.key]: _gone, ...rest } = this.errors\n\t\t\t\tthis.errors = rest\n\t\t\t}\n\t\t\tthis.notice = null\n\t\t},\n\n\t\t/**\n\t\t * Read the settings and fill the fields; a secret's value is never put\n\t\t * in the form, only whether one is stored.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.loadError = ''\n\t\t\ttry {\n\t\t\t\t// One GET per distinct read target; the section's own target must\n\t\t\t\t// answer, a field's own target may fail on its own.\n\t\t\t\tconst targets = [...new Set(this.fields\n\t\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t\t.map((field) => this.readUrlOf(field))\n\t\t\t\t\t.filter(Boolean))]\n\t\t\t\tconst answers = {}\n\t\t\t\tconst failures = {}\n\t\t\t\tawait Promise.all(targets.map(async (target) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst response = await axios.get(prefixUrl(target))\n\t\t\t\t\t\tanswers[target] = response?.data ?? {}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tfailures[target] = error\n\t\t\t\t\t}\n\t\t\t\t}))\n\t\t\t\tif (this.readTarget && failures[this.readTarget]) {\n\t\t\t\t\tthrow failures[this.readTarget]\n\t\t\t\t}\n\t\t\t\tconst form = {}\n\t\t\t\tconst stored = {}\n\t\t\t\tconst unreadable = {}\n\t\t\t\tfor (const field of this.fields || []) {\n\t\t\t\t\tif (!field || !field.key) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst target = this.readUrlOf(field)\n\t\t\t\t\tif (target && failures[target]) {\n\t\t\t\t\t\tunreadable[field.key] = t('nextcloud-vue', 'This value could not be loaded: {reason}', { reason: serverErrorMessage(failures[target]) })\n\t\t\t\t\t}\n\t\t\t\t\tconst raw = this.readValue(field, target ? answers[target] : null)\n\t\t\t\t\tif (field.type === 'secret') {\n\t\t\t\t\t\tstored[field.key] = raw !== null && raw !== undefined && raw !== '' && raw !== false\n\t\t\t\t\t\tform[field.key] = ''\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tform[field.key] = raw === undefined ? (field.default ?? (field.type === 'switch' ? false : '')) : raw\n\t\t\t\t}\n\t\t\t\tthis.form = form\n\t\t\t\tthis.baseline = { ...form }\n\t\t\t\tthis.storedSecrets = stored\n\t\t\t\tthis.unreadable = unreadable\n\t\t\t\tthis.errors = {}\n\t\t\t} catch (error) {\n\t\t\t\tthis.loadError = t('nextcloud-vue', 'The settings could not be loaded: {reason}', { reason: serverErrorMessage(error) })\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Where a field is read: its own `url`, else the section's.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {string}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-field-can-read-and-save-elsewhere\n\t\t */\n\t\treadUrlOf(field) {\n\t\t\treturn field.url || this.readTarget\n\t\t},\n\n\t\t/**\n\t\t * A field's value in its read answer: at its `valuePath` (a field with its\n\t\t * own `url`), else at the section's `valuesPath`, then its `key`.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {object|null} data The answer of the field's read target.\n\t\t * @return {unknown}\n\t\t */\n\t\treadValue(field, data) {\n\t\t\tif (!data || typeof data !== 'object') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (field.url) {\n\t\t\t\treturn readPath(data, field.valuePath || field.key)\n\t\t\t}\n\t\t\tconst values = (this.valuesPath ? readPath(data, this.valuesPath) : data) || {}\n\t\t\treturn Object.hasOwn(values, field.key) ? values[field.key] : readPath(values, field.key)\n\t\t},\n\n\t\t/**\n\t\t * Check the fields: required, pattern, and a number's min and max.\n\t\t *\n\t\t * @return {object} Field key to message; empty when all pass.\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-failing-field-says-why\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.resolvedFields) {\n\t\t\t\tconst value = this.form[field.key]\n\t\t\t\tconst empty = value === null || value === undefined || value === ''\n\t\t\t\tif (field.type === 'switch' || field.type === 'image' || this.unreadable[field.key]) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.required && empty && !(field.type === 'secret' && field.secretSet)) {\n\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} is required.', { field: field.label })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (empty) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n\t\t\t\t\terrors[field.key] = field.patternMessage ? this.tr(field.patternMessage) : t('nextcloud-vue', '{field} is not in the right form.', { field: field.label })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'number') {\n\t\t\t\t\tconst number = Number(value)\n\t\t\t\t\tif (Number.isNaN(number)) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be a number.', { field: field.label })\n\t\t\t\t\t} else if (field.min !== undefined && number < field.min) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be at least {min}.', { field: field.label, min: field.min })\n\t\t\t\t\t} else if (field.max !== undefined && number > field.max) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be at most {max}.', { field: field.label, max: field.max })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\n\t\t/**\n\t\t * Save the section's fields in one request, after checking them. A\n\t\t * secret is sent only when something was typed.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-one-save-per-section-with-a-dirty-state\n\t\t */\n\t\tasync save() {\n\t\t\tif (this.saving) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.errors = this.validate()\n\t\t\tconst failing = Object.keys(this.errors)\n\t\t\tif (failing.length > 0) {\n\t\t\t\tthis.notice = { type: 'error', text: t('nextcloud-vue', 'Check the marked fields.') }\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst el = this.$el?.querySelector?.('[aria-invalid=\"true\"], .cn-settings-field__error')\n\t\t\t\t\tconst control = el?.closest?.('.cn-settings-field')?.querySelector?.('input, textarea, [role=\"combobox\"]')\n\t\t\t\t\tcontrol?.focus?.()\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The changed fields, one request per save target, each carrying its\n\t\t\t// own fields under their `saveKey`. A secret goes only when typed; an\n\t\t\t// image is uploaded on its own; a field that could not be read is left out.\n\t\t\tconst groups = new Map()\n\t\t\tfor (const field of this.resolvedFields) {\n\t\t\t\tconst value = this.form[field.key]\n\t\t\t\tif (field.type === 'image' || this.unreadable[field.key] || this.same(value, this.baseline[field.key])) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'secret' && (value === '' || value === null || value === undefined)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst url = field.saveUrl || (field.url ? field.url : this.writeTarget)\n\t\t\t\tconst method = String(field.saveMethod || (field.saveUrl || field.url ? 'POST' : this.saveMethod)).toLowerCase()\n\t\t\t\tconst id = `${method} ${url}`\n\t\t\t\tif (!groups.has(id)) {\n\t\t\t\t\tgroups.set(id, { url, method, fields: [], data: {} })\n\t\t\t\t}\n\t\t\t\tconst group = groups.get(id)\n\t\t\t\tgroup.fields.push(field)\n\t\t\t\tgroup.data[field.saveKey || field.key] = value\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\tthis.notice = null\n\t\t\tconst failed = []\n\t\t\tconst sent = {}\n\t\t\tfor (const group of groups.values()) {\n\t\t\t\ttry {\n\t\t\t\t\tawait axios.request({ url: prefixUrl(group.url), method: group.method, data: group.data })\n\t\t\t\t\tObject.assign(sent, group.data)\n\t\t\t\t\tthis.acceptSaved(group.fields)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailed.push({ group, error })\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.saving = false\n\t\t\tif (failed.length === 0) {\n\t\t\t\tthis.notice = { type: 'success', text: t('nextcloud-vue', 'Saved.') }\n\t\t\t\t/**\n\t\t\t\t * @event saved The section saved. Payload: what was sent, by save key.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', sent)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst reasons = failed.map(({ group, error }) => (groups.size > 1\n\t\t\t\t? t('nextcloud-vue', '{fields}: {reason}', { fields: group.fields.map((field) => field.label).join(', '), reason: serverErrorMessage(error) })\n\t\t\t\t: serverErrorMessage(error)))\n\t\t\tthis.notice = { type: 'error', text: t('nextcloud-vue', 'The settings could not be saved: {reason}', { reason: reasons.join('; ') }) }\n\t\t\t/**\n\t\t\t * @event save-error A save request failed. Payload: the first error.\n\t\t\t */\n\t\t\tthis.$emit('save-error', failed[0].error)\n\t\t},\n\n\t\t/**\n\t\t * Take saved fields as the new baseline; a saved secret reads as stored.\n\t\t *\n\t\t * @param {Array<object>} fields The fields that saved.\n\t\t * @return {void}\n\t\t */\n\t\tacceptSaved(fields) {\n\t\t\tconst form = { ...this.form }\n\t\t\tconst baseline = { ...this.baseline }\n\t\t\tconst stored = { ...this.storedSecrets }\n\t\t\tfor (const field of fields) {\n\t\t\t\tif (field.type === 'secret') {\n\t\t\t\t\tstored[field.key] = true\n\t\t\t\t\tform[field.key] = ''\n\t\t\t\t}\n\t\t\t\tbaseline[field.key] = form[field.key]\n\t\t\t}\n\t\t\tthis.form = form\n\t\t\tthis.baseline = baseline\n\t\t\tthis.storedSecrets = stored\n\t\t},\n\n\t\t/**\n\t\t * An image field uploaded a file: its new value is the baseline too.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {string} value The new image url.\n\t\t * @return {void}\n\t\t */\n\t\tonUploaded(field, value) {\n\t\t\tthis.form = { ...this.form, [field.key]: value }\n\t\t\tthis.baseline = { ...this.baseline, [field.key]: value }\n\t\t\tthis.notice = { type: 'success', text: t('nextcloud-vue', 'Saved.') }\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-fields {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\tcontainer-type: inline-size;\n}\n\n.cn-settings-fields__grid {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(0, 1fr);\n\tgap: 16px 20px;\n}\n\n/* Two or three columns where the card is wide enough; stacked otherwise. */\n@container (min-width: 520px) {\n\t.cn-settings-fields__grid--2,\n\t.cn-settings-fields__grid--3 {\n\t\tgrid-template-columns: repeat(2, minmax(0, 1fr));\n\t}\n}\n\n@container (min-width: 780px) {\n\t.cn-settings-fields__grid--3 {\n\t\tgrid-template-columns: repeat(3, minmax(0, 1fr));\n\t}\n}\n\n.cn-settings-fields__field--wide {\n\tgrid-column: 1 / -1;\n}\n\n.cn-settings-fields__save {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tgap: 12px;\n}\n\n.cn-settings-fields__dirty {\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnSettingsField","CnSettingsSection","t","axios"],"mappings":"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"}
1
+ {"version":3,"file":"CnSettingsFieldsSection.vue2.js","sources":["../../../../src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<CnSettingsSection\n\t\t:name=\"name\"\n\t\t:description=\"description\"\n\t\t:docUrl=\"docUrl\"\n\t\t:wide=\"wide\"\n\t\t:loading=\"loading\"\n\t\t:error=\"loadError !== ''\"\n\t\t:errorMessage=\"loadError\"\n\t\t:onRetry=\"load\"\n\t\tdata-testid=\"cn-settings-fields-section\">\n\t\t<form v-if=\"!loading && loadError === ''\"\n\t\t\tclass=\"cn-settings-fields\"\n\t\t\tnovalidate\n\t\t\t@submit.prevent=\"save\">\n\t\t\t<div class=\"cn-settings-fields__grid\" :class=\"`cn-settings-fields__grid--${columnCount}`\">\n\t\t\t\t<CnSettingsField\n\t\t\t\t\tv-for=\"field in resolvedFields\"\n\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\tclass=\"cn-settings-fields__field\"\n\t\t\t\t\t:class=\"{ 'cn-settings-fields__field--wide': field.wide }\"\n\t\t\t\t\t:field=\"field\"\n\t\t\t\t\t:modelValue=\"form[field.key]\"\n\t\t\t\t\t:error=\"errors[field.key] || unreadable[field.key] || ''\"\n\t\t\t\t\t:hint=\"field.hint\"\n\t\t\t\t\t@update:modelValue=\"setValue(field, $event)\"\n\t\t\t\t\t@uploaded=\"onUploaded(field, $event)\" />\n\t\t\t</div>\n\n\t\t\t<NcNoteCard v-if=\"notice\"\n\t\t\t\t:type=\"notice.type\"\n\t\t\t\t:role=\"notice.type === 'error' ? 'alert' : 'status'\"\n\t\t\t\tdata-testid=\"cn-settings-fields-notice\">\n\t\t\t\t{{ notice.text }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<div class=\"cn-settings-section__save cn-settings-fields__save\">\n\t\t\t\t<span v-if=\"dirty\" class=\"cn-settings-fields__dirty\" data-testid=\"cn-settings-fields-dirty\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Unsaved changes') }}\n\t\t\t\t</span>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t:disabled=\"saving || !dirty\"\n\t\t\t\t\tdata-testid=\"cn-settings-fields-save\">\n\t\t\t\t\t<template v-if=\"saving\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ resolvedSaveLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</form>\n\t</CnSettingsSection>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\nimport { CnSettingsField } from '../CnSettingsField/index.js'\nimport { CnSettingsSection } from '../CnSettingsSection/index.js'\n\n/**\n * A value by key: the key as written when the object holds it (a flat map\n * with dotted keys such as `integration.brk.mode`), else as a dotted path.\n *\n * @param {object} data The object.\n * @param {string} key The key or dotted path.\n * @return {unknown}\n */\nfunction literalFirst(data, key) {\n\tif (data && typeof data === 'object' && Object.hasOwn(data, key)) {\n\t\treturn data[key]\n\t}\n\treturn readPath(data, key)\n}\n\n/**\n * The request options for a target: an OCS endpoint (`/ocs/...`) needs the\n * `OCS-APIRequest` header; `prefixUrl` already serves it outside index.php.\n *\n * @param {string} url The target.\n * @return {object}\n */\nfunction ocsConfig(url) {\n\treturn /^\\/ocs\\//.test(String(url)) ? { headers: { 'OCS-APIRequest': 'true' } } : {}\n}\n\n/**\n * Up to two initials of a name, for an image field without an image.\n *\n * @param {unknown} name The name.\n * @return {string}\n */\nfunction initialsOf(name) {\n\treturn String(name || '').trim().split(/\\s+/).filter(Boolean).slice(0, 2).map((word) => word[0].toUpperCase()).join('')\n}\n\n/**\n * CnSettingsFieldsSection - a settings section declared as fields: it reads\n * the app's settings, draws the fields in one to three columns, and saves\n * them with ONE Save button for the section.\n *\n * It reads `GET url` (default `/apps/<appId>/api/settings`) and writes the\n * section's fields with `PUT saveUrl` (default the same url). The Save button\n * is on while something changed (\"Unsaved changes\"); a save checks the\n * fields first (required, pattern, min, max) and marks each failing field,\n * then says it saved or why it did not. A `secret` field never shows the\n * stored value: it says a value is stored, and is sent only when something\n * was typed.\n *\n * It renders inside CnAdminSettingsShell and CnSettingsPage, in the default\n * and the board look.\n *\n * ```vue\n * <CnSettingsFieldsSection\n * app-id=\"myapp\"\n * name=\"Connection\"\n * :columns=\"2\"\n * :fields=\"[\n * { key: 'baseUrl', label: 'Server address', required: true, pattern: '^https://', patternMessage: 'Starts with https://' },\n * { key: 'apiKey', label: 'API key', type: 'secret' },\n * { key: 'mode', label: 'Mode', type: 'select', options: [{ value: 'live', label: 'Live' }, { value: 'test', label: 'Test' }] },\n * { key: 'enabled', label: 'Enabled', type: 'switch', hint: 'Off stops every call.' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnSettingsFieldsSection',\n\n\tcomponents: { CnSettingsField, CnSettingsSection, NcButton, NcLoadingIcon, NcNoteCard },\n\n\tinject: {\n\t\t/** The app's translate function; identity outside CnAppRoot. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The section title. */\n\t\tname: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The line under the title. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** A documentation link beside the title. */\n\t\tdocUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Span the whole settings grid in the board look. */\n\t\twide: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * The fields: `[{ key, label, type?, hint?, placeholder?, required?,\n\t\t * pattern?, patternMessage?, min?, max?, rows?, options?, optionsUrl?,\n\t\t * optionsPath?, optionLabel?, optionValue?, default?, readOnly?, wide?,\n\t\t * url?, valuePath?, saveUrl?, saveMethod?, saveKey?, saveParams?, uploadUrl?,\n\t\t * uploadField?, uploadParams?, uploadValuePath?, initials?, initialsFrom? }]`.\n\t\t * `type` is `text` (default), `textarea`, `number`, `switch`, `select`,\n\t\t * `secret` or `image`. `key` names the setting; a dotted key reads a\n\t\t * nested value and is written as written. A field with its own `url`\n\t\t * is read there (at `valuePath`, default its key) and saved to its\n\t\t * `saveUrl` (default that `url`) with `saveMethod` (default POST) under\n\t\t * `saveKey` (default its key), with `saveParams` merged into the body,\n\t\t * so one section can hold the settings of several apps. A target under\n\t\t * `/ocs/` is sent the `OCS-APIRequest` header. An `image` uploads at once to `uploadUrl`. Labels,\n\t\t * hints, placeholders and messages go through the app's translate\n\t\t * function.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-section-declared-as-fields-reads-and-saves-the-apps-settings\n\t\t */\n\t\tfields: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Columns of the field grid: 1, 2 or 3. A narrow card stacks them. */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 1,\n\t\t\tvalidator: (value) => [1, 2, 3].includes(value),\n\t\t},\n\n\t\t/** The app id, for the default url `/apps/<appId>/api/settings`. */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Where the settings are read (`GET`); app-absolute or a full URL. */\n\t\turl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Where the section saves; defaults to `url`. */\n\t\tsaveUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The method of the save request. */\n\t\tsaveMethod: {\n\t\t\ttype: String,\n\t\t\tdefault: 'PUT',\n\t\t\tvalidator: (value) => ['PUT', 'POST', 'PATCH'].includes(String(value).toUpperCase()),\n\t\t},\n\n\t\t/** A dotted path to the values in the GET answer; empty reads the answer itself. */\n\t\tvaluesPath: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The Save button's label. Defaults to \"Save\". */\n\t\tsaveLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved', 'save-error'],\n\n\tdata() {\n\t\treturn {\n\t\t\tform: {},\n\t\t\tbaseline: {},\n\t\t\tstoredSecrets: {},\n\t\t\t// Field key to the message of a read target that failed.\n\t\t\tunreadable: {},\n\t\t\terrors: {},\n\t\t\tloading: true,\n\t\t\tloadError: '',\n\t\t\tsaving: false,\n\t\t\tnotice: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {number} The column count, 1 to 3. */\n\t\tcolumnCount() {\n\t\t\treturn [1, 2, 3].includes(this.columns) ? this.columns : 1\n\t\t},\n\n\t\t/** @return {string} Where the settings are read: `url`, else the app's settings endpoint. */\n\t\treadTarget() {\n\t\t\treturn this.url || (this.appId ? `/apps/${this.appId}/api/settings` : '')\n\t\t},\n\n\t\t/** @return {string} Where the section saves: `saveUrl`, else the read target. */\n\t\twriteTarget() {\n\t\t\treturn this.saveUrl || this.readTarget\n\t\t},\n\n\t\t/**\n\t\t * The declared fields with their text translated and a secret's stored state.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tresolvedFields() {\n\t\t\treturn (Array.isArray(this.fields) ? this.fields : [])\n\t\t\t\t.filter((field) => field && typeof field.key === 'string' && field.key !== '')\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\thint: this.tr(field.hint || ''),\n\t\t\t\t\tplaceholder: this.tr(field.placeholder || ''),\n\t\t\t\t\tsecretSet: field.type === 'secret' ? !!this.storedSecrets[field.key] : undefined,\n\t\t\t\t\tinitials: field.type === 'image' ? initialsOf(field.initialsFrom ? this.form[field.initialsFrom] : field.initials) : undefined,\n\t\t\t\t\treadOnly: field.readOnly || !!this.unreadable[field.key],\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Whether something changed since the last load or save.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-one-save-per-section-with-a-dirty-state\n\t\t */\n\t\tdirty() {\n\t\t\treturn this.resolvedFields.some((field) => field.type !== 'image' && !this.same(this.form[field.key], this.baseline[field.key]))\n\t\t},\n\n\t\t/** @return {string} The Save button's label. */\n\t\tresolvedSaveLabel() {\n\t\t\treturn this.saveLabel ? this.tr(this.saveLabel) : t('nextcloud-vue', 'Save')\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} value A declared string.\n\t\t * @return {string} It through the app's translate function.\n\t\t */\n\t\ttr(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : (value || '')\n\t\t},\n\n\t\t/**\n\t\t * @param {unknown} a A value.\n\t\t * @param {unknown} b Another.\n\t\t * @return {boolean} Whether they count as the same setting.\n\t\t */\n\t\tsame(a, b) {\n\t\t\tconst empty = (v) => v === null || v === undefined || v === ''\n\t\t\tif (empty(a) && empty(b)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn JSON.stringify(a) === JSON.stringify(b)\n\t\t},\n\n\t\t/**\n\t\t * Change a field's value; its error goes until the next save.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {unknown} value The value.\n\t\t * @return {void}\n\t\t */\n\t\tsetValue(field, value) {\n\t\t\tthis.form = { ...this.form, [field.key]: value }\n\t\t\tif (this.errors[field.key]) {\n\t\t\t\tconst { [field.key]: _gone, ...rest } = this.errors\n\t\t\t\tthis.errors = rest\n\t\t\t}\n\t\t\tthis.notice = null\n\t\t},\n\n\t\t/**\n\t\t * Read the settings and fill the fields; a secret's value is never put\n\t\t * in the form, only whether one is stored.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.loadError = ''\n\t\t\ttry {\n\t\t\t\t// One GET per distinct read target; the section's own target must\n\t\t\t\t// answer, a field's own target may fail on its own.\n\t\t\t\tconst targets = [...new Set(this.fields\n\t\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t\t.map((field) => this.readUrlOf(field))\n\t\t\t\t\t.filter(Boolean))]\n\t\t\t\tconst answers = {}\n\t\t\t\tconst failures = {}\n\t\t\t\tawait Promise.all(targets.map(async (target) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst response = await axios.get(prefixUrl(target), ocsConfig(target))\n\t\t\t\t\t\tanswers[target] = response?.data ?? {}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tfailures[target] = error\n\t\t\t\t\t}\n\t\t\t\t}))\n\t\t\t\tif (this.readTarget && failures[this.readTarget]) {\n\t\t\t\t\tthrow failures[this.readTarget]\n\t\t\t\t}\n\t\t\t\tconst form = {}\n\t\t\t\tconst stored = {}\n\t\t\t\tconst unreadable = {}\n\t\t\t\tfor (const field of this.fields || []) {\n\t\t\t\t\tif (!field || !field.key) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst target = this.readUrlOf(field)\n\t\t\t\t\tif (target && failures[target]) {\n\t\t\t\t\t\tunreadable[field.key] = t('nextcloud-vue', 'This value could not be loaded: {reason}', { reason: serverErrorMessage(failures[target]) })\n\t\t\t\t\t}\n\t\t\t\t\tconst raw = this.readValue(field, target ? answers[target] : null)\n\t\t\t\t\tif (field.type === 'secret') {\n\t\t\t\t\t\tstored[field.key] = raw !== null && raw !== undefined && raw !== '' && raw !== false\n\t\t\t\t\t\tform[field.key] = ''\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tform[field.key] = raw === undefined ? (field.default ?? (field.type === 'switch' ? false : '')) : raw\n\t\t\t\t}\n\t\t\t\tthis.form = form\n\t\t\t\tthis.baseline = { ...form }\n\t\t\t\tthis.storedSecrets = stored\n\t\t\t\tthis.unreadable = unreadable\n\t\t\t\tthis.errors = {}\n\t\t\t} catch (error) {\n\t\t\t\tthis.loadError = t('nextcloud-vue', 'The settings could not be loaded: {reason}', { reason: serverErrorMessage(error) })\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Where a field is read: its own `url`, else the section's.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {string}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-field-can-read-and-save-elsewhere\n\t\t */\n\t\treadUrlOf(field) {\n\t\t\treturn field.url || this.readTarget\n\t\t},\n\n\t\t/**\n\t\t * A field's value in its read answer: at its `valuePath` (a field with its\n\t\t * own `url`), else at the section's `valuesPath`, then its `key`. A key\n\t\t * the answer holds as written wins over reading it as a dotted path.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {object|null} data The answer of the field's read target.\n\t\t * @return {unknown}\n\t\t */\n\t\treadValue(field, data) {\n\t\t\tif (!data || typeof data !== 'object') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (field.url) {\n\t\t\t\treturn literalFirst(data, field.valuePath || field.key)\n\t\t\t}\n\t\t\tconst values = (this.valuesPath ? literalFirst(data, this.valuesPath) : data) || {}\n\t\t\treturn literalFirst(values, field.key)\n\t\t},\n\n\t\t/**\n\t\t * Check the fields: required, pattern, and a number's min and max.\n\t\t *\n\t\t * @return {object} Field key to message; empty when all pass.\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-failing-field-says-why\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.resolvedFields) {\n\t\t\t\tconst value = this.form[field.key]\n\t\t\t\tconst empty = value === null || value === undefined || value === ''\n\t\t\t\tif (field.type === 'switch' || field.type === 'image' || this.unreadable[field.key]) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.required && empty && !(field.type === 'secret' && field.secretSet)) {\n\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} is required.', { field: field.label })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (empty) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n\t\t\t\t\terrors[field.key] = field.patternMessage ? this.tr(field.patternMessage) : t('nextcloud-vue', '{field} is not in the right form.', { field: field.label })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'number') {\n\t\t\t\t\tconst number = Number(value)\n\t\t\t\t\tif (Number.isNaN(number)) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be a number.', { field: field.label })\n\t\t\t\t\t} else if (field.min !== undefined && number < field.min) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be at least {min}.', { field: field.label, min: field.min })\n\t\t\t\t\t} else if (field.max !== undefined && number > field.max) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be at most {max}.', { field: field.label, max: field.max })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\n\t\t/**\n\t\t * Save the section's fields in one request, after checking them. A\n\t\t * secret is sent only when something was typed.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-one-save-per-section-with-a-dirty-state\n\t\t */\n\t\tasync save() {\n\t\t\tif (this.saving) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.errors = this.validate()\n\t\t\tconst failing = Object.keys(this.errors)\n\t\t\tif (failing.length > 0) {\n\t\t\t\tthis.notice = { type: 'error', text: t('nextcloud-vue', 'Check the marked fields.') }\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst el = this.$el?.querySelector?.('[aria-invalid=\"true\"], .cn-settings-field__error')\n\t\t\t\t\tconst control = el?.closest?.('.cn-settings-field')?.querySelector?.('input, textarea, [role=\"combobox\"]')\n\t\t\t\t\tcontrol?.focus?.()\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The changed fields, one request per save target, each carrying its\n\t\t\t// own fields under their `saveKey`. A secret goes only when typed; an\n\t\t\t// image is uploaded on its own; a field that could not be read is left out.\n\t\t\tconst groups = new Map()\n\t\t\tfor (const field of this.resolvedFields) {\n\t\t\t\tconst value = this.form[field.key]\n\t\t\t\tif (field.type === 'image' || this.unreadable[field.key] || this.same(value, this.baseline[field.key])) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'secret' && (value === '' || value === null || value === undefined)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst url = field.saveUrl || (field.url ? field.url : this.writeTarget)\n\t\t\t\tconst method = String(field.saveMethod || (field.saveUrl || field.url ? 'POST' : this.saveMethod)).toLowerCase()\n\t\t\t\tconst id = `${method} ${url}`\n\t\t\t\tif (!groups.has(id)) {\n\t\t\t\t\tgroups.set(id, { url, method, fields: [], data: {} })\n\t\t\t\t}\n\t\t\t\tconst group = groups.get(id)\n\t\t\t\tgroup.fields.push(field)\n\t\t\t\tObject.assign(group.data, field.saveParams || {})\n\t\t\t\tgroup.data[field.saveKey || field.key] = value\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\tthis.notice = null\n\t\t\tconst failed = []\n\t\t\tconst sent = {}\n\t\t\tfor (const group of groups.values()) {\n\t\t\t\ttry {\n\t\t\t\t\tawait axios.request({ url: prefixUrl(group.url), method: group.method, data: group.data, ...ocsConfig(group.url) })\n\t\t\t\t\tObject.assign(sent, group.data)\n\t\t\t\t\tthis.acceptSaved(group.fields)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailed.push({ group, error })\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.saving = false\n\t\t\tif (failed.length === 0) {\n\t\t\t\tthis.notice = { type: 'success', text: t('nextcloud-vue', 'Saved.') }\n\t\t\t\t/**\n\t\t\t\t * @event saved The section saved. Payload: what was sent, by save key.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', sent)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst reasons = failed.map(({ group, error }) => (groups.size > 1\n\t\t\t\t? t('nextcloud-vue', '{fields}: {reason}', { fields: group.fields.map((field) => field.label).join(', '), reason: serverErrorMessage(error) })\n\t\t\t\t: serverErrorMessage(error)))\n\t\t\tthis.notice = { type: 'error', text: t('nextcloud-vue', 'The settings could not be saved: {reason}', { reason: reasons.join('; ') }) }\n\t\t\t/**\n\t\t\t * @event save-error A save request failed. Payload: the first error.\n\t\t\t */\n\t\t\tthis.$emit('save-error', failed[0].error)\n\t\t},\n\n\t\t/**\n\t\t * Take saved fields as the new baseline; a saved secret reads as stored.\n\t\t *\n\t\t * @param {Array<object>} fields The fields that saved.\n\t\t * @return {void}\n\t\t */\n\t\tacceptSaved(fields) {\n\t\t\tconst form = { ...this.form }\n\t\t\tconst baseline = { ...this.baseline }\n\t\t\tconst stored = { ...this.storedSecrets }\n\t\t\tfor (const field of fields) {\n\t\t\t\tif (field.type === 'secret') {\n\t\t\t\t\tstored[field.key] = true\n\t\t\t\t\tform[field.key] = ''\n\t\t\t\t}\n\t\t\t\tbaseline[field.key] = form[field.key]\n\t\t\t}\n\t\t\tthis.form = form\n\t\t\tthis.baseline = baseline\n\t\t\tthis.storedSecrets = stored\n\t\t},\n\n\t\t/**\n\t\t * An image field uploaded a file: its new value is the baseline too.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {string} value The new image url.\n\t\t * @return {void}\n\t\t */\n\t\tonUploaded(field, value) {\n\t\t\tthis.form = { ...this.form, [field.key]: value }\n\t\t\tthis.baseline = { ...this.baseline, [field.key]: value }\n\t\t\tthis.notice = { type: 'success', text: t('nextcloud-vue', 'Saved.') }\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-fields {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\tcontainer-type: inline-size;\n}\n\n.cn-settings-fields__grid {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(0, 1fr);\n\tgap: 16px 20px;\n}\n\n/* Two or three columns where the card is wide enough; stacked otherwise. */\n@container (min-width: 520px) {\n\t.cn-settings-fields__grid--2,\n\t.cn-settings-fields__grid--3 {\n\t\tgrid-template-columns: repeat(2, minmax(0, 1fr));\n\t}\n}\n\n@container (min-width: 780px) {\n\t.cn-settings-fields__grid--3 {\n\t\tgrid-template-columns: repeat(3, minmax(0, 1fr));\n\t}\n}\n\n.cn-settings-fields__field--wide {\n\tgrid-column: 1 / -1;\n}\n\n.cn-settings-fields__save {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tgap: 12px;\n}\n\n.cn-settings-fields__dirty {\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnSettingsField","CnSettingsSection","t","axios"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnSettingsFieldsSection.vue3.js","sources":["../../../../src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue?vue&type=template&id=65c151a8&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<CnSettingsSection\n\t\t:name=\"name\"\n\t\t:description=\"description\"\n\t\t:docUrl=\"docUrl\"\n\t\t:wide=\"wide\"\n\t\t:loading=\"loading\"\n\t\t:error=\"loadError !== ''\"\n\t\t:errorMessage=\"loadError\"\n\t\t:onRetry=\"load\"\n\t\tdata-testid=\"cn-settings-fields-section\">\n\t\t<form v-if=\"!loading && loadError === ''\"\n\t\t\tclass=\"cn-settings-fields\"\n\t\t\tnovalidate\n\t\t\t@submit.prevent=\"save\">\n\t\t\t<div class=\"cn-settings-fields__grid\" :class=\"`cn-settings-fields__grid--${columnCount}`\">\n\t\t\t\t<CnSettingsField\n\t\t\t\t\tv-for=\"field in resolvedFields\"\n\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\tclass=\"cn-settings-fields__field\"\n\t\t\t\t\t:class=\"{ 'cn-settings-fields__field--wide': field.wide }\"\n\t\t\t\t\t:field=\"field\"\n\t\t\t\t\t:modelValue=\"form[field.key]\"\n\t\t\t\t\t:error=\"errors[field.key] || unreadable[field.key] || ''\"\n\t\t\t\t\t:hint=\"field.hint\"\n\t\t\t\t\t@update:modelValue=\"setValue(field, $event)\"\n\t\t\t\t\t@uploaded=\"onUploaded(field, $event)\" />\n\t\t\t</div>\n\n\t\t\t<NcNoteCard v-if=\"notice\"\n\t\t\t\t:type=\"notice.type\"\n\t\t\t\t:role=\"notice.type === 'error' ? 'alert' : 'status'\"\n\t\t\t\tdata-testid=\"cn-settings-fields-notice\">\n\t\t\t\t{{ notice.text }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<div class=\"cn-settings-section__save cn-settings-fields__save\">\n\t\t\t\t<span v-if=\"dirty\" class=\"cn-settings-fields__dirty\" data-testid=\"cn-settings-fields-dirty\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Unsaved changes') }}\n\t\t\t\t</span>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t:disabled=\"saving || !dirty\"\n\t\t\t\t\tdata-testid=\"cn-settings-fields-save\">\n\t\t\t\t\t<template v-if=\"saving\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ resolvedSaveLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</form>\n\t</CnSettingsSection>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\nimport { CnSettingsField } from '../CnSettingsField/index.js'\nimport { CnSettingsSection } from '../CnSettingsSection/index.js'\n\n/**\n * Up to two initials of a name, for an image field without an image.\n *\n * @param {unknown} name The name.\n * @return {string}\n */\nfunction initialsOf(name) {\n\treturn String(name || '').trim().split(/\\s+/).filter(Boolean).slice(0, 2).map((word) => word[0].toUpperCase()).join('')\n}\n\n/**\n * CnSettingsFieldsSection - a settings section declared as fields: it reads\n * the app's settings, draws the fields in one to three columns, and saves\n * them with ONE Save button for the section.\n *\n * It reads `GET url` (default `/apps/<appId>/api/settings`) and writes the\n * section's fields with `PUT saveUrl` (default the same url). The Save button\n * is on while something changed (\"Unsaved changes\"); a save checks the\n * fields first (required, pattern, min, max) and marks each failing field,\n * then says it saved or why it did not. A `secret` field never shows the\n * stored value: it says a value is stored, and is sent only when something\n * was typed.\n *\n * It renders inside CnAdminSettingsShell and CnSettingsPage, in the default\n * and the board look.\n *\n * ```vue\n * <CnSettingsFieldsSection\n * app-id=\"myapp\"\n * name=\"Connection\"\n * :columns=\"2\"\n * :fields=\"[\n * { key: 'baseUrl', label: 'Server address', required: true, pattern: '^https://', patternMessage: 'Starts with https://' },\n * { key: 'apiKey', label: 'API key', type: 'secret' },\n * { key: 'mode', label: 'Mode', type: 'select', options: [{ value: 'live', label: 'Live' }, { value: 'test', label: 'Test' }] },\n * { key: 'enabled', label: 'Enabled', type: 'switch', hint: 'Off stops every call.' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnSettingsFieldsSection',\n\n\tcomponents: { CnSettingsField, CnSettingsSection, NcButton, NcLoadingIcon, NcNoteCard },\n\n\tinject: {\n\t\t/** The app's translate function; identity outside CnAppRoot. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The section title. */\n\t\tname: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The line under the title. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** A documentation link beside the title. */\n\t\tdocUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Span the whole settings grid in the board look. */\n\t\twide: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * The fields: `[{ key, label, type?, hint?, placeholder?, required?,\n\t\t * pattern?, patternMessage?, min?, max?, rows?, options?, optionsUrl?,\n\t\t * optionsPath?, optionLabel?, optionValue?, default?, readOnly?, wide?,\n\t\t * url?, valuePath?, saveUrl?, saveMethod?, saveKey?, uploadUrl?,\n\t\t * uploadField?, uploadParams?, uploadValuePath?, initials?, initialsFrom? }]`.\n\t\t * `type` is `text` (default), `textarea`, `number`, `switch`, `select`,\n\t\t * `secret` or `image`. `key` names the setting; a dotted key reads a\n\t\t * nested value and is written as written. A field with its own `url`\n\t\t * is read there (at `valuePath`, default its key) and saved to its\n\t\t * `saveUrl` (default that `url`) with `saveMethod` (default POST) under\n\t\t * `saveKey` (default its key), so one section can hold the settings of\n\t\t * several apps. An `image` uploads at once to `uploadUrl`. Labels,\n\t\t * hints, placeholders and messages go through the app's translate\n\t\t * function.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-section-declared-as-fields-reads-and-saves-the-apps-settings\n\t\t */\n\t\tfields: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Columns of the field grid: 1, 2 or 3. A narrow card stacks them. */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 1,\n\t\t\tvalidator: (value) => [1, 2, 3].includes(value),\n\t\t},\n\n\t\t/** The app id, for the default url `/apps/<appId>/api/settings`. */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Where the settings are read (`GET`); app-absolute or a full URL. */\n\t\turl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Where the section saves; defaults to `url`. */\n\t\tsaveUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The method of the save request. */\n\t\tsaveMethod: {\n\t\t\ttype: String,\n\t\t\tdefault: 'PUT',\n\t\t\tvalidator: (value) => ['PUT', 'POST', 'PATCH'].includes(String(value).toUpperCase()),\n\t\t},\n\n\t\t/** A dotted path to the values in the GET answer; empty reads the answer itself. */\n\t\tvaluesPath: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The Save button's label. Defaults to \"Save\". */\n\t\tsaveLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved', 'save-error'],\n\n\tdata() {\n\t\treturn {\n\t\t\tform: {},\n\t\t\tbaseline: {},\n\t\t\tstoredSecrets: {},\n\t\t\t// Field key to the message of a read target that failed.\n\t\t\tunreadable: {},\n\t\t\terrors: {},\n\t\t\tloading: true,\n\t\t\tloadError: '',\n\t\t\tsaving: false,\n\t\t\tnotice: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {number} The column count, 1 to 3. */\n\t\tcolumnCount() {\n\t\t\treturn [1, 2, 3].includes(this.columns) ? this.columns : 1\n\t\t},\n\n\t\t/** @return {string} Where the settings are read: `url`, else the app's settings endpoint. */\n\t\treadTarget() {\n\t\t\treturn this.url || (this.appId ? `/apps/${this.appId}/api/settings` : '')\n\t\t},\n\n\t\t/** @return {string} Where the section saves: `saveUrl`, else the read target. */\n\t\twriteTarget() {\n\t\t\treturn this.saveUrl || this.readTarget\n\t\t},\n\n\t\t/**\n\t\t * The declared fields with their text translated and a secret's stored state.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tresolvedFields() {\n\t\t\treturn (Array.isArray(this.fields) ? this.fields : [])\n\t\t\t\t.filter((field) => field && typeof field.key === 'string' && field.key !== '')\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\thint: this.tr(field.hint || ''),\n\t\t\t\t\tplaceholder: this.tr(field.placeholder || ''),\n\t\t\t\t\tsecretSet: field.type === 'secret' ? !!this.storedSecrets[field.key] : undefined,\n\t\t\t\t\tinitials: field.type === 'image' ? initialsOf(field.initialsFrom ? this.form[field.initialsFrom] : field.initials) : undefined,\n\t\t\t\t\treadOnly: field.readOnly || !!this.unreadable[field.key],\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Whether something changed since the last load or save.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-one-save-per-section-with-a-dirty-state\n\t\t */\n\t\tdirty() {\n\t\t\treturn this.resolvedFields.some((field) => field.type !== 'image' && !this.same(this.form[field.key], this.baseline[field.key]))\n\t\t},\n\n\t\t/** @return {string} The Save button's label. */\n\t\tresolvedSaveLabel() {\n\t\t\treturn this.saveLabel ? this.tr(this.saveLabel) : t('nextcloud-vue', 'Save')\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} value A declared string.\n\t\t * @return {string} It through the app's translate function.\n\t\t */\n\t\ttr(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : (value || '')\n\t\t},\n\n\t\t/**\n\t\t * @param {unknown} a A value.\n\t\t * @param {unknown} b Another.\n\t\t * @return {boolean} Whether they count as the same setting.\n\t\t */\n\t\tsame(a, b) {\n\t\t\tconst empty = (v) => v === null || v === undefined || v === ''\n\t\t\tif (empty(a) && empty(b)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn JSON.stringify(a) === JSON.stringify(b)\n\t\t},\n\n\t\t/**\n\t\t * Change a field's value; its error goes until the next save.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {unknown} value The value.\n\t\t * @return {void}\n\t\t */\n\t\tsetValue(field, value) {\n\t\t\tthis.form = { ...this.form, [field.key]: value }\n\t\t\tif (this.errors[field.key]) {\n\t\t\t\tconst { [field.key]: _gone, ...rest } = this.errors\n\t\t\t\tthis.errors = rest\n\t\t\t}\n\t\t\tthis.notice = null\n\t\t},\n\n\t\t/**\n\t\t * Read the settings and fill the fields; a secret's value is never put\n\t\t * in the form, only whether one is stored.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.loadError = ''\n\t\t\ttry {\n\t\t\t\t// One GET per distinct read target; the section's own target must\n\t\t\t\t// answer, a field's own target may fail on its own.\n\t\t\t\tconst targets = [...new Set(this.fields\n\t\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t\t.map((field) => this.readUrlOf(field))\n\t\t\t\t\t.filter(Boolean))]\n\t\t\t\tconst answers = {}\n\t\t\t\tconst failures = {}\n\t\t\t\tawait Promise.all(targets.map(async (target) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst response = await axios.get(prefixUrl(target))\n\t\t\t\t\t\tanswers[target] = response?.data ?? {}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tfailures[target] = error\n\t\t\t\t\t}\n\t\t\t\t}))\n\t\t\t\tif (this.readTarget && failures[this.readTarget]) {\n\t\t\t\t\tthrow failures[this.readTarget]\n\t\t\t\t}\n\t\t\t\tconst form = {}\n\t\t\t\tconst stored = {}\n\t\t\t\tconst unreadable = {}\n\t\t\t\tfor (const field of this.fields || []) {\n\t\t\t\t\tif (!field || !field.key) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst target = this.readUrlOf(field)\n\t\t\t\t\tif (target && failures[target]) {\n\t\t\t\t\t\tunreadable[field.key] = t('nextcloud-vue', 'This value could not be loaded: {reason}', { reason: serverErrorMessage(failures[target]) })\n\t\t\t\t\t}\n\t\t\t\t\tconst raw = this.readValue(field, target ? answers[target] : null)\n\t\t\t\t\tif (field.type === 'secret') {\n\t\t\t\t\t\tstored[field.key] = raw !== null && raw !== undefined && raw !== '' && raw !== false\n\t\t\t\t\t\tform[field.key] = ''\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tform[field.key] = raw === undefined ? (field.default ?? (field.type === 'switch' ? false : '')) : raw\n\t\t\t\t}\n\t\t\t\tthis.form = form\n\t\t\t\tthis.baseline = { ...form }\n\t\t\t\tthis.storedSecrets = stored\n\t\t\t\tthis.unreadable = unreadable\n\t\t\t\tthis.errors = {}\n\t\t\t} catch (error) {\n\t\t\t\tthis.loadError = t('nextcloud-vue', 'The settings could not be loaded: {reason}', { reason: serverErrorMessage(error) })\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Where a field is read: its own `url`, else the section's.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {string}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-field-can-read-and-save-elsewhere\n\t\t */\n\t\treadUrlOf(field) {\n\t\t\treturn field.url || this.readTarget\n\t\t},\n\n\t\t/**\n\t\t * A field's value in its read answer: at its `valuePath` (a field with its\n\t\t * own `url`), else at the section's `valuesPath`, then its `key`.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {object|null} data The answer of the field's read target.\n\t\t * @return {unknown}\n\t\t */\n\t\treadValue(field, data) {\n\t\t\tif (!data || typeof data !== 'object') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (field.url) {\n\t\t\t\treturn readPath(data, field.valuePath || field.key)\n\t\t\t}\n\t\t\tconst values = (this.valuesPath ? readPath(data, this.valuesPath) : data) || {}\n\t\t\treturn Object.hasOwn(values, field.key) ? values[field.key] : readPath(values, field.key)\n\t\t},\n\n\t\t/**\n\t\t * Check the fields: required, pattern, and a number's min and max.\n\t\t *\n\t\t * @return {object} Field key to message; empty when all pass.\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-failing-field-says-why\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.resolvedFields) {\n\t\t\t\tconst value = this.form[field.key]\n\t\t\t\tconst empty = value === null || value === undefined || value === ''\n\t\t\t\tif (field.type === 'switch' || field.type === 'image' || this.unreadable[field.key]) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.required && empty && !(field.type === 'secret' && field.secretSet)) {\n\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} is required.', { field: field.label })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (empty) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n\t\t\t\t\terrors[field.key] = field.patternMessage ? this.tr(field.patternMessage) : t('nextcloud-vue', '{field} is not in the right form.', { field: field.label })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'number') {\n\t\t\t\t\tconst number = Number(value)\n\t\t\t\t\tif (Number.isNaN(number)) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be a number.', { field: field.label })\n\t\t\t\t\t} else if (field.min !== undefined && number < field.min) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be at least {min}.', { field: field.label, min: field.min })\n\t\t\t\t\t} else if (field.max !== undefined && number > field.max) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be at most {max}.', { field: field.label, max: field.max })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\n\t\t/**\n\t\t * Save the section's fields in one request, after checking them. A\n\t\t * secret is sent only when something was typed.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-one-save-per-section-with-a-dirty-state\n\t\t */\n\t\tasync save() {\n\t\t\tif (this.saving) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.errors = this.validate()\n\t\t\tconst failing = Object.keys(this.errors)\n\t\t\tif (failing.length > 0) {\n\t\t\t\tthis.notice = { type: 'error', text: t('nextcloud-vue', 'Check the marked fields.') }\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst el = this.$el?.querySelector?.('[aria-invalid=\"true\"], .cn-settings-field__error')\n\t\t\t\t\tconst control = el?.closest?.('.cn-settings-field')?.querySelector?.('input, textarea, [role=\"combobox\"]')\n\t\t\t\t\tcontrol?.focus?.()\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The changed fields, one request per save target, each carrying its\n\t\t\t// own fields under their `saveKey`. A secret goes only when typed; an\n\t\t\t// image is uploaded on its own; a field that could not be read is left out.\n\t\t\tconst groups = new Map()\n\t\t\tfor (const field of this.resolvedFields) {\n\t\t\t\tconst value = this.form[field.key]\n\t\t\t\tif (field.type === 'image' || this.unreadable[field.key] || this.same(value, this.baseline[field.key])) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'secret' && (value === '' || value === null || value === undefined)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst url = field.saveUrl || (field.url ? field.url : this.writeTarget)\n\t\t\t\tconst method = String(field.saveMethod || (field.saveUrl || field.url ? 'POST' : this.saveMethod)).toLowerCase()\n\t\t\t\tconst id = `${method} ${url}`\n\t\t\t\tif (!groups.has(id)) {\n\t\t\t\t\tgroups.set(id, { url, method, fields: [], data: {} })\n\t\t\t\t}\n\t\t\t\tconst group = groups.get(id)\n\t\t\t\tgroup.fields.push(field)\n\t\t\t\tgroup.data[field.saveKey || field.key] = value\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\tthis.notice = null\n\t\t\tconst failed = []\n\t\t\tconst sent = {}\n\t\t\tfor (const group of groups.values()) {\n\t\t\t\ttry {\n\t\t\t\t\tawait axios.request({ url: prefixUrl(group.url), method: group.method, data: group.data })\n\t\t\t\t\tObject.assign(sent, group.data)\n\t\t\t\t\tthis.acceptSaved(group.fields)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailed.push({ group, error })\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.saving = false\n\t\t\tif (failed.length === 0) {\n\t\t\t\tthis.notice = { type: 'success', text: t('nextcloud-vue', 'Saved.') }\n\t\t\t\t/**\n\t\t\t\t * @event saved The section saved. Payload: what was sent, by save key.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', sent)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst reasons = failed.map(({ group, error }) => (groups.size > 1\n\t\t\t\t? t('nextcloud-vue', '{fields}: {reason}', { fields: group.fields.map((field) => field.label).join(', '), reason: serverErrorMessage(error) })\n\t\t\t\t: serverErrorMessage(error)))\n\t\t\tthis.notice = { type: 'error', text: t('nextcloud-vue', 'The settings could not be saved: {reason}', { reason: reasons.join('; ') }) }\n\t\t\t/**\n\t\t\t * @event save-error A save request failed. Payload: the first error.\n\t\t\t */\n\t\t\tthis.$emit('save-error', failed[0].error)\n\t\t},\n\n\t\t/**\n\t\t * Take saved fields as the new baseline; a saved secret reads as stored.\n\t\t *\n\t\t * @param {Array<object>} fields The fields that saved.\n\t\t * @return {void}\n\t\t */\n\t\tacceptSaved(fields) {\n\t\t\tconst form = { ...this.form }\n\t\t\tconst baseline = { ...this.baseline }\n\t\t\tconst stored = { ...this.storedSecrets }\n\t\t\tfor (const field of fields) {\n\t\t\t\tif (field.type === 'secret') {\n\t\t\t\t\tstored[field.key] = true\n\t\t\t\t\tform[field.key] = ''\n\t\t\t\t}\n\t\t\t\tbaseline[field.key] = form[field.key]\n\t\t\t}\n\t\t\tthis.form = form\n\t\t\tthis.baseline = baseline\n\t\t\tthis.storedSecrets = stored\n\t\t},\n\n\t\t/**\n\t\t * An image field uploaded a file: its new value is the baseline too.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {string} value The new image url.\n\t\t * @return {void}\n\t\t */\n\t\tonUploaded(field, value) {\n\t\t\tthis.form = { ...this.form, [field.key]: value }\n\t\t\tthis.baseline = { ...this.baseline, [field.key]: value }\n\t\t\tthis.notice = { type: 'success', text: t('nextcloud-vue', 'Saved.') }\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-fields {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\tcontainer-type: inline-size;\n}\n\n.cn-settings-fields__grid {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(0, 1fr);\n\tgap: 16px 20px;\n}\n\n/* Two or three columns where the card is wide enough; stacked otherwise. */\n@container (min-width: 520px) {\n\t.cn-settings-fields__grid--2,\n\t.cn-settings-fields__grid--3 {\n\t\tgrid-template-columns: repeat(2, minmax(0, 1fr));\n\t}\n}\n\n@container (min-width: 780px) {\n\t.cn-settings-fields__grid--3 {\n\t\tgrid-template-columns: repeat(3, minmax(0, 1fr));\n\t}\n}\n\n.cn-settings-fields__field--wide {\n\tgrid-column: 1 / -1;\n}\n\n.cn-settings-fields__save {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tgap: 12px;\n}\n\n.cn-settings-fields__dirty {\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_createElementVNode","_toDisplayString","_createTextVNode","_createVNode"],"mappings":";;AA8CQ,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oDAAO,GAAA;;;AACJ,EAAA,KAAA,EAAA,2BAAA;AAAA,EAAA,aAAA,EAAA,0BAAA;;;;;;;;;;uBApCfA;AACC,IAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,IAAA,WAAW,EAAE,MAAU,CAAA,WAAA;AACvB,IAAA,MAAM,EAAE,MAAK,CAAA,MAAA;AACb,IAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,IAAA,OAAO,EAAE,KAAO,CAAA,OAAA;AAChB,IAAA,KAAK,EAAE,KAAS,CAAA,SAAA,KAAA,EAAA;AAChB,IAAA,YAAS,EAAA,KAAA,CAAA,SAAA;AACT,IAAA,OAAO,EAAE,QAAI,CAAA,IAAA;AACb,IAAA,aAAU,EAAC,4BAA4B;;qBACtC;AAAW,MAAA,CAAA,CAAA,KAAA,CAAA,OAAI,IAAA,KAAA,CAAA,SAAA,KAAA,EAAA;wBAAfC;;iBACI,EAAC,oBAAoB;AACxB,YAAA,UAAQ,EAAR,EAAQ;AACR,YAAA,QAAK,wDAAU,QAAI,CAAA,IAAA,IAAA,QAAA,CAAA,IAAA,CAAA,GAAA,IAAA,CAAA,CAAA,EAAA,CAAA,SAAA,CAAA,CAAA,CAAA;;8BAaf,CAAA,KAAA,EAAA;cAZD,KAAK,EAAAC,cAAA,CAAA,CAAA,0BAA2B,EAAA,CAAA,0BAAA,EAAsB,QAAA,CAAA,WAAA,CAAA,CAAA,CAAA,CAAA;;kDAWjB,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CATxB,QAAY,CAAA,cAAA,EAAA,CAArB,KAAK,KAAA;gDAS4B,CAAA,0BAAA,EAAA;kBARvC,GAAG,EAAE,KAAK,CAAC,GAAG;AACf,kBAAA,KAAK,EAACF,cAAA,CAAA,CAAA,2BAAW,EAC0B,EAAA,iCAAA,EAAA,KAAA,CAAA,IAAA,EAAA,CAAA,CAAA;AAC1C,kBAAA,KAAK,EAAE,KAAK;AACZ,kBAAA,UAAE,EAAA,KAAA,CAAA,IAAA,CAAA,KAAA,CAAA,GAAA,CAAA;;kBAEF,IAAI,EAAE,KAAK,CAAC,IAAI;AAChB,kBAAA,qBAAgB,EAAA,MAAA,KAAA,QAAA,CAAA,QAAA,CAAA,KAAA,EAAA,MAAA,CAAA,CAAA;AAChB,kBAAA,UAAQ,EAAE,MAAA,KAAA,QAAA,CAAA,UAAW,CAAA,KAAK,EAAC,MAAO,CAAA,CAAA;;;;aAGzB,KAAA,CAAA,MAAA;8BAAZF,WAKO,CAAA,qBAAA,EAAA;;;kBAHL,IAAI,EAAE,KAAM,CAAA,MAAA,CAAC,IAAI,KAAA,OAAA,GAAA,OAAA,GAAA,QAAA;AAClB,kBAAA,aAAW,EAAC,2BAA2B;;mCACtC,MAAgB;gEAAR,CAAC,IAAI,CAAA,EAAA,CAAA,YAAA;;;;;AAGb,YAAAK,kBAAA,QAAA;eACW,QAAC,CAAA,KAAA;kDAEN,CAAA,MAAA,YAAA,EAAAC,eAAA,CADH,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,iBAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;uBAGG,EAAC,SAAS;AACjB,gBAAA,IAAI,EAAC,QAAK;gBACT,QAAQ,EAAE,KAAM,CAAA,MAAA,IAAA,CAAK,QAAA,CAAA,KAAA;AACtB,gBAAA,aAAS,EAAA,yBAAA;;iCAGE;AAAA,kBAAAC,eAAA,CAAA;;;;iBAFR,KAAA,CAAA,MAAA;;4BAAA,MAAA;kCACF,MAAE;wBAAFC,WAAE,CAAA,wBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnSettingsFieldsSection.vue3.js","sources":["../../../../src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue?vue&type=template&id=090828e4&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<CnSettingsSection\n\t\t:name=\"name\"\n\t\t:description=\"description\"\n\t\t:docUrl=\"docUrl\"\n\t\t:wide=\"wide\"\n\t\t:loading=\"loading\"\n\t\t:error=\"loadError !== ''\"\n\t\t:errorMessage=\"loadError\"\n\t\t:onRetry=\"load\"\n\t\tdata-testid=\"cn-settings-fields-section\">\n\t\t<form v-if=\"!loading && loadError === ''\"\n\t\t\tclass=\"cn-settings-fields\"\n\t\t\tnovalidate\n\t\t\t@submit.prevent=\"save\">\n\t\t\t<div class=\"cn-settings-fields__grid\" :class=\"`cn-settings-fields__grid--${columnCount}`\">\n\t\t\t\t<CnSettingsField\n\t\t\t\t\tv-for=\"field in resolvedFields\"\n\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\tclass=\"cn-settings-fields__field\"\n\t\t\t\t\t:class=\"{ 'cn-settings-fields__field--wide': field.wide }\"\n\t\t\t\t\t:field=\"field\"\n\t\t\t\t\t:modelValue=\"form[field.key]\"\n\t\t\t\t\t:error=\"errors[field.key] || unreadable[field.key] || ''\"\n\t\t\t\t\t:hint=\"field.hint\"\n\t\t\t\t\t@update:modelValue=\"setValue(field, $event)\"\n\t\t\t\t\t@uploaded=\"onUploaded(field, $event)\" />\n\t\t\t</div>\n\n\t\t\t<NcNoteCard v-if=\"notice\"\n\t\t\t\t:type=\"notice.type\"\n\t\t\t\t:role=\"notice.type === 'error' ? 'alert' : 'status'\"\n\t\t\t\tdata-testid=\"cn-settings-fields-notice\">\n\t\t\t\t{{ notice.text }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<div class=\"cn-settings-section__save cn-settings-fields__save\">\n\t\t\t\t<span v-if=\"dirty\" class=\"cn-settings-fields__dirty\" data-testid=\"cn-settings-fields-dirty\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Unsaved changes') }}\n\t\t\t\t</span>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t:disabled=\"saving || !dirty\"\n\t\t\t\t\tdata-testid=\"cn-settings-fields-save\">\n\t\t\t\t\t<template v-if=\"saving\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ resolvedSaveLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</form>\n\t</CnSettingsSection>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\nimport { CnSettingsField } from '../CnSettingsField/index.js'\nimport { CnSettingsSection } from '../CnSettingsSection/index.js'\n\n/**\n * A value by key: the key as written when the object holds it (a flat map\n * with dotted keys such as `integration.brk.mode`), else as a dotted path.\n *\n * @param {object} data The object.\n * @param {string} key The key or dotted path.\n * @return {unknown}\n */\nfunction literalFirst(data, key) {\n\tif (data && typeof data === 'object' && Object.hasOwn(data, key)) {\n\t\treturn data[key]\n\t}\n\treturn readPath(data, key)\n}\n\n/**\n * The request options for a target: an OCS endpoint (`/ocs/...`) needs the\n * `OCS-APIRequest` header; `prefixUrl` already serves it outside index.php.\n *\n * @param {string} url The target.\n * @return {object}\n */\nfunction ocsConfig(url) {\n\treturn /^\\/ocs\\//.test(String(url)) ? { headers: { 'OCS-APIRequest': 'true' } } : {}\n}\n\n/**\n * Up to two initials of a name, for an image field without an image.\n *\n * @param {unknown} name The name.\n * @return {string}\n */\nfunction initialsOf(name) {\n\treturn String(name || '').trim().split(/\\s+/).filter(Boolean).slice(0, 2).map((word) => word[0].toUpperCase()).join('')\n}\n\n/**\n * CnSettingsFieldsSection - a settings section declared as fields: it reads\n * the app's settings, draws the fields in one to three columns, and saves\n * them with ONE Save button for the section.\n *\n * It reads `GET url` (default `/apps/<appId>/api/settings`) and writes the\n * section's fields with `PUT saveUrl` (default the same url). The Save button\n * is on while something changed (\"Unsaved changes\"); a save checks the\n * fields first (required, pattern, min, max) and marks each failing field,\n * then says it saved or why it did not. A `secret` field never shows the\n * stored value: it says a value is stored, and is sent only when something\n * was typed.\n *\n * It renders inside CnAdminSettingsShell and CnSettingsPage, in the default\n * and the board look.\n *\n * ```vue\n * <CnSettingsFieldsSection\n * app-id=\"myapp\"\n * name=\"Connection\"\n * :columns=\"2\"\n * :fields=\"[\n * { key: 'baseUrl', label: 'Server address', required: true, pattern: '^https://', patternMessage: 'Starts with https://' },\n * { key: 'apiKey', label: 'API key', type: 'secret' },\n * { key: 'mode', label: 'Mode', type: 'select', options: [{ value: 'live', label: 'Live' }, { value: 'test', label: 'Test' }] },\n * { key: 'enabled', label: 'Enabled', type: 'switch', hint: 'Off stops every call.' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnSettingsFieldsSection',\n\n\tcomponents: { CnSettingsField, CnSettingsSection, NcButton, NcLoadingIcon, NcNoteCard },\n\n\tinject: {\n\t\t/** The app's translate function; identity outside CnAppRoot. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The section title. */\n\t\tname: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The line under the title. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** A documentation link beside the title. */\n\t\tdocUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Span the whole settings grid in the board look. */\n\t\twide: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * The fields: `[{ key, label, type?, hint?, placeholder?, required?,\n\t\t * pattern?, patternMessage?, min?, max?, rows?, options?, optionsUrl?,\n\t\t * optionsPath?, optionLabel?, optionValue?, default?, readOnly?, wide?,\n\t\t * url?, valuePath?, saveUrl?, saveMethod?, saveKey?, saveParams?, uploadUrl?,\n\t\t * uploadField?, uploadParams?, uploadValuePath?, initials?, initialsFrom? }]`.\n\t\t * `type` is `text` (default), `textarea`, `number`, `switch`, `select`,\n\t\t * `secret` or `image`. `key` names the setting; a dotted key reads a\n\t\t * nested value and is written as written. A field with its own `url`\n\t\t * is read there (at `valuePath`, default its key) and saved to its\n\t\t * `saveUrl` (default that `url`) with `saveMethod` (default POST) under\n\t\t * `saveKey` (default its key), with `saveParams` merged into the body,\n\t\t * so one section can hold the settings of several apps. A target under\n\t\t * `/ocs/` is sent the `OCS-APIRequest` header. An `image` uploads at once to `uploadUrl`. Labels,\n\t\t * hints, placeholders and messages go through the app's translate\n\t\t * function.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-section-declared-as-fields-reads-and-saves-the-apps-settings\n\t\t */\n\t\tfields: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Columns of the field grid: 1, 2 or 3. A narrow card stacks them. */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 1,\n\t\t\tvalidator: (value) => [1, 2, 3].includes(value),\n\t\t},\n\n\t\t/** The app id, for the default url `/apps/<appId>/api/settings`. */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Where the settings are read (`GET`); app-absolute or a full URL. */\n\t\turl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Where the section saves; defaults to `url`. */\n\t\tsaveUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The method of the save request. */\n\t\tsaveMethod: {\n\t\t\ttype: String,\n\t\t\tdefault: 'PUT',\n\t\t\tvalidator: (value) => ['PUT', 'POST', 'PATCH'].includes(String(value).toUpperCase()),\n\t\t},\n\n\t\t/** A dotted path to the values in the GET answer; empty reads the answer itself. */\n\t\tvaluesPath: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The Save button's label. Defaults to \"Save\". */\n\t\tsaveLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved', 'save-error'],\n\n\tdata() {\n\t\treturn {\n\t\t\tform: {},\n\t\t\tbaseline: {},\n\t\t\tstoredSecrets: {},\n\t\t\t// Field key to the message of a read target that failed.\n\t\t\tunreadable: {},\n\t\t\terrors: {},\n\t\t\tloading: true,\n\t\t\tloadError: '',\n\t\t\tsaving: false,\n\t\t\tnotice: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {number} The column count, 1 to 3. */\n\t\tcolumnCount() {\n\t\t\treturn [1, 2, 3].includes(this.columns) ? this.columns : 1\n\t\t},\n\n\t\t/** @return {string} Where the settings are read: `url`, else the app's settings endpoint. */\n\t\treadTarget() {\n\t\t\treturn this.url || (this.appId ? `/apps/${this.appId}/api/settings` : '')\n\t\t},\n\n\t\t/** @return {string} Where the section saves: `saveUrl`, else the read target. */\n\t\twriteTarget() {\n\t\t\treturn this.saveUrl || this.readTarget\n\t\t},\n\n\t\t/**\n\t\t * The declared fields with their text translated and a secret's stored state.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tresolvedFields() {\n\t\t\treturn (Array.isArray(this.fields) ? this.fields : [])\n\t\t\t\t.filter((field) => field && typeof field.key === 'string' && field.key !== '')\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\thint: this.tr(field.hint || ''),\n\t\t\t\t\tplaceholder: this.tr(field.placeholder || ''),\n\t\t\t\t\tsecretSet: field.type === 'secret' ? !!this.storedSecrets[field.key] : undefined,\n\t\t\t\t\tinitials: field.type === 'image' ? initialsOf(field.initialsFrom ? this.form[field.initialsFrom] : field.initials) : undefined,\n\t\t\t\t\treadOnly: field.readOnly || !!this.unreadable[field.key],\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Whether something changed since the last load or save.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-one-save-per-section-with-a-dirty-state\n\t\t */\n\t\tdirty() {\n\t\t\treturn this.resolvedFields.some((field) => field.type !== 'image' && !this.same(this.form[field.key], this.baseline[field.key]))\n\t\t},\n\n\t\t/** @return {string} The Save button's label. */\n\t\tresolvedSaveLabel() {\n\t\t\treturn this.saveLabel ? this.tr(this.saveLabel) : t('nextcloud-vue', 'Save')\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} value A declared string.\n\t\t * @return {string} It through the app's translate function.\n\t\t */\n\t\ttr(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : (value || '')\n\t\t},\n\n\t\t/**\n\t\t * @param {unknown} a A value.\n\t\t * @param {unknown} b Another.\n\t\t * @return {boolean} Whether they count as the same setting.\n\t\t */\n\t\tsame(a, b) {\n\t\t\tconst empty = (v) => v === null || v === undefined || v === ''\n\t\t\tif (empty(a) && empty(b)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn JSON.stringify(a) === JSON.stringify(b)\n\t\t},\n\n\t\t/**\n\t\t * Change a field's value; its error goes until the next save.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {unknown} value The value.\n\t\t * @return {void}\n\t\t */\n\t\tsetValue(field, value) {\n\t\t\tthis.form = { ...this.form, [field.key]: value }\n\t\t\tif (this.errors[field.key]) {\n\t\t\t\tconst { [field.key]: _gone, ...rest } = this.errors\n\t\t\t\tthis.errors = rest\n\t\t\t}\n\t\t\tthis.notice = null\n\t\t},\n\n\t\t/**\n\t\t * Read the settings and fill the fields; a secret's value is never put\n\t\t * in the form, only whether one is stored.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.loadError = ''\n\t\t\ttry {\n\t\t\t\t// One GET per distinct read target; the section's own target must\n\t\t\t\t// answer, a field's own target may fail on its own.\n\t\t\t\tconst targets = [...new Set(this.fields\n\t\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t\t.map((field) => this.readUrlOf(field))\n\t\t\t\t\t.filter(Boolean))]\n\t\t\t\tconst answers = {}\n\t\t\t\tconst failures = {}\n\t\t\t\tawait Promise.all(targets.map(async (target) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst response = await axios.get(prefixUrl(target), ocsConfig(target))\n\t\t\t\t\t\tanswers[target] = response?.data ?? {}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tfailures[target] = error\n\t\t\t\t\t}\n\t\t\t\t}))\n\t\t\t\tif (this.readTarget && failures[this.readTarget]) {\n\t\t\t\t\tthrow failures[this.readTarget]\n\t\t\t\t}\n\t\t\t\tconst form = {}\n\t\t\t\tconst stored = {}\n\t\t\t\tconst unreadable = {}\n\t\t\t\tfor (const field of this.fields || []) {\n\t\t\t\t\tif (!field || !field.key) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst target = this.readUrlOf(field)\n\t\t\t\t\tif (target && failures[target]) {\n\t\t\t\t\t\tunreadable[field.key] = t('nextcloud-vue', 'This value could not be loaded: {reason}', { reason: serverErrorMessage(failures[target]) })\n\t\t\t\t\t}\n\t\t\t\t\tconst raw = this.readValue(field, target ? answers[target] : null)\n\t\t\t\t\tif (field.type === 'secret') {\n\t\t\t\t\t\tstored[field.key] = raw !== null && raw !== undefined && raw !== '' && raw !== false\n\t\t\t\t\t\tform[field.key] = ''\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tform[field.key] = raw === undefined ? (field.default ?? (field.type === 'switch' ? false : '')) : raw\n\t\t\t\t}\n\t\t\t\tthis.form = form\n\t\t\t\tthis.baseline = { ...form }\n\t\t\t\tthis.storedSecrets = stored\n\t\t\t\tthis.unreadable = unreadable\n\t\t\t\tthis.errors = {}\n\t\t\t} catch (error) {\n\t\t\t\tthis.loadError = t('nextcloud-vue', 'The settings could not be loaded: {reason}', { reason: serverErrorMessage(error) })\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Where a field is read: its own `url`, else the section's.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {string}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-field-can-read-and-save-elsewhere\n\t\t */\n\t\treadUrlOf(field) {\n\t\t\treturn field.url || this.readTarget\n\t\t},\n\n\t\t/**\n\t\t * A field's value in its read answer: at its `valuePath` (a field with its\n\t\t * own `url`), else at the section's `valuesPath`, then its `key`. A key\n\t\t * the answer holds as written wins over reading it as a dotted path.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {object|null} data The answer of the field's read target.\n\t\t * @return {unknown}\n\t\t */\n\t\treadValue(field, data) {\n\t\t\tif (!data || typeof data !== 'object') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (field.url) {\n\t\t\t\treturn literalFirst(data, field.valuePath || field.key)\n\t\t\t}\n\t\t\tconst values = (this.valuesPath ? literalFirst(data, this.valuesPath) : data) || {}\n\t\t\treturn literalFirst(values, field.key)\n\t\t},\n\n\t\t/**\n\t\t * Check the fields: required, pattern, and a number's min and max.\n\t\t *\n\t\t * @return {object} Field key to message; empty when all pass.\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-a-failing-field-says-why\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.resolvedFields) {\n\t\t\t\tconst value = this.form[field.key]\n\t\t\t\tconst empty = value === null || value === undefined || value === ''\n\t\t\t\tif (field.type === 'switch' || field.type === 'image' || this.unreadable[field.key]) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.required && empty && !(field.type === 'secret' && field.secretSet)) {\n\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} is required.', { field: field.label })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (empty) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n\t\t\t\t\terrors[field.key] = field.patternMessage ? this.tr(field.patternMessage) : t('nextcloud-vue', '{field} is not in the right form.', { field: field.label })\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'number') {\n\t\t\t\t\tconst number = Number(value)\n\t\t\t\t\tif (Number.isNaN(number)) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be a number.', { field: field.label })\n\t\t\t\t\t} else if (field.min !== undefined && number < field.min) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be at least {min}.', { field: field.label, min: field.min })\n\t\t\t\t\t} else if (field.max !== undefined && number > field.max) {\n\t\t\t\t\t\terrors[field.key] = t('nextcloud-vue', '{field} must be at most {max}.', { field: field.label, max: field.max })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\n\t\t/**\n\t\t * Save the section's fields in one request, after checking them. A\n\t\t * secret is sent only when something was typed.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-one-save-per-section-with-a-dirty-state\n\t\t */\n\t\tasync save() {\n\t\t\tif (this.saving) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.errors = this.validate()\n\t\t\tconst failing = Object.keys(this.errors)\n\t\t\tif (failing.length > 0) {\n\t\t\t\tthis.notice = { type: 'error', text: t('nextcloud-vue', 'Check the marked fields.') }\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst el = this.$el?.querySelector?.('[aria-invalid=\"true\"], .cn-settings-field__error')\n\t\t\t\t\tconst control = el?.closest?.('.cn-settings-field')?.querySelector?.('input, textarea, [role=\"combobox\"]')\n\t\t\t\t\tcontrol?.focus?.()\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The changed fields, one request per save target, each carrying its\n\t\t\t// own fields under their `saveKey`. A secret goes only when typed; an\n\t\t\t// image is uploaded on its own; a field that could not be read is left out.\n\t\t\tconst groups = new Map()\n\t\t\tfor (const field of this.resolvedFields) {\n\t\t\t\tconst value = this.form[field.key]\n\t\t\t\tif (field.type === 'image' || this.unreadable[field.key] || this.same(value, this.baseline[field.key])) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'secret' && (value === '' || value === null || value === undefined)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst url = field.saveUrl || (field.url ? field.url : this.writeTarget)\n\t\t\t\tconst method = String(field.saveMethod || (field.saveUrl || field.url ? 'POST' : this.saveMethod)).toLowerCase()\n\t\t\t\tconst id = `${method} ${url}`\n\t\t\t\tif (!groups.has(id)) {\n\t\t\t\t\tgroups.set(id, { url, method, fields: [], data: {} })\n\t\t\t\t}\n\t\t\t\tconst group = groups.get(id)\n\t\t\t\tgroup.fields.push(field)\n\t\t\t\tObject.assign(group.data, field.saveParams || {})\n\t\t\t\tgroup.data[field.saveKey || field.key] = value\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\tthis.notice = null\n\t\t\tconst failed = []\n\t\t\tconst sent = {}\n\t\t\tfor (const group of groups.values()) {\n\t\t\t\ttry {\n\t\t\t\t\tawait axios.request({ url: prefixUrl(group.url), method: group.method, data: group.data, ...ocsConfig(group.url) })\n\t\t\t\t\tObject.assign(sent, group.data)\n\t\t\t\t\tthis.acceptSaved(group.fields)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailed.push({ group, error })\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.saving = false\n\t\t\tif (failed.length === 0) {\n\t\t\t\tthis.notice = { type: 'success', text: t('nextcloud-vue', 'Saved.') }\n\t\t\t\t/**\n\t\t\t\t * @event saved The section saved. Payload: what was sent, by save key.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', sent)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst reasons = failed.map(({ group, error }) => (groups.size > 1\n\t\t\t\t? t('nextcloud-vue', '{fields}: {reason}', { fields: group.fields.map((field) => field.label).join(', '), reason: serverErrorMessage(error) })\n\t\t\t\t: serverErrorMessage(error)))\n\t\t\tthis.notice = { type: 'error', text: t('nextcloud-vue', 'The settings could not be saved: {reason}', { reason: reasons.join('; ') }) }\n\t\t\t/**\n\t\t\t * @event save-error A save request failed. Payload: the first error.\n\t\t\t */\n\t\t\tthis.$emit('save-error', failed[0].error)\n\t\t},\n\n\t\t/**\n\t\t * Take saved fields as the new baseline; a saved secret reads as stored.\n\t\t *\n\t\t * @param {Array<object>} fields The fields that saved.\n\t\t * @return {void}\n\t\t */\n\t\tacceptSaved(fields) {\n\t\t\tconst form = { ...this.form }\n\t\t\tconst baseline = { ...this.baseline }\n\t\t\tconst stored = { ...this.storedSecrets }\n\t\t\tfor (const field of fields) {\n\t\t\t\tif (field.type === 'secret') {\n\t\t\t\t\tstored[field.key] = true\n\t\t\t\t\tform[field.key] = ''\n\t\t\t\t}\n\t\t\t\tbaseline[field.key] = form[field.key]\n\t\t\t}\n\t\t\tthis.form = form\n\t\t\tthis.baseline = baseline\n\t\t\tthis.storedSecrets = stored\n\t\t},\n\n\t\t/**\n\t\t * An image field uploaded a file: its new value is the baseline too.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @param {string} value The new image url.\n\t\t * @return {void}\n\t\t */\n\t\tonUploaded(field, value) {\n\t\t\tthis.form = { ...this.form, [field.key]: value }\n\t\t\tthis.baseline = { ...this.baseline, [field.key]: value }\n\t\t\tthis.notice = { type: 'success', text: t('nextcloud-vue', 'Saved.') }\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-fields {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\tcontainer-type: inline-size;\n}\n\n.cn-settings-fields__grid {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(0, 1fr);\n\tgap: 16px 20px;\n}\n\n/* Two or three columns where the card is wide enough; stacked otherwise. */\n@container (min-width: 520px) {\n\t.cn-settings-fields__grid--2,\n\t.cn-settings-fields__grid--3 {\n\t\tgrid-template-columns: repeat(2, minmax(0, 1fr));\n\t}\n}\n\n@container (min-width: 780px) {\n\t.cn-settings-fields__grid--3 {\n\t\tgrid-template-columns: repeat(3, minmax(0, 1fr));\n\t}\n}\n\n.cn-settings-fields__field--wide {\n\tgrid-column: 1 / -1;\n}\n\n.cn-settings-fields__save {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tgap: 12px;\n}\n\n.cn-settings-fields__dirty {\n\tfont-size: 14px;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_createElementVNode","_toDisplayString","_createTextVNode","_createVNode"],"mappings":";;AA8CQ,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oDAAO,GAAA;;;AACJ,EAAA,KAAA,EAAA,2BAAA;AAAA,EAAA,aAAA,EAAA,0BAAA;;;;;;;;;;uBApCfA;AACC,IAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,IAAA,WAAW,EAAE,MAAU,CAAA,WAAA;AACvB,IAAA,MAAM,EAAE,MAAK,CAAA,MAAA;AACb,IAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,IAAA,OAAO,EAAE,KAAO,CAAA,OAAA;AAChB,IAAA,KAAK,EAAE,KAAS,CAAA,SAAA,KAAA,EAAA;AAChB,IAAA,YAAS,EAAA,KAAA,CAAA,SAAA;AACT,IAAA,OAAO,EAAE,QAAI,CAAA,IAAA;AACb,IAAA,aAAU,EAAC,4BAA4B;;qBACtC;AAAW,MAAA,CAAA,CAAA,KAAA,CAAA,OAAI,IAAA,KAAA,CAAA,SAAA,KAAA,EAAA;wBAAfC;;iBACI,EAAC,oBAAoB;AACxB,YAAA,UAAQ,EAAR,EAAQ;AACR,YAAA,QAAK,wDAAU,QAAI,CAAA,IAAA,IAAA,QAAA,CAAA,IAAA,CAAA,GAAA,IAAA,CAAA,CAAA,EAAA,CAAA,SAAA,CAAA,CAAA,CAAA;;8BAaf,CAAA,KAAA,EAAA;cAZD,KAAK,EAAAC,cAAA,CAAA,CAAA,0BAA2B,EAAA,CAAA,0BAAA,EAAsB,QAAA,CAAA,WAAA,CAAA,CAAA,CAAA,CAAA;;kDAWjB,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CATxB,QAAY,CAAA,cAAA,EAAA,CAArB,KAAK,KAAA;gDAS4B,CAAA,0BAAA,EAAA;kBARvC,GAAG,EAAE,KAAK,CAAC,GAAG;AACf,kBAAA,KAAK,EAACF,cAAA,CAAA,CAAA,2BAAW,EAC0B,EAAA,iCAAA,EAAA,KAAA,CAAA,IAAA,EAAA,CAAA,CAAA;AAC1C,kBAAA,KAAK,EAAE,KAAK;AACZ,kBAAA,UAAE,EAAA,KAAA,CAAA,IAAA,CAAA,KAAA,CAAA,GAAA,CAAA;;kBAEF,IAAI,EAAE,KAAK,CAAC,IAAI;AAChB,kBAAA,qBAAgB,EAAA,MAAA,KAAA,QAAA,CAAA,QAAA,CAAA,KAAA,EAAA,MAAA,CAAA,CAAA;AAChB,kBAAA,UAAQ,EAAE,MAAA,KAAA,QAAA,CAAA,UAAW,CAAA,KAAK,EAAC,MAAO,CAAA,CAAA;;;;aAGzB,KAAA,CAAA,MAAA;8BAAZF,WAKO,CAAA,qBAAA,EAAA;;;kBAHL,IAAI,EAAE,KAAM,CAAA,MAAA,CAAC,IAAI,KAAA,OAAA,GAAA,OAAA,GAAA,QAAA;AAClB,kBAAA,aAAW,EAAC,2BAA2B;;mCACtC,MAAgB;gEAAR,CAAC,IAAI,CAAA,EAAA,CAAA,YAAA;;;;;AAGb,YAAAK,kBAAA,QAAA;eACW,QAAC,CAAA,KAAA;kDAEN,CAAA,MAAA,YAAA,EAAAC,eAAA,CADH,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,iBAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;uBAGG,EAAC,SAAS;AACjB,gBAAA,IAAI,EAAC,QAAK;gBACT,QAAQ,EAAE,KAAM,CAAA,MAAA,IAAA,CAAK,QAAA,CAAA,KAAA;AACtB,gBAAA,aAAS,EAAA,yBAAA;;iCAGE;AAAA,kBAAAC,eAAA,CAAA;;;;iBAFR,KAAA,CAAA,MAAA;;4BAAA,MAAA;kCACF,MAAE;wBAAFC,WAAE,CAAA,wBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;;;;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnSetupWizard.vue2.js';
2
2
  import { render } from './CnSetupWizard.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-5670b06f";
5
+ script.__scopeId = "data-v-44988d3d";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -5,6 +5,7 @@ import __sfc_main$1 from '../CnChoiceCards/CnChoiceCards.vue.js';
5
5
  import __sfc_main$2 from '../CnLeafDependencySettings/CnLeafDependencySettings.vue.js';
6
6
  import __sfc_main from '../CnWizardDialog/CnWizardDialog.vue.js';
7
7
  import { missingRequiredApps, readServerAppStatuses, checkDependencies, isDependencyResolved } from '../../composables/useDependencyCheck.js';
8
+ import { useLook } from '../../composables/useLook.js';
8
9
  import { useSetupStatus } from '../../composables/useSetupStatus.js';
9
10
  import { fieldsFromSchema } from '../../utils/schema.js';
10
11
 
@@ -221,12 +222,13 @@ var script = {
221
222
  * @return {object} `setupStatus` / `setupStatusLoading` for the option resolver.
222
223
  */
223
224
  setup(props) {
224
- const wantsSource = (props.steps || []).some((s) => s && s.optionsSource);
225
+ const { isBoard } = useLook();
226
+ const wantsSource = (props.steps || []).some((s) => s && (s.optionsSource || s.visibleWhen));
225
227
  if (!wantsSource) {
226
- return {}
228
+ return { isBoard }
227
229
  }
228
230
  const { status, loading } = useSetupStatus(props.appId, { setup: { steps: props.steps } });
229
- return { setupStatus: status, setupStatusLoading: loading }
231
+ return { isBoard, setupStatus: status, setupStatusLoading: loading }
230
232
  },
231
233
 
232
234
  data() {
@@ -289,7 +291,7 @@ var script = {
289
291
 
290
292
  setupSteps() {
291
293
  const skipped = new Set(this.skippedSteps.map((entry) => entry.step.id));
292
- return this.declaredSteps.filter((s) => !skipped.has(s.id))
294
+ return this.declaredSteps.filter((s) => !skipped.has(s.id) && this.stepConditionHolds(s))
293
295
  },
294
296
 
295
297
  /**
@@ -586,6 +588,18 @@ var script = {
586
588
  return raw === '' ? '' : this.cnTranslate(raw)
587
589
  },
588
590
 
591
+ /**
592
+ * The footnote under a choice step's options, from the step's `note`
593
+ * (or `footnote`), translated like the body.
594
+ *
595
+ * @param {object} step The step declaration.
596
+ * @return {string} The translated footnote, or ''.
597
+ */
598
+ stepNote(step) {
599
+ const raw = (step && (step.note || step.footnote)) || '';
600
+ return raw === '' ? '' : this.cnTranslate(raw)
601
+ },
602
+
589
603
  stepSlot(step) {
590
604
  return 'step-' + step.id
591
605
  },
@@ -640,6 +654,39 @@ var script = {
640
654
  })
641
655
  },
642
656
 
657
+ /**
658
+ * Whether a step's `visibleWhen` holds: the value at that path in the
659
+ * setup status (`"flags.hasPayouts"`, dots walk into objects) is truthy,
660
+ * or equals `equals` when the step gives `{ path, equals }`. A step
661
+ * without `visibleWhen` always shows; one with it stays hidden until the
662
+ * status says so, also while the status loads (DqInstellen: the dwangsom
663
+ * step only when the app pays penalties).
664
+ *
665
+ * @param {object} step The step declaration.
666
+ * @return {boolean}
667
+ */
668
+ stepConditionHolds(step) {
669
+ const when = step && step.visibleWhen;
670
+ if (when === undefined || when === null || when === '') {
671
+ return true
672
+ }
673
+ const path = typeof when === 'string' ? when : when.path;
674
+ if (typeof path !== 'string' || path === '') {
675
+ return true
676
+ }
677
+ let node = this.setupStatus;
678
+ for (const key of path.split('.')) {
679
+ if (node === null || node === undefined || typeof node !== 'object') {
680
+ return false
681
+ }
682
+ node = node[key];
683
+ }
684
+ if (typeof when === 'object' && Object.hasOwn(when, 'equals')) {
685
+ return node === when.equals
686
+ }
687
+ return Boolean(node)
688
+ },
689
+
643
690
  /**
644
691
  * The option list a `choice` step declares `optionsSource` for, read
645
692
  * from the setup status document the app already serves.