@conduction/nextcloud-vue 2.83.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.
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue.js +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +125 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +5 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +203 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +31 -2
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +37 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +135 -2
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +95 -43
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue.js +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +17 -2
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +33 -3
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue.js +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +16 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +38 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +448 -296
- package/dist/esm/utils/buildManifest.js +37 -3
- package/dist/esm/utils/buildManifest.js.map +1 -1
- package/dist/esm/utils/detailWidgetVisibility.js +149 -0
- package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
- package/dist/esm/utils/formatMetric.js +39 -3
- package/dist/esm/utils/formatMetric.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +1 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +9 -0
- package/dist/esm/utils/sentinelTokens.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1706 -454
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +448 -296
- package/l10n/en.json +16 -1
- package/l10n/nl.json +38 -1
- package/package.json +1 -1
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
- package/src/components/CnBodySections/CnBodySections.vue +22 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +128 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +5 -0
- package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +203 -4
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
- package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +31 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +37 -2
- package/src/components/CnReportsPage/CnReportsPage.vue +179 -1
- package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
- package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +34 -3
- package/src/components/CnTabsWidget/CnTabsWidget.vue +33 -3
- package/src/components/CnWalkthrough/CnWalkthrough.vue +16 -0
- package/src/css/form-field.css +14 -0
- package/src/css/form-widget.css +11 -0
- package/src/css/look-board-reports.css +58 -0
- package/src/css/look-board.css +12 -0
- package/src/dialogs/CnTransitionInputDialog.vue +87 -7
- package/src/schemas/app-manifest-v2.schema.json +23 -2
- package/src/utils/buildManifest.js +36 -2
- package/src/utils/detailWidgetVisibility.js +145 -0
- package/src/utils/formatMetric.js +38 -2
- package/src/utils/resolveManifestTokens.js +1 -0
- package/src/utils/sentinelTokens.js +9 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -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":";;;;;;;AA0DA;AACC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;;;AAIA;AACA;;AAEA;;;AAGA;;;;AAIA;AACA;;;;AAID,aAAe;AACd,CAAA,IAAI,EAAE,yBAAyB;;AAE/B,CAAA,UAAU,EAAE,oBAAEA,UAAgB,oBAAEC,YAAgB,EAAE,QAAQ,EAAE,WAAW;;AAEvE,CAAA,MAAM,EAAE;AACP;AACC;;;;;;AAMD,EAAA,kBAAkB,EAAE,EAAE,OAAO,EAAE,MAAM;AACrC;;AAEC;;AAED,EAAA,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK;EAC5C;;AAED,CAAA,KAAK,EAAE;AACN;AACC;AACA;AACA;;AAED,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,CAAC;GACnB;EACD;;AAED,CAAA,KAAK,EAAE,CAAC,OAAO,CAAC;;AAEhB,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,IAAI,EAAE;IACL,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE;AAC5B,IAAA,MAAM,EAAE,EAAE;IACV,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,EAAE;IACX;;GAED,MAAM,EAAE,KAAK;AACb,GAAA,YAAY,EAAE,EAAE;AAChB,GAAA,YAAY,EAAE,EAAE;;AAEhB,GAAA,SAAS,EAAE,IAAI;;GAEf,KAAK,EAAE,IAAI;AACZ,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;EACT,QAAQ,GAAG;AACV,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,UAAS;GACzC;;AAED,EAAA,MAAM,GAAG;AACR,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,MAAK,IAAK,eAAc;GAC5C;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,YAAa,IAAG,QAAO;GAC3C;;AAED,EAAA,gBAAgB,GAAG;AAClB,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,oBAAoB,MAAK;GAC7C;;EAED,QAAQ,GAAG;AACV,GAAA,OAAO,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;GACvC;;AAED;AACA,EAAA,cAAc,GAAG;AAChB,GAAA,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAA,IAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAA;AAC7E,MAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,MAAM,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,OAAQ,IAAG,OAAO,CAAC,KAAK,CAAA,EAAG,CAAC,CAAA;MACnG;MACA,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAEC,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA,EAAG;MACzD,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA,EAAG;MACtD,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,MAAM,GAAG;AACrD,MAAA;GACF;;AAED;;;AAGC;;AAEA;AACA;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,MAAM,UAAS,GAAI,CAAC,IAAI,CAAC,SAAQ,IAAK,IAAI,CAAC,SAAS,CAAC,UAAU,KAAK,GAAC;GACrE,MAAM,IAAG,GAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,YAAW,IAAK,SAAS,EAAA;GAC9D,IAAI,QAAQ,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAE,IAAG,IAAI,CAAC,IAAI,CAAC,MAAK,GAAI,CAAC,EAAE;IAC7D,MAAM,MAAO,GAAE,IAAI,CAAC,eAAe,CAAA,IAAK,IAAI,CAAC,UAAS,IAAK,GAAC;AAC5D,IAAA,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,CAAA,EAAG,CAAC,CAAA;AAC9F,IAAA;AACA,GAAA,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAA;AACzC,MAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,MAAM,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,OAAQ,IAAG,OAAO,CAAC,KAAK,CAAA,EAAG,CAAC,CAAA;AACrG,MAAE,EAAC;GACJ;;AAED;;;;AAIC;;;AAGA;AACA;;;EAGD,UAAU,GAAG;AACZ,GAAA,OAAO;AACN,IAAA,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,gBAAgB;IAC1F,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,WAAU,EAAG;IAC1D,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,YAAW,EAAG;AAC1D,IAAA,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,CAAA,EAAG;IACvE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,EAAE,IAAG,EAAG;AAC5G,IAAA;GACA;;EAED,WAAW,GAAG;AACb,GAAA,IAAI;AACH,IAAA,OAAO,cAAc,EAAC;AACvB,IAAA,CAAE,MAAM;AACP,IAAA,OAAO,IAAG;AACX,IAAA;GACA;;AAED,EAAA,YAAY,GAAG;GACd,MAAM,CAAA,GAAI,IAAI,CAAC,mBAAiB;AAChC,GAAA,IAAI,CAAC,CAAC,EAAE;AACP,IAAA,OAAO,IAAG;AACX,IAAA;GACA,OAAO,CAAC,OAAO,CAAA,KAAM,QAAS,IAAG,OAAQ,IAAG,CAAC,IAAI,CAAC,CAAC,KAAI,GAAI,CAAA;GAC3D;;AAED;EACA,UAAU,GAAG;GACZ,MAAM,EAAG,GAAE,IAAI,CAAC,YAAW,IAAK,IAAI,CAAC,YAAY,CAAC,eAAa;AAC/D,GAAA,OAAO,KAAK,MAAM,CAAC,EAAE,CAAA,GAAI,EAAC;GAC1B;;EAED,WAAW,GAAG;AACb,GAAA,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAC,IAAK,IAAI,CAAC,IAAI,CAAC,OAAO,CAAA;GACjF;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;GACnC;;EAED,WAAW,GAAG;AACb,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;GAClC;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;GACnC;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;GACnC;;AAED,EAAA,YAAY,GAAG;AACd,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;GACnC;;AAED;AACA,EAAA,aAAa,GAAG;GACf,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE;AAC7B,IAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAA;AAC1C,IAAA;AACA,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;GACpC;;EAED,WAAW,GAAG;AACb,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;GACnC;;AAED;;;AAGC;AACA;;EAED,SAAS,GAAG;AACX,GAAA,MAAM,KAAI,GAAI,IAAI,CAAC,OAAO,CAAC,YAAU;GACrC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAC,IAAK,CAAC,KAAM,IAAG,CAAC,IAAI,CAAC,OAAO,EAAE;AAC7D,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,IAAI;AACH,IAAA,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,IAAK,EAAC,CAAC,CAAC,IAAK,IAAG,EAAC;AACtF,IAAA,CAAE,MAAM;AACP,IAAA,OAAO,EAAC;AACT,IAAA;GACA;;AAED;EACA,SAAS,GAAG;GACX,OAAOA,SAAC,CAAC,eAAe,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,KAAM,IAAG,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,KAAI,EAAG,CAAA;GAC3H;;AAED;AACA,EAAA,eAAe,GAAG;AACjB,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,mBAAmB,KAAI;GAC3C;EACD;;AAED,CAAA,KAAK,EAAE;;EAEN,UAAU,EAAE;AACX,GAAA,SAAS,EAAE,IAAI;GACf,OAAO,CAAC,EAAE,EAAE;AACX,IAAA,IAAI,EAAE,EAAE;AACP,KAAA,IAAI,CAAC,IAAI,CAAC,MAAK,GAAI,GAAC;AACrB,KAAA;IACA;GACD;EACD;;AAED,CAAA,OAAO,GAAG;AACT,EAAA,IAAI,CAAC,UAAU,GAAC;EAChB;;AAED,CAAA,OAAO,EAAE;AACR;;;AAGC;;;AAGD,EAAA,MAAM,UAAU,GAAG;GAClB,MAAM,KAAM,GAAE,IAAI,CAAC,YAAU;GAC7B,IAAI,CAAC,SAAS,OAAO,KAAK,CAAC,gBAAgB,UAAU,EAAE;IACtD,MAAK;AACN,IAAA;AACA,GAAA,IAAI;;AAEH,IAAA,IAAI,OAAO,KAAK,CAAC,kBAAiB,KAAM,UAAW,IAAG,CAAC,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE;AACjG,KAAA,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAA;AACnE,KAAA;IACA,IAAI,CAAC,SAAU,GAAE,MAAM,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAA;AACvD,IAAA,CAAE,MAAM;AACP,IAAA,IAAI,CAAC,SAAQ,GAAI,KAAG;AACrB,IAAA;GACA;;AAED;AACA;;;;;;;AAOC;AACA;;AAEA;;AAED,EAAA,IAAI,CAAC,GAAG,EAAE;AACT,GAAA,IAAI,OAAO,GAAE,KAAM,QAAO,IAAK,GAAE,KAAM,EAAE,EAAE;AAC1C,IAAA,OAAO,GAAE;AACV,IAAA;AACA,GAAA,MAAM,IAAK,GAAE,OAAO,IAAI,CAAC,WAAU,KAAM,UAAW,GAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAE,GAAE,IAAE;GAChF,IAAI,OAAO,IAAK,KAAI,QAAO,IAAK,IAAG,KAAM,EAAG,IAAG,IAAK,KAAI,GAAG,EAAE;AAC5D,IAAA,OAAO,IAAG;AACX,IAAA;GACA,OAAOA,SAAC,CAAC,eAAe,EAAE,GAAG,CAAA;GAC7B;;AAED,EAAA,YAAY,GAAG;GACd,MAAM,EAAG,GAAE,CAAC,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,GAAC;AACvD,GAAA,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAE,IAAG,EAAE,CAAC,MAAK,GAAI,CAAA,IAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,UAAS;GACvE;;AAED;;;AAGC;AACA;;;;EAID,aAAa,CAAC,EAAE,GAAG,EAAE,KAAM,EAAC,EAAE;AAC7B,GAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAA,GAAI,MAAI;AACrB,GAAA,IAAI,GAAE,KAAM,SAAS,EAAE;AACtB,IAAA,IAAI,CAAC,cAAc,CAAC,eAAe,EAAE,KAAK,EAAA;AAC3C,IAAA;GACA;;AAED;;AAEC;AACA;;AAED,EAAA,cAAc,CAAC,EAAE,EAAE;GAClB,IAAI,CAAC,IAAI,CAAC,MAAO,GAAE,EAAG,IAAG,GAAC;GAC1B,IAAI,CAAC,cAAc,CAAC,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAA;GACtD;;AAED;;AAEC;AACA;;EAED,eAAe,CAAC,MAAM,EAAE;AACvB,GAAA,MAAM,KAAK,MAAM,CAAC,CAAC,WAAW,MAAM,CAAC,EAAC,KAAM,MAAM,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,EAAA;AAC1F,GAAA,IAAI,EAAE,EAAE;AACP,IAAA,IAAI,CAAC,IAAI,CAAC,MAAK,GAAI,GAAC;AACpB,IAAA,IAAI,CAAC,cAAc,CAAC,gBAAgB,EAAE,EAAE,EAAA;AACzC,IAAA;GACA;;AAED;AACC;AACA;AACA;;;;AAIA;AACA;;;EAGD,cAAc,CAAC,GAAG,EAAE,KAAK,EAAE;AAC1B,GAAA,MAAM,MAAO,GAAE,IAAI,CAAC,mBAAiB;GACrC,IAAI,CAAC,IAAI,CAAC,eAAgB,IAAG,CAAC,MAAO,IAAG,OAAO,MAAO,KAAI,QAAQ,EAAE;IACnE,MAAK;AACN,IAAA;AACA,GAAA,IAAI,OAAQ,IAAG,MAAM,EAAE;IACtB,MAAM,CAAC,QAAQ,EAAE,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,GAAG,GAAG,KAAM,GAAA;IACvD,MAAK;AACN,IAAA;AACA,GAAA,MAAM,CAAC,GAAG,CAAE,GAAE,MAAI;GAClB;;AAED;;;AAGC;;;AAGD,EAAA,MAAM,UAAU,GAAG;AAClB,GAAA,IAAI,CAAC,YAAW,GAAI,GAAC;AACrB,GAAA,IAAI,CAAC,YAAW,GAAI,GAAC;GACrB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAQ,IAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE;IACpD,IAAI,CAAC,YAAa,GAAEA,SAAC,CAAC,eAAe,EAAE,qBAAqB,EAAA;IAC5D,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;IACtB,IAAI,CAAC,YAAa,GAAEA,SAAC,CAAC,eAAe,EAAE,uBAAuB,EAAA;IAC9D,MAAK;AACN,IAAA;GACA,MAAM,IAAI,IAAI,CAAC,OAAQ,IAAG,GAAC;;;;;;GAM3B,MAAM,OAAM,GAAI;AACf,IAAA,IAAI,CAAC,CAAC,QAAO,IAAK,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC,YAAW,IAAK,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;AACvD,IAAA,CAAC,CAAC,CAAC,YAAa,IAAG,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO;IAChD,CAAC,CAAC,CAAC,gBAAe,IAAK,aAAa,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;AAChE,KAAA;AACA,GAAA,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;IACrB,OAAO,CAAC,CAAC,CAAC,WAAY,IAAG,QAAQ,CAAE,GAAE,IAAI,CAAC,IAAI,CAAC,OAAK;AACrD,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;IACtB,OAAO,CAAC,CAAC,CAAC,YAAa,IAAG,SAAS,CAAA,GAAI,IAAI,CAAC,IAAI,CAAC,QAAM;AACxD,IAAA;AACA,GAAA,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;IACtB,OAAO,CAAC,CAAC,CAAC,YAAa,IAAG,SAAS,CAAA,GAAI,IAAI,CAAC,IAAI,CAAC,QAAM;AACxD,IAAA;;AAEA,GAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AACjB,GAAA,IAAI,CAAC,KAAM,GAAE,KAAG;AAChB,GAAA,IAAI;IACH,IAAI,OAAO,IAAI,CAAC,WAAW,CAAC,kBAAiB,KAAM,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE;AACvH,KAAA,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAA;AAC9E,KAAA;AACA,IAAA,MAAM,MAAK,GAAI,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAA;IACvE,IAAI,CAAC,MAAM,EAAE;KACZ,IAAI,CAAC,YAAa,GAAEA,SAAC,CAAC,eAAe,EAAE,4BAA4B,EAAA;KACnE,MAAK;AACN,KAAA;AACA;AACC;;;IAGD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAA;IAC1B,IAAI,CAAC,KAAI,GAAI;AACZ,KAAA,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,EAAC,KAAM,MAAM,CAAC,OAAO,CAAA,IAAK,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAE,IAAG,EAAE,CAAC;AACtE,KAAA,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,gBAAgB,SAAS,CAAC;AAC5C,MAAA;;AAEA,IAAA,IAAI,CAAC,IAAI,CAAC,OAAM,GAAI,GAAC;AACrB,IAAA,IAAI,CAAC,IAAI,CAAC,OAAM,GAAI,GAAC;AACrB,IAAA,IAAI,CAAC,IAAI,CAAC,OAAM,GAAI,GAAC;AACrB,IAAA,IAAI,CAAC,cAAc,CAAC,eAAe,EAAE,EAAE,EAAA;AACxC,IAAA,CAAE,OAAO,CAAC,EAAE;AACX,IAAA,IAAI,CAAC,YAAa,GAAE,CAAC,CAAA,IAAK,CAAC,CAAC,OAAO,KAAKA,SAAC,CAAC,eAAe,EAAE,4BAA4B,EAAA;AACxF,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,MAAO,GAAE,MAAI;AACnB,IAAA;GACA;EACD;AACF;;;;"}
|
|
@@ -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 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-
|
|
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
|
|
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
|
|
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
|
*
|