@conduction/nextcloud-vue 2.88.0 → 2.89.1
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/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +64 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +27 -3
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +19 -0
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +4 -4
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +5 -5
- package/dist/nextcloud-vue.cjs.js +112 -8
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +5 -5
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +64 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +27 -3
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +20 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { translate } from '@nextcloud/l10n';
|
|
2
2
|
import { NcButton } from '@nextcloud/vue';
|
|
3
|
+
import { camelize } from 'vue';
|
|
3
4
|
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue';
|
|
4
5
|
import __sfc_main from '../CnFormWidgetBase/CnFormWidgetBase.vue.js';
|
|
5
6
|
import __sfc_main$1 from '../CnResourceSelect/CnResourceSelect.vue.js';
|
|
@@ -74,6 +75,11 @@ var script = {
|
|
|
74
75
|
cnTranslate: { default: () => (key) => key },
|
|
75
76
|
},
|
|
76
77
|
|
|
78
|
+
// A dashboard binds the widget's `content` keys as attributes too. Falling
|
|
79
|
+
// through, `submitLabel` would reach CnFormWidgetBase and win over the
|
|
80
|
+
// translated label the widget passes, so attributes go through rootAttrs.
|
|
81
|
+
inheritAttrs: false,
|
|
82
|
+
|
|
77
83
|
props: {
|
|
78
84
|
/**
|
|
79
85
|
* Persisted configuration blob (see component description for the shape).
|
|
@@ -109,6 +115,19 @@ var script = {
|
|
|
109
115
|
},
|
|
110
116
|
|
|
111
117
|
computed: {
|
|
118
|
+
/**
|
|
119
|
+
* The attributes for the root form: everything the widget was given,
|
|
120
|
+
* minus the names CnFormWidgetBase takes as props, which the widget
|
|
121
|
+
* sets itself.
|
|
122
|
+
*
|
|
123
|
+
* @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
|
|
124
|
+
* @return {object} The attributes to bind.
|
|
125
|
+
*/
|
|
126
|
+
rootAttrs() {
|
|
127
|
+
const own = new Set(Object.keys(__sfc_main.props || {}));
|
|
128
|
+
return Object.fromEntries(Object.entries(this.$attrs).filter(([key]) => !own.has(camelize(key))))
|
|
129
|
+
},
|
|
130
|
+
|
|
112
131
|
register() {
|
|
113
132
|
return this.content.register || 'pipelinq'
|
|
114
133
|
},
|
|
@@ -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.map((channel) => ({ ...channel, label: this.copy(channel && channel.label) }))\n\t\t\t\t: [\n\t\t\t\t\t\t{ value: 'telefoon', label: t('nextcloud-vue', 'Phone') },\n\t\t\t\t\t\t{ value: 'email', label: t('nextcloud-vue', 'Email') },\n\t\t\t\t\t\t{ value: 'chat', label: t('nextcloud-vue', 'Chat') },\n\t\t\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Outcome options `{ value, label }`, from the target schema's enum so\n\t\t * the form can only offer values the schema accepts. Labels come from\n\t\t * the property's `x-enum-labels`, translated. `content.outcomes` is the\n\t\t * fallback for a schema without an enum on that property.\n\t\t *\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\toutcomeOptions() {\n\t\t\tconst properties = (this.schemaDef && this.schemaDef.properties) || {}\n\t\t\tconst prop = properties[this.content.outcomeField || 'outcome']\n\t\t\tif (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\tconst labels = prop['x-enum-labels'] || prop.enumLabels || {}\n\t\t\t\treturn prop.enum.map((value) => ({ value, label: this.copy(labels[value] || String(value)) }))\n\t\t\t}\n\t\t\treturn Array.isArray(this.content.outcomes)\n\t\t\t\t? this.content.outcomes.map((outcome) => ({ ...outcome, label: this.copy(outcome && outcome.label) }))\n\t\t\t\t: []\n\t\t},\n\n\t\t/**\n\t\t * The field descriptors handed to CnFormWidgetBase — the whole of this\n\t\t * widget's form, declared rather than drawn. Order matters: it is the\n\t\t * render order.\n\t\t *\n\t\t * `client` is declared here so it keeps its place in the stack and its\n\t\t * field wrapper, but its control comes from the `#field-client` slot\n\t\t * (a CnResourceSelect, which the base has no type for).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tformFields() {\n\t\t\treturn [\n\t\t\t\t{ key: 'channel', type: 'select', label: this.channelLabel, options: this.channelOptions },\n\t\t\t\t{ key: 'client', type: 'custom', label: this.clientLabel },\n\t\t\t\t{ key: 'subject', type: 'text', label: this.subjectLabel },\n\t\t\t\t{ key: 'summary', type: 'textarea', label: this.summaryLabel, rows: 4 },\n\t\t\t\t{ key: 'outcome', type: 'select', label: this.outcomeLabel, options: this.outcomeOptions, clearable: true },\n\t\t\t]\n\t\t},\n\n\t\tobjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/** The page's client in focus, or an empty string. */\n\t\tpageClient() {\n\t\t\tconst id = this.workspaceCtx && this.workspaceCtx.selectedClient\n\t\t\treturn id ? String(id) : ''\n\t\t},\n\n\t\tcanRegister() {\n\t\t\treturn Boolean(this.form.subject && this.form.subject.trim() && this.form.channel)\n\t\t},\n\n\t\tchannelLabel() {\n\t\t\treturn t('nextcloud-vue', 'Channel')\n\t\t},\n\n\t\tclientLabel() {\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tsubjectLabel() {\n\t\t\treturn t('nextcloud-vue', 'Subject')\n\t\t},\n\n\t\tsummaryLabel() {\n\t\t\treturn t('nextcloud-vue', 'Summary')\n\t\t},\n\n\t\toutcomeLabel() {\n\t\t\treturn t('nextcloud-vue', 'Outcome')\n\t\t},\n\n\t\t// `content.submitLabel` names what the form creates, e.g. \"Save contact moment\".\n\t\tregisterLabel() {\n\t\t\tif (this.content.submitLabel) {\n\t\t\t\treturn this.copy(this.content.submitLabel)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Register')\n\t\t},\n\n\t\tsavingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Saving…')\n\t\t},\n\n\t\t/**\n\t\t * URL of the saved object's page, from `content.detailRoute`. Empty\n\t\t * without a route or router, and then no Open button is shown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsavedHref() {\n\t\t\tconst route = this.content.detailRoute\n\t\t\tif (!this.saved || !this.saved.id || !route || !this.$router) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: route, params: { id: this.saved.id } }).href || ''\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\t// `escape: false` because the label renders through `{{ }}`, which escapes.\n\t\topenLabel() {\n\t\t\treturn t('nextcloud-vue', 'Open \"{title}\"', { title: (this.saved && this.saved.title) || '' }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the form writes its client and summary to the workspace context. */\n\t\twritesWorkspace() {\n\t\t\treturn this.content.writeWorkspace !== false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A new page client pre-fills the field; the field stays editable.\n\t\tpageClient: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.form.client = id\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.loadSchema()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Fetch the target schema, which the outcome options derive from. A\n\t\t * failure leaves the `content.outcomes` fallback in place.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadSchema() {\n\t\t\tconst store = this.objectStore\n\t\t\tif (!store || typeof store.fetchSchema !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// Re-registering an existing type resets its cached objects and schema.\n\t\t\t\tif (typeof store.registerObjectType === 'function' && !store.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tstore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tthis.schemaDef = await store.fetchSchema(this.typeSlug)\n\t\t\t} catch {\n\t\t\t\tthis.schemaDef = null\n\t\t\t}\n\t\t},\n\n\t\t/** The default channel value (first configured channel, else `telefoon`). */\n\t\t/**\n\t\t * A label the manifest supplies (a channel, an outcome, the submit\n\t\t * label), translated: the app's own translation first, since the words\n\t\t * are the app's, then the library's catalogue, then the text as\n\t\t * written. Before, a channel label was shown as written and the submit\n\t\t * label only reached the app's translate, so pipelinq showed \"Phone\"\n\t\t * and \"Save contact moment\" in English to a Dutch reader.\n\t\t *\n\t\t * @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language\n\t\t * @param {string} key The label as the manifest writes it.\n\t\t * @return {string} The translated label.\n\t\t */\n\t\tcopy(key) {\n\t\t\tif (typeof key !== 'string' || key === '') {\n\t\t\t\treturn key\n\t\t\t}\n\t\t\tconst host = typeof this.cnTranslate === 'function' ? this.cnTranslate(key) : key\n\t\t\tif (typeof host === 'string' && host !== '' && host !== key) {\n\t\t\t\treturn host\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', key)\n\t\t},\n\n\t\tfirstChannel() {\n\t\t\tconst ch = (this.content && this.content.channels) || []\n\t\t\treturn (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'\n\t\t},\n\n\t\t/**\n\t\t * A field edit from CnFormWidgetBase. `summary` also streams into the\n\t\t * workspace context. `client` never arrives here: its control is the\n\t\t * `#field-client` slot, which calls onClientChange.\n\t\t *\n\t\t * @param {{key: string, value: unknown}} payload The changed field.\n\t\t * @return {void}\n\t\t */\n\t\tonFieldUpdate({ key, value }) {\n\t\t\tthis.form[key] = value\n\t\t\tif (key === 'summary') {\n\t\t\t\tthis.writeWorkspace('activeSummary', value)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The submission's client, also written to the workspace context.\n\t\t *\n\t\t * @param {string} id The selected client id.\n\t\t */\n\t\tonClientChange(id) {\n\t\t\tthis.form.client = id || ''\n\t\t\tthis.writeWorkspace('selectedClient', this.form.client)\n\t\t},\n\n\t\t/**\n\t\t * A client created inline via \"Create '<name>'\" — select it.\n\t\t *\n\t\t * @param {object} client The created client object.\n\t\t */\n\t\tonClientCreated(client) {\n\t\t\tconst id = String((client && (client.id || (client['@self'] && client['@self'].id))) || '')\n\t\t\tif (id) {\n\t\t\t\tthis.form.client = id\n\t\t\t\tthis.writeWorkspace('selectedClient', id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Write a key into the workspace context, unless `content.writeWorkspace`\n\t\t * is false.\n\t\t *\n\t\t * Vue 2.7's Options-API `inject` auto-unwraps the provided ref, so the\n\t\t * holder is usually the plain reactive object; the raw-ref shape\n\t\t * (`.value`) is supported for Composition-API consumers.\n\t\t *\n\t\t * @param {string} key The context key.\n\t\t * @param {unknown} value The value.\n\t\t */\n\t\twriteWorkspace(key, value) {\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!this.writesWorkspace || !holder || typeof holder !== 'object') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ('value' in holder) {\n\t\t\t\tholder.value = { ...(holder.value || {}), [key]: value }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tholder[key] = value\n\t\t},\n\n\t\t/**\n\t\t * Persist the contactmoment. The agent identity is left to the server\n\t\t * (never sent from the client). Surfaces errors inline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRegister() {\n\t\t\tthis.subjectError = ''\n\t\t\tthis.errorMessage = ''\n\t\t\tif (!this.form.subject || !this.form.subject.trim()) {\n\t\t\t\tthis.subjectError = t('nextcloud-vue', 'Subject is required')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectStore) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Cannot save right now')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst c = this.content || {}\n\t\t\t// `defaults` is spread FIRST so the mapped fields below always win.\n\t\t\t// It exists for schemas that need a fixed value on every create the\n\t\t\t// form itself has no input for — e.g. a discriminator on a supertype\n\t\t\t// schema (`{ ticketType: 'contactmoment' }`), which would otherwise be\n\t\t\t// omitted and fail validation.\n\t\t\tconst payload = {\n\t\t\t\t...(c.defaults || {}),\n\t\t\t\t[c.subjectField || 'subject']: this.form.subject.trim(),\n\t\t\t\t[c.channelField || 'channel']: this.form.channel,\n\t\t\t\t[c.contactedAtField || 'contactedAt']: new Date().toISOString(),\n\t\t\t}\n\t\t\tif (this.form.client) {\n\t\t\t\tpayload[c.clientField || 'client'] = this.form.client\n\t\t\t}\n\t\t\tif (this.form.summary) {\n\t\t\t\tpayload[c.summaryField || 'summary'] = this.form.summary\n\t\t\t}\n\t\t\tif (this.form.outcome) {\n\t\t\t\tpayload[c.outcomeField || 'outcome'] = this.form.outcome\n\t\t\t}\n\n\t\t\tthis.saving = true\n\t\t\tthis.saved = null\n\t\t\ttry {\n\t\t\t\tif (typeof this.objectStore.registerObjectType === 'function' && !this.objectStore.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tthis.objectStore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tconst result = await this.objectStore.saveObject(this.typeSlug, payload)\n\t\t\t\tif (!result) {\n\t\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Failed to save interaction')\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event saved A contactmoment was persisted.\n\t\t\t\t * @type {object} The saved object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', result)\n\t\t\t\tthis.saved = {\n\t\t\t\t\tid: String(result.id || (result['@self'] && result['@self'].id) || ''),\n\t\t\t\t\ttitle: payload[c.subjectField || 'subject'],\n\t\t\t\t}\n\t\t\t\t// Reset the per-interaction fields; keep the client for follow-ups.\n\t\t\t\tthis.form.subject = ''\n\t\t\t\tthis.form.summary = ''\n\t\t\t\tthis.form.outcome = ''\n\t\t\t\tthis.writeWorkspace('activeSummary', '')\n\t\t\t} catch (e) {\n\t\t\t\tthis.errorMessage = (e && e.message) || t('nextcloud-vue', 'Failed to save interaction')\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<!--\n The scoped style block is gone on purpose. The markup now lives in\n CnFormWidgetBase, so a scoped rule here would carry THIS component's\n data-v attribute and match none of it. The rules moved verbatim to\n src/css/form-widget.css, keyed on `cn-form-widget*`; the base also emits\n the `cn-interaction-form-widget*` names on the same elements, so app CSS\n targeting those keeps matching.\n-->\n"],"names":["CnFormWidgetBase","CnResourceSelect","t"],"mappings":"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"}
|
|
1
|
+
{"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\tv-bind=\"rootAttrs\"\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 { camelize } from '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\t// A dashboard binds the widget's `content` keys as attributes too. Falling\n\t// through, `submitLabel` would reach CnFormWidgetBase and win over the\n\t// translated label the widget passes, so attributes go through rootAttrs.\n\tinheritAttrs: false,\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\t/**\n\t\t * The attributes for the root form: everything the widget was given,\n\t\t * minus the names CnFormWidgetBase takes as props, which the widget\n\t\t * sets itself.\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 * @return {object} The attributes to bind.\n\t\t */\n\t\trootAttrs() {\n\t\t\tconst own = new Set(Object.keys(CnFormWidgetBase.props || {}))\n\t\t\treturn Object.fromEntries(Object.entries(this.$attrs).filter(([key]) => !own.has(camelize(key))))\n\t\t},\n\n\t\tregister() {\n\t\t\treturn this.content.register || 'pipelinq'\n\t\t},\n\n\t\tschema() {\n\t\t\treturn this.content.schema || 'contactmoment'\n\t\t},\n\n\t\tclientSchema() {\n\t\t\treturn this.content.clientSchema || 'client'\n\t\t},\n\n\t\tclientLabelField() {\n\t\t\treturn this.content.clientLabelField || 'name'\n\t\t},\n\n\t\ttypeSlug() {\n\t\t\treturn `${this.register}-${this.schema}`\n\t\t},\n\n\t\t/** Channel options `{ value, label }`. */\n\t\tchannelOptions() {\n\t\t\treturn Array.isArray(this.content.channels) && this.content.channels.length > 0\n\t\t\t\t? this.content.channels.map((channel) => ({ ...channel, label: this.copy(channel && channel.label) }))\n\t\t\t\t: [\n\t\t\t\t\t\t{ value: 'telefoon', label: t('nextcloud-vue', 'Phone') },\n\t\t\t\t\t\t{ value: 'email', label: t('nextcloud-vue', 'Email') },\n\t\t\t\t\t\t{ value: 'chat', label: t('nextcloud-vue', 'Chat') },\n\t\t\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * Outcome options `{ value, label }`, from the target schema's enum so\n\t\t * the form can only offer values the schema accepts. Labels come from\n\t\t * the property's `x-enum-labels`, translated. `content.outcomes` is the\n\t\t * fallback for a schema without an enum on that property.\n\t\t *\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\toutcomeOptions() {\n\t\t\tconst properties = (this.schemaDef && this.schemaDef.properties) || {}\n\t\t\tconst prop = properties[this.content.outcomeField || 'outcome']\n\t\t\tif (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\tconst labels = prop['x-enum-labels'] || prop.enumLabels || {}\n\t\t\t\treturn prop.enum.map((value) => ({ value, label: this.copy(labels[value] || String(value)) }))\n\t\t\t}\n\t\t\treturn Array.isArray(this.content.outcomes)\n\t\t\t\t? this.content.outcomes.map((outcome) => ({ ...outcome, label: this.copy(outcome && outcome.label) }))\n\t\t\t\t: []\n\t\t},\n\n\t\t/**\n\t\t * The field descriptors handed to CnFormWidgetBase — the whole of this\n\t\t * widget's form, declared rather than drawn. Order matters: it is the\n\t\t * render order.\n\t\t *\n\t\t * `client` is declared here so it keeps its place in the stack and its\n\t\t * field wrapper, but its control comes from the `#field-client` slot\n\t\t * (a CnResourceSelect, which the base has no type for).\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tformFields() {\n\t\t\treturn [\n\t\t\t\t{ key: 'channel', type: 'select', label: this.channelLabel, options: this.channelOptions },\n\t\t\t\t{ key: 'client', type: 'custom', label: this.clientLabel },\n\t\t\t\t{ key: 'subject', type: 'text', label: this.subjectLabel },\n\t\t\t\t{ key: 'summary', type: 'textarea', label: this.summaryLabel, rows: 4 },\n\t\t\t\t{ key: 'outcome', type: 'select', label: this.outcomeLabel, options: this.outcomeOptions, clearable: true },\n\t\t\t]\n\t\t},\n\n\t\tobjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\tworkspaceCtx() {\n\t\t\tconst c = this.cnWorkspaceContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/** The page's client in focus, or an empty string. */\n\t\tpageClient() {\n\t\t\tconst id = this.workspaceCtx && this.workspaceCtx.selectedClient\n\t\t\treturn id ? String(id) : ''\n\t\t},\n\n\t\tcanRegister() {\n\t\t\treturn Boolean(this.form.subject && this.form.subject.trim() && this.form.channel)\n\t\t},\n\n\t\tchannelLabel() {\n\t\t\treturn t('nextcloud-vue', 'Channel')\n\t\t},\n\n\t\tclientLabel() {\n\t\t\treturn t('nextcloud-vue', 'Client')\n\t\t},\n\n\t\tsubjectLabel() {\n\t\t\treturn t('nextcloud-vue', 'Subject')\n\t\t},\n\n\t\tsummaryLabel() {\n\t\t\treturn t('nextcloud-vue', 'Summary')\n\t\t},\n\n\t\toutcomeLabel() {\n\t\t\treturn t('nextcloud-vue', 'Outcome')\n\t\t},\n\n\t\t// `content.submitLabel` names what the form creates, e.g. \"Save contact moment\".\n\t\tregisterLabel() {\n\t\t\tif (this.content.submitLabel) {\n\t\t\t\treturn this.copy(this.content.submitLabel)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Register')\n\t\t},\n\n\t\tsavingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Saving…')\n\t\t},\n\n\t\t/**\n\t\t * URL of the saved object's page, from `content.detailRoute`. Empty\n\t\t * without a route or router, and then no Open button is shown.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsavedHref() {\n\t\t\tconst route = this.content.detailRoute\n\t\t\tif (!this.saved || !this.saved.id || !route || !this.$router) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn this.$router.resolve({ name: route, params: { id: this.saved.id } }).href || ''\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\t// `escape: false` because the label renders through `{{ }}`, which escapes.\n\t\topenLabel() {\n\t\t\treturn t('nextcloud-vue', 'Open \"{title}\"', { title: (this.saved && this.saved.title) || '' }, undefined, { escape: false })\n\t\t},\n\n\t\t/** Whether the form writes its client and summary to the workspace context. */\n\t\twritesWorkspace() {\n\t\t\treturn this.content.writeWorkspace !== false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A new page client pre-fills the field; the field stays editable.\n\t\tpageClient: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.form.client = id\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.loadSchema()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Fetch the target schema, which the outcome options derive from. A\n\t\t * failure leaves the `content.outcomes` fallback in place.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadSchema() {\n\t\t\tconst store = this.objectStore\n\t\t\tif (!store || typeof store.fetchSchema !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// Re-registering an existing type resets its cached objects and schema.\n\t\t\t\tif (typeof store.registerObjectType === 'function' && !store.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tstore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tthis.schemaDef = await store.fetchSchema(this.typeSlug)\n\t\t\t} catch {\n\t\t\t\tthis.schemaDef = null\n\t\t\t}\n\t\t},\n\n\t\t/** The default channel value (first configured channel, else `telefoon`). */\n\t\t/**\n\t\t * A label the manifest supplies (a channel, an outcome, the submit\n\t\t * label), translated: the app's own translation first, since the words\n\t\t * are the app's, then the library's catalogue, then the text as\n\t\t * written. Before, a channel label was shown as written and the submit\n\t\t * label only reached the app's translate, so pipelinq showed \"Phone\"\n\t\t * and \"Save contact moment\" in English to a Dutch reader.\n\t\t *\n\t\t * @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language\n\t\t * @param {string} key The label as the manifest writes it.\n\t\t * @return {string} The translated label.\n\t\t */\n\t\tcopy(key) {\n\t\t\tif (typeof key !== 'string' || key === '') {\n\t\t\t\treturn key\n\t\t\t}\n\t\t\tconst host = typeof this.cnTranslate === 'function' ? this.cnTranslate(key) : key\n\t\t\tif (typeof host === 'string' && host !== '' && host !== key) {\n\t\t\t\treturn host\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', key)\n\t\t},\n\n\t\tfirstChannel() {\n\t\t\tconst ch = (this.content && this.content.channels) || []\n\t\t\treturn (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'\n\t\t},\n\n\t\t/**\n\t\t * A field edit from CnFormWidgetBase. `summary` also streams into the\n\t\t * workspace context. `client` never arrives here: its control is the\n\t\t * `#field-client` slot, which calls onClientChange.\n\t\t *\n\t\t * @param {{key: string, value: unknown}} payload The changed field.\n\t\t * @return {void}\n\t\t */\n\t\tonFieldUpdate({ key, value }) {\n\t\t\tthis.form[key] = value\n\t\t\tif (key === 'summary') {\n\t\t\t\tthis.writeWorkspace('activeSummary', value)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The submission's client, also written to the workspace context.\n\t\t *\n\t\t * @param {string} id The selected client id.\n\t\t */\n\t\tonClientChange(id) {\n\t\t\tthis.form.client = id || ''\n\t\t\tthis.writeWorkspace('selectedClient', this.form.client)\n\t\t},\n\n\t\t/**\n\t\t * A client created inline via \"Create '<name>'\" — select it.\n\t\t *\n\t\t * @param {object} client The created client object.\n\t\t */\n\t\tonClientCreated(client) {\n\t\t\tconst id = String((client && (client.id || (client['@self'] && client['@self'].id))) || '')\n\t\t\tif (id) {\n\t\t\t\tthis.form.client = id\n\t\t\t\tthis.writeWorkspace('selectedClient', id)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Write a key into the workspace context, unless `content.writeWorkspace`\n\t\t * is false.\n\t\t *\n\t\t * Vue 2.7's Options-API `inject` auto-unwraps the provided ref, so the\n\t\t * holder is usually the plain reactive object; the raw-ref shape\n\t\t * (`.value`) is supported for Composition-API consumers.\n\t\t *\n\t\t * @param {string} key The context key.\n\t\t * @param {unknown} value The value.\n\t\t */\n\t\twriteWorkspace(key, value) {\n\t\t\tconst holder = this.cnWorkspaceContext\n\t\t\tif (!this.writesWorkspace || !holder || typeof holder !== 'object') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ('value' in holder) {\n\t\t\t\tholder.value = { ...(holder.value || {}), [key]: value }\n\t\t\t\treturn\n\t\t\t}\n\t\t\tholder[key] = value\n\t\t},\n\n\t\t/**\n\t\t * Persist the contactmoment. The agent identity is left to the server\n\t\t * (never sent from the client). Surfaces errors inline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRegister() {\n\t\t\tthis.subjectError = ''\n\t\t\tthis.errorMessage = ''\n\t\t\tif (!this.form.subject || !this.form.subject.trim()) {\n\t\t\t\tthis.subjectError = t('nextcloud-vue', 'Subject is required')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectStore) {\n\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Cannot save right now')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst c = this.content || {}\n\t\t\t// `defaults` is spread FIRST so the mapped fields below always win.\n\t\t\t// It exists for schemas that need a fixed value on every create the\n\t\t\t// form itself has no input for — e.g. a discriminator on a supertype\n\t\t\t// schema (`{ ticketType: 'contactmoment' }`), which would otherwise be\n\t\t\t// omitted and fail validation.\n\t\t\tconst payload = {\n\t\t\t\t...(c.defaults || {}),\n\t\t\t\t[c.subjectField || 'subject']: this.form.subject.trim(),\n\t\t\t\t[c.channelField || 'channel']: this.form.channel,\n\t\t\t\t[c.contactedAtField || 'contactedAt']: new Date().toISOString(),\n\t\t\t}\n\t\t\tif (this.form.client) {\n\t\t\t\tpayload[c.clientField || 'client'] = this.form.client\n\t\t\t}\n\t\t\tif (this.form.summary) {\n\t\t\t\tpayload[c.summaryField || 'summary'] = this.form.summary\n\t\t\t}\n\t\t\tif (this.form.outcome) {\n\t\t\t\tpayload[c.outcomeField || 'outcome'] = this.form.outcome\n\t\t\t}\n\n\t\t\tthis.saving = true\n\t\t\tthis.saved = null\n\t\t\ttry {\n\t\t\t\tif (typeof this.objectStore.registerObjectType === 'function' && !this.objectStore.objectTypeRegistry?.[this.typeSlug]) {\n\t\t\t\t\tthis.objectStore.registerObjectType(this.typeSlug, this.schema, this.register)\n\t\t\t\t}\n\t\t\t\tconst result = await this.objectStore.saveObject(this.typeSlug, payload)\n\t\t\t\tif (!result) {\n\t\t\t\t\tthis.errorMessage = t('nextcloud-vue', 'Failed to save interaction')\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event saved A contactmoment was persisted.\n\t\t\t\t * @type {object} The saved object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', result)\n\t\t\t\tthis.saved = {\n\t\t\t\t\tid: String(result.id || (result['@self'] && result['@self'].id) || ''),\n\t\t\t\t\ttitle: payload[c.subjectField || 'subject'],\n\t\t\t\t}\n\t\t\t\t// Reset the per-interaction fields; keep the client for follow-ups.\n\t\t\t\tthis.form.subject = ''\n\t\t\t\tthis.form.summary = ''\n\t\t\t\tthis.form.outcome = ''\n\t\t\t\tthis.writeWorkspace('activeSummary', '')\n\t\t\t} catch (e) {\n\t\t\t\tthis.errorMessage = (e && e.message) || t('nextcloud-vue', 'Failed to save interaction')\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<!--\n The scoped style block is gone on purpose. The markup now lives in\n CnFormWidgetBase, so a scoped rule here would carry THIS component's\n data-v attribute and match none of it. The rules moved verbatim to\n src/css/form-widget.css, keyed on `cn-form-widget*`; the base also emits\n the `cn-interaction-form-widget*` names on the same elements, so app CSS\n targeting those keeps matching.\n-->\n"],"names":["CnFormWidgetBase","CnResourceSelect","t"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveComponent, openBlock, createBlock, withCtx, createVNode, createTextVNode, toDisplayString, createCommentVNode } from 'vue';
|
|
1
|
+
import { resolveComponent, openBlock, createBlock, mergeProps, withCtx, createVNode, createTextVNode, toDisplayString, createCommentVNode } from 'vue';
|
|
2
2
|
|
|
3
3
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
4
4
|
const _component_CnResourceSelect = resolveComponent("CnResourceSelect");
|
|
@@ -6,7 +6,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
6
6
|
const _component_NcButton = resolveComponent("NcButton");
|
|
7
7
|
const _component_CnFormWidgetBase = resolveComponent("CnFormWidgetBase");
|
|
8
8
|
|
|
9
|
-
return (openBlock(), createBlock(_component_CnFormWidgetBase, {
|
|
9
|
+
return (openBlock(), createBlock(_component_CnFormWidgetBase, mergeProps($options.rootAttrs, {
|
|
10
10
|
blockClass: "cn-interaction-form-widget",
|
|
11
11
|
fields: $options.formFields,
|
|
12
12
|
model: $data.form,
|
|
@@ -18,7 +18,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
18
18
|
errorMessage: $data.errorMessage,
|
|
19
19
|
"onUpdate:field": $options.onFieldUpdate,
|
|
20
20
|
onSubmit: $options.onRegister
|
|
21
|
-
}, {
|
|
21
|
+
}), {
|
|
22
22
|
"field-client": withCtx(() => [
|
|
23
23
|
createVNode(_component_CnResourceSelect, {
|
|
24
24
|
register: $options.register,
|
|
@@ -51,7 +51,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
51
51
|
: createCommentVNode("v-if", true)
|
|
52
52
|
]),
|
|
53
53
|
_: 1 /* STABLE */
|
|
54
|
-
},
|
|
54
|
+
}, 16 /* FULL_PROPS */, ["fields", "model", "errors", "canSubmit", "submitting", "submitLabel", "submittingLabel", "errorMessage", "onUpdate:field", "onSubmit"]))
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export { render };
|
|
@@ -1 +1 @@
|
|
|
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;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnInteractionFormWidget.vue3.js","sources":["../../../../src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue?vue&type=template&id=3605170a&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\tv-bind=\"rootAttrs\"\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 { camelize } from '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\t// A dashboard binds the widget's `content` keys as attributes too. Falling\n\t// through, `submitLabel` would reach CnFormWidgetBase and win over the\n\t// translated label the widget passes, so attributes go through rootAttrs.\n\tinheritAttrs: false,\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\t/**\n\t\t * The attributes for the root form: everything the widget was given,\n\t\t * minus the names CnFormWidgetBase takes as props, which the widget\n\t\t * sets itself.\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 * @return {object} The attributes to bind.\n\t\t */\n\t\trootAttrs() {\n\t\t\tconst own = new Set(Object.keys(CnFormWidgetBase.props || {}))\n\t\t\treturn Object.fromEntries(Object.entries(this.$attrs).filter(([key]) => !own.has(camelize(key))))\n\t\t},\n\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":";;;;;;;;0EAUU,SAAS,CAAA,SAAA,EAAA;AACjB,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;;;;;;;;;;;;"}
|