@conduction/nextcloud-vue 2.82.0 → 2.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +2 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +24 -0
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +9 -4
  7. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
  10. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBodySections/CnBodySections.vue.js +1 -1
  13. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
  14. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
  15. package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  17. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
  18. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  19. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  21. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +125 -3
  22. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
  24. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +14 -0
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
  30. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
  31. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
  32. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +203 -4
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  37. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
  38. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
  40. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
  42. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
  43. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
  44. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue3.js.map +1 -1
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +96 -12
  46. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +95 -50
  48. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
  50. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  51. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  52. package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js +2 -2
  53. package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js.map +1 -1
  54. package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js +2 -2
  55. package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js.map +1 -1
  56. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  57. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +31 -2
  58. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  60. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +1 -1
  61. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +16 -2
  62. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +3 -3
  64. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  66. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +37 -2
  67. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  68. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  69. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  70. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +176 -0
  71. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  72. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +181 -52
  73. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  74. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +1 -1
  75. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +70 -5
  76. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +61 -35
  78. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -1
  79. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue.js +1 -1
  80. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
  81. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  82. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
  83. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  84. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  85. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +4 -2
  86. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  87. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  88. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
  89. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +72 -5
  90. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  91. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +88 -71
  92. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  93. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +1 -1
  94. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +18 -4
  95. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -1
  96. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -1
  97. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  98. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +33 -3
  99. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  100. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  101. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue.js +1 -1
  102. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
  103. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  104. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  105. package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
  106. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
  107. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
  108. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
  109. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
  110. package/dist/esm/l10n/en.json.js +16 -1
  111. package/dist/esm/l10n/en.json.js.map +1 -1
  112. package/dist/esm/l10n/nl.json.js +41 -4
  113. package/dist/esm/l10n/nl.json.js.map +1 -1
  114. package/dist/esm/library-style.js +1 -1
  115. package/dist/esm/nextcloud-vue.css +900 -537
  116. package/dist/esm/utils/buildManifest.js +37 -3
  117. package/dist/esm/utils/buildManifest.js.map +1 -1
  118. package/dist/esm/utils/detailWidgetVisibility.js +149 -0
  119. package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
  120. package/dist/esm/utils/fetchAggregate.js +25 -2
  121. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  122. package/dist/esm/utils/formatMetric.js +39 -3
  123. package/dist/esm/utils/formatMetric.js.map +1 -1
  124. package/dist/esm/utils/resolveManifestTokens.js +1 -0
  125. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  126. package/dist/esm/utils/sentinelTokens.js +9 -0
  127. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  128. package/dist/esm/utils/switchValues.js +62 -0
  129. package/dist/esm/utils/switchValues.js.map +1 -0
  130. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  131. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  132. package/dist/nextcloud-vue.cjs.js +36711 -34909
  133. package/dist/nextcloud-vue.cjs.js.map +2 -2
  134. package/dist/nextcloud-vue.css +900 -537
  135. package/l10n/en.json +16 -1
  136. package/l10n/nl.json +41 -4
  137. package/package.json +1 -1
  138. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +31 -2
  139. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
  140. package/src/components/CnBodySections/CnBodySections.vue +22 -1
  141. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
  142. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +128 -6
  143. package/src/components/CnDashboardPage/CnDashboardPage.vue +14 -0
  144. package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
  145. package/src/components/CnDetailPage/CnDetailPage.md +2 -0
  146. package/src/components/CnDetailPage/CnDetailPage.vue +203 -4
  147. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
  148. package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
  149. package/src/components/CnIndexPage/CnIndexPage.vue +125 -15
  150. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
  151. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
  152. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +31 -2
  153. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +54 -3
  154. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +37 -2
  155. package/src/components/CnReportsPage/CnReportsPage.vue +263 -5
  156. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +110 -18
  157. package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
  158. package/src/components/CnStatWidget/CnStatWidget.vue +4 -2
  159. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +125 -7
  160. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +18 -4
  161. package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -3
  162. package/src/components/CnWalkthrough/CnWalkthrough.vue +16 -0
  163. package/src/css/form-field.css +14 -0
  164. package/src/css/form-widget.css +11 -0
  165. package/src/css/index-page.css +11 -0
  166. package/src/css/index.css +1 -0
  167. package/src/css/look-board-reports.css +175 -0
  168. package/src/css/look-board.css +44 -1
  169. package/src/dialogs/CnTransitionInputDialog.vue +87 -7
  170. package/src/schemas/app-manifest-v2.schema.json +24 -3
  171. package/src/utils/buildManifest.js +36 -2
  172. package/src/utils/detailWidgetVisibility.js +145 -0
  173. package/src/utils/fetchAggregate.js +24 -1
  174. package/src/utils/formatMetric.js +38 -2
  175. package/src/utils/resolveManifestTokens.js +1 -0
  176. package/src/utils/sentinelTokens.js +9 -0
  177. package/src/utils/switchValues.js +59 -0
  178. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -132,7 +132,7 @@ var script = {
132
132
  /** Channel options `{ value, label }`. */
133
133
  channelOptions() {
134
134
  return Array.isArray(this.content.channels) && this.content.channels.length > 0
135
- ? this.content.channels
135
+ ? this.content.channels.map((channel) => ({ ...channel, label: this.copy(channel && channel.label) }))
136
136
  : [
137
137
  { value: 'telefoon', label: translate('nextcloud-vue', 'Phone') },
138
138
  { value: 'email', label: translate('nextcloud-vue', 'Email') },
@@ -153,10 +153,11 @@ var script = {
153
153
  const prop = properties[this.content.outcomeField || 'outcome'];
154
154
  if (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {
155
155
  const labels = prop['x-enum-labels'] || prop.enumLabels || {};
156
- const tr = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k;
157
- return prop.enum.map((value) => ({ value, label: tr(labels[value] || String(value)) }))
156
+ return prop.enum.map((value) => ({ value, label: this.copy(labels[value] || String(value)) }))
158
157
  }
159
- return Array.isArray(this.content.outcomes) ? this.content.outcomes : []
158
+ return Array.isArray(this.content.outcomes)
159
+ ? this.content.outcomes.map((outcome) => ({ ...outcome, label: this.copy(outcome && outcome.label) }))
160
+ : []
160
161
  },
161
162
 
162
163
  /**
@@ -229,8 +230,7 @@ var script = {
229
230
  // `content.submitLabel` names what the form creates, e.g. "Save contact moment".
230
231
  registerLabel() {
231
232
  if (this.content.submitLabel) {
232
- const tr = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k;
233
- return tr(this.content.submitLabel)
233
+ return this.copy(this.content.submitLabel)
234
234
  }
235
235
  return translate('nextcloud-vue', 'Register')
236
236
  },
@@ -308,6 +308,29 @@ var script = {
308
308
  },
309
309
 
310
310
  /** The default channel value (first configured channel, else `telefoon`). */
311
+ /**
312
+ * A label the manifest supplies (a channel, an outcome, the submit
313
+ * label), translated: the app's own translation first, since the words
314
+ * are the app's, then the library's catalogue, then the text as
315
+ * written. Before, a channel label was shown as written and the submit
316
+ * label only reached the app's translate, so pipelinq showed "Phone"
317
+ * and "Save contact moment" in English to a Dutch reader.
318
+ *
319
+ * @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language
320
+ * @param {string} key The label as the manifest writes it.
321
+ * @return {string} The translated label.
322
+ */
323
+ copy(key) {
324
+ if (typeof key !== 'string' || key === '') {
325
+ return key
326
+ }
327
+ const host = typeof this.cnTranslate === 'function' ? this.cnTranslate(key) : key;
328
+ if (typeof host === 'string' && host !== '' && host !== key) {
329
+ return host
330
+ }
331
+ return translate('nextcloud-vue', key)
332
+ },
333
+
311
334
  firstChannel() {
312
335
  const ch = (this.content && this.content.channels) || [];
313
336
  return (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'
@@ -1 +1 @@
1
- {"version":3,"file":"CnInteractionFormWidget.vue2.js","sources":["../../../../src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnFormWidgetBase\n\t\tblockClass=\"cn-interaction-form-widget\"\n\t\t:fields=\"formFields\"\n\t\t:model=\"form\"\n\t\t:errors=\"{ subject: subjectError }\"\n\t\t:canSubmit=\"canRegister\"\n\t\t:submitting=\"saving\"\n\t\t:submitLabel=\"registerLabel\"\n\t\t:submittingLabel=\"savingLabel\"\n\t\t:errorMessage=\"errorMessage\"\n\t\t@update:field=\"onFieldUpdate\"\n\t\t@submit=\"onRegister\">\n\t\t<!-- The client picker is the one control the base does not know about:\n\t\t a CnResourceSelect, so typing a name that does not exist yet offers\n\t\t \"Create '<name>'\" inline rather than a dead \"no results\" path. The\n\t\t base still supplies its field wrapper and spacing. -->\n\t\t<template #field-client>\n\t\t\t<CnResourceSelect\n\t\t\t\t:register=\"register\"\n\t\t\t\t:schema=\"clientSchema\"\n\t\t\t\t:labelField=\"clientLabelField\"\n\t\t\t\t:modelValue=\"form.client\"\n\t\t\t\t:inputLabel=\"clientLabel\"\n\t\t\t\t:preload=\"true\"\n\t\t\t\t@update:modelValue=\"onClientChange\"\n\t\t\t\t@create=\"onClientCreated\" />\n\t\t</template>\n\n\t\t<!-- Opens what was just saved in a new tab, beside the submit button. -->\n\t\t<template #actions-start>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"savedHref\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:href=\"savedHref\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\tdata-testid=\"cn-interaction-form-open\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnFormWidgetBase>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport CnFormWidgetBase from '../CnFormWidgetBase/CnFormWidgetBase.vue'\nimport CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'\nimport { useObjectStore } from '../../store/index.js'\n\n/**\n * CnInteractionFormWidget — the \"active interaction\" quick-log form of a\n * workspace page.\n *\n * Persists a contactmoment (channel / client / subject / summary / outcome) to\n * OpenRegister via `useObjectStore().saveObject`. It reads `selectedClient`\n * from the page-level workspace context (the page's client in focus, set by a\n * page-level picker) to pre-fill its Client field.\n *\n * By default it also writes the chosen client to `selectedClient` and the live\n * summary text to `activeSummary`, so sibling widgets react: a client list\n * filtered on `@workspace.selectedClient`, or a `CnKbSearchWidget` bound to\n * `activeSummary`. A page whose client in focus comes from its own picker sets\n * `writeWorkspace: false`, which keeps the form's values to the submission.\n *\n * The Client field belongs to this submission only. It is a\n * `CnResourceSelect`, so typing a name that doesn't exist yet offers\n * \"Create '<name>'\" inline — no dead \"no results\" path.\n *\n * Resolved by its registry type key `interaction-form`. All schema/field/enum\n * choices come from `content`, so the widget carries no app-specific vocabulary.\n *\n * Example content blob:\n * ```js\n * content: {\n * register: 'pipelinq',\n * schema: 'ticket',\n * defaults: { ticketType: 'contactmoment' }, // fixed fields stamped on every create\n * clientSchema: 'client',\n * clientField: 'client',\n * summaryField: 'description',\n * channels: [{ value: 'telefoon', label: 'Phone' }, …],\n * }\n * ```\n *\n * The outcome options are read from the target schema on load: the enum of\n * its `outcomeField` property, labelled by that property's `x-enum-labels`.\n * `outcomes: [{ value, label }]` is only a fallback for a schema that has no\n * enum there.\n *\n * With `detailRoute` set (a route name taking `:id`, like an object list's\n * `rowRoute`), a save puts an \"Open\" button left of the submit button that\n * opens the saved object in a new tab.\n *\n * `defaults` covers the case where the target schema requires a value the form\n * has no input for — typically a discriminator on a supertype schema. It is\n * merged before the mapped fields, so those always take precedence.\n */\nexport default {\n\tname: 'CnInteractionFormWidget',\n\n\tcomponents: { CnFormWidgetBase, CnResourceSelect, NcButton, OpenInNew },\n\n\tinject: {\n\t\t/**\n\t\t * Page-level workspace context (reactive `ref({})`) from CnDashboardPage.\n\t\t * The widget reads `selectedClient` from it and, unless\n\t\t * `content.writeWorkspace` is false, writes `selectedClient` and\n\t\t * `activeSummary` back. Null on pages that don't provide one (the form\n\t\t * still saves).\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/**\n\t\t * Host translate function from CnAppRoot, bound to the host app's id.\n\t\t * Translates the schema's outcome labels.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted configuration blob (see component description for the shape).\n\t\t *\n\t\t * @type {{register?: string, schema?: string, defaults?: object, clientSchema?: string, clientField?: string, clientLabelField?: string, subjectField?: string, summaryField?: string, channelField?: string, outcomeField?: string, channels?: Array, outcomes?: Array, submitLabel?: string, detailRoute?: string, writeWorkspace?: boolean}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['saved'],\n\n\tdata() {\n\t\treturn {\n\t\t\tform: {\n\t\t\t\tchannel: this.firstChannel(),\n\t\t\t\tclient: '',\n\t\t\t\tsubject: '',\n\t\t\t\tsummary: '',\n\t\t\t\toutcome: '',\n\t\t\t},\n\n\t\t\tsaving: false,\n\t\t\terrorMessage: '',\n\t\t\tsubjectError: '',\n\t\t\t// The target schema, fetched on load for its outcome enum.\n\t\t\tschemaDef: null,\n\t\t\t// The last saved object, `{ id, title }`, until the next save.\n\t\t\tsaved: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tregister() {\n\t\t\treturn this.content.register || 'pipelinq'\n\t\t},\n\n\t\tschema() {\n\t\t\treturn this.content.schema || 'contactmoment'\n\t\t},\n\n\t\tclientSchema() {\n\t\t\treturn this.content.clientSchema || 'client'\n\t\t},\n\n\t\tclientLabelField() {\n\t\t\treturn this.content.clientLabelField || 'name'\n\t\t},\n\n\t\ttypeSlug() {\n\t\t\treturn `${this.register}-${this.schema}`\n\t\t},\n\n\t\t/** Channel options `{ value, label }`. */\n\t\tchannelOptions() {\n\t\t\treturn Array.isArray(this.content.channels) && this.content.channels.length > 0\n\t\t\t\t? this.content.channels\n\t\t\t\t: [\n\t\t\t\t\t\t{ value: 'telefoon', label: t('nextcloud-vue', 'Phone') },\n\t\t\t\t\t\t{ value: 'email', label: t('nextcloud-vue', 'Email') },\n\t\t\t\t\t\t{ value: 'chat', label: t('nextcloud-vue', 'Chat') },\n\t\t\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Outcome options `{ value, label }`, from the target schema's enum so\n\t\t * the form can only offer values the schema accepts. Labels come from\n\t\t * the property's `x-enum-labels`, translated. `content.outcomes` is the\n\t\t * fallback for a schema without an enum on that property.\n\t\t *\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\toutcomeOptions() {\n\t\t\tconst properties = (this.schemaDef && this.schemaDef.properties) || {}\n\t\t\tconst prop = properties[this.content.outcomeField || 'outcome']\n\t\t\tif (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\tconst labels = prop['x-enum-labels'] || prop.enumLabels || {}\n\t\t\t\tconst tr = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\t\treturn prop.enum.map((value) => ({ value, label: tr(labels[value] || String(value)) }))\n\t\t\t}\n\t\t\treturn Array.isArray(this.content.outcomes) ? this.content.outcomes : []\n\t\t},\n\n\t\t/**\n\t\t * The field descriptors handed to CnFormWidgetBase — the whole of this\n\t\t * widget's form, declared rather than drawn. Order matters: it is the\n\t\t * render order.\n\t\t *\n\t\t * `client` is declared here so it keeps its place in the stack and its\n\t\t * field wrapper, but its control comes from the `#field-client` slot\n\t\t * (a CnResourceSelect, which the base has no type for).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tformFields() {\n\t\t\treturn [\n\t\t\t\t{ key: 'channel', type: 'select', label: this.channelLabel, options: this.channelOptions },\n\t\t\t\t{ key: 'client', type: 'custom', label: this.clientLabel },\n\t\t\t\t{ key: 'subject', type: 'text', label: this.subjectLabel },\n\t\t\t\t{ key: 'summary', type: 'textarea', label: this.summaryLabel, rows: 4 },\n\t\t\t\t{ key: 'outcome', type: 'select', label: this.outcomeLabel, options: this.outcomeOptions, clearable: true },\n\t\t\t]\n\t\t},\n\n\t\tobjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/** The page's client in focus, or an empty string. */\n\t\tpageClient() {\n\t\t\tconst id = this.workspaceCtx && this.workspaceCtx.selectedClient\n\t\t\treturn id ? String(id) : ''\n\t\t},\n\n\t\tcanRegister() {\n\t\t\treturn Boolean(this.form.subject && this.form.subject.trim() && this.form.channel)\n\t\t},\n\n\t\tchannelLabel() {\n\t\t\treturn t('nextcloud-vue', 'Channel')\n\t\t},\n\n\t\tclientLabel() {\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tsubjectLabel() {\n\t\t\treturn t('nextcloud-vue', 'Subject')\n\t\t},\n\n\t\tsummaryLabel() {\n\t\t\treturn t('nextcloud-vue', 'Summary')\n\t\t},\n\n\t\toutcomeLabel() {\n\t\t\treturn t('nextcloud-vue', 'Outcome')\n\t\t},\n\n\t\t// `content.submitLabel` names what the form creates, e.g. \"Save contact moment\".\n\t\tregisterLabel() {\n\t\t\tif (this.content.submitLabel) {\n\t\t\t\tconst tr = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\t\treturn tr(this.content.submitLabel)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Register')\n\t\t},\n\n\t\tsavingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Saving…')\n\t\t},\n\n\t\t/**\n\t\t * URL of the saved object's page, from `content.detailRoute`. Empty\n\t\t * without a route or router, and then no Open button is shown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsavedHref() {\n\t\t\tconst route = this.content.detailRoute\n\t\t\tif (!this.saved || !this.saved.id || !route || !this.$router) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: route, params: { id: this.saved.id } }).href || ''\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\t// `escape: false` because the label renders through `{{ }}`, which escapes.\n\t\topenLabel() {\n\t\t\treturn t('nextcloud-vue', 'Open \"{title}\"', { title: (this.saved && this.saved.title) || '' }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the form writes its client and summary to the workspace context. */\n\t\twritesWorkspace() {\n\t\t\treturn this.content.writeWorkspace !== false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A new page client pre-fills the field; the field stays editable.\n\t\tpageClient: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.form.client = id\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.loadSchema()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Fetch the target schema, which the outcome options derive from. A\n\t\t * failure leaves the `content.outcomes` fallback in place.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadSchema() {\n\t\t\tconst store = this.objectStore\n\t\t\tif (!store || typeof store.fetchSchema !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// Re-registering an existing type resets its cached objects and schema.\n\t\t\t\tif (typeof store.registerObjectType === 'function' && !store.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tstore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tthis.schemaDef = await store.fetchSchema(this.typeSlug)\n\t\t\t} catch {\n\t\t\t\tthis.schemaDef = null\n\t\t\t}\n\t\t},\n\n\t\t/** The default channel value (first configured channel, else `telefoon`). */\n\t\tfirstChannel() {\n\t\t\tconst ch = (this.content && this.content.channels) || []\n\t\t\treturn (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'\n\t\t},\n\n\t\t/**\n\t\t * A field edit from CnFormWidgetBase. `summary` also streams into the\n\t\t * workspace context. `client` never arrives here: its control is the\n\t\t * `#field-client` slot, which calls onClientChange.\n\t\t *\n\t\t * @param {{key: string, value: unknown}} payload The changed field.\n\t\t * @return {void}\n\t\t */\n\t\tonFieldUpdate({ key, value }) {\n\t\t\tthis.form[key] = value\n\t\t\tif (key === 'summary') {\n\t\t\t\tthis.writeWorkspace('activeSummary', value)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The submission's client, also written to the workspace context.\n\t\t *\n\t\t * @param {string} id The selected client id.\n\t\t */\n\t\tonClientChange(id) {\n\t\t\tthis.form.client = id || ''\n\t\t\tthis.writeWorkspace('selectedClient', this.form.client)\n\t\t},\n\n\t\t/**\n\t\t * A client created inline via \"Create '<name>'\" — select it.\n\t\t *\n\t\t * @param {object} client The created client object.\n\t\t */\n\t\tonClientCreated(client) {\n\t\t\tconst id = String((client && (client.id || (client['@self'] && client['@self'].id))) || '')\n\t\t\tif (id) {\n\t\t\t\tthis.form.client = id\n\t\t\t\tthis.writeWorkspace('selectedClient', id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Write a key into the workspace context, unless `content.writeWorkspace`\n\t\t * is false.\n\t\t *\n\t\t * Vue 2.7's Options-API `inject` auto-unwraps the provided ref, so the\n\t\t * holder is usually the plain reactive object; the raw-ref shape\n\t\t * (`.value`) is supported for Composition-API consumers.\n\t\t *\n\t\t * @param {string} key The context key.\n\t\t * @param {unknown} value The value.\n\t\t */\n\t\twriteWorkspace(key, value) {\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!this.writesWorkspace || !holder || typeof holder !== 'object') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ('value' in holder) {\n\t\t\t\tholder.value = { ...(holder.value || {}), [key]: value }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tholder[key] = value\n\t\t},\n\n\t\t/**\n\t\t * Persist the contactmoment. The agent identity is left to the server\n\t\t * (never sent from the client). Surfaces errors inline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRegister() {\n\t\t\tthis.subjectError = ''\n\t\t\tthis.errorMessage = ''\n\t\t\tif (!this.form.subject || !this.form.subject.trim()) {\n\t\t\t\tthis.subjectError = t('nextcloud-vue', 'Subject is required')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectStore) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Cannot save right now')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst c = this.content || {}\n\t\t\t// `defaults` is spread FIRST so the mapped fields below always win.\n\t\t\t// It exists for schemas that need a fixed value on every create the\n\t\t\t// form itself has no input for — e.g. a discriminator on a supertype\n\t\t\t// schema (`{ ticketType: 'contactmoment' }`), which would otherwise be\n\t\t\t// omitted and fail validation.\n\t\t\tconst payload = {\n\t\t\t\t...(c.defaults || {}),\n\t\t\t\t[c.subjectField || 'subject']: this.form.subject.trim(),\n\t\t\t\t[c.channelField || 'channel']: this.form.channel,\n\t\t\t\t[c.contactedAtField || 'contactedAt']: new Date().toISOString(),\n\t\t\t}\n\t\t\tif (this.form.client) {\n\t\t\t\tpayload[c.clientField || 'client'] = this.form.client\n\t\t\t}\n\t\t\tif (this.form.summary) {\n\t\t\t\tpayload[c.summaryField || 'summary'] = this.form.summary\n\t\t\t}\n\t\t\tif (this.form.outcome) {\n\t\t\t\tpayload[c.outcomeField || 'outcome'] = this.form.outcome\n\t\t\t}\n\n\t\t\tthis.saving = true\n\t\t\tthis.saved = null\n\t\t\ttry {\n\t\t\t\tif (typeof this.objectStore.registerObjectType === 'function' && !this.objectStore.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tthis.objectStore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tconst result = await this.objectStore.saveObject(this.typeSlug, payload)\n\t\t\t\tif (!result) {\n\t\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Failed to save interaction')\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event saved A contactmoment was persisted.\n\t\t\t\t * @type {object} The saved object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', result)\n\t\t\t\tthis.saved = {\n\t\t\t\t\tid: String(result.id || (result['@self'] && result['@self'].id) || ''),\n\t\t\t\t\ttitle: payload[c.subjectField || 'subject'],\n\t\t\t\t}\n\t\t\t\t// Reset the per-interaction fields; keep the client for follow-ups.\n\t\t\t\tthis.form.subject = ''\n\t\t\t\tthis.form.summary = ''\n\t\t\t\tthis.form.outcome = ''\n\t\t\t\tthis.writeWorkspace('activeSummary', '')\n\t\t\t} catch (e) {\n\t\t\t\tthis.errorMessage = (e && e.message) || t('nextcloud-vue', 'Failed to save interaction')\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<!--\n The scoped style block is gone on purpose. The markup now lives in\n CnFormWidgetBase, so a scoped rule here would carry THIS component's\n data-v attribute and match none of it. The rules moved verbatim to\n src/css/form-widget.css, keyed on `cn-form-widget*`; the base also emits\n the `cn-interaction-form-widget*` names on the same elements, so app CSS\n targeting those keeps matching.\n-->\n"],"names":["CnFormWidgetBase","CnResourceSelect","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnInteractionFormWidget.vue2.js","sources":["../../../../src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnFormWidgetBase\n\t\tblockClass=\"cn-interaction-form-widget\"\n\t\t:fields=\"formFields\"\n\t\t:model=\"form\"\n\t\t:errors=\"{ subject: subjectError }\"\n\t\t:canSubmit=\"canRegister\"\n\t\t:submitting=\"saving\"\n\t\t:submitLabel=\"registerLabel\"\n\t\t:submittingLabel=\"savingLabel\"\n\t\t:errorMessage=\"errorMessage\"\n\t\t@update:field=\"onFieldUpdate\"\n\t\t@submit=\"onRegister\">\n\t\t<!-- The client picker is the one control the base does not know about:\n\t\t a CnResourceSelect, so typing a name that does not exist yet offers\n\t\t \"Create '<name>'\" inline rather than a dead \"no results\" path. The\n\t\t base still supplies its field wrapper and spacing. -->\n\t\t<template #field-client>\n\t\t\t<CnResourceSelect\n\t\t\t\t:register=\"register\"\n\t\t\t\t:schema=\"clientSchema\"\n\t\t\t\t:labelField=\"clientLabelField\"\n\t\t\t\t:modelValue=\"form.client\"\n\t\t\t\t:inputLabel=\"clientLabel\"\n\t\t\t\t:preload=\"true\"\n\t\t\t\t@update:modelValue=\"onClientChange\"\n\t\t\t\t@create=\"onClientCreated\" />\n\t\t</template>\n\n\t\t<!-- Opens what was just saved in a new tab, beside the submit button. -->\n\t\t<template #actions-start>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"savedHref\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:href=\"savedHref\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\tdata-testid=\"cn-interaction-form-open\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnFormWidgetBase>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport CnFormWidgetBase from '../CnFormWidgetBase/CnFormWidgetBase.vue'\nimport CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'\nimport { useObjectStore } from '../../store/index.js'\n\n/**\n * CnInteractionFormWidget — the \"active interaction\" quick-log form of a\n * workspace page.\n *\n * Persists a contactmoment (channel / client / subject / summary / outcome) to\n * OpenRegister via `useObjectStore().saveObject`. It reads `selectedClient`\n * from the page-level workspace context (the page's client in focus, set by a\n * page-level picker) to pre-fill its Client field.\n *\n * By default it also writes the chosen client to `selectedClient` and the live\n * summary text to `activeSummary`, so sibling widgets react: a client list\n * filtered on `@workspace.selectedClient`, or a `CnKbSearchWidget` bound to\n * `activeSummary`. A page whose client in focus comes from its own picker sets\n * `writeWorkspace: false`, which keeps the form's values to the submission.\n *\n * The Client field belongs to this submission only. It is a\n * `CnResourceSelect`, so typing a name that doesn't exist yet offers\n * \"Create '<name>'\" inline — no dead \"no results\" path.\n *\n * Resolved by its registry type key `interaction-form`. All schema/field/enum\n * choices come from `content`, so the widget carries no app-specific vocabulary.\n *\n * Example content blob:\n * ```js\n * content: {\n * register: 'pipelinq',\n * schema: 'ticket',\n * defaults: { ticketType: 'contactmoment' }, // fixed fields stamped on every create\n * clientSchema: 'client',\n * clientField: 'client',\n * summaryField: 'description',\n * channels: [{ value: 'telefoon', label: 'Phone' }, …],\n * }\n * ```\n *\n * The outcome options are read from the target schema on load: the enum of\n * its `outcomeField` property, labelled by that property's `x-enum-labels`.\n * `outcomes: [{ value, label }]` is only a fallback for a schema that has no\n * enum there.\n *\n * With `detailRoute` set (a route name taking `:id`, like an object list's\n * `rowRoute`), a save puts an \"Open\" button left of the submit button that\n * opens the saved object in a new tab.\n *\n * `defaults` covers the case where the target schema requires a value the form\n * has no input for — typically a discriminator on a supertype schema. It is\n * merged before the mapped fields, so those always take precedence.\n */\nexport default {\n\tname: 'CnInteractionFormWidget',\n\n\tcomponents: { CnFormWidgetBase, CnResourceSelect, NcButton, OpenInNew },\n\n\tinject: {\n\t\t/**\n\t\t * Page-level workspace context (reactive `ref({})`) from CnDashboardPage.\n\t\t * The widget reads `selectedClient` from it and, unless\n\t\t * `content.writeWorkspace` is false, writes `selectedClient` and\n\t\t * `activeSummary` back. Null on pages that don't provide one (the form\n\t\t * still saves).\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/**\n\t\t * Host translate function from CnAppRoot, bound to the host app's id.\n\t\t * Translates the schema's outcome labels.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted configuration blob (see component description for the shape).\n\t\t *\n\t\t * @type {{register?: string, schema?: string, defaults?: object, clientSchema?: string, clientField?: string, clientLabelField?: string, subjectField?: string, summaryField?: string, channelField?: string, outcomeField?: string, channels?: Array, outcomes?: Array, submitLabel?: string, detailRoute?: string, writeWorkspace?: boolean}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['saved'],\n\n\tdata() {\n\t\treturn {\n\t\t\tform: {\n\t\t\t\tchannel: this.firstChannel(),\n\t\t\t\tclient: '',\n\t\t\t\tsubject: '',\n\t\t\t\tsummary: '',\n\t\t\t\toutcome: '',\n\t\t\t},\n\n\t\t\tsaving: false,\n\t\t\terrorMessage: '',\n\t\t\tsubjectError: '',\n\t\t\t// The target schema, fetched on load for its outcome enum.\n\t\t\tschemaDef: null,\n\t\t\t// The last saved object, `{ id, title }`, until the next save.\n\t\t\tsaved: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tregister() {\n\t\t\treturn this.content.register || 'pipelinq'\n\t\t},\n\n\t\tschema() {\n\t\t\treturn this.content.schema || 'contactmoment'\n\t\t},\n\n\t\tclientSchema() {\n\t\t\treturn this.content.clientSchema || 'client'\n\t\t},\n\n\t\tclientLabelField() {\n\t\t\treturn this.content.clientLabelField || 'name'\n\t\t},\n\n\t\ttypeSlug() {\n\t\t\treturn `${this.register}-${this.schema}`\n\t\t},\n\n\t\t/** Channel options `{ value, label }`. */\n\t\tchannelOptions() {\n\t\t\treturn Array.isArray(this.content.channels) && this.content.channels.length > 0\n\t\t\t\t? this.content.channels.map((channel) => ({ ...channel, label: this.copy(channel && channel.label) }))\n\t\t\t\t: [\n\t\t\t\t\t\t{ value: 'telefoon', label: t('nextcloud-vue', 'Phone') },\n\t\t\t\t\t\t{ value: 'email', label: t('nextcloud-vue', 'Email') },\n\t\t\t\t\t\t{ value: 'chat', label: t('nextcloud-vue', 'Chat') },\n\t\t\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Outcome options `{ value, label }`, from the target schema's enum so\n\t\t * the form can only offer values the schema accepts. Labels come from\n\t\t * the property's `x-enum-labels`, translated. `content.outcomes` is the\n\t\t * fallback for a schema without an enum on that property.\n\t\t *\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\toutcomeOptions() {\n\t\t\tconst properties = (this.schemaDef && this.schemaDef.properties) || {}\n\t\t\tconst prop = properties[this.content.outcomeField || 'outcome']\n\t\t\tif (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\tconst labels = prop['x-enum-labels'] || prop.enumLabels || {}\n\t\t\t\treturn prop.enum.map((value) => ({ value, label: this.copy(labels[value] || String(value)) }))\n\t\t\t}\n\t\t\treturn Array.isArray(this.content.outcomes)\n\t\t\t\t? this.content.outcomes.map((outcome) => ({ ...outcome, label: this.copy(outcome && outcome.label) }))\n\t\t\t\t: []\n\t\t},\n\n\t\t/**\n\t\t * The field descriptors handed to CnFormWidgetBase — the whole of this\n\t\t * widget's form, declared rather than drawn. Order matters: it is the\n\t\t * render order.\n\t\t *\n\t\t * `client` is declared here so it keeps its place in the stack and its\n\t\t * field wrapper, but its control comes from the `#field-client` slot\n\t\t * (a CnResourceSelect, which the base has no type for).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tformFields() {\n\t\t\treturn [\n\t\t\t\t{ key: 'channel', type: 'select', label: this.channelLabel, options: this.channelOptions },\n\t\t\t\t{ key: 'client', type: 'custom', label: this.clientLabel },\n\t\t\t\t{ key: 'subject', type: 'text', label: this.subjectLabel },\n\t\t\t\t{ key: 'summary', type: 'textarea', label: this.summaryLabel, rows: 4 },\n\t\t\t\t{ key: 'outcome', type: 'select', label: this.outcomeLabel, options: this.outcomeOptions, clearable: true },\n\t\t\t]\n\t\t},\n\n\t\tobjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/** The page's client in focus, or an empty string. */\n\t\tpageClient() {\n\t\t\tconst id = this.workspaceCtx && this.workspaceCtx.selectedClient\n\t\t\treturn id ? String(id) : ''\n\t\t},\n\n\t\tcanRegister() {\n\t\t\treturn Boolean(this.form.subject && this.form.subject.trim() && this.form.channel)\n\t\t},\n\n\t\tchannelLabel() {\n\t\t\treturn t('nextcloud-vue', 'Channel')\n\t\t},\n\n\t\tclientLabel() {\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tsubjectLabel() {\n\t\t\treturn t('nextcloud-vue', 'Subject')\n\t\t},\n\n\t\tsummaryLabel() {\n\t\t\treturn t('nextcloud-vue', 'Summary')\n\t\t},\n\n\t\toutcomeLabel() {\n\t\t\treturn t('nextcloud-vue', 'Outcome')\n\t\t},\n\n\t\t// `content.submitLabel` names what the form creates, e.g. \"Save contact moment\".\n\t\tregisterLabel() {\n\t\t\tif (this.content.submitLabel) {\n\t\t\t\treturn this.copy(this.content.submitLabel)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Register')\n\t\t},\n\n\t\tsavingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Saving…')\n\t\t},\n\n\t\t/**\n\t\t * URL of the saved object's page, from `content.detailRoute`. Empty\n\t\t * without a route or router, and then no Open button is shown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsavedHref() {\n\t\t\tconst route = this.content.detailRoute\n\t\t\tif (!this.saved || !this.saved.id || !route || !this.$router) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: route, params: { id: this.saved.id } }).href || ''\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\t// `escape: false` because the label renders through `{{ }}`, which escapes.\n\t\topenLabel() {\n\t\t\treturn t('nextcloud-vue', 'Open \"{title}\"', { title: (this.saved && this.saved.title) || '' }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the form writes its client and summary to the workspace context. */\n\t\twritesWorkspace() {\n\t\t\treturn this.content.writeWorkspace !== false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A new page client pre-fills the field; the field stays editable.\n\t\tpageClient: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.form.client = id\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.loadSchema()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Fetch the target schema, which the outcome options derive from. A\n\t\t * failure leaves the `content.outcomes` fallback in place.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadSchema() {\n\t\t\tconst store = this.objectStore\n\t\t\tif (!store || typeof store.fetchSchema !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// Re-registering an existing type resets its cached objects and schema.\n\t\t\t\tif (typeof store.registerObjectType === 'function' && !store.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tstore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tthis.schemaDef = await store.fetchSchema(this.typeSlug)\n\t\t\t} catch {\n\t\t\t\tthis.schemaDef = null\n\t\t\t}\n\t\t},\n\n\t\t/** The default channel value (first configured channel, else `telefoon`). */\n\t\t/**\n\t\t * A label the manifest supplies (a channel, an outcome, the submit\n\t\t * label), translated: the app's own translation first, since the words\n\t\t * are the app's, then the library's catalogue, then the text as\n\t\t * written. Before, a channel label was shown as written and the submit\n\t\t * label only reached the app's translate, so pipelinq showed \"Phone\"\n\t\t * and \"Save contact moment\" in English to a Dutch reader.\n\t\t *\n\t\t * @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language\n\t\t * @param {string} key The label as the manifest writes it.\n\t\t * @return {string} The translated label.\n\t\t */\n\t\tcopy(key) {\n\t\t\tif (typeof key !== 'string' || key === '') {\n\t\t\t\treturn key\n\t\t\t}\n\t\t\tconst host = typeof this.cnTranslate === 'function' ? this.cnTranslate(key) : key\n\t\t\tif (typeof host === 'string' && host !== '' && host !== key) {\n\t\t\t\treturn host\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', key)\n\t\t},\n\n\t\tfirstChannel() {\n\t\t\tconst ch = (this.content && this.content.channels) || []\n\t\t\treturn (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'\n\t\t},\n\n\t\t/**\n\t\t * A field edit from CnFormWidgetBase. `summary` also streams into the\n\t\t * workspace context. `client` never arrives here: its control is the\n\t\t * `#field-client` slot, which calls onClientChange.\n\t\t *\n\t\t * @param {{key: string, value: unknown}} payload The changed field.\n\t\t * @return {void}\n\t\t */\n\t\tonFieldUpdate({ key, value }) {\n\t\t\tthis.form[key] = value\n\t\t\tif (key === 'summary') {\n\t\t\t\tthis.writeWorkspace('activeSummary', value)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The submission's client, also written to the workspace context.\n\t\t *\n\t\t * @param {string} id The selected client id.\n\t\t */\n\t\tonClientChange(id) {\n\t\t\tthis.form.client = id || ''\n\t\t\tthis.writeWorkspace('selectedClient', this.form.client)\n\t\t},\n\n\t\t/**\n\t\t * A client created inline via \"Create '<name>'\" — select it.\n\t\t *\n\t\t * @param {object} client The created client object.\n\t\t */\n\t\tonClientCreated(client) {\n\t\t\tconst id = String((client && (client.id || (client['@self'] && client['@self'].id))) || '')\n\t\t\tif (id) {\n\t\t\t\tthis.form.client = id\n\t\t\t\tthis.writeWorkspace('selectedClient', id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Write a key into the workspace context, unless `content.writeWorkspace`\n\t\t * is false.\n\t\t *\n\t\t * Vue 2.7's Options-API `inject` auto-unwraps the provided ref, so the\n\t\t * holder is usually the plain reactive object; the raw-ref shape\n\t\t * (`.value`) is supported for Composition-API consumers.\n\t\t *\n\t\t * @param {string} key The context key.\n\t\t * @param {unknown} value The value.\n\t\t */\n\t\twriteWorkspace(key, value) {\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!this.writesWorkspace || !holder || typeof holder !== 'object') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ('value' in holder) {\n\t\t\t\tholder.value = { ...(holder.value || {}), [key]: value }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tholder[key] = value\n\t\t},\n\n\t\t/**\n\t\t * Persist the contactmoment. The agent identity is left to the server\n\t\t * (never sent from the client). Surfaces errors inline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRegister() {\n\t\t\tthis.subjectError = ''\n\t\t\tthis.errorMessage = ''\n\t\t\tif (!this.form.subject || !this.form.subject.trim()) {\n\t\t\t\tthis.subjectError = t('nextcloud-vue', 'Subject is required')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectStore) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Cannot save right now')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst c = this.content || {}\n\t\t\t// `defaults` is spread FIRST so the mapped fields below always win.\n\t\t\t// It exists for schemas that need a fixed value on every create the\n\t\t\t// form itself has no input for — e.g. a discriminator on a supertype\n\t\t\t// schema (`{ ticketType: 'contactmoment' }`), which would otherwise be\n\t\t\t// omitted and fail validation.\n\t\t\tconst payload = {\n\t\t\t\t...(c.defaults || {}),\n\t\t\t\t[c.subjectField || 'subject']: this.form.subject.trim(),\n\t\t\t\t[c.channelField || 'channel']: this.form.channel,\n\t\t\t\t[c.contactedAtField || 'contactedAt']: new Date().toISOString(),\n\t\t\t}\n\t\t\tif (this.form.client) {\n\t\t\t\tpayload[c.clientField || 'client'] = this.form.client\n\t\t\t}\n\t\t\tif (this.form.summary) {\n\t\t\t\tpayload[c.summaryField || 'summary'] = this.form.summary\n\t\t\t}\n\t\t\tif (this.form.outcome) {\n\t\t\t\tpayload[c.outcomeField || 'outcome'] = this.form.outcome\n\t\t\t}\n\n\t\t\tthis.saving = true\n\t\t\tthis.saved = null\n\t\t\ttry {\n\t\t\t\tif (typeof this.objectStore.registerObjectType === 'function' && !this.objectStore.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tthis.objectStore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tconst result = await this.objectStore.saveObject(this.typeSlug, payload)\n\t\t\t\tif (!result) {\n\t\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Failed to save interaction')\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event saved A contactmoment was persisted.\n\t\t\t\t * @type {object} The saved object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', result)\n\t\t\t\tthis.saved = {\n\t\t\t\t\tid: String(result.id || (result['@self'] && result['@self'].id) || ''),\n\t\t\t\t\ttitle: payload[c.subjectField || 'subject'],\n\t\t\t\t}\n\t\t\t\t// Reset the per-interaction fields; keep the client for follow-ups.\n\t\t\t\tthis.form.subject = ''\n\t\t\t\tthis.form.summary = ''\n\t\t\t\tthis.form.outcome = ''\n\t\t\t\tthis.writeWorkspace('activeSummary', '')\n\t\t\t} catch (e) {\n\t\t\t\tthis.errorMessage = (e && e.message) || t('nextcloud-vue', 'Failed to save interaction')\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<!--\n The scoped style block is gone on purpose. The markup now lives in\n CnFormWidgetBase, so a scoped rule here would carry THIS component's\n data-v attribute and match none of it. The rules moved verbatim to\n src/css/form-widget.css, keyed on `cn-form-widget*`; the base also emits\n the `cn-interaction-form-widget*` names on the same elements, so app CSS\n targeting those keeps matching.\n-->\n"],"names":["CnFormWidgetBase","CnResourceSelect","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnInteractionFormWidget.vue3.js","sources":["../../../../src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue?vue&type=template&id=693645c3&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnFormWidgetBase\n\t\tblockClass=\"cn-interaction-form-widget\"\n\t\t:fields=\"formFields\"\n\t\t:model=\"form\"\n\t\t:errors=\"{ subject: subjectError }\"\n\t\t:canSubmit=\"canRegister\"\n\t\t:submitting=\"saving\"\n\t\t:submitLabel=\"registerLabel\"\n\t\t:submittingLabel=\"savingLabel\"\n\t\t:errorMessage=\"errorMessage\"\n\t\t@update:field=\"onFieldUpdate\"\n\t\t@submit=\"onRegister\">\n\t\t<!-- The client picker is the one control the base does not know about:\n\t\t a CnResourceSelect, so typing a name that does not exist yet offers\n\t\t \"Create '<name>'\" inline rather than a dead \"no results\" path. The\n\t\t base still supplies its field wrapper and spacing. -->\n\t\t<template #field-client>\n\t\t\t<CnResourceSelect\n\t\t\t\t:register=\"register\"\n\t\t\t\t:schema=\"clientSchema\"\n\t\t\t\t:labelField=\"clientLabelField\"\n\t\t\t\t:modelValue=\"form.client\"\n\t\t\t\t:inputLabel=\"clientLabel\"\n\t\t\t\t:preload=\"true\"\n\t\t\t\t@update:modelValue=\"onClientChange\"\n\t\t\t\t@create=\"onClientCreated\" />\n\t\t</template>\n\n\t\t<!-- Opens what was just saved in a new tab, beside the submit button. -->\n\t\t<template #actions-start>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"savedHref\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:href=\"savedHref\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\tdata-testid=\"cn-interaction-form-open\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnFormWidgetBase>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport CnFormWidgetBase from '../CnFormWidgetBase/CnFormWidgetBase.vue'\nimport CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'\nimport { useObjectStore } from '../../store/index.js'\n\n/**\n * CnInteractionFormWidget — the \"active interaction\" quick-log form of a\n * workspace page.\n *\n * Persists a contactmoment (channel / client / subject / summary / outcome) to\n * OpenRegister via `useObjectStore().saveObject`. It reads `selectedClient`\n * from the page-level workspace context (the page's client in focus, set by a\n * page-level picker) to pre-fill its Client field.\n *\n * By default it also writes the chosen client to `selectedClient` and the live\n * summary text to `activeSummary`, so sibling widgets react: a client list\n * filtered on `@workspace.selectedClient`, or a `CnKbSearchWidget` bound to\n * `activeSummary`. A page whose client in focus comes from its own picker sets\n * `writeWorkspace: false`, which keeps the form's values to the submission.\n *\n * The Client field belongs to this submission only. It is a\n * `CnResourceSelect`, so typing a name that doesn't exist yet offers\n * \"Create '<name>'\" inline — no dead \"no results\" path.\n *\n * Resolved by its registry type key `interaction-form`. All schema/field/enum\n * choices come from `content`, so the widget carries no app-specific vocabulary.\n *\n * Example content blob:\n * ```js\n * content: {\n * register: 'pipelinq',\n * schema: 'ticket',\n * defaults: { ticketType: 'contactmoment' }, // fixed fields stamped on every create\n * clientSchema: 'client',\n * clientField: 'client',\n * summaryField: 'description',\n * channels: [{ value: 'telefoon', label: 'Phone' }, …],\n * }\n * ```\n *\n * The outcome options are read from the target schema on load: the enum of\n * its `outcomeField` property, labelled by that property's `x-enum-labels`.\n * `outcomes: [{ value, label }]` is only a fallback for a schema that has no\n * enum there.\n *\n * With `detailRoute` set (a route name taking `:id`, like an object list's\n * `rowRoute`), a save puts an \"Open\" button left of the submit button that\n * opens the saved object in a new tab.\n *\n * `defaults` covers the case where the target schema requires a value the form\n * has no input for — typically a discriminator on a supertype schema. It is\n * merged before the mapped fields, so those always take precedence.\n */\nexport default {\n\tname: 'CnInteractionFormWidget',\n\n\tcomponents: { CnFormWidgetBase, CnResourceSelect, NcButton, OpenInNew },\n\n\tinject: {\n\t\t/**\n\t\t * Page-level workspace context (reactive `ref({})`) from CnDashboardPage.\n\t\t * The widget reads `selectedClient` from it and, unless\n\t\t * `content.writeWorkspace` is false, writes `selectedClient` and\n\t\t * `activeSummary` back. Null on pages that don't provide one (the form\n\t\t * still saves).\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/**\n\t\t * Host translate function from CnAppRoot, bound to the host app's id.\n\t\t * Translates the schema's outcome labels.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted configuration blob (see component description for the shape).\n\t\t *\n\t\t * @type {{register?: string, schema?: string, defaults?: object, clientSchema?: string, clientField?: string, clientLabelField?: string, subjectField?: string, summaryField?: string, channelField?: string, outcomeField?: string, channels?: Array, outcomes?: Array, submitLabel?: string, detailRoute?: string, writeWorkspace?: boolean}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['saved'],\n\n\tdata() {\n\t\treturn {\n\t\t\tform: {\n\t\t\t\tchannel: this.firstChannel(),\n\t\t\t\tclient: '',\n\t\t\t\tsubject: '',\n\t\t\t\tsummary: '',\n\t\t\t\toutcome: '',\n\t\t\t},\n\n\t\t\tsaving: false,\n\t\t\terrorMessage: '',\n\t\t\tsubjectError: '',\n\t\t\t// The target schema, fetched on load for its outcome enum.\n\t\t\tschemaDef: null,\n\t\t\t// The last saved object, `{ id, title }`, until the next save.\n\t\t\tsaved: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tregister() {\n\t\t\treturn this.content.register || 'pipelinq'\n\t\t},\n\n\t\tschema() {\n\t\t\treturn this.content.schema || 'contactmoment'\n\t\t},\n\n\t\tclientSchema() {\n\t\t\treturn this.content.clientSchema || 'client'\n\t\t},\n\n\t\tclientLabelField() {\n\t\t\treturn this.content.clientLabelField || 'name'\n\t\t},\n\n\t\ttypeSlug() {\n\t\t\treturn `${this.register}-${this.schema}`\n\t\t},\n\n\t\t/** Channel options `{ value, label }`. */\n\t\tchannelOptions() {\n\t\t\treturn Array.isArray(this.content.channels) && this.content.channels.length > 0\n\t\t\t\t? this.content.channels\n\t\t\t\t: [\n\t\t\t\t\t\t{ value: 'telefoon', label: t('nextcloud-vue', 'Phone') },\n\t\t\t\t\t\t{ value: 'email', label: t('nextcloud-vue', 'Email') },\n\t\t\t\t\t\t{ value: 'chat', label: t('nextcloud-vue', 'Chat') },\n\t\t\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Outcome options `{ value, label }`, from the target schema's enum so\n\t\t * the form can only offer values the schema accepts. Labels come from\n\t\t * the property's `x-enum-labels`, translated. `content.outcomes` is the\n\t\t * fallback for a schema without an enum on that property.\n\t\t *\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\toutcomeOptions() {\n\t\t\tconst properties = (this.schemaDef && this.schemaDef.properties) || {}\n\t\t\tconst prop = properties[this.content.outcomeField || 'outcome']\n\t\t\tif (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\tconst labels = prop['x-enum-labels'] || prop.enumLabels || {}\n\t\t\t\tconst tr = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\t\treturn prop.enum.map((value) => ({ value, label: tr(labels[value] || String(value)) }))\n\t\t\t}\n\t\t\treturn Array.isArray(this.content.outcomes) ? this.content.outcomes : []\n\t\t},\n\n\t\t/**\n\t\t * The field descriptors handed to CnFormWidgetBase — the whole of this\n\t\t * widget's form, declared rather than drawn. Order matters: it is the\n\t\t * render order.\n\t\t *\n\t\t * `client` is declared here so it keeps its place in the stack and its\n\t\t * field wrapper, but its control comes from the `#field-client` slot\n\t\t * (a CnResourceSelect, which the base has no type for).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tformFields() {\n\t\t\treturn [\n\t\t\t\t{ key: 'channel', type: 'select', label: this.channelLabel, options: this.channelOptions },\n\t\t\t\t{ key: 'client', type: 'custom', label: this.clientLabel },\n\t\t\t\t{ key: 'subject', type: 'text', label: this.subjectLabel },\n\t\t\t\t{ key: 'summary', type: 'textarea', label: this.summaryLabel, rows: 4 },\n\t\t\t\t{ key: 'outcome', type: 'select', label: this.outcomeLabel, options: this.outcomeOptions, clearable: true },\n\t\t\t]\n\t\t},\n\n\t\tobjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/** The page's client in focus, or an empty string. */\n\t\tpageClient() {\n\t\t\tconst id = this.workspaceCtx && this.workspaceCtx.selectedClient\n\t\t\treturn id ? String(id) : ''\n\t\t},\n\n\t\tcanRegister() {\n\t\t\treturn Boolean(this.form.subject && this.form.subject.trim() && this.form.channel)\n\t\t},\n\n\t\tchannelLabel() {\n\t\t\treturn t('nextcloud-vue', 'Channel')\n\t\t},\n\n\t\tclientLabel() {\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tsubjectLabel() {\n\t\t\treturn t('nextcloud-vue', 'Subject')\n\t\t},\n\n\t\tsummaryLabel() {\n\t\t\treturn t('nextcloud-vue', 'Summary')\n\t\t},\n\n\t\toutcomeLabel() {\n\t\t\treturn t('nextcloud-vue', 'Outcome')\n\t\t},\n\n\t\t// `content.submitLabel` names what the form creates, e.g. \"Save contact moment\".\n\t\tregisterLabel() {\n\t\t\tif (this.content.submitLabel) {\n\t\t\t\tconst tr = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\t\treturn tr(this.content.submitLabel)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Register')\n\t\t},\n\n\t\tsavingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Saving…')\n\t\t},\n\n\t\t/**\n\t\t * URL of the saved object's page, from `content.detailRoute`. Empty\n\t\t * without a route or router, and then no Open button is shown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsavedHref() {\n\t\t\tconst route = this.content.detailRoute\n\t\t\tif (!this.saved || !this.saved.id || !route || !this.$router) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: route, params: { id: this.saved.id } }).href || ''\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\t// `escape: false` because the label renders through `{{ }}`, which escapes.\n\t\topenLabel() {\n\t\t\treturn t('nextcloud-vue', 'Open \"{title}\"', { title: (this.saved && this.saved.title) || '' }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the form writes its client and summary to the workspace context. */\n\t\twritesWorkspace() {\n\t\t\treturn this.content.writeWorkspace !== false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A new page client pre-fills the field; the field stays editable.\n\t\tpageClient: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.form.client = id\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.loadSchema()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Fetch the target schema, which the outcome options derive from. A\n\t\t * failure leaves the `content.outcomes` fallback in place.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadSchema() {\n\t\t\tconst store = this.objectStore\n\t\t\tif (!store || typeof store.fetchSchema !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// Re-registering an existing type resets its cached objects and schema.\n\t\t\t\tif (typeof store.registerObjectType === 'function' && !store.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tstore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tthis.schemaDef = await store.fetchSchema(this.typeSlug)\n\t\t\t} catch {\n\t\t\t\tthis.schemaDef = null\n\t\t\t}\n\t\t},\n\n\t\t/** The default channel value (first configured channel, else `telefoon`). */\n\t\tfirstChannel() {\n\t\t\tconst ch = (this.content && this.content.channels) || []\n\t\t\treturn (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'\n\t\t},\n\n\t\t/**\n\t\t * A field edit from CnFormWidgetBase. `summary` also streams into the\n\t\t * workspace context. `client` never arrives here: its control is the\n\t\t * `#field-client` slot, which calls onClientChange.\n\t\t *\n\t\t * @param {{key: string, value: unknown}} payload The changed field.\n\t\t * @return {void}\n\t\t */\n\t\tonFieldUpdate({ key, value }) {\n\t\t\tthis.form[key] = value\n\t\t\tif (key === 'summary') {\n\t\t\t\tthis.writeWorkspace('activeSummary', value)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The submission's client, also written to the workspace context.\n\t\t *\n\t\t * @param {string} id The selected client id.\n\t\t */\n\t\tonClientChange(id) {\n\t\t\tthis.form.client = id || ''\n\t\t\tthis.writeWorkspace('selectedClient', this.form.client)\n\t\t},\n\n\t\t/**\n\t\t * A client created inline via \"Create '<name>'\" — select it.\n\t\t *\n\t\t * @param {object} client The created client object.\n\t\t */\n\t\tonClientCreated(client) {\n\t\t\tconst id = String((client && (client.id || (client['@self'] && client['@self'].id))) || '')\n\t\t\tif (id) {\n\t\t\t\tthis.form.client = id\n\t\t\t\tthis.writeWorkspace('selectedClient', id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Write a key into the workspace context, unless `content.writeWorkspace`\n\t\t * is false.\n\t\t *\n\t\t * Vue 2.7's Options-API `inject` auto-unwraps the provided ref, so the\n\t\t * holder is usually the plain reactive object; the raw-ref shape\n\t\t * (`.value`) is supported for Composition-API consumers.\n\t\t *\n\t\t * @param {string} key The context key.\n\t\t * @param {unknown} value The value.\n\t\t */\n\t\twriteWorkspace(key, value) {\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!this.writesWorkspace || !holder || typeof holder !== 'object') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ('value' in holder) {\n\t\t\t\tholder.value = { ...(holder.value || {}), [key]: value }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tholder[key] = value\n\t\t},\n\n\t\t/**\n\t\t * Persist the contactmoment. The agent identity is left to the server\n\t\t * (never sent from the client). Surfaces errors inline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRegister() {\n\t\t\tthis.subjectError = ''\n\t\t\tthis.errorMessage = ''\n\t\t\tif (!this.form.subject || !this.form.subject.trim()) {\n\t\t\t\tthis.subjectError = t('nextcloud-vue', 'Subject is required')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectStore) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Cannot save right now')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst c = this.content || {}\n\t\t\t// `defaults` is spread FIRST so the mapped fields below always win.\n\t\t\t// It exists for schemas that need a fixed value on every create the\n\t\t\t// form itself has no input for — e.g. a discriminator on a supertype\n\t\t\t// schema (`{ ticketType: 'contactmoment' }`), which would otherwise be\n\t\t\t// omitted and fail validation.\n\t\t\tconst payload = {\n\t\t\t\t...(c.defaults || {}),\n\t\t\t\t[c.subjectField || 'subject']: this.form.subject.trim(),\n\t\t\t\t[c.channelField || 'channel']: this.form.channel,\n\t\t\t\t[c.contactedAtField || 'contactedAt']: new Date().toISOString(),\n\t\t\t}\n\t\t\tif (this.form.client) {\n\t\t\t\tpayload[c.clientField || 'client'] = this.form.client\n\t\t\t}\n\t\t\tif (this.form.summary) {\n\t\t\t\tpayload[c.summaryField || 'summary'] = this.form.summary\n\t\t\t}\n\t\t\tif (this.form.outcome) {\n\t\t\t\tpayload[c.outcomeField || 'outcome'] = this.form.outcome\n\t\t\t}\n\n\t\t\tthis.saving = true\n\t\t\tthis.saved = null\n\t\t\ttry {\n\t\t\t\tif (typeof this.objectStore.registerObjectType === 'function' && !this.objectStore.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tthis.objectStore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tconst result = await this.objectStore.saveObject(this.typeSlug, payload)\n\t\t\t\tif (!result) {\n\t\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Failed to save interaction')\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event saved A contactmoment was persisted.\n\t\t\t\t * @type {object} The saved object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', result)\n\t\t\t\tthis.saved = {\n\t\t\t\t\tid: String(result.id || (result['@self'] && result['@self'].id) || ''),\n\t\t\t\t\ttitle: payload[c.subjectField || 'subject'],\n\t\t\t\t}\n\t\t\t\t// Reset the per-interaction fields; keep the client for follow-ups.\n\t\t\t\tthis.form.subject = ''\n\t\t\t\tthis.form.summary = ''\n\t\t\t\tthis.form.outcome = ''\n\t\t\t\tthis.writeWorkspace('activeSummary', '')\n\t\t\t} catch (e) {\n\t\t\t\tthis.errorMessage = (e && e.message) || t('nextcloud-vue', 'Failed to save interaction')\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<!--\n The scoped style block is gone on purpose. The markup now lives in\n CnFormWidgetBase, so a scoped rule here would carry THIS component's\n data-v attribute and match none of it. The rules moved verbatim to\n src/css/form-widget.css, keyed on `cn-form-widget*`; the base also emits\n the `cn-interaction-form-widget*` names on the same elements, so app CSS\n targeting those keeps matching.\n-->\n"],"names":["_createBlock","_createVNode","_createTextVNode"],"mappings":";;;;;;;;uBASEA;AACA,IAAA,UAAU,EAAC,4BAAY;AACtB,IAAA,MAAM,EAAE,QAAU,CAAA,UAAA;AAClB,IAAA,KAAK,EAAE,KAAI,CAAA,IAAA;AACX,IAAA,MAAM,aAAa,KAAS,CAAA,YAAA,EAAA;AAC5B,IAAA,SAAS,EAAE,QAAW,CAAA,WAAA;AACtB,IAAA,UAAU,EAAE,KAAM,CAAA,MAAA;AAClB,IAAA,WAAW,EAAE,QAAM,CAAA,aAAA;AACnB,IAAA,eAAe,EAAE,QAAU,CAAA,WAAA;gBACf,EAAE,KAAW,CAAA,YAAA;oBACb,EAAE,QAAa,CAAA,aAAA;AACvB,IAAA,QAAE,EAAC,QAAW,CAAA,UAAA;;AAKR,IAAA,cAAY,gBASC;iBAAA,CAAA,2BAAA,EAAA;AAPrB,QAAA,QAAQ,EAAE,QAAQ,CAAA,QAAA;AAClB,QAAA,MAAM,EAAE,QAAK,CAAA,YAAA;AACb,QAAA,UAAU,EAAE,QAAgB,CAAA,gBAAA;QAC5B,UAAU,EAAE,KAAI,CAAA,IAAA,CAAC,MAAM;AACvB,QAAA,UAAO,EAAA,QAAA,CAAA,WAAA;;AAEP,QAAA,qBAAiB,EAAE,QAAc,CAAA,cAAA;AACjC,QAAA,UAAQ,QAAa,CAAA,eAAA;;;AAIb,IAAA,eAAO,UAChB;OACM,QAAS,CAAA,SAAA;wBADfA;;AAEA,YAAA,OAAO,EAAC,UAAO;AACd,YAAA,IAAI,EAAE,QAAS,CAAA,SAAA;AAChB,YAAA,MAAM,EAAC,QAAG;AACV,YAAA,aAAW,EAAA,0BAAG;;AACL,YAAA,IAAA,UACR,MAAO;cAAPC,WAAO,CAAA,oBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;6BACG;AAAA,cAAAC,eAAA,CAAA;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnInteractionFormWidget.vue3.js","sources":["../../../../src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue?vue&type=template&id=fc6b3252&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<CnFormWidgetBase\n\t\tblockClass=\"cn-interaction-form-widget\"\n\t\t:fields=\"formFields\"\n\t\t:model=\"form\"\n\t\t:errors=\"{ subject: subjectError }\"\n\t\t:canSubmit=\"canRegister\"\n\t\t:submitting=\"saving\"\n\t\t:submitLabel=\"registerLabel\"\n\t\t:submittingLabel=\"savingLabel\"\n\t\t:errorMessage=\"errorMessage\"\n\t\t@update:field=\"onFieldUpdate\"\n\t\t@submit=\"onRegister\">\n\t\t<!-- The client picker is the one control the base does not know about:\n\t\t a CnResourceSelect, so typing a name that does not exist yet offers\n\t\t \"Create '<name>'\" inline rather than a dead \"no results\" path. The\n\t\t base still supplies its field wrapper and spacing. -->\n\t\t<template #field-client>\n\t\t\t<CnResourceSelect\n\t\t\t\t:register=\"register\"\n\t\t\t\t:schema=\"clientSchema\"\n\t\t\t\t:labelField=\"clientLabelField\"\n\t\t\t\t:modelValue=\"form.client\"\n\t\t\t\t:inputLabel=\"clientLabel\"\n\t\t\t\t:preload=\"true\"\n\t\t\t\t@update:modelValue=\"onClientChange\"\n\t\t\t\t@create=\"onClientCreated\" />\n\t\t</template>\n\n\t\t<!-- Opens what was just saved in a new tab, beside the submit button. -->\n\t\t<template #actions-start>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"savedHref\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:href=\"savedHref\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\tdata-testid=\"cn-interaction-form-open\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnFormWidgetBase>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport CnFormWidgetBase from '../CnFormWidgetBase/CnFormWidgetBase.vue'\nimport CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'\nimport { useObjectStore } from '../../store/index.js'\n\n/**\n * CnInteractionFormWidget — the \"active interaction\" quick-log form of a\n * workspace page.\n *\n * Persists a contactmoment (channel / client / subject / summary / outcome) to\n * OpenRegister via `useObjectStore().saveObject`. It reads `selectedClient`\n * from the page-level workspace context (the page's client in focus, set by a\n * page-level picker) to pre-fill its Client field.\n *\n * By default it also writes the chosen client to `selectedClient` and the live\n * summary text to `activeSummary`, so sibling widgets react: a client list\n * filtered on `@workspace.selectedClient`, or a `CnKbSearchWidget` bound to\n * `activeSummary`. A page whose client in focus comes from its own picker sets\n * `writeWorkspace: false`, which keeps the form's values to the submission.\n *\n * The Client field belongs to this submission only. It is a\n * `CnResourceSelect`, so typing a name that doesn't exist yet offers\n * \"Create '<name>'\" inline — no dead \"no results\" path.\n *\n * Resolved by its registry type key `interaction-form`. All schema/field/enum\n * choices come from `content`, so the widget carries no app-specific vocabulary.\n *\n * Example content blob:\n * ```js\n * content: {\n * register: 'pipelinq',\n * schema: 'ticket',\n * defaults: { ticketType: 'contactmoment' }, // fixed fields stamped on every create\n * clientSchema: 'client',\n * clientField: 'client',\n * summaryField: 'description',\n * channels: [{ value: 'telefoon', label: 'Phone' }, …],\n * }\n * ```\n *\n * The outcome options are read from the target schema on load: the enum of\n * its `outcomeField` property, labelled by that property's `x-enum-labels`.\n * `outcomes: [{ value, label }]` is only a fallback for a schema that has no\n * enum there.\n *\n * With `detailRoute` set (a route name taking `:id`, like an object list's\n * `rowRoute`), a save puts an \"Open\" button left of the submit button that\n * opens the saved object in a new tab.\n *\n * `defaults` covers the case where the target schema requires a value the form\n * has no input for — typically a discriminator on a supertype schema. It is\n * merged before the mapped fields, so those always take precedence.\n */\nexport default {\n\tname: 'CnInteractionFormWidget',\n\n\tcomponents: { CnFormWidgetBase, CnResourceSelect, NcButton, OpenInNew },\n\n\tinject: {\n\t\t/**\n\t\t * Page-level workspace context (reactive `ref({})`) from CnDashboardPage.\n\t\t * The widget reads `selectedClient` from it and, unless\n\t\t * `content.writeWorkspace` is false, writes `selectedClient` and\n\t\t * `activeSummary` back. Null on pages that don't provide one (the form\n\t\t * still saves).\n\t\t */\n\t\tcnWorkspaceContext: { default: null },\n\t\t/**\n\t\t * Host translate function from CnAppRoot, bound to the host app's id.\n\t\t * Translates the schema's outcome labels.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted configuration blob (see component description for the shape).\n\t\t *\n\t\t * @type {{register?: string, schema?: string, defaults?: object, clientSchema?: string, clientField?: string, clientLabelField?: string, subjectField?: string, summaryField?: string, channelField?: string, outcomeField?: string, channels?: Array, outcomes?: Array, submitLabel?: string, detailRoute?: string, writeWorkspace?: boolean}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\temits: ['saved'],\n\n\tdata() {\n\t\treturn {\n\t\t\tform: {\n\t\t\t\tchannel: this.firstChannel(),\n\t\t\t\tclient: '',\n\t\t\t\tsubject: '',\n\t\t\t\tsummary: '',\n\t\t\t\toutcome: '',\n\t\t\t},\n\n\t\t\tsaving: false,\n\t\t\terrorMessage: '',\n\t\t\tsubjectError: '',\n\t\t\t// The target schema, fetched on load for its outcome enum.\n\t\t\tschemaDef: null,\n\t\t\t// The last saved object, `{ id, title }`, until the next save.\n\t\t\tsaved: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tregister() {\n\t\t\treturn this.content.register || 'pipelinq'\n\t\t},\n\n\t\tschema() {\n\t\t\treturn this.content.schema || 'contactmoment'\n\t\t},\n\n\t\tclientSchema() {\n\t\t\treturn this.content.clientSchema || 'client'\n\t\t},\n\n\t\tclientLabelField() {\n\t\t\treturn this.content.clientLabelField || 'name'\n\t\t},\n\n\t\ttypeSlug() {\n\t\t\treturn `${this.register}-${this.schema}`\n\t\t},\n\n\t\t/** Channel options `{ value, label }`. */\n\t\tchannelOptions() {\n\t\t\treturn Array.isArray(this.content.channels) && this.content.channels.length > 0\n\t\t\t\t? this.content.channels.map((channel) => ({ ...channel, label: this.copy(channel && channel.label) }))\n\t\t\t\t: [\n\t\t\t\t\t\t{ value: 'telefoon', label: t('nextcloud-vue', 'Phone') },\n\t\t\t\t\t\t{ value: 'email', label: t('nextcloud-vue', 'Email') },\n\t\t\t\t\t\t{ value: 'chat', label: t('nextcloud-vue', 'Chat') },\n\t\t\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Outcome options `{ value, label }`, from the target schema's enum so\n\t\t * the form can only offer values the schema accepts. Labels come from\n\t\t * the property's `x-enum-labels`, translated. `content.outcomes` is the\n\t\t * fallback for a schema without an enum on that property.\n\t\t *\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\toutcomeOptions() {\n\t\t\tconst properties = (this.schemaDef && this.schemaDef.properties) || {}\n\t\t\tconst prop = properties[this.content.outcomeField || 'outcome']\n\t\t\tif (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\tconst labels = prop['x-enum-labels'] || prop.enumLabels || {}\n\t\t\t\treturn prop.enum.map((value) => ({ value, label: this.copy(labels[value] || String(value)) }))\n\t\t\t}\n\t\t\treturn Array.isArray(this.content.outcomes)\n\t\t\t\t? this.content.outcomes.map((outcome) => ({ ...outcome, label: this.copy(outcome && outcome.label) }))\n\t\t\t\t: []\n\t\t},\n\n\t\t/**\n\t\t * The field descriptors handed to CnFormWidgetBase — the whole of this\n\t\t * widget's form, declared rather than drawn. Order matters: it is the\n\t\t * render order.\n\t\t *\n\t\t * `client` is declared here so it keeps its place in the stack and its\n\t\t * field wrapper, but its control comes from the `#field-client` slot\n\t\t * (a CnResourceSelect, which the base has no type for).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tformFields() {\n\t\t\treturn [\n\t\t\t\t{ key: 'channel', type: 'select', label: this.channelLabel, options: this.channelOptions },\n\t\t\t\t{ key: 'client', type: 'custom', label: this.clientLabel },\n\t\t\t\t{ key: 'subject', type: 'text', label: this.subjectLabel },\n\t\t\t\t{ key: 'summary', type: 'textarea', label: this.summaryLabel, rows: 4 },\n\t\t\t\t{ key: 'outcome', type: 'select', label: this.outcomeLabel, options: this.outcomeOptions, clearable: true },\n\t\t\t]\n\t\t},\n\n\t\tobjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/** The page's client in focus, or an empty string. */\n\t\tpageClient() {\n\t\t\tconst id = this.workspaceCtx && this.workspaceCtx.selectedClient\n\t\t\treturn id ? String(id) : ''\n\t\t},\n\n\t\tcanRegister() {\n\t\t\treturn Boolean(this.form.subject && this.form.subject.trim() && this.form.channel)\n\t\t},\n\n\t\tchannelLabel() {\n\t\t\treturn t('nextcloud-vue', 'Channel')\n\t\t},\n\n\t\tclientLabel() {\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tsubjectLabel() {\n\t\t\treturn t('nextcloud-vue', 'Subject')\n\t\t},\n\n\t\tsummaryLabel() {\n\t\t\treturn t('nextcloud-vue', 'Summary')\n\t\t},\n\n\t\toutcomeLabel() {\n\t\t\treturn t('nextcloud-vue', 'Outcome')\n\t\t},\n\n\t\t// `content.submitLabel` names what the form creates, e.g. \"Save contact moment\".\n\t\tregisterLabel() {\n\t\t\tif (this.content.submitLabel) {\n\t\t\t\treturn this.copy(this.content.submitLabel)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Register')\n\t\t},\n\n\t\tsavingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Saving…')\n\t\t},\n\n\t\t/**\n\t\t * URL of the saved object's page, from `content.detailRoute`. Empty\n\t\t * without a route or router, and then no Open button is shown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsavedHref() {\n\t\t\tconst route = this.content.detailRoute\n\t\t\tif (!this.saved || !this.saved.id || !route || !this.$router) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: route, params: { id: this.saved.id } }).href || ''\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\t// `escape: false` because the label renders through `{{ }}`, which escapes.\n\t\topenLabel() {\n\t\t\treturn t('nextcloud-vue', 'Open \"{title}\"', { title: (this.saved && this.saved.title) || '' }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the form writes its client and summary to the workspace context. */\n\t\twritesWorkspace() {\n\t\t\treturn this.content.writeWorkspace !== false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A new page client pre-fills the field; the field stays editable.\n\t\tpageClient: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.form.client = id\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.loadSchema()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Fetch the target schema, which the outcome options derive from. A\n\t\t * failure leaves the `content.outcomes` fallback in place.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadSchema() {\n\t\t\tconst store = this.objectStore\n\t\t\tif (!store || typeof store.fetchSchema !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// Re-registering an existing type resets its cached objects and schema.\n\t\t\t\tif (typeof store.registerObjectType === 'function' && !store.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tstore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tthis.schemaDef = await store.fetchSchema(this.typeSlug)\n\t\t\t} catch {\n\t\t\t\tthis.schemaDef = null\n\t\t\t}\n\t\t},\n\n\t\t/** The default channel value (first configured channel, else `telefoon`). */\n\t\t/**\n\t\t * A label the manifest supplies (a channel, an outcome, the submit\n\t\t * label), translated: the app's own translation first, since the words\n\t\t * are the app's, then the library's catalogue, then the text as\n\t\t * written. Before, a channel label was shown as written and the submit\n\t\t * label only reached the app's translate, so pipelinq showed \"Phone\"\n\t\t * and \"Save contact moment\" in English to a Dutch reader.\n\t\t *\n\t\t * @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language\n\t\t * @param {string} key The label as the manifest writes it.\n\t\t * @return {string} The translated label.\n\t\t */\n\t\tcopy(key) {\n\t\t\tif (typeof key !== 'string' || key === '') {\n\t\t\t\treturn key\n\t\t\t}\n\t\t\tconst host = typeof this.cnTranslate === 'function' ? this.cnTranslate(key) : key\n\t\t\tif (typeof host === 'string' && host !== '' && host !== key) {\n\t\t\t\treturn host\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', key)\n\t\t},\n\n\t\tfirstChannel() {\n\t\t\tconst ch = (this.content && this.content.channels) || []\n\t\t\treturn (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'\n\t\t},\n\n\t\t/**\n\t\t * A field edit from CnFormWidgetBase. `summary` also streams into the\n\t\t * workspace context. `client` never arrives here: its control is the\n\t\t * `#field-client` slot, which calls onClientChange.\n\t\t *\n\t\t * @param {{key: string, value: unknown}} payload The changed field.\n\t\t * @return {void}\n\t\t */\n\t\tonFieldUpdate({ key, value }) {\n\t\t\tthis.form[key] = value\n\t\t\tif (key === 'summary') {\n\t\t\t\tthis.writeWorkspace('activeSummary', value)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The submission's client, also written to the workspace context.\n\t\t *\n\t\t * @param {string} id The selected client id.\n\t\t */\n\t\tonClientChange(id) {\n\t\t\tthis.form.client = id || ''\n\t\t\tthis.writeWorkspace('selectedClient', this.form.client)\n\t\t},\n\n\t\t/**\n\t\t * A client created inline via \"Create '<name>'\" — select it.\n\t\t *\n\t\t * @param {object} client The created client object.\n\t\t */\n\t\tonClientCreated(client) {\n\t\t\tconst id = String((client && (client.id || (client['@self'] && client['@self'].id))) || '')\n\t\t\tif (id) {\n\t\t\t\tthis.form.client = id\n\t\t\t\tthis.writeWorkspace('selectedClient', id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Write a key into the workspace context, unless `content.writeWorkspace`\n\t\t * is false.\n\t\t *\n\t\t * Vue 2.7's Options-API `inject` auto-unwraps the provided ref, so the\n\t\t * holder is usually the plain reactive object; the raw-ref shape\n\t\t * (`.value`) is supported for Composition-API consumers.\n\t\t *\n\t\t * @param {string} key The context key.\n\t\t * @param {unknown} value The value.\n\t\t */\n\t\twriteWorkspace(key, value) {\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!this.writesWorkspace || !holder || typeof holder !== 'object') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ('value' in holder) {\n\t\t\t\tholder.value = { ...(holder.value || {}), [key]: value }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tholder[key] = value\n\t\t},\n\n\t\t/**\n\t\t * Persist the contactmoment. The agent identity is left to the server\n\t\t * (never sent from the client). Surfaces errors inline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRegister() {\n\t\t\tthis.subjectError = ''\n\t\t\tthis.errorMessage = ''\n\t\t\tif (!this.form.subject || !this.form.subject.trim()) {\n\t\t\t\tthis.subjectError = t('nextcloud-vue', 'Subject is required')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectStore) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Cannot save right now')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst c = this.content || {}\n\t\t\t// `defaults` is spread FIRST so the mapped fields below always win.\n\t\t\t// It exists for schemas that need a fixed value on every create the\n\t\t\t// form itself has no input for — e.g. a discriminator on a supertype\n\t\t\t// schema (`{ ticketType: 'contactmoment' }`), which would otherwise be\n\t\t\t// omitted and fail validation.\n\t\t\tconst payload = {\n\t\t\t\t...(c.defaults || {}),\n\t\t\t\t[c.subjectField || 'subject']: this.form.subject.trim(),\n\t\t\t\t[c.channelField || 'channel']: this.form.channel,\n\t\t\t\t[c.contactedAtField || 'contactedAt']: new Date().toISOString(),\n\t\t\t}\n\t\t\tif (this.form.client) {\n\t\t\t\tpayload[c.clientField || 'client'] = this.form.client\n\t\t\t}\n\t\t\tif (this.form.summary) {\n\t\t\t\tpayload[c.summaryField || 'summary'] = this.form.summary\n\t\t\t}\n\t\t\tif (this.form.outcome) {\n\t\t\t\tpayload[c.outcomeField || 'outcome'] = this.form.outcome\n\t\t\t}\n\n\t\t\tthis.saving = true\n\t\t\tthis.saved = null\n\t\t\ttry {\n\t\t\t\tif (typeof this.objectStore.registerObjectType === 'function' && !this.objectStore.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tthis.objectStore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tconst result = await this.objectStore.saveObject(this.typeSlug, payload)\n\t\t\t\tif (!result) {\n\t\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Failed to save interaction')\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event saved A contactmoment was persisted.\n\t\t\t\t * @type {object} The saved object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', result)\n\t\t\t\tthis.saved = {\n\t\t\t\t\tid: String(result.id || (result['@self'] && result['@self'].id) || ''),\n\t\t\t\t\ttitle: payload[c.subjectField || 'subject'],\n\t\t\t\t}\n\t\t\t\t// Reset the per-interaction fields; keep the client for follow-ups.\n\t\t\t\tthis.form.subject = ''\n\t\t\t\tthis.form.summary = ''\n\t\t\t\tthis.form.outcome = ''\n\t\t\t\tthis.writeWorkspace('activeSummary', '')\n\t\t\t} catch (e) {\n\t\t\t\tthis.errorMessage = (e && e.message) || t('nextcloud-vue', 'Failed to save interaction')\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<!--\n The scoped style block is gone on purpose. The markup now lives in\n CnFormWidgetBase, so a scoped rule here would carry THIS component's\n data-v attribute and match none of it. The rules moved verbatim to\n src/css/form-widget.css, keyed on `cn-form-widget*`; the base also emits\n the `cn-interaction-form-widget*` names on the same elements, so app CSS\n targeting those keeps matching.\n-->\n"],"names":["_createBlock","_createVNode","_createTextVNode"],"mappings":";;;;;;;;uBASEA;AACA,IAAA,UAAU,EAAC,4BAAY;AACtB,IAAA,MAAM,EAAE,QAAU,CAAA,UAAA;AAClB,IAAA,KAAK,EAAE,KAAI,CAAA,IAAA;AACX,IAAA,MAAM,aAAa,KAAS,CAAA,YAAA,EAAA;AAC5B,IAAA,SAAS,EAAE,QAAW,CAAA,WAAA;AACtB,IAAA,UAAU,EAAE,KAAM,CAAA,MAAA;AAClB,IAAA,WAAW,EAAE,QAAM,CAAA,aAAA;AACnB,IAAA,eAAe,EAAE,QAAU,CAAA,WAAA;gBACf,EAAE,KAAW,CAAA,YAAA;oBACb,EAAE,QAAa,CAAA,aAAA;AACvB,IAAA,QAAE,EAAC,QAAW,CAAA,UAAA;;AAKR,IAAA,cAAY,gBASC;iBAAA,CAAA,2BAAA,EAAA;AAPrB,QAAA,QAAQ,EAAE,QAAQ,CAAA,QAAA;AAClB,QAAA,MAAM,EAAE,QAAK,CAAA,YAAA;AACb,QAAA,UAAU,EAAE,QAAgB,CAAA,gBAAA;QAC5B,UAAU,EAAE,KAAI,CAAA,IAAA,CAAC,MAAM;AACvB,QAAA,UAAO,EAAA,QAAA,CAAA,WAAA;;AAEP,QAAA,qBAAiB,EAAE,QAAc,CAAA,cAAA;AACjC,QAAA,UAAQ,QAAa,CAAA,eAAA;;;AAIb,IAAA,eAAO,UAChB;OACM,QAAS,CAAA,SAAA;wBADfA;;AAEA,YAAA,OAAO,EAAC,UAAO;AACd,YAAA,IAAI,EAAE,QAAS,CAAA,SAAA;AAChB,YAAA,MAAM,EAAC,QAAG;AACV,YAAA,aAAW,EAAA,0BAAG;;AACL,YAAA,IAAA,UACR,MAAO;cAAPC,WAAO,CAAA,oBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;6BACG;AAAA,cAAAC,eAAA,CAAA;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import { translate } from '@nextcloud/l10n';
2
2
  import { NcActions, NcActionButton } from '@nextcloud/vue';
3
3
  import ContentCopy from 'vue-material-design-icons/ContentCopy.vue';
4
4
  import Export from 'vue-material-design-icons/Export.vue';
5
- import Import from 'vue-material-design-icons/Import.vue';
5
+ import ImportIcon from 'vue-material-design-icons/Import.vue';
6
6
  import TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue';
7
7
  import TuneVariant from 'vue-material-design-icons/TuneVariant.vue';
8
8
 
@@ -31,7 +31,7 @@ var script = {
31
31
  TuneVariant,
32
32
  ContentCopy,
33
33
  TrashCanOutline,
34
- Import,
34
+ Import: ImportIcon,
35
35
  Export,
36
36
  },
37
37
 
@@ -1 +1 @@
1
- {"version":3,"file":"CnMassActionBar.vue2.js","sources":["../../../../src/components/CnMassActionBar/CnMassActionBar.vue"],"sourcesContent":["<template>\n\t<NcActions\n\t\tv-if=\"count > 0\"\n\t\t:menuName=\"menuLabel\"\n\t\t:inline=\"0\">\n\t\t<template #icon>\n\t\t\t<TuneVariant :size=\"20\" />\n\t\t</template>\n\n\t\t<!-- Built-in: Import -->\n\t\t<NcActionButton\n\t\t\tv-if=\"showImport\"\n\t\t\t@click=\"emitMassImport\">\n\t\t\t<template #icon>\n\t\t\t\t<Import :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ importLabel }}\n\t\t</NcActionButton>\n\n\t\t<!-- Built-in: Export -->\n\t\t<NcActionButton\n\t\t\tv-if=\"showExport\"\n\t\t\t@click=\"emitMassExport\">\n\t\t\t<template #icon>\n\t\t\t\t<Export :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ exportLabel }}\n\t\t</NcActionButton>\n\n\t\t<!-- Built-in: Copy -->\n\t\t<NcActionButton\n\t\t\tv-if=\"showCopy\"\n\t\t\t@click=\"emitMassCopy\">\n\t\t\t<template #icon>\n\t\t\t\t<ContentCopy :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ copyLabel }}\n\t\t</NcActionButton>\n\n\t\t<!-- Built-in: Delete -->\n\t\t<NcActionButton\n\t\t\tv-if=\"showDelete\"\n\t\t\t@click=\"emitMassDelete\">\n\t\t\t<template #icon>\n\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ deleteLabel }}\n\t\t</NcActionButton>\n\n\t\t<!-- @slot actions Additional app-specific mass-action buttons. Slot scope: `{ count, selectedIds }`. -->\n\t\t<slot name=\"actions\" :count=\"count\" :selectedIds=\"selectedIds\" />\n\t</NcActions>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActions } from '@nextcloud/vue'\nimport ContentCopy from 'vue-material-design-icons/ContentCopy.vue'\nimport Export from 'vue-material-design-icons/Export.vue'\nimport Import from 'vue-material-design-icons/Import.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\nimport TuneVariant from 'vue-material-design-icons/TuneVariant.vue'\n\n/**\n * CnMassActionBar — Mass action dropdown button for selected items.\n *\n * Renders as a single \"Mass Actions (N)\" dropdown button with built-in\n * Import, Export, Copy, and Delete actions plus a slot for custom actions.\n * Place this in the header/action bar of your index page.\n *\n * Use the `#actions` scoped slot to add app-specific mass actions\n * (use `NcActionButton` for slot content).\n *\n * @event mass-import Emitted when the Import action is clicked. No payload.\n * @event mass-export Emitted when the Export action is clicked. No payload.\n * @event mass-copy Emitted when the Copy action is clicked. No payload.\n * @event mass-delete Emitted when the Delete action is clicked. No payload.\n * @slot actions Additional app-specific mass-action buttons. Slot scope: `{ count, selectedIds }`.\n */\nexport default {\n\tname: 'CnMassActionBar',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tTuneVariant,\n\t\tContentCopy,\n\t\tTrashCanOutline,\n\t\tImport,\n\t\tExport,\n\t},\n\n\tprops: {\n\t\t/** Array of selected item IDs */\n\t\tselectedIds: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Number of selected items */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Whether to show the built-in Import action */\n\t\tshowImport: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Whether to show the built-in Export action */\n\t\tshowExport: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Whether to show the built-in Copy action */\n\t\tshowCopy: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Whether to show the built-in Delete action */\n\t\tshowDelete: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Label template for the menu button. Use {count} for the count. */\n\t\tmenuLabelTemplate: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Mass actions ({count})'),\n\t\t},\n\n\t\t/** Label for the built-in Import mass-action button. */\n\t\timportLabel: { type: String, default: () => t('nextcloud-vue', 'Import') },\n\t\t/** Label for the built-in Export mass-action button. */\n\t\texportLabel: { type: String, default: () => t('nextcloud-vue', 'Export') },\n\t\t/** Label for the built-in Copy mass-action button. */\n\t\tcopyLabel: { type: String, default: () => t('nextcloud-vue', 'Copy') },\n\t\t/** Label for the built-in Delete mass-action button. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },\n\t},\n\n\temits: ['mass-copy', 'mass-delete', 'mass-export', 'mass-import'],\n\n\tcomputed: {\n\t\tmenuLabel() {\n\t\t\treturn this.menuLabelTemplate.replace('{count}', String(this.count))\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Emit the built-in Import click. No payload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitMassImport() {\n\t\t\t/**\n\t\t\t * @event mass-import Emitted when the Import action is clicked. No payload.\n\t\t\t */\n\t\t\tthis.$emit('mass-import')\n\t\t},\n\n\t\t/**\n\t\t * Emit the built-in Export click. No payload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitMassExport() {\n\t\t\t/**\n\t\t\t * @event mass-export Emitted when the Export action is clicked. No payload.\n\t\t\t */\n\t\t\tthis.$emit('mass-export')\n\t\t},\n\n\t\t/**\n\t\t * Emit the built-in Copy click. No payload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitMassCopy() {\n\t\t\t/**\n\t\t\t * @event mass-copy Emitted when the Copy action is clicked. No payload.\n\t\t\t */\n\t\t\tthis.$emit('mass-copy')\n\t\t},\n\n\t\t/**\n\t\t * Emit the built-in Delete click. No payload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitMassDelete() {\n\t\t\t/**\n\t\t\t * @event mass-delete Emitted when the Delete action is clicked. No payload.\n\t\t\t */\n\t\t\tthis.$emit('mass-delete')\n\t\t},\n\t},\n}\n</script>\n"],"names":["t"],"mappings":";;;;;;;;AA+DA;;AAEC;;;;AAIA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAED,aAAe;CACd,IAAI,EAAE,iBAAiB;;CAEvB,UAAU,EAAE;AACX,EAAA,SAAS;AACT,EAAA,cAAc;EACd,WAAW;EACX,WAAW;AACX,EAAA,eAAe;AACf,EAAA,MAAM;AACN,EAAA,MAAM;EACN;;AAED,CAAA,KAAK,EAAE;;EAEN,WAAW,EAAE;GACZ,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;;AAGD,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,CAAC;GACV;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;AAED;AACA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;;AAGD,EAAA,iBAAiB,EAAE;GAClB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,wBAAwB,CAAC;GAC3D;;;AAGD,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,QAAQ,GAAG;;AAE1E,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,QAAQ,GAAG;AAC1E;EACA,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA,EAAG;;AAEtE,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,QAAQ,GAAG;EAC1E;;CAED,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,CAAC;;AAEjE,CAAA,QAAQ,EAAE;EACT,SAAS,GAAG;AACX,GAAA,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;GACnE;EACD;;AAED,CAAA,OAAO,EAAE;AACR;AACC;AACA;;;AAGD,EAAA,cAAc,GAAG;AAChB;AACC;;GAED,IAAI,CAAC,KAAK,CAAC,aAAa,EAAA;GACxB;;AAED;AACC;AACA;;;AAGD,EAAA,cAAc,GAAG;AAChB;AACC;;GAED,IAAI,CAAC,KAAK,CAAC,aAAa,EAAA;GACxB;;AAED;AACC;AACA;;;AAGD,EAAA,YAAY,GAAG;AACd;;;GAGA,IAAI,CAAC,KAAK,CAAC,WAAW,EAAA;GACtB;;AAED;AACC;AACA;;;AAGD,EAAA,cAAc,GAAG;AAChB;AACC;;GAED,IAAI,CAAC,KAAK,CAAC,aAAa,EAAA;GACxB;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnMassActionBar.vue2.js","sources":["../../../../src/components/CnMassActionBar/CnMassActionBar.vue"],"sourcesContent":["<template>\n\t<NcActions\n\t\tv-if=\"count > 0\"\n\t\t:menuName=\"menuLabel\"\n\t\t:inline=\"0\">\n\t\t<template #icon>\n\t\t\t<TuneVariant :size=\"20\" />\n\t\t</template>\n\n\t\t<!-- Built-in: Import -->\n\t\t<NcActionButton\n\t\t\tv-if=\"showImport\"\n\t\t\t@click=\"emitMassImport\">\n\t\t\t<template #icon>\n\t\t\t\t<Import :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ importLabel }}\n\t\t</NcActionButton>\n\n\t\t<!-- Built-in: Export -->\n\t\t<NcActionButton\n\t\t\tv-if=\"showExport\"\n\t\t\t@click=\"emitMassExport\">\n\t\t\t<template #icon>\n\t\t\t\t<Export :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ exportLabel }}\n\t\t</NcActionButton>\n\n\t\t<!-- Built-in: Copy -->\n\t\t<NcActionButton\n\t\t\tv-if=\"showCopy\"\n\t\t\t@click=\"emitMassCopy\">\n\t\t\t<template #icon>\n\t\t\t\t<ContentCopy :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ copyLabel }}\n\t\t</NcActionButton>\n\n\t\t<!-- Built-in: Delete -->\n\t\t<NcActionButton\n\t\t\tv-if=\"showDelete\"\n\t\t\t@click=\"emitMassDelete\">\n\t\t\t<template #icon>\n\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ deleteLabel }}\n\t\t</NcActionButton>\n\n\t\t<!-- @slot actions Additional app-specific mass-action buttons. Slot scope: `{ count, selectedIds }`. -->\n\t\t<slot name=\"actions\" :count=\"count\" :selectedIds=\"selectedIds\" />\n\t</NcActions>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActions } from '@nextcloud/vue'\nimport ContentCopy from 'vue-material-design-icons/ContentCopy.vue'\nimport Export from 'vue-material-design-icons/Export.vue'\nimport Import from 'vue-material-design-icons/Import.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\nimport TuneVariant from 'vue-material-design-icons/TuneVariant.vue'\n\n/**\n * CnMassActionBar — Mass action dropdown button for selected items.\n *\n * Renders as a single \"Mass Actions (N)\" dropdown button with built-in\n * Import, Export, Copy, and Delete actions plus a slot for custom actions.\n * Place this in the header/action bar of your index page.\n *\n * Use the `#actions` scoped slot to add app-specific mass actions\n * (use `NcActionButton` for slot content).\n *\n * @event mass-import Emitted when the Import action is clicked. No payload.\n * @event mass-export Emitted when the Export action is clicked. No payload.\n * @event mass-copy Emitted when the Copy action is clicked. No payload.\n * @event mass-delete Emitted when the Delete action is clicked. No payload.\n * @slot actions Additional app-specific mass-action buttons. Slot scope: `{ count, selectedIds }`.\n */\nexport default {\n\tname: 'CnMassActionBar',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tTuneVariant,\n\t\tContentCopy,\n\t\tTrashCanOutline,\n\t\tImport,\n\t\tExport,\n\t},\n\n\tprops: {\n\t\t/** Array of selected item IDs */\n\t\tselectedIds: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Number of selected items */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Whether to show the built-in Import action */\n\t\tshowImport: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Whether to show the built-in Export action */\n\t\tshowExport: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Whether to show the built-in Copy action */\n\t\tshowCopy: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Whether to show the built-in Delete action */\n\t\tshowDelete: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Label template for the menu button. Use {count} for the count. */\n\t\tmenuLabelTemplate: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Mass actions ({count})'),\n\t\t},\n\n\t\t/** Label for the built-in Import mass-action button. */\n\t\timportLabel: { type: String, default: () => t('nextcloud-vue', 'Import') },\n\t\t/** Label for the built-in Export mass-action button. */\n\t\texportLabel: { type: String, default: () => t('nextcloud-vue', 'Export') },\n\t\t/** Label for the built-in Copy mass-action button. */\n\t\tcopyLabel: { type: String, default: () => t('nextcloud-vue', 'Copy') },\n\t\t/** Label for the built-in Delete mass-action button. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },\n\t},\n\n\temits: ['mass-copy', 'mass-delete', 'mass-export', 'mass-import'],\n\n\tcomputed: {\n\t\tmenuLabel() {\n\t\t\treturn this.menuLabelTemplate.replace('{count}', String(this.count))\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Emit the built-in Import click. No payload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitMassImport() {\n\t\t\t/**\n\t\t\t * @event mass-import Emitted when the Import action is clicked. No payload.\n\t\t\t */\n\t\t\tthis.$emit('mass-import')\n\t\t},\n\n\t\t/**\n\t\t * Emit the built-in Export click. No payload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitMassExport() {\n\t\t\t/**\n\t\t\t * @event mass-export Emitted when the Export action is clicked. No payload.\n\t\t\t */\n\t\t\tthis.$emit('mass-export')\n\t\t},\n\n\t\t/**\n\t\t * Emit the built-in Copy click. No payload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitMassCopy() {\n\t\t\t/**\n\t\t\t * @event mass-copy Emitted when the Copy action is clicked. No payload.\n\t\t\t */\n\t\t\tthis.$emit('mass-copy')\n\t\t},\n\n\t\t/**\n\t\t * Emit the built-in Delete click. No payload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\temitMassDelete() {\n\t\t\t/**\n\t\t\t * @event mass-delete Emitted when the Delete action is clicked. No payload.\n\t\t\t */\n\t\t\tthis.$emit('mass-delete')\n\t\t},\n\t},\n}\n</script>\n"],"names":["Import","t"],"mappings":";;;;;;;;AA+DA;;AAEC;;;;AAIA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAED,aAAe;CACd,IAAI,EAAE,iBAAiB;;CAEvB,UAAU,EAAE;AACX,EAAA,SAAS;AACT,EAAA,cAAc;EACd,WAAW;EACX,WAAW;AACX,EAAA,eAAe;AACf,UAAAA,UAAM;AACN,EAAA,MAAM;EACN;;AAED,CAAA,KAAK,EAAE;;EAEN,WAAW,EAAE;GACZ,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;;AAGD,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,CAAC;GACV;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;AAED;AACA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;AAED;EACA,UAAU,EAAE;GACX,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,IAAI;GACb;;;AAGD,EAAA,iBAAiB,EAAE;GAClB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,wBAAwB,CAAC;GAC3D;;;AAGD,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,QAAQ,GAAG;;AAE1E,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,QAAQ,GAAG;AAC1E;EACA,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA,EAAG;;AAEtE,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,QAAQ,GAAG;EAC1E;;CAED,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,CAAC;;AAEjE,CAAA,QAAQ,EAAE;EACT,SAAS,GAAG;AACX,GAAA,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;GACnE;EACD;;AAED,CAAA,OAAO,EAAE;AACR;AACC;AACA;;;AAGD,EAAA,cAAc,GAAG;AAChB;AACC;;GAED,IAAI,CAAC,KAAK,CAAC,aAAa,EAAA;GACxB;;AAED;AACC;AACA;;;AAGD,EAAA,cAAc,GAAG;AAChB;AACC;;GAED,IAAI,CAAC,KAAK,CAAC,aAAa,EAAA;GACxB;;AAED;AACC;AACA;;;AAGD,EAAA,YAAY,GAAG;AACd;;;GAGA,IAAI,CAAC,KAAK,CAAC,WAAW,EAAA;GACtB;;AAED;AACC;AACA;;;AAGD,EAAA,cAAc,GAAG;AAChB;AACC;;GAED,IAAI,CAAC,KAAK,CAAC,aAAa,EAAA;GACxB;EACD;AACF;;;;"}
@@ -1,7 +1,7 @@
1
1
  import { translate } from '@nextcloud/l10n';
2
2
  import { NcButton, NcNoteCard, NcLoadingIcon, NcCheckboxRadioSwitch } from '@nextcloud/vue';
3
3
  import ChevronDown from 'vue-material-design-icons/ChevronDown.vue';
4
- import Import from 'vue-material-design-icons/Import.vue';
4
+ import ImportIcon from 'vue-material-design-icons/Import.vue';
5
5
  import Upload from 'vue-material-design-icons/Upload.vue';
6
6
  import __sfc_main from '../CnDialog/CnDialog.vue.js';
7
7
  import { dialogBoardMixin } from '../../mixins/dialogBoard.js';
@@ -62,7 +62,7 @@ var script = {
62
62
  NcLoadingIcon,
63
63
  NcCheckboxRadioSwitch,
64
64
  Upload,
65
- ImportIcon: Import,
65
+ ImportIcon,
66
66
  ChevronDown,
67
67
  },
68
68
 
@@ -1 +1 @@
1
- {"version":3,"file":"CnMassImportDialog.vue2.js","sources":["../../../../src/components/CnMassImportDialog/CnMassImportDialog.vue"],"sourcesContent":["<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:noClose=\"loading\"\n\t\t@closing=\"$emit('close')\">\n\t\t<!-- Success/error messages -->\n\t\t<NcNoteCard v-if=\"result && result.success && !hasErrors\" type=\"success\">\n\t\t\t{{ successText }}\n\t\t</NcNoteCard>\n\t\t<NcNoteCard v-if=\"result && result.success && hasErrors\" type=\"warning\">\n\t\t\t{{ partialSuccessText }}\n\t\t</NcNoteCard>\n\t\t<NcNoteCard v-if=\"result && result.error\" type=\"error\">\n\t\t\t{{ result.error }}\n\t\t</NcNoteCard>\n\n\t\t<!-- Results summary table -->\n\t\t<div v-if=\"result && result.summary\"\n\t\t\tclass=\"cn-mass-import__results\"\n\t\t\tdata-testid=\"cn-modal\"\n\t\t\tdata-testid-modal=\"cn-mass-import-dialog\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<h3>{{ summaryTitle }}</h3>\n\t\t\t<table class=\"cn-mass-import__summary-table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th>{{ sheetLabel }}</th>\n\t\t\t\t\t\t<th>{{ foundLabel }}</th>\n\t\t\t\t\t\t<th>{{ createdLabel }}</th>\n\t\t\t\t\t\t<th>{{ updatedLabel }}</th>\n\t\t\t\t\t\t<th>{{ unchangedLabel }}</th>\n\t\t\t\t\t\t<th>{{ errorsLabel }}</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<template v-for=\"(sheet, key) in result.summary\" :key=\"key\">\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__sheet-name\">\n\t\t\t\t\t\t\t\t{{ key }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--found\">\n\t\t\t\t\t\t\t\t{{ sheet.found || 0 }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--created\">\n\t\t\t\t\t\t\t\t{{ getCount(sheet.created) }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--updated\">\n\t\t\t\t\t\t\t\t{{ getCount(sheet.updated) }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--unchanged\">\n\t\t\t\t\t\t\t\t{{ getCount(sheet.unchanged) }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--errors\">\n\t\t\t\t\t\t\t\t<span>{{ getCount(sheet.errors) }}</span>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tv-if=\"getCount(sheet.errors) > 0\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-mass-import__expand\"\n\t\t\t\t\t\t\t\t\t:class=\"{ 'cn-mass-import__expand--open': expandedErrors[key] }\"\n\t\t\t\t\t\t\t\t\t@click=\"toggleErrors(key)\">\n\t\t\t\t\t\t\t\t\t<ChevronDown :size=\"16\" />\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t<!-- Error details row -->\n\t\t\t\t\t\t<tr\n\t\t\t\t\t\t\tv-if=\"expandedErrors[key] && sheet.errors && sheet.errors.length\"\n\t\t\t\t\t\t\t:key=\"`${key}-errors`\"\n\t\t\t\t\t\t\tclass=\"cn-mass-import__error-row\">\n\t\t\t\t\t\t\t<td colspan=\"6\">\n\t\t\t\t\t\t\t\t<table class=\"cn-mass-import__error-table\">\n\t\t\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t<th>Row</th>\n\t\t\t\t\t\t\t\t\t\t\t<th>Type</th>\n\t\t\t\t\t\t\t\t\t\t\t<th>Message</th>\n\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t\t<tr v-for=\"(err, idx) in sheet.errors\" :key=\"idx\">\n\t\t\t\t\t\t\t\t\t\t\t<td>{{ err.row || '—' }}</td>\n\t\t\t\t\t\t\t\t\t\t\t<td>{{ err.type || 'Unknown' }}</td>\n\t\t\t\t\t\t\t\t\t\t\t<td>{{ err.error || err.message || '—' }}</td>\n\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</template>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\n\t\t<!-- Upload form -->\n\t\t<div v-if=\"!result\"\n\t\t\tclass=\"cn-mass-import__form\"\n\t\t\tdata-testid=\"cn-modal\"\n\t\t\tdata-testid-modal=\"cn-mass-import-dialog\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<input\n\t\t\t\tref=\"fileInput\"\n\t\t\t\ttype=\"file\"\n\t\t\t\t:accept=\"acceptedTypes\"\n\t\t\t\tstyle=\"display: none\"\n\t\t\t\t@change=\"handleFileSelect\">\n\n\t\t\t<div class=\"cn-mass-import__file-row\">\n\t\t\t\t<NcButton @click=\"$refs.fileInput.click()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Upload :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ selectFileLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<div v-if=\"selectedFile\" class=\"cn-mass-import__file-info\">\n\t\t\t\t\t<span class=\"cn-mass-import__file-name\">{{ selectedFile.name }}</span>\n\t\t\t\t\t<span class=\"cn-mass-import__file-size\">({{ formatFileSize(selectedFile.size) }})</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Additional fields slot (e.g., register/schema selectors) -->\n\t\t\t<slot name=\"fields\" :file=\"selectedFile\" />\n\n\t\t\t<!-- File type help text -->\n\t\t\t<div v-if=\"fileTypeHelp\" class=\"cn-mass-import__help\">\n\t\t\t\t<p><strong>{{ supportedFormatsLabel }}</strong></p>\n\t\t\t\t<ul>\n\t\t\t\t\t<li v-for=\"(help, idx) in fileTypeHelp\" :key=\"idx\">\n\t\t\t\t\t\t<strong>{{ help.label }}</strong> — {{ help.description }}\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\n\t\t\t<!-- Import options -->\n\t\t\t<div v-if=\"options.length > 0\" class=\"cn-mass-import__options\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-for=\"opt in options\"\n\t\t\t\t\t:key=\"opt.key\"\n\t\t\t\t\t:modelValue=\"optionValues[opt.key]\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setOption(opt.key, $event)\">\n\t\t\t\t\t{{ opt.label }}\n\t\t\t\t\t<template v-if=\"opt.description\" #helper>\n\t\t\t\t\t\t{{ opt.description }}\n\t\t\t\t\t</template>\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Loading indicator -->\n\t\t<NcNoteCard v-if=\"loading\" type=\"info\">\n\t\t\t{{ loadingText }}\n\t\t</NcNoteCard>\n\n\t\t<template #actions>\n\t\t\t<NcButton @click=\"$emit('close')\">\n\t\t\t\t{{ result ? closeLabel : cancelLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"!result\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"loading || !selectedFile || !canSubmit\"\n\t\t\t\t@click=\"executeImport\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t<ImportIcon v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ confirmLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport ChevronDown from 'vue-material-design-icons/ChevronDown.vue'\nimport ImportIcon from 'vue-material-design-icons/Import.vue'\nimport Upload from 'vue-material-design-icons/Upload.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport { dialogBoardMixin } from '../../mixins/dialogBoard.js'\n\n/**\n * CnMassImportDialog — File import dialog with options and results summary.\n *\n * Supports file upload (JSON, Excel, CSV), configurable import options via\n * toggle switches, and a results summary table showing created/updated/error\n * counts per sheet. Based on the OpenRegister ImportRegister pattern.\n *\n * The dialog does NOT perform the import itself — it emits a `confirm` event\n * with the file and options. The parent handles the API call and calls\n * `setResult()` via a ref.\n *\n * ```vue\n * <CnMassImportDialog\n * v-if=\"showImportDialog\"\n * ref=\"importDialog\"\n * :options=\"importOptions\"\n * @confirm=\"onImportConfirm\"\n * @close=\"showImportDialog = false\">\n * <template #fields=\"{ file }\">\n * <NcSelect v-if=\"file\" :options=\"schemas\" @update:model-value=\"selectedSchema = $event\" />\n * </template>\n * </CnMassImportDialog>\n * ```\n *\n * // In data:\n * importOptions: [\n * { key: 'validation', label: 'Enable validation', description: 'Validate against schema', default: true },\n * { key: 'publish', label: 'Auto-publish', description: 'Set published date', default: false },\n * ]\n *\n * // In methods:\n * async onImportConfirm({ file, options }) {\n * try {\n * const formData = new FormData()\n * formData.append('file', file)\n * const result = await axios.post('/api/import', formData, { params: options })\n * this.$refs.importDialog.setResult({ success: true, summary: result.data.summary })\n * } catch (e) {\n * this.$refs.importDialog.setResult({ error: e.message })\n * }\n * }\n *\n * @event confirm Emitted when the user clicks the import button. Payload: `{ file: File, options: Record<string, boolean> }`.\n * @event close Emitted when the dialog should be closed (cancel, close button, or auto-close after success).\n * @slot fields Custom form fields rendered between the file picker and the options list. Receives `{ file }` as the slot scope so the parent can react to file selection.\n */\nexport default {\n\tname: 'CnMassImportDialog',\n\n\tcomponents: {\n\t\tCnDialog,\n\t\tNcButton,\n\t\tNcNoteCard,\n\t\tNcLoadingIcon,\n\t\tNcCheckboxRadioSwitch,\n\t\tUpload,\n\t\tImportIcon,\n\t\tChevronDown,\n\t},\n\n\tmixins: [dialogBoardMixin],\n\n\tprops: {\n\t\t/** Dialog title */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Import data'),\n\t\t},\n\n\t\t/** Accepted file types (input accept attribute) */\n\t\tacceptedTypes: {\n\t\t\ttype: String,\n\t\t\tdefault: '.json,.xlsx,.xls,.csv',\n\t\t},\n\n\t\t/** Import option definitions */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** File type help entries */\n\t\tfileTypeHelp: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [\n\t\t\t\t{ label: 'JSON', description: 'Configuration and objects.' },\n\t\t\t\t{ label: 'Excel (.xlsx, .xls)', description: 'Tabular objects data with multiple sheets.' },\n\t\t\t\t{ label: 'CSV', description: 'Single table of objects data.' },\n\t\t\t],\n\t\t},\n\n\t\t/** Whether the form is ready to submit (parent can control via slot logic) */\n\t\tcanSubmit: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Success text when all rows imported without errors */\n\t\tsuccessText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Import completed successfully!'),\n\t\t},\n\n\t\t/** Text when import partially succeeded */\n\t\tpartialSuccessText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Import completed with errors. Check the details below.'),\n\t\t},\n\n\t\t/** Text shown while importing */\n\t\tloadingText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Importing data — this may take a moment for large files…'),\n\t\t},\n\n\t\t/** Heading rendered above the per-sheet results table. */\n\t\tsummaryTitle: { type: String, default: () => t('nextcloud-vue', 'Import summary') },\n\t\t/** Label for the \"Supported file types\" intro line. */\n\t\tsupportedFormatsLabel: { type: String, default: () => t('nextcloud-vue', 'Supported file types:') },\n\t\t/** Label for the file-picker trigger button. */\n\t\tselectFileLabel: { type: String, default: () => t('nextcloud-vue', 'Select file') },\n\t\t/** Label for the cancel button (visible before the import runs). */\n\t\tcancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },\n\t\t/** Label for the close button (visible after import completes). */\n\t\tcloseLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },\n\t\t/** Label for the primary confirm button that triggers the import. */\n\t\tconfirmLabel: { type: String, default: () => t('nextcloud-vue', 'Import') },\n\t\t/** Column header for the per-sheet results row label. */\n\t\tsheetLabel: { type: String, default: () => t('nextcloud-vue', 'Sheet') },\n\t\t/** Column header for the \"rows found\" summary count. */\n\t\tfoundLabel: { type: String, default: () => t('nextcloud-vue', 'Found') },\n\t\t/** Column header for the \"rows created\" summary count. */\n\t\tcreatedLabel: { type: String, default: () => t('nextcloud-vue', 'Created') },\n\t\t/** Column header for the \"rows updated\" summary count. */\n\t\tupdatedLabel: { type: String, default: () => t('nextcloud-vue', 'Updated') },\n\t\t/** Column header for the \"rows unchanged\" summary count. */\n\t\tunchangedLabel: { type: String, default: () => t('nextcloud-vue', 'Unchanged') },\n\t\t/** Column header / collapsible toggle for the per-sheet errors list. */\n\t\terrorsLabel: { type: String, default: () => t('nextcloud-vue', 'Errors') },\n\t},\n\n\temits: ['close', 'confirm'],\n\n\tdata() {\n\t\tconst optionValues = {}\n\t\tthis.options.forEach((opt) => {\n\t\t\toptionValues[opt.key] = opt.default !== undefined ? opt.default : false\n\t\t})\n\t\treturn {\n\t\t\tselectedFile: null,\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\toptionValues,\n\t\t\texpandedErrors: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\thasErrors() {\n\t\t\tif (!this.result || !this.result.summary) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Object.values(this.result.summary).some((sheet) => sheet.errors && sheet.errors.length > 0)\n\t\t},\n\t},\n\n\tmethods: {\n\t\thandleFileSelect(event) {\n\t\t\tconst file = event.target.files[0]\n\t\t\tthis.selectedFile = file || null\n\t\t},\n\n\t\tsetOption(key, value) {\n\t\t\tthis.optionValues[key] = value\n\t\t},\n\n\t\tformatFileSize(bytes) {\n\t\t\tif (bytes === 0) {\n\t\t\t\treturn '0 B'\n\t\t\t}\n\t\t\tconst k = 1024\n\t\t\tconst sizes = ['B', 'KB', 'MB', 'GB']\n\t\t\tconst i = Math.floor(Math.log(bytes) / Math.log(k))\n\t\t\treturn parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]\n\t\t},\n\n\t\tgetCount(val) {\n\t\t\tif (Array.isArray(val)) {\n\t\t\t\treturn val.length\n\t\t\t}\n\t\t\tif (typeof val === 'number') {\n\t\t\t\treturn val\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\ttoggleErrors(key) {\n\t\t\tthis.expandedErrors[key] = !this.expandedErrors[key]\n\t\t},\n\n\t\texecuteImport() {\n\t\t\tthis.loading = true\n\t\t\tthis.$emit('confirm', {\n\t\t\t\tfile: this.selectedFile,\n\t\t\t\toptions: { ...this.optionValues },\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Set the result of the import operation.\n\t\t *\n\t\t * @param {{ success?: boolean, error?: string, summary?: object }} resultData - Result data to pass to the dialog\n\t\t * @public\n\t\t */\n\t\tsetResult(resultData) {\n\t\t\tthis.loading = false\n\t\t\tthis.result = resultData\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-mass-import__form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-mass-import__file-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n\n.cn-mass-import__file-info {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-mass-import__file-name {\n\tfont-weight: 500;\n}\n\n.cn-mass-import__file-size {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-mass-import__help {\n\tpadding: 12px;\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-mass-import__help p {\n\tmargin: 0 0 8px;\n}\n\n.cn-mass-import__help ul {\n\tmargin: 0;\n\tpadding-left: 20px;\n}\n\n.cn-mass-import__options {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n/* Summary table */\n.cn-mass-import__results h3 {\n\tmargin: 0 0 12px;\n\tfont-size: 1.1rem;\n}\n\n.cn-mass-import__summary-table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.cn-mass-import__summary-table th,\n.cn-mass-import__summary-table td {\n\tpadding: 8px 12px;\n\ttext-align: left;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-mass-import__summary-table th {\n\tbackground: var(--color-background-dark);\n\tfont-weight: 600;\n\tfont-size: 0.85rem;\n\ttext-transform: uppercase;\n}\n\n.cn-mass-import__sheet-name {\n\tfont-weight: 600;\n}\n\n.cn-mass-import__stat {\n\ttext-align: center;\n\tfont-weight: 600;\n}\n\n.cn-mass-import__stat--found { color: var(--color-primary-element); }\n\n.cn-mass-import__stat--created { color: var(--color-success); }\n\n.cn-mass-import__stat--updated { color: var(--color-warning); }\n\n.cn-mass-import__stat--unchanged { color: var(--color-text-maxcontrast); }\n\n.cn-mass-import__stat--errors { color: var(--color-error); }\n\n.cn-mass-import__expand {\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n\tpadding: 2px;\n\tborder-radius: var(--border-radius-small, 4px);\n\tcolor: var(--color-error);\n\ttransition: transform 0.2s ease;\n\tvertical-align: middle;\n\tmargin-left: 4px;\n}\n\n.cn-mass-import__expand:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-mass-import__expand--open {\n\ttransform: rotate(180deg);\n}\n\n/* Error details */\n.cn-mass-import__error-row td {\n\tpadding: 8px 12px;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-mass-import__error-table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 0.9em;\n}\n\n.cn-mass-import__error-table th,\n.cn-mass-import__error-table td {\n\tpadding: 6px 10px;\n\ttext-align: left;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-mass-import__error-table th {\n\tbackground: var(--color-background-dark);\n\tfont-weight: 600;\n\tfont-size: 0.85rem;\n}\n</style>\n"],"names":["CnDialog","ImportIcon","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnMassImportDialog.vue2.js","sources":["../../../../src/components/CnMassImportDialog/CnMassImportDialog.vue"],"sourcesContent":["<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:noClose=\"loading\"\n\t\t@closing=\"$emit('close')\">\n\t\t<!-- Success/error messages -->\n\t\t<NcNoteCard v-if=\"result && result.success && !hasErrors\" type=\"success\">\n\t\t\t{{ successText }}\n\t\t</NcNoteCard>\n\t\t<NcNoteCard v-if=\"result && result.success && hasErrors\" type=\"warning\">\n\t\t\t{{ partialSuccessText }}\n\t\t</NcNoteCard>\n\t\t<NcNoteCard v-if=\"result && result.error\" type=\"error\">\n\t\t\t{{ result.error }}\n\t\t</NcNoteCard>\n\n\t\t<!-- Results summary table -->\n\t\t<div v-if=\"result && result.summary\"\n\t\t\tclass=\"cn-mass-import__results\"\n\t\t\tdata-testid=\"cn-modal\"\n\t\t\tdata-testid-modal=\"cn-mass-import-dialog\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<h3>{{ summaryTitle }}</h3>\n\t\t\t<table class=\"cn-mass-import__summary-table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th>{{ sheetLabel }}</th>\n\t\t\t\t\t\t<th>{{ foundLabel }}</th>\n\t\t\t\t\t\t<th>{{ createdLabel }}</th>\n\t\t\t\t\t\t<th>{{ updatedLabel }}</th>\n\t\t\t\t\t\t<th>{{ unchangedLabel }}</th>\n\t\t\t\t\t\t<th>{{ errorsLabel }}</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t<template v-for=\"(sheet, key) in result.summary\" :key=\"key\">\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__sheet-name\">\n\t\t\t\t\t\t\t\t{{ key }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--found\">\n\t\t\t\t\t\t\t\t{{ sheet.found || 0 }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--created\">\n\t\t\t\t\t\t\t\t{{ getCount(sheet.created) }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--updated\">\n\t\t\t\t\t\t\t\t{{ getCount(sheet.updated) }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--unchanged\">\n\t\t\t\t\t\t\t\t{{ getCount(sheet.unchanged) }}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td class=\"cn-mass-import__stat cn-mass-import__stat--errors\">\n\t\t\t\t\t\t\t\t<span>{{ getCount(sheet.errors) }}</span>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tv-if=\"getCount(sheet.errors) > 0\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-mass-import__expand\"\n\t\t\t\t\t\t\t\t\t:class=\"{ 'cn-mass-import__expand--open': expandedErrors[key] }\"\n\t\t\t\t\t\t\t\t\t@click=\"toggleErrors(key)\">\n\t\t\t\t\t\t\t\t\t<ChevronDown :size=\"16\" />\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t<!-- Error details row -->\n\t\t\t\t\t\t<tr\n\t\t\t\t\t\t\tv-if=\"expandedErrors[key] && sheet.errors && sheet.errors.length\"\n\t\t\t\t\t\t\t:key=\"`${key}-errors`\"\n\t\t\t\t\t\t\tclass=\"cn-mass-import__error-row\">\n\t\t\t\t\t\t\t<td colspan=\"6\">\n\t\t\t\t\t\t\t\t<table class=\"cn-mass-import__error-table\">\n\t\t\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t\t<th>Row</th>\n\t\t\t\t\t\t\t\t\t\t\t<th>Type</th>\n\t\t\t\t\t\t\t\t\t\t\t<th>Message</th>\n\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t\t<tr v-for=\"(err, idx) in sheet.errors\" :key=\"idx\">\n\t\t\t\t\t\t\t\t\t\t\t<td>{{ err.row || '—' }}</td>\n\t\t\t\t\t\t\t\t\t\t\t<td>{{ err.type || 'Unknown' }}</td>\n\t\t\t\t\t\t\t\t\t\t\t<td>{{ err.error || err.message || '—' }}</td>\n\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</template>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\n\t\t<!-- Upload form -->\n\t\t<div v-if=\"!result\"\n\t\t\tclass=\"cn-mass-import__form\"\n\t\t\tdata-testid=\"cn-modal\"\n\t\t\tdata-testid-modal=\"cn-mass-import-dialog\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<input\n\t\t\t\tref=\"fileInput\"\n\t\t\t\ttype=\"file\"\n\t\t\t\t:accept=\"acceptedTypes\"\n\t\t\t\tstyle=\"display: none\"\n\t\t\t\t@change=\"handleFileSelect\">\n\n\t\t\t<div class=\"cn-mass-import__file-row\">\n\t\t\t\t<NcButton @click=\"$refs.fileInput.click()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Upload :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ selectFileLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<div v-if=\"selectedFile\" class=\"cn-mass-import__file-info\">\n\t\t\t\t\t<span class=\"cn-mass-import__file-name\">{{ selectedFile.name }}</span>\n\t\t\t\t\t<span class=\"cn-mass-import__file-size\">({{ formatFileSize(selectedFile.size) }})</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Additional fields slot (e.g., register/schema selectors) -->\n\t\t\t<slot name=\"fields\" :file=\"selectedFile\" />\n\n\t\t\t<!-- File type help text -->\n\t\t\t<div v-if=\"fileTypeHelp\" class=\"cn-mass-import__help\">\n\t\t\t\t<p><strong>{{ supportedFormatsLabel }}</strong></p>\n\t\t\t\t<ul>\n\t\t\t\t\t<li v-for=\"(help, idx) in fileTypeHelp\" :key=\"idx\">\n\t\t\t\t\t\t<strong>{{ help.label }}</strong> — {{ help.description }}\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\n\t\t\t<!-- Import options -->\n\t\t\t<div v-if=\"options.length > 0\" class=\"cn-mass-import__options\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-for=\"opt in options\"\n\t\t\t\t\t:key=\"opt.key\"\n\t\t\t\t\t:modelValue=\"optionValues[opt.key]\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setOption(opt.key, $event)\">\n\t\t\t\t\t{{ opt.label }}\n\t\t\t\t\t<template v-if=\"opt.description\" #helper>\n\t\t\t\t\t\t{{ opt.description }}\n\t\t\t\t\t</template>\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Loading indicator -->\n\t\t<NcNoteCard v-if=\"loading\" type=\"info\">\n\t\t\t{{ loadingText }}\n\t\t</NcNoteCard>\n\n\t\t<template #actions>\n\t\t\t<NcButton @click=\"$emit('close')\">\n\t\t\t\t{{ result ? closeLabel : cancelLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"!result\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"loading || !selectedFile || !canSubmit\"\n\t\t\t\t@click=\"executeImport\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t<ImportIcon v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ confirmLabel }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport ChevronDown from 'vue-material-design-icons/ChevronDown.vue'\nimport ImportIcon from 'vue-material-design-icons/Import.vue'\nimport Upload from 'vue-material-design-icons/Upload.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport { dialogBoardMixin } from '../../mixins/dialogBoard.js'\n\n/**\n * CnMassImportDialog — File import dialog with options and results summary.\n *\n * Supports file upload (JSON, Excel, CSV), configurable import options via\n * toggle switches, and a results summary table showing created/updated/error\n * counts per sheet. Based on the OpenRegister ImportRegister pattern.\n *\n * The dialog does NOT perform the import itself — it emits a `confirm` event\n * with the file and options. The parent handles the API call and calls\n * `setResult()` via a ref.\n *\n * ```vue\n * <CnMassImportDialog\n * v-if=\"showImportDialog\"\n * ref=\"importDialog\"\n * :options=\"importOptions\"\n * @confirm=\"onImportConfirm\"\n * @close=\"showImportDialog = false\">\n * <template #fields=\"{ file }\">\n * <NcSelect v-if=\"file\" :options=\"schemas\" @update:model-value=\"selectedSchema = $event\" />\n * </template>\n * </CnMassImportDialog>\n * ```\n *\n * // In data:\n * importOptions: [\n * { key: 'validation', label: 'Enable validation', description: 'Validate against schema', default: true },\n * { key: 'publish', label: 'Auto-publish', description: 'Set published date', default: false },\n * ]\n *\n * // In methods:\n * async onImportConfirm({ file, options }) {\n * try {\n * const formData = new FormData()\n * formData.append('file', file)\n * const result = await axios.post('/api/import', formData, { params: options })\n * this.$refs.importDialog.setResult({ success: true, summary: result.data.summary })\n * } catch (e) {\n * this.$refs.importDialog.setResult({ error: e.message })\n * }\n * }\n *\n * @event confirm Emitted when the user clicks the import button. Payload: `{ file: File, options: Record<string, boolean> }`.\n * @event close Emitted when the dialog should be closed (cancel, close button, or auto-close after success).\n * @slot fields Custom form fields rendered between the file picker and the options list. Receives `{ file }` as the slot scope so the parent can react to file selection.\n */\nexport default {\n\tname: 'CnMassImportDialog',\n\n\tcomponents: {\n\t\tCnDialog,\n\t\tNcButton,\n\t\tNcNoteCard,\n\t\tNcLoadingIcon,\n\t\tNcCheckboxRadioSwitch,\n\t\tUpload,\n\t\tImportIcon,\n\t\tChevronDown,\n\t},\n\n\tmixins: [dialogBoardMixin],\n\n\tprops: {\n\t\t/** Dialog title */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Import data'),\n\t\t},\n\n\t\t/** Accepted file types (input accept attribute) */\n\t\tacceptedTypes: {\n\t\t\ttype: String,\n\t\t\tdefault: '.json,.xlsx,.xls,.csv',\n\t\t},\n\n\t\t/** Import option definitions */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** File type help entries */\n\t\tfileTypeHelp: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [\n\t\t\t\t{ label: 'JSON', description: 'Configuration and objects.' },\n\t\t\t\t{ label: 'Excel (.xlsx, .xls)', description: 'Tabular objects data with multiple sheets.' },\n\t\t\t\t{ label: 'CSV', description: 'Single table of objects data.' },\n\t\t\t],\n\t\t},\n\n\t\t/** Whether the form is ready to submit (parent can control via slot logic) */\n\t\tcanSubmit: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Success text when all rows imported without errors */\n\t\tsuccessText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Import completed successfully!'),\n\t\t},\n\n\t\t/** Text when import partially succeeded */\n\t\tpartialSuccessText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Import completed with errors. Check the details below.'),\n\t\t},\n\n\t\t/** Text shown while importing */\n\t\tloadingText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Importing data — this may take a moment for large files…'),\n\t\t},\n\n\t\t/** Heading rendered above the per-sheet results table. */\n\t\tsummaryTitle: { type: String, default: () => t('nextcloud-vue', 'Import summary') },\n\t\t/** Label for the \"Supported file types\" intro line. */\n\t\tsupportedFormatsLabel: { type: String, default: () => t('nextcloud-vue', 'Supported file types:') },\n\t\t/** Label for the file-picker trigger button. */\n\t\tselectFileLabel: { type: String, default: () => t('nextcloud-vue', 'Select file') },\n\t\t/** Label for the cancel button (visible before the import runs). */\n\t\tcancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },\n\t\t/** Label for the close button (visible after import completes). */\n\t\tcloseLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },\n\t\t/** Label for the primary confirm button that triggers the import. */\n\t\tconfirmLabel: { type: String, default: () => t('nextcloud-vue', 'Import') },\n\t\t/** Column header for the per-sheet results row label. */\n\t\tsheetLabel: { type: String, default: () => t('nextcloud-vue', 'Sheet') },\n\t\t/** Column header for the \"rows found\" summary count. */\n\t\tfoundLabel: { type: String, default: () => t('nextcloud-vue', 'Found') },\n\t\t/** Column header for the \"rows created\" summary count. */\n\t\tcreatedLabel: { type: String, default: () => t('nextcloud-vue', 'Created') },\n\t\t/** Column header for the \"rows updated\" summary count. */\n\t\tupdatedLabel: { type: String, default: () => t('nextcloud-vue', 'Updated') },\n\t\t/** Column header for the \"rows unchanged\" summary count. */\n\t\tunchangedLabel: { type: String, default: () => t('nextcloud-vue', 'Unchanged') },\n\t\t/** Column header / collapsible toggle for the per-sheet errors list. */\n\t\terrorsLabel: { type: String, default: () => t('nextcloud-vue', 'Errors') },\n\t},\n\n\temits: ['close', 'confirm'],\n\n\tdata() {\n\t\tconst optionValues = {}\n\t\tthis.options.forEach((opt) => {\n\t\t\toptionValues[opt.key] = opt.default !== undefined ? opt.default : false\n\t\t})\n\t\treturn {\n\t\t\tselectedFile: null,\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\toptionValues,\n\t\t\texpandedErrors: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\thasErrors() {\n\t\t\tif (!this.result || !this.result.summary) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Object.values(this.result.summary).some((sheet) => sheet.errors && sheet.errors.length > 0)\n\t\t},\n\t},\n\n\tmethods: {\n\t\thandleFileSelect(event) {\n\t\t\tconst file = event.target.files[0]\n\t\t\tthis.selectedFile = file || null\n\t\t},\n\n\t\tsetOption(key, value) {\n\t\t\tthis.optionValues[key] = value\n\t\t},\n\n\t\tformatFileSize(bytes) {\n\t\t\tif (bytes === 0) {\n\t\t\t\treturn '0 B'\n\t\t\t}\n\t\t\tconst k = 1024\n\t\t\tconst sizes = ['B', 'KB', 'MB', 'GB']\n\t\t\tconst i = Math.floor(Math.log(bytes) / Math.log(k))\n\t\t\treturn parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]\n\t\t},\n\n\t\tgetCount(val) {\n\t\t\tif (Array.isArray(val)) {\n\t\t\t\treturn val.length\n\t\t\t}\n\t\t\tif (typeof val === 'number') {\n\t\t\t\treturn val\n\t\t\t}\n\t\t\treturn 0\n\t\t},\n\n\t\ttoggleErrors(key) {\n\t\t\tthis.expandedErrors[key] = !this.expandedErrors[key]\n\t\t},\n\n\t\texecuteImport() {\n\t\t\tthis.loading = true\n\t\t\tthis.$emit('confirm', {\n\t\t\t\tfile: this.selectedFile,\n\t\t\t\toptions: { ...this.optionValues },\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Set the result of the import operation.\n\t\t *\n\t\t * @param {{ success?: boolean, error?: string, summary?: object }} resultData - Result data to pass to the dialog\n\t\t * @public\n\t\t */\n\t\tsetResult(resultData) {\n\t\t\tthis.loading = false\n\t\t\tthis.result = resultData\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-mass-import__form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-mass-import__file-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n\n.cn-mass-import__file-info {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-mass-import__file-name {\n\tfont-weight: 500;\n}\n\n.cn-mass-import__file-size {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-mass-import__help {\n\tpadding: 12px;\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-mass-import__help p {\n\tmargin: 0 0 8px;\n}\n\n.cn-mass-import__help ul {\n\tmargin: 0;\n\tpadding-left: 20px;\n}\n\n.cn-mass-import__options {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n/* Summary table */\n.cn-mass-import__results h3 {\n\tmargin: 0 0 12px;\n\tfont-size: 1.1rem;\n}\n\n.cn-mass-import__summary-table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tborder-radius: var(--border-radius);\n\toverflow: hidden;\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.cn-mass-import__summary-table th,\n.cn-mass-import__summary-table td {\n\tpadding: 8px 12px;\n\ttext-align: left;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-mass-import__summary-table th {\n\tbackground: var(--color-background-dark);\n\tfont-weight: 600;\n\tfont-size: 0.85rem;\n\ttext-transform: uppercase;\n}\n\n.cn-mass-import__sheet-name {\n\tfont-weight: 600;\n}\n\n.cn-mass-import__stat {\n\ttext-align: center;\n\tfont-weight: 600;\n}\n\n.cn-mass-import__stat--found { color: var(--color-primary-element); }\n\n.cn-mass-import__stat--created { color: var(--color-success); }\n\n.cn-mass-import__stat--updated { color: var(--color-warning); }\n\n.cn-mass-import__stat--unchanged { color: var(--color-text-maxcontrast); }\n\n.cn-mass-import__stat--errors { color: var(--color-error); }\n\n.cn-mass-import__expand {\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n\tpadding: 2px;\n\tborder-radius: var(--border-radius-small, 4px);\n\tcolor: var(--color-error);\n\ttransition: transform 0.2s ease;\n\tvertical-align: middle;\n\tmargin-left: 4px;\n}\n\n.cn-mass-import__expand:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-mass-import__expand--open {\n\ttransform: rotate(180deg);\n}\n\n/* Error details */\n.cn-mass-import__error-row td {\n\tpadding: 8px 12px;\n\tbackground: var(--color-background-hover);\n}\n\n.cn-mass-import__error-table {\n\twidth: 100%;\n\tborder-collapse: collapse;\n\tfont-size: 0.9em;\n}\n\n.cn-mass-import__error-table th,\n.cn-mass-import__error-table td {\n\tpadding: 6px 10px;\n\ttext-align: left;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-mass-import__error-table th {\n\tbackground: var(--color-background-dark);\n\tfont-weight: 600;\n\tfont-size: 0.85rem;\n}\n</style>\n"],"names":["CnDialog","t"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnObjectDataWidget.vue2.js';
2
2
  import { render } from './CnObjectDataWidget.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-59ab12e6";
5
+ script.__scopeId = "data-v-76edf75d";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -16,6 +16,7 @@ import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js';
16
16
  import { fieldsFromSchema, formatValue } from '../../utils/schema.js';
17
17
  import { schemaRefSlug } from '../../utils/schemaRefSlug.js';
18
18
  import { searchNextcloudUsers, resolveNextcloudUser } from '../../utils/userAutocomplete.js';
19
+ import { isLocallyDecidableVisibleWhen, evaluateVisibleWhenLocal } from '../../utils/visibleWhen.js';
19
20
  import __sfc_main from '../CnIcon/CnIcon.vue.js';
20
21
  import __sfc_main$3 from '../CnObjectMetadataModal/CnObjectMetadataModal.vue.js';
21
22
  import __sfc_main$2 from '../CnWidgetWrapper/CnWidgetWrapper.vue.js';
@@ -538,13 +539,18 @@ var script = {
538
539
  gridRow: (this.resolvedOverrides[field.key] && this.resolvedOverrides[field.key].gridRow) || 1,
539
540
  }));
540
541
 
542
+ // Per-field condition: `overrides[key].visibleWhen` shows a field only
543
+ // for the objects it belongs to, such as a person's preferred name on
544
+ // a client that can also be an organisation.
545
+ const shown = withSpans.filter((field) => this.isVisibleByCondition(field));
546
+
541
547
  if (!this.hideEmpty) {
542
- return withSpans
548
+ return shown
543
549
  }
544
550
 
545
551
  // Keep a field the user is currently editing or has pending changes for,
546
552
  // otherwise it would vanish mid-edit the moment its value is cleared.
547
- return withSpans.filter((field) => field.key === this.editingField
553
+ return shown.filter((field) => field.key === this.editingField
548
554
  || field.key in this.dirtyFields
549
555
  || !this.isEmptyValue((this.objectData || {})[field.key]))
550
556
  },
@@ -1308,6 +1314,29 @@ var script = {
1308
1314
  return !field.readOnly
1309
1315
  },
1310
1316
 
1317
+ /**
1318
+ * Evaluate a field's `visibleWhen` override against the object.
1319
+ *
1320
+ * The rule is the shared visibility predicate in its LOCAL mode
1321
+ * (`{ field, op, value }`, `all` / `any`, `appInstalled`), evaluated by
1322
+ * `evaluateVisibleWhenLocal` against the object with its unsaved
1323
+ * changes, so a field appears or goes the moment the field it depends
1324
+ * on changes. A rule that needs a request (`endpoint`, `source`) cannot
1325
+ * be decided here and is ignored: the field shows, as it did before
1326
+ * the rule existed.
1327
+ *
1328
+ * @param {object} field - Resolved field definition.
1329
+ * @return {boolean} True when the field is shown.
1330
+ */
1331
+ isVisibleByCondition(field) {
1332
+ const override = this.resolvedOverrides[field.key];
1333
+ const rule = override ? override.visibleWhen : undefined;
1334
+ if (rule === undefined || rule === null || !isLocallyDecidableVisibleWhen(rule)) {
1335
+ return true
1336
+ }
1337
+ return evaluateVisibleWhenLocal(rule, { ...(this.objectData || {}), ...this.dirtyFields })
1338
+ },
1339
+
1311
1340
  /**
1312
1341
  * Evaluate a field's conditional read-only rule against the object data.
1313
1342
  *