@conduction/nextcloud-vue 2.56.0-beta.20 → 2.56.0-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js +25 -0
  2. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js.map +1 -1
  3. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js +3 -1
  4. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js.map +1 -1
  5. package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js +2 -0
  6. package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js.map +1 -1
  7. package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js +7 -0
  8. package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js.map +1 -1
  9. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js +10 -0
  10. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js.map +1 -0
  11. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js +150 -0
  12. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js.map +1 -0
  13. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js +54 -0
  14. package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js.map +1 -0
  15. package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js +36 -1
  16. package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js.map +1 -1
  17. package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue3.js.map +1 -1
  18. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js +37 -0
  19. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js.map +1 -1
  20. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js +4 -2
  21. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js.map +1 -1
  22. package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js +7 -10
  23. package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js.map +1 -1
  24. package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js +23 -13
  25. package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js.map +1 -1
  26. package/dist/esm/dialogs/CnRunNodeDialog.vue2.js +8 -6
  27. package/dist/esm/dialogs/CnRunNodeDialog.vue2.js.map +1 -1
  28. package/dist/esm/dialogs/CnRunNodeDialog.vue3.js.map +1 -1
  29. package/dist/esm/index.js +1 -0
  30. package/dist/esm/index.js.map +1 -1
  31. package/dist/esm/l10n/en.json.js +15 -1
  32. package/dist/esm/l10n/en.json.js.map +1 -1
  33. package/dist/esm/l10n/nl.json.js +15 -1
  34. package/dist/esm/l10n/nl.json.js.map +1 -1
  35. package/dist/esm/nextcloud-vue.css +64 -0
  36. package/dist/esm/utils/selectOptions.js +57 -0
  37. package/dist/esm/utils/selectOptions.js.map +1 -0
  38. package/dist/nextcloud-vue.cjs.js +6050 -5649
  39. package/dist/nextcloud-vue.cjs.js.map +2 -2
  40. package/dist/nextcloud-vue.css +64 -0
  41. package/l10n/en.json +15 -1
  42. package/l10n/nl.json +15 -1
  43. package/package.json +1 -1
  44. package/scripts/openregister-reliance.js +206 -0
  45. package/src/components/CnFlowDetail/CnFlowDetail.vue +27 -0
  46. package/src/components/CnFlowDetail/CnRunDetailSidebar.vue +6 -0
  47. package/src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue +250 -0
  48. package/src/components/CnFlowStepOutcomes/index.js +8 -0
  49. package/src/components/CnGraphCanvas/CnFlowNode.vue +36 -1
  50. package/src/components/CnGraphCanvas/CnGraphCanvas.vue +40 -1
  51. package/src/components/index.js +1 -0
  52. package/src/dialogs/CnFlowNodeEditModal.vue +41 -20
  53. package/src/dialogs/CnRunNodeDialog.vue +8 -6
  54. package/src/index.js +1 -0
  55. package/src/utils/selectOptions.js +54 -0
@@ -1 +1 @@
1
- {"version":3,"file":"CnFlowNodeEditModal.vue3.js","sources":["../../../src/dialogs/CnFlowNodeEditModal.vue?vue&type=template&id=e107484a&lang.js"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<NcDialog size=\"normal\"\n\t\t:name=\"typeLabel\"\n\t\t@closing=\"cancel\">\n\t\t<div class=\"cn-flow-node-edit\">\n\t\t\t<!-- What this step IS, in the engine's own words. -->\n\t\t\t<p v-if=\"entry && entry.description\" class=\"cn-flow-node-edit__description\">\n\t\t\t\t{{ entry.description }}\n\t\t\t</p>\n\t\t\t<p class=\"cn-flow-node-edit__id\">\n\t\t\t\t{{ node.type }}\n\t\t\t</p>\n\n\t\t\t<NcTextField :modelValue=\"draft.name\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Step name')\"\n\t\t\t\t:placeholder=\"typeLabel\"\n\t\t\t\t:helperText=\"t('nextcloud-vue', 'Shown on the card. Leave empty to show the step type.')\"\n\t\t\t\t@update:modelValue=\"draft.name = $event\" />\n\n\t\t\t<!-- One field per option the ENGINE declares for this step\n\t\t\t (`configKeys`), plus any key already set on the node. The\n\t\t\t widget follows the value: switches for booleans, a number\n\t\t\t field for numbers, a JSON area for structured values. -->\n\t\t\t<div v-for=\"key in formKeys\" :key=\"key\" class=\"cn-flow-node-edit__field\">\n\t\t\t\t<NcCheckboxRadioSwitch v-if=\"widgetFor(key) === 'switch'\"\n\t\t\t\t\t:modelValue=\"draft.config[key] === true\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\">\n\t\t\t\t\t{{ labelFor(key) }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<NcSelect v-else-if=\"widgetFor(key) === 'method'\"\n\t\t\t\t\t:modelValue=\"draft.config[key] || null\"\n\t\t\t\t\t:options=\"HTTP_METHODS\"\n\t\t\t\t\t:inputLabel=\"labelFor(key)\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'GET')\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\" />\n\n\t\t\t\t<!-- A `select` field with `optionsFrom` renders as a picker fed\n\t\t\t\t by the URL the OWNING APP declared — never a bare uuid\n\t\t\t\t text box. -->\n\t\t\t\t<NcSelect v-else-if=\"widgetFor(key) === 'select'\"\n\t\t\t\t\t:modelValue=\"selectedOption(key)\"\n\t\t\t\t\t:options=\"selectOptions[key] || []\"\n\t\t\t\t\t:inputLabel=\"labelFor(key)\"\n\t\t\t\t\t:loading=\"selectLoading[key] === true\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Pick one…')\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event ? $event.id : '')\" />\n\n\t\t\t\t<NcTextArea v-else-if=\"widgetFor(key) === 'textarea'\"\n\t\t\t\t\t:modelValue=\"String(draft.config[key] ?? '')\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t:helperText=\"hintFor(key)\"\n\t\t\t\t\trows=\"4\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\" />\n\n\t\t\t\t<NcTextField v-else-if=\"widgetFor(key) === 'number'\"\n\t\t\t\t\t:modelValue=\"String(draft.config[key] ?? '')\"\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t@update:modelValue=\"setNumberKey(key, $event)\" />\n\n\t\t\t\t<NcTextArea v-else-if=\"widgetFor(key) === 'json'\"\n\t\t\t\t\t:modelValue=\"jsonDraftFor(key)\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t:error=\"jsonErrors[key] !== undefined\"\n\t\t\t\t\t:helperText=\"jsonErrors[key] || t('nextcloud-vue', 'A structured value, as JSON.')\"\n\t\t\t\t\trows=\"4\"\n\t\t\t\t\t@update:modelValue=\"setJsonKey(key, $event)\" />\n\n\t\t\t\t<CnCronField v-else-if=\"widgetFor(key) === 'cron'\"\n\t\t\t\t\t:modelValue=\"String(draft.config[key] ?? '')\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\" />\n\n\t\t\t\t<!-- `runAs` is a Nextcloud user id, and the SERVER decides\n\t\t\t\t whether this saver may act as that user — see\n\t\t\t\t FlowTriggerValidator::validateDelegation(). The picker\n\t\t\t\t defaults to the current user, which asserts no delegation\n\t\t\t\t at all; naming anyone else is a request the save may\n\t\t\t\t refuse, and refusing it here would only hide the reason. -->\n\t\t\t\t<!-- WHO the step asks. Users AND groups, because the step has\n\t\t\t\t always been able to ask either and the field could only ever\n\t\t\t\t say one word. Each option carries its own TYPE, so\n\t\t\t\t `{type: 'group', id: 'bezwaar'}` is stored as a group and\n\t\t\t\t nothing has to guess later. -->\n\t\t\t\t<NcSelect v-else-if=\"widgetFor(key) === 'principal'\"\n\t\t\t\t\t:modelValue=\"principalOptions(key)\"\n\t\t\t\t\t:options=\"principalChoices\"\n\t\t\t\t\t:inputLabel=\"labelFor(key)\"\n\t\t\t\t\t:loading=\"principalsLoading\"\n\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\tkeepOpen\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Search people and groups…')\"\n\t\t\t\t\t:data-testid=\"`flow-node-principal-${key}`\"\n\t\t\t\t\t@search=\"searchPrincipals\"\n\t\t\t\t\t@update:modelValue=\"setPrincipals(key, $event)\" />\n\n\t\t\t\t<NcSelect v-else-if=\"widgetFor(key) === 'user'\"\n\t\t\t\t\t:modelValue=\"userOption(key)\"\n\t\t\t\t\t:options=\"userOptions\"\n\t\t\t\t\t:inputLabel=\"labelFor(key)\"\n\t\t\t\t\t:loading=\"usersLoading\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Pick a user…')\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event ? $event.id : '')\" />\n\n\t\t\t\t<NcTextField v-else\n\t\t\t\t\t:modelValue=\"String(draft.config[key] ?? '')\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t:helperText=\"hintFor(key)\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\" />\n\t\t\t</div>\n\n\t\t\t<p v-if=\"!formKeys.length\" class=\"cn-flow-node-edit__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This step has no options.') }}\n\t\t\t</p>\n\n\t\t\t<!-- The whole document, for anything the fields above cannot say.\n\t\t\t Collapsed: the fields ARE the interface; this is the escape\n\t\t\t hatch that keeps every present and future key reachable.\n\n\t\t\t HIDDEN when it can reach nothing. A step the engine declares no\n\t\t\t options for, carrying no config, was showing \"This step has no\n\t\t\t options\" and then — as the only thing on the dialog you could\n\t\t\t actually touch — a JSON editor containing `{}`. That reads as\n\t\t\t \"configuring this needs JSON\", which is both wrong and the\n\t\t\t opposite of what the form is for. An escape hatch with nothing\n\t\t\t to escape to is not an advanced feature; it is noise on the\n\t\t\t simplest step in the editor. -->\n\t\t\t<details v-if=\"canEditAsJson\" class=\"cn-flow-node-edit__advanced\">\n\t\t\t\t<summary>{{ t('nextcloud-vue', 'Advanced: edit as JSON') }}</summary>\n\t\t\t\t<NcTextArea :modelValue=\"advancedJson\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Configuration (JSON)')\"\n\t\t\t\t\t:error=\"advancedError !== null\"\n\t\t\t\t\t:helperText=\"advancedError || t('nextcloud-vue', 'The full configuration document. The fields above update along.')\"\n\t\t\t\t\trows=\"8\"\n\t\t\t\t\t@update:modelValue=\"onAdvancedInput\" />\n\t\t\t</details>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton variant=\"error\" @click=\"removeStep\">\n\t\t\t\t{{ t('nextcloud-vue', 'Remove step') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton @click=\"cancel\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" :disabled=\"hasErrors\" @click=\"done\">\n\t\t\t\t{{ t('nextcloud-vue', 'Done') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport {\n\tNcButton,\n\tNcCheckboxRadioSwitch,\n\tNcDialog,\n\tNcSelect,\n\tNcTextArea,\n\tNcTextField,\n} from '@nextcloud/vue'\nimport CnCronField from '../components/CnCronField/CnCronField.vue'\nimport { useFlowStore } from '../composables/useFlowStore.js'\n\n/** The verbs an HTTP-shaped `method` option can take. */\n/**\n * The config keys that name WHO a step asks.\n *\n * These are the fields that carried a bare name and meant a user OR a group.\n * `runAs` is deliberately NOT here: it names the identity the step runs as,\n * which is a different question with a different answer type.\n */\nconst PRINCIPAL_KEYS = [\n\t'assignee',\n\t'candidates',\n\t'candidateUsers',\n\t'candidateGroups',\n\t'candidateRole',\n\t'routingFallback',\n]\n\nconst HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']\n\n/**\n * CnFlowNodeEditModal — edit one flow step through a real form.\n *\n * One field per option the ENGINE declares for the step (the catalogue's\n * `configKeys`), plus any key already present on the node, each rendered with\n * a widget that follows the value: switches for booleans, a number field for\n * numbers, a JSON area for structured values, a method picker for `method`.\n * The catalogue publishes no per-key schema, so the widgets are derived — and\n * an Advanced section keeps the whole document editable, which is what keeps\n * every present and future key reachable without this dialog knowing it.\n *\n * Edits land on a DRAFT: Done commits it to `useFlowStore`, Cancel discards\n * it. Opened by setting `useFlowStore().editingNodeId`; hosted by\n * CnFlowDetail so it exists wherever the canvas does.\n */\nexport default {\n\tname: 'CnFlowNodeEditModal',\n\n\tcomponents: {\n\t\tCnCronField,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcDialog,\n\t\tNcSelect,\n\t\tNcTextArea,\n\t\tNcTextField,\n\t},\n\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tdata() {\n\t\tconst node = useFlowStore().editingNode || { config: {} }\n\n\t\treturn {\n\t\t\tHTTP_METHODS,\n\n\t\t\t// Everybody a step could ask, and whether that lookup is in\n\t\t\t// flight. Empty is a legitimate resting state: a stored reference\n\t\t\t// still shows, because it is synthesised rather than looked up.\n\t\t\tprincipals: [],\n\t\t\tprincipalsLoading: false,\n\n\t\t\t// Users for a `runAs`-shaped field, and whether that request is in\n\t\t\t// flight. Empty is a legitimate resting state: the picker still\n\t\t\t// offers the current user, so a schedule is authorable even when\n\t\t\t// the user list cannot be read.\n\t\t\tusers: [],\n\t\t\tusersLoading: false,\n\n\t\t\t// The draft the form edits. Committed on Done, discarded on Cancel.\n\t\t\tdraft: {\n\t\t\t\tname: node.name || '',\n\t\t\t\tconfig: JSON.parse(JSON.stringify(node.config || {})),\n\t\t\t},\n\n\t\t\t// Per-key raw text while a JSON-valued field is mid-edit, so typing\n\t\t\t// is not reformatted under the cursor.\n\t\t\tjsonDrafts: {},\n\t\t\tjsonErrors: {},\n\n\t\t\t// Options per select field, loaded from the `optionsFrom` URL the\n\t\t\t// OWNING APP declares for it in the node's configForm.\n\t\t\tselectOptions: {},\n\t\t\tselectLoading: {},\n\n\t\t\tadvancedDraft: null,\n\t\t\tadvancedError: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {object} The node being edited. Falls back to an empty shape\n\t\t * so a dialog outliving its node cannot throw mid-render.\n\t\t */\n\t\tnode() {\n\t\t\treturn this.store.editingNode || { type: '', config: {} }\n\t\t},\n\n\t\t/**\n\t\t * @return {object|null} The catalogue entry for this node's type.\n\t\t */\n\t\tentry() {\n\t\t\treturn this.store.catalogEntry(this.node.type)\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The dialog title: the step's human name.\n\t\t */\n\t\ttypeLabel() {\n\t\t\treturn this.entry ? (this.entry.displayName || this.entry.id) : (this.node.type || '—')\n\t\t},\n\n\t\t/**\n\t\t * The node's per-field declarations from the catalogue's `configForm`\n\t\t * ({key, label, type, help, required, optionsFrom}), keyed by config\n\t\t * key. `configKeys` is the degraded form. Either way the node's OWNER\n\t\t * declares the vocabulary — this dialog never invents fields.\n\t\t *\n\t\t * @return {object} key → field declaration.\n\t\t */\n\t\tfieldSpecs() {\n\t\t\tconst specs = {}\n\t\t\tfor (const field of (this.entry?.configForm || [])) {\n\t\t\t\tif (field && field.key) {\n\t\t\t\t\tspecs[field.key] = field\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn specs\n\t\t},\n\n\t\t/**\n\t\t * The keys the form renders: the declared vocabulary first, in its\n\t\t * order — `configForm` beats `configKeys` where both exist, keys only\n\t\t * one names are still rendered — then any key already set that neither\n\t\t * declares. `$`-prefixed keys are authoring annotations and stay in\n\t\t * Advanced.\n\t\t *\n\t\t * @return {Array<string>} The keys.\n\t\t */\n\t\t/**\n\t\t * Whether the raw-JSON escape hatch has anything to reach.\n\t\t *\n\t\t * True when the step declares options, or when the node already carries\n\t\t * config the form does not render. False for a step with neither, where\n\t\t * the hatch would only ever show an empty document.\n\t\t *\n\t\t * @return {boolean} Whether to offer the JSON editor.\n\t\t */\n\t\tcanEditAsJson() {\n\t\t\tif (this.formKeys.length > 0) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\treturn Object.keys(this.draft.config || {}).length > 0\n\t\t},\n\n\t\t/**\n\t\t * The signed-in user's id, or '' when there is no session.\n\t\t *\n\t\t * @return {string} The uid.\n\t\t */\n\t\tcurrentUid() {\n\t\t\treturn String(getCurrentUser()?.uid || '')\n\t\t},\n\n\t\t/**\n\t\t * The users offered for a `runAs` field.\n\t\t *\n\t\t * The current user is always present, and first. Naming yourself asserts\n\t\t * no delegation at all, so it is the one choice the server can never\n\t\t * refuse — which makes it both the right default and the right fallback\n\t\t * when the user list could not be read.\n\t\t *\n\t\t * @return {Array<object>} The options.\n\t\t */\n\t\tuserOptions() {\n\t\t\tconst me = this.currentUid\n\t\t\tconst mine = me === '' ? [] : [{ id: me, label: t('nextcloud-vue', '{uid} (you)', { uid: me }) }]\n\n\t\t\treturn [...mine, ...this.users.filter((user) => user.id !== me)]\n\t\t},\n\n\t\t/**\n\t\t * Everybody the picker can currently offer, users and groups together.\n\t\t *\n\t\t * @return {Array<object>} The options.\n\t\t */\n\t\tprincipalChoices() {\n\t\t\treturn this.principals\n\t\t},\n\n\t\tformKeys() {\n\t\t\tconst fromForm = (this.entry?.configForm || []).map((f) => f.key).filter(Boolean)\n\t\t\tconst declared = [\n\t\t\t\t...fromForm,\n\t\t\t\t...(this.entry?.configKeys || []).filter((k) => !fromForm.includes(k)),\n\t\t\t]\n\t\t\tconst present = Object.keys(this.draft.config).filter((k) => !k.startsWith('$') && !declared.includes(k))\n\n\t\t\treturn [...declared, ...present]\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The draft config as pretty JSON, for Advanced.\n\t\t */\n\t\tadvancedJson() {\n\t\t\tif (this.advancedDraft !== null) {\n\t\t\t\treturn this.advancedDraft\n\t\t\t}\n\n\t\t\treturn JSON.stringify(this.draft.config, null, 2)\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether any field holds unparseable JSON.\n\t\t */\n\t\thasErrors() {\n\t\t\treturn this.advancedError !== null || Object.keys(this.jsonErrors).length > 0\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// Select fields need their options; everything else is local.\n\t\tfor (const [key, spec] of Object.entries(this.fieldSpecs)) {\n\t\t\tif (spec?.type === 'select' && spec?.optionsFrom) {\n\t\t\t\tthis.loadSelectOptions(key, spec)\n\t\t\t}\n\t\t}\n\n\t\tif (this.formKeys.some((key) => this.widgetFor(key) === 'principal')) {\n\t\t\t// An empty search returns the first page, so the picker opens with\n\t\t\t// something in it rather than an empty box the author has to guess\n\t\t\t// how to fill.\n\t\t\tthis.searchPrincipals('')\n\t\t}\n\n\t\tif (this.formKeys.some((key) => this.widgetFor(key) === 'user')) {\n\t\t\tthis.loadUsers()\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Load the users a `runAs` field can name.\n\t\t *\n\t\t * Failure is not surfaced. The picker always offers the current user —\n\t\t * see `userOptions` — so a failed lookup costs the author the ability to\n\t\t * DELEGATE, not the ability to author a schedule at all. Blocking the\n\t\t * dialog on it would be a worse trade.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\t/**\n\t\t * The stored references on a field, as options the select can show.\n\t\t *\n\t\t * 🔴 A REFERENCE THE CURRENT USER CANNOT SEE MUST STILL SHOW. The\n\t\t * autocomplete endpoint answers with what THIS author may look up, and\n\t\t * a delegation is routinely to somebody they cannot. If an unmatched\n\t\t * reference rendered as nothing, opening a step and pressing Done would\n\t\t * silently clear it — a save that deletes the assignment while\n\t\t * appearing to change nothing.\n\t\t *\n\t\t * So every stored reference is synthesised into an option whether or\n\t\t * not the lookup found it, exactly as the single-user picker already\n\t\t * does for an unseen uid.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {Array<object>} The selected options.\n\t\t */\n\t\tprincipalOptions(key) {\n\t\t\treturn this.storedPrincipals(key).map((reference) => {\n\t\t\t\tconst id = `${reference.type}:${reference.id}`\n\t\t\t\tconst known = this.principals.find((option) => option.id === id)\n\n\t\t\t\treturn known || {\n\t\t\t\t\tid,\n\t\t\t\t\ttype: reference.type,\n\t\t\t\t\tvalue: reference.id,\n\t\t\t\t\tlabel: reference.id,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * What a performer field currently holds, as `{type, id}` references.\n\t\t *\n\t\t * Reads both spellings, because both are stored across the fleet: a\n\t\t * bare string (which has always meant a user or a group of that name),\n\t\t * and a typed reference. A bare one is shown under the type the FIELD\n\t\t * NAME implies, so a `candidateGroups` entry does not appear as a\n\t\t * person.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {Array<object>} The references.\n\t\t */\n\t\tstoredPrincipals(key) {\n\t\t\tconst raw = this.draft.config[key]\n\t\t\tconst bareType = key === 'candidateGroups' || key === 'candidateRole' ? 'group' : 'user'\n\t\t\tconst list = Array.isArray(raw) ? raw : (raw === null || raw === undefined || raw === '' ? [] : [raw])\n\n\t\t\treturn list\n\t\t\t\t.map((entry) => {\n\t\t\t\t\tif (typeof entry === 'string') {\n\t\t\t\t\t\treturn entry.trim() === '' ? null : { type: bareType, id: entry.trim() }\n\t\t\t\t\t}\n\t\t\t\t\tif (entry && typeof entry === 'object') {\n\t\t\t\t\t\tconst id = String(entry.id ?? entry.value ?? '').trim()\n\n\t\t\t\t\t\treturn id === '' ? null : { type: String(entry.type || bareType), id }\n\t\t\t\t\t}\n\n\t\t\t\t\treturn null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t},\n\n\t\t/**\n\t\t * Write the picked references back onto the field.\n\t\t *\n\t\t * Always as typed references, never as bare strings: the whole point is\n\t\t * that the document records which kind of thing was meant, so a save\n\t\t * from this picker is never ambiguous again.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @param {Array<object>} picked What the select handed back.\n\t\t * @return {void}\n\t\t */\n\t\tsetPrincipals(key, picked) {\n\t\t\tconst references = (picked || []).map((option) => ({\n\t\t\t\ttype: option.type || 'user',\n\t\t\t\tid: option.value || String(option.id || '').split(':').slice(1).join(':'),\n\t\t\t})).filter((reference) => reference.id !== '')\n\n\t\t\t// A single-valued field keeps its shape: `assignee` is one\n\t\t\t// performer, and turning it into a list would change what the\n\t\t\t// engine reads.\n\t\t\tif (key === 'assignee' || key === 'routingFallback' || key === 'candidateRole') {\n\t\t\t\tthis.setKey(key, references[0] || '')\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.setKey(key, references)\n\t\t},\n\n\t\t/**\n\t\t * Look people and groups up as the author types.\n\t\t *\n\t\t * 🔑 THE SAME ENDPOINT THE MENTION PICKER USES, with `shareTypes[]` 0\n\t\t * AND 1 — users and groups. OpenRegister publishes no user list and the\n\t\t * OCS user-listing API is admin-only, so this is the only source a\n\t\t * non-admin author can read.\n\t\t *\n\t\t * ⚠️ IT DOES NOT DECIDE WHAT IS VALID. A type this editor cannot search\n\t\t * — a position, a function, a case role — is still a legal reference,\n\t\t * and the server's resolver registry is what says so. That is why a\n\t\t * stored reference is synthesised rather than filtered against these\n\t\t * results.\n\t\t *\n\t\t * @param {string} query What the author typed.\n\t\t * @return {Promise<void>} When the lookup has settled.\n\t\t */\n\t\tasync searchPrincipals(query) {\n\t\t\tthis.principalsLoading = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.get(\n\t\t\t\t\tgenerateUrl('/ocs/v2.php/core/autocomplete/get'),\n\t\t\t\t\t{\n\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\tsearch: String(query || ''),\n\t\t\t\t\t\t\titemType: '',\n\t\t\t\t\t\t\titemId: '',\n\t\t\t\t\t\t\t'shareTypes[]': [0, 1],\n\t\t\t\t\t\t\tlimit: 50,\n\t\t\t\t\t\t},\n\t\t\t\t\t\theaders: { 'OCS-APIRequest': 'true', Accept: 'application/json' },\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\tconst rows = response?.data?.ocs?.data || []\n\t\t\t\tthis.principals = (Array.isArray(rows) ? rows : [])\n\t\t\t\t\t.map((row) => {\n\t\t\t\t\t\tconst type = row.source === 'groups' ? 'group' : 'user'\n\t\t\t\t\t\tconst id = String(row.id ?? '')\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: `${type}:${id}`,\n\t\t\t\t\t\t\ttype,\n\t\t\t\t\t\t\tvalue: id,\n\t\t\t\t\t\t\tlabel: `${String(row.label ?? id)}${type === 'group' ? this.t('nextcloud-vue', ' (group)') : ''}`,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.filter((option) => option.value !== '')\n\t\t\t} catch {\n\t\t\t\t// A failed lookup must not clear what is already picked: the\n\t\t\t\t// selected options are synthesised from the document, not from\n\t\t\t\t// this list.\n\t\t\t\tthis.principals = []\n\t\t\t} finally {\n\t\t\t\tthis.principalsLoading = false\n\t\t\t}\n\t\t},\n\n\t\tasync loadUsers() {\n\t\t\tthis.usersLoading = true\n\t\t\ttry {\n\t\t\t\t// Nextcloud's OWN autocomplete endpoint — the one the mention\n\t\t\t\t// picker uses. OpenRegister publishes no user list, and the OCS\n\t\t\t\t// user-listing API is admin-only, so this is the only source a\n\t\t\t\t// non-admin author can read. `shareTypes[]=0` restricts it to\n\t\t\t\t// users, keeping groups and circles out of a field that takes a\n\t\t\t\t// single uid.\n\t\t\t\tconst response = await axios.get(\n\t\t\t\t\tgenerateUrl('/ocs/v2.php/core/autocomplete/get'),\n\t\t\t\t\t{\n\t\t\t\t\t\tparams: { search: '', itemType: '', itemId: '', 'shareTypes[]': 0, limit: 50 },\n\t\t\t\t\t\theaders: { 'OCS-APIRequest': 'true', Accept: 'application/json' },\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\tconst rows = response?.data?.ocs?.data || []\n\t\t\t\tthis.users = (Array.isArray(rows) ? rows : [])\n\t\t\t\t\t.filter((row) => row.source === 'users')\n\t\t\t\t\t.map((row) => ({ id: String(row.id ?? ''), label: String(row.label ?? row.id ?? '') }))\n\t\t\t\t\t.filter((row) => row.id !== '')\n\t\t\t} catch {\n\t\t\t\tthis.users = []\n\t\t\t} finally {\n\t\t\t\tthis.usersLoading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The selected user for a key, synthesised when it is not in the list.\n\t\t *\n\t\t * A stored uid the current user cannot see must still SHOW. Dropping it\n\t\t * would make the field look empty and a save would then clear a\n\t\t * delegation the author never touched.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {object|null} The option.\n\t\t */\n\t\tuserOption(key) {\n\t\t\tconst value = String(this.draft.config[key] ?? '')\n\t\t\tif (value === '') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\treturn this.userOptions.find((option) => option.id === value) || { id: value, label: value }\n\t\t},\n\n\t\t/**\n\t\t * Which widget a key gets: the owner's declaration first, the VALUE\n\t\t * second, the key's name last.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {string} 'select' | 'switch' | 'number' | 'textarea' | 'json' | 'method' | 'text'\n\t\t */\n\t\twidgetFor(key) {\n\t\t\tconst spec = this.fieldSpecs[key]\n\t\t\tif (spec?.type === 'select' && spec?.optionsFrom) {\n\t\t\t\treturn 'select'\n\t\t\t}\n\t\t\tif (spec?.type === 'boolean') {\n\t\t\t\treturn 'switch'\n\t\t\t}\n\t\t\tif (spec?.type === 'number') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\tif (spec?.type === 'textarea') {\n\t\t\t\treturn 'textarea'\n\t\t\t}\n\n\t\t\t// Keyed on the engine's own declaration where it makes one, and on\n\t\t\t// the key name otherwise. `cron` and `runAs` are the schedule\n\t\t\t// trigger's required vocabulary — a bare text box for either is a\n\t\t\t// schedule that fails at 03:00, with nobody watching.\n\t\t\tif (spec?.type === 'cron' || spec?.format === 'cron' || key === 'cron') {\n\t\t\t\treturn 'cron'\n\t\t\t}\n\t\t\t// 🔴 `runAs` KEEPS THE SINGLE-USER PICKER. It is not a performer:\n\t\t\t// it names the identity the step RUNS AS, the server decides\n\t\t\t// whether this author may delegate to it, and a group cannot be\n\t\t\t// one. Checked BEFORE the principal branch so a step that declares\n\t\t\t// `runAs` as a principal cannot widen it.\n\t\t\tif (key === 'runAs') {\n\t\t\t\treturn 'user'\n\t\t\t}\n\n\t\t\t// Who the step ASKS. Keyed on the engine's own declaration where it\n\t\t\t// makes one, and on the performer key names otherwise — those are\n\t\t\t// the fields that carried a bare name and meant a user OR a group.\n\t\t\tif (spec?.type === 'principal' || PRINCIPAL_KEYS.includes(key)) {\n\t\t\t\treturn 'principal'\n\t\t\t}\n\n\t\t\tif (spec?.type === 'user') {\n\t\t\t\treturn 'user'\n\t\t\t}\n\n\t\t\tconst value = this.draft.config[key]\n\t\t\tif (typeof value === 'boolean') {\n\t\t\t\treturn 'switch'\n\t\t\t}\n\t\t\tif (typeof value === 'number') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\tif (value !== null && value !== undefined && typeof value === 'object') {\n\t\t\t\treturn 'json'\n\t\t\t}\n\t\t\tif (key === 'method') {\n\t\t\t\treturn 'method'\n\t\t\t}\n\n\t\t\treturn 'text'\n\t\t},\n\n\t\t/**\n\t\t * The picker option matching a select field's stored value —\n\t\t * synthesised from the raw value when it is not in the loaded options,\n\t\t * so an existing configuration is never blanked.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {object|null} The selected option.\n\t\t */\n\t\tselectedOption(key) {\n\t\t\tconst value = this.draft.config[key]\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst options = this.selectOptions[key] || []\n\t\t\treturn options.find((o) => o.id === value) || { id: value, label: String(value) }\n\t\t},\n\n\t\t/**\n\t\t * Load the pickable choices for one select field, from the URL its\n\t\t * owning app declared (`optionsFrom`).\n\t\t *\n\t\t * Accepts `{results: [...]}` or a bare array; items as `{id, label}`,\n\t\t * `{value, label}`, or OpenRegister objects (uuid from `@self`, label\n\t\t * from name/title) — the app owns the endpoint, this dialog only has\n\t\t * to read it.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @param {object} spec The field declaration carrying `optionsFrom`.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadSelectOptions(key, spec) {\n\t\t\tthis.selectLoading = { ...this.selectLoading, [key]: true }\n\t\t\ttry {\n\t\t\t\tconst url = String(spec.optionsFrom)\n\t\t\t\tconst response = await axios.get(url.startsWith('/') && !url.startsWith('/apps') && !url.startsWith('/index.php')\n\t\t\t\t\t? generateUrl(url)\n\t\t\t\t\t: url)\n\t\t\t\tconst rows = Array.isArray(response.data) ? response.data : (response.data?.results || [])\n\t\t\t\tconst options = rows.map((row) => {\n\t\t\t\t\tconst id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tlabel: row.label || row.name || row.title || String(id),\n\t\t\t\t\t}\n\t\t\t\t}).filter((o) => o.id !== undefined && o.id !== null && o.id !== '')\n\t\t\t\tthis.selectOptions = { ...this.selectOptions, [key]: options }\n\t\t\t} catch (error) {\n\t\t\t\t// A picker that could not load degrades to showing the stored\n\t\t\t\t// value; the Advanced editor still reaches everything.\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error(`cn-flow: could not load options for \"${key}\"`, error)\n\t\t\t} finally {\n\t\t\t\tthis.selectLoading = { ...this.selectLoading, [key]: false }\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A key as a field label: the owner's translated `label` when the\n\t\t * configForm declares one, else `sourceId` → `Source id`.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {string} The label.\n\t\t */\n\t\tlabelFor(key) {\n\t\t\tconst declared = this.fieldSpecs[key]?.label\n\t\t\tif (declared) {\n\t\t\t\treturn declared\n\t\t\t}\n\n\t\t\tconst spaced = key\n\t\t\t\t.replace(/[_-]+/g, ' ')\n\t\t\t\t.replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n\t\t\t\t.toLowerCase()\n\n\t\t\treturn spaced.charAt(0).toUpperCase() + spaced.slice(1)\n\t\t},\n\n\t\t/**\n\t\t * The helper line under a field: the owner's `help` (prefixed when the\n\t\t * field is required), else the built-in hints for keys that earn one.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {string|undefined} The helper line.\n\t\t */\n\t\thintFor(key) {\n\t\t\tconst spec = this.fieldSpecs[key]\n\t\t\tif (spec?.help) {\n\t\t\t\treturn spec.required === true\n\t\t\t\t\t? `${this.t('nextcloud-vue', 'Required.')} ${spec.help}`\n\t\t\t\t\t: spec.help\n\t\t\t}\n\t\t\tif (spec?.required === true) {\n\t\t\t\treturn this.t('nextcloud-vue', 'Required.')\n\t\t\t}\n\t\t\tif (key === 'cron') {\n\t\t\t\treturn this.t('nextcloud-vue', 'For example 0 9 * * 1 — 09:00 every Monday.')\n\t\t\t}\n\n\t\t\treturn undefined\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The config key.\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tsetKey(key, value) {\n\t\t\tthis.draft.config = { ...this.draft.config, [key]: value }\n\t\t\tthis.advancedDraft = null\n\t\t\tthis.advancedError = null\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The config key.\n\t\t * @param {string} text The number field's text.\n\t\t * @return {void}\n\t\t */\n\t\tsetNumberKey(key, text) {\n\t\t\tconst parsed = Number(text)\n\t\t\tthis.setKey(key, Number.isFinite(parsed) ? parsed : text)\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The config key.\n\t\t * @return {string} The key's value as JSON text, or its mid-edit draft.\n\t\t */\n\t\tjsonDraftFor(key) {\n\t\t\tif (this.jsonDrafts[key] !== undefined) {\n\t\t\t\treturn this.jsonDrafts[key]\n\t\t\t}\n\n\t\t\treturn JSON.stringify(this.draft.config[key], null, 2)\n\t\t},\n\n\t\t/**\n\t\t * Parse one JSON-valued field, keeping the last valid value on error.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @param {string} text The raw JSON text.\n\t\t * @return {void}\n\t\t */\n\t\tsetJsonKey(key, text) {\n\t\t\tthis.jsonDrafts = { ...this.jsonDrafts, [key]: text }\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(text)\n\t\t\t\tconst rest = { ...this.jsonErrors }\n\t\t\t\tdelete rest[key]\n\t\t\t\tthis.jsonErrors = rest\n\t\t\t\tthis.setKey(key, parsed)\n\t\t\t} catch {\n\t\t\t\tthis.jsonErrors = {\n\t\t\t\t\t...this.jsonErrors,\n\t\t\t\t\t[key]: this.t('nextcloud-vue', 'Not valid JSON, so this option keeps its previous value.'),\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Parse the whole document, keeping the last valid draft on error.\n\t\t *\n\t\t * @param {string} text The raw JSON text.\n\t\t * @return {void}\n\t\t */\n\t\tonAdvancedInput(text) {\n\t\t\tthis.advancedDraft = text\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(text || '{}')\n\t\t\t\tif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\t\tthis.advancedError = this.t('nextcloud-vue', 'A step configuration must be a JSON object.')\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis.advancedError = null\n\t\t\t\tthis.draft.config = parsed\n\t\t\t\tthis.jsonDrafts = {}\n\t\t\t\tthis.jsonErrors = {}\n\t\t\t} catch {\n\t\t\t\tthis.advancedError = this.t('nextcloud-vue', 'Not valid JSON, so the configuration keeps its previous value.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Commit the draft and close.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdone() {\n\t\t\tconst id = this.store.editingNodeId\n\t\t\tif (id !== null) {\n\t\t\t\tthis.store.setNodeConfigById(id, this.draft.config)\n\t\t\t\tthis.store.setNodeName(id, this.draft.name)\n\t\t\t}\n\t\t\tthis.store.editingNodeId = null\n\t\t},\n\n\t\t/**\n\t\t * Discard the draft and close.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcancel() {\n\t\t\tthis.store.editingNodeId = null\n\t\t},\n\n\t\t/**\n\t\t * Remove the step entirely and close.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tremoveStep() {\n\t\t\tconst id = this.store.editingNodeId\n\t\t\tthis.store.editingNodeId = null\n\t\t\tif (id !== null) {\n\t\t\t\tthis.store.removeNode(id)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-node-edit {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding-block-end: 8px;\n}\n\n.cn-flow-node-edit__description {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-node-edit__id,\n.cn-flow-node-edit__hint {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-node-edit__advanced summary {\n\tcursor: pointer;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-block-end: 8px;\n}\n</style>\n"],"names":["_createBlock","_createVNode","_createElementVNode","_toDisplayString","_createElementBlock","_Fragment","_renderList","_createTextVNode"],"mappings":";;AAYM,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,mBAAA,GAAA;;;AAEY,EAAA,KAAA,EAAA,gCAAA;;AAGZ,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,uBAAuB,GAAA;;;AAwGL,EAAA,KAAK,EAAC,yBAAyB;;;;AAgB5B,EAAA,KAAE,EAAA,6BAAA;;;;;;;;;;;;uBAhIhCA;AAAQ,IAAA,IAAI,EAAC,QAAQ;AACrB,IAAA,IAAI,EAAE,QAAS,CAAA,SAAA;AACd,IAAA,SAAM,EAAE,QAAM,CAAA,MAAA;;AAyIL,IAAA,OAAO,UAChB,MAEU;MAFVC,WAEU,CAAA,mBAAA,EAAA;AAFD,QAAA,OAAO,EAAC,OAAO;AAAE,QAAA,OAAI,EAAG,QAAA,CAAA,UAAA;;yBACjC,MAAS;0CAAN,IAAC,CAAA,CAAA,CAAA,eAAA,EAAA,aAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;iBAII,CAAA,mBAAA,EAAA,EAFE,OAAK,EAAE,QAAM,CAAA,MAAA,EAAA,EAAA;yBACvB,MAAS;0CAAN,IAAC,CAAA,CAAA,CAAA,eAAA,EAAA,QAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;MAELA;AAAQ,QAAA,OAAA,EAAA,SAAA;AAAA,QAAA,QAAA,EAAA,QAAA,CAAA,SAAA;AAAA,QAAA,OAAA,EAAA,QAAA,CAAA,IAAA;;;;;;;;qBA/IR,MAsIK;AAtIL,MAAAC,kBAAA,CAsIK,OAtIL,UAsIK,EAAA;SApII,QAAK,CAAA,KAAA,IAAC,QAAA,CAAA,KAAA,CAAA,WAAA;8DACZC,eAAA,CAAA,QAAA,CAAA,KAAA,CAAA,WAAA,CAAA,EAAA,CAAA,YAAA;;AAEH,QAAAD,kBAAA,CAEI,GAFJ,EAAA,UAEI,EADAC,eAAA,CAAA,QAAA,CAAA,IAAI,CAAC,IAAI,CAAA,EAAA,CAAA,YAAA;QAGZF,WAI2C,CAAA,sBAAA,EAAA;UAJ9B,UAAU,EAAE,KAAM,CAAA,KAAA,CAAA,IAAI;;AAElC,UAAA,WAAW,EAAE,QAAS,CAAA,SAAA;oBACZ,EAAE,IAAC,CAAA,CAAA,CAAA,eAAA,EAAA,uDAAA,CAAA;+BACK,EAAC,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAA,KAAA,CAAA,KAAK,CAAC,IAAI,GAAG,MAAM,CAAA,CAAA;;0BAMtCG,kBAwFC,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CAxFgB,QAAQ,CAAA,QAAA,EAAA,CAAf,GAAG,KAAA;+BAAbF,kBAwFC,CAAA,KAAA,EAAA;AAxF2B,YAAA,GAAE,EAAG,GAAG;AAAE,YAAA,KAAE,EAAA,0BAAA;;AACtB,YAAA,CAAA,QAAA,CAAA,SAAA,CAAA,GAAA,CAAA,KAAA,QAAA;8BAAlBJ,WAKsB,CAAA,gCAAA,EAAA;;AAJrB,kBAAA,UAAU,EAAE,KAAA,CAAA,KAAK,CAAC,MAAG,CAAA,GAAA,CAAA,KAAA,IAAA;;AAErB,kBAAA,qBAAiB,EAAE,MAAA,KAAA,QAAA,CAAA,MAAM,CAAC,GAAG,EAAE,MAAK,CAAA,CAAA;;mCACrC,MAAmB;AAAhB,oBAAAO,eAAA,CAAAJ,eAAA,CAAA,QAAA,CAAA,QAAQ,CAAC,GAAG,CAAA,CAAA,EAAA,CAAA,YAAA;;;;AAGK,gBAAA,CAAA,QAAA,CAAA,SAAS,CAAC,GAAG,CAAA,KAAA,QAAA;gCAAjCH,WAKgB,CAAA,mBAAA,EAAA;;AAJf,oBAAA,UAAU,EAAE,KAAA,CAAA,KAAK,CAAC,MAAM,CAAC,GAAG,CAAA,IAAA,IAAA;;oBAE5B,UAAU,EAAE,QAAQ,CAAA,QAAA,CAAC,GAAE,CAAA;AACpB,oBAAA,WAAQ,MAAG,CAAA,CAAA,CAAA,eAAA,EAAA,KAAA,CAAA;yCACC,EAAA,MAAA,KAAA,QAAA,CAAA,MAAA,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;AAKI,kBAAA,CAAA,QAAA,CAAA,SAAS,CAAC,GAAG,CAAA,KAAA,QAAA;kCAAjCA,WAMqB,CAAA,mBAAA,EAAA;;sBALpB,UAAU,EAAE,QAAc,CAAA,cAAA,CAAC,GAAG,CAAA;sBAC9B,OAAO,EAAE,KAAa,CAAA,aAAA,CAAC,GAAG,CAAA,IAAA,EAAA;;sBAE1B,OAAO,EAAC,KAAc,CAAA,aAAA,CAAC,GAAG,CAAA,KAAA,IAAA;AAC1B,sBAAA,WAAW,EAAE,IAAC,CAAA,CAAA,CAAA,eAAA,EAAA,WAAA,CAAA;sBACd,qBAAiB,EAAA,MAAA,KAAE,eAAC,CAAA,GAAA,EAAA,MAAA,GAAA,MAAA,CAAA,EAAA,GAAA,EAAA,CAAA,CAAA;;AAEd,oBAAA,CAAA,QAAA,CAAA,SAAA,CAAA,GAAA,CAAA,KAAA,UAAA;oCAAPA,WAKY,CAAA,qBAAA,EAAA;;AAJX,wBAAA,UAAU,EAAE,MAAM,CAAC,WAAK,CAAC,MAAM,CAAC,GAAG,CAAA,IAAA,EAAA,CAAA;;wBAEnC,YAAY,QAAO,CAAA,OAAA,CAAC,GAAG,CAAA;AACxB,wBAAA,IAAI,EAAC,GAAG;AACP,wBAAA,qBAAW,EAAA,MAAA,KAAA,QAAA,CAAA,MAAA,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;AAEW,sBAAA,CAAA,QAAA,CAAA,SAAS,CAAC,GAAG,CAAA,KAAA,QAAA;iDAIf,CAAA,sBAAA,EAAA;;;AAFrB,0BAAA,IAAI,EAAC,QAAQ;0BACZ,KAAK,EAAE,QAAQ,CAAA,QAAA,CAAC,GAAG,CAAA;AACnB,0BAAA,qBAAiB,EAAE,MAAA,KAAA,QAAA,CAAA,YAAC,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;AAEC,wBAAA,CAAA,QAAA,CAAA,UAAU,GAAG,CAAA,KAAA,MAAA;wCAAnCA,WAMqB,CAAA,qBAAA,EAAA;;4BALpB,UAAM,EAAA,QAAA,CAAA,YAAA,CAAA,GAAA,CAAA;4BACN,KAAK,EAAE,QAAQ,CAAA,QAAA,CAAC,GAAG,CAAA;;AAEnB,4BAAA,YAAY,KAAA,CAAA,UAAU,CAAC,GAAG,KAAK,IAAC,CAAA,CAAA,CAAA,eAAA,EAAA,8BAAA,CAAA;AACjC,4BAAA,IAAI,EAAC,GAAG;AACP,4BAAA,qBAAiB,EAAE,MAAA,KAAA,QAAA,CAAA,UAAC,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;;qDAKsB,CAAA,sBAAA,EAAA;;AAF1C,8BAAA,UAAS,EAAG,MAAM,CAAC,WAAK,CAAE,MAAK,CAAC,GAAG,CAAA,IAAA,EAAA,CAAA;8BAChC,KAAE,EAAE,QAAS,CAAA,QAAA,CAAA,GAAG,CAAA;mDACF,EAAE,MAAA,KAAA,QAAA,CAAA,MAAM,CAAC,GAAG,EAAE,MAAM,CAAA,CAAA;;AAalB,4BAAA,CAAA,QAAA,CAAA,SAAO,CAAA,GAAA,CAAA,KAAA,WAAA;uDAUN,CAAA,mBAAA,EAAA;;gCATpB,UAAU,EAAE,QAAE,CAAA,gBAAA,CAAA,GAAA,CAAA;AACd,gCAAA,OAAM,EAAA,QAAA,CAAA,gBAAA;gCACN,UAAU,EAAE,QAAQ,CAAA,QAAA,CAAC,GAAG,CAAA;AACxB,gCAAA,OAAO,EAAE,KAAiB,CAAA,iBAAA;AAC1B,gCAAA,QAAQ,EAAE,IAAI;AACf,gCAAA,QAAQ,EAAR,EAAQ;;AAEP,gCAAA,aAAW,yBAAyB,GAAG,CAAA,CAAA;AACvC,gCAAA,QAAM,EAAE,QAAgB,CAAA,gBAAA;AACxB,gCAAA,qBAAiB,EAAE,MAAA,KAAA,QAAA,CAAA,aAAC,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;AAED,8BAAA,CAAA,QAAA,CAAA,SAAE,CAAA,GAAA,CAAA,KAAA,MAAA;yDAMD,CAAA,mBAAA,EAAA;;kCALpB,UAAU,EAAE,QAAU,CAAA,UAAA,CAAC,GAAG,CAAA;AAC1B,kCAAA,OAAO,EAAE,QAAW,CAAA,WAAA;;AAEpB,kCAAA,OAAO,EAAE,KAAO,CAAA,YAAA;AAChB,kCAAA,WAAW,EAAE,IAAC,CAAA,CAAA,CAAA,eAAA,EAAA,cAAA,CAAA;kCACd,qBAAiB,EAAA,MAAA,KAAE,eAAC,CAAA,GAAA,EAAA,MAAA,GAAA,MAAA,CAAA,EAAA,GAAA,EAAA,CAAA,CAAA;;8CAErBA,WAI2C,CAAA,sBAAA,EAAA;;AAH1C,kCAAA,UAAE,EAAA,MAAA,CAAA,WAAA,CAAA,MAAA,CAAA,GAAA,CAAA,IAAA,EAAA,CAAA;;kCAEF,UAAU,EAAE,QAAO,CAAA,OAAA,CAAC,GAAI,CAAA;uDACP,4BAAO,CAAE,GAAG,EAAE,MAAK,CAAA,CAAA;;;;AAG7B,QAAA,CAAA,CAAA,QAAA,CAAA,QAAQ,CAAC,MAAM;0BAAzBI,wBAAA,gCACK,CAAA,CAAA,CAAA,eAAA,EAAA,2BAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;SAeU,QAAa,CAAA,aAAA;6CAQlB,qBAAA,EAAA;iCAP4D,iCAAzD,IAAC,CAAA,CAAA,CAAA,eAAA,EAAA,wBAAA,CAAA,CAAA,EAAA,CAAA,YAAA;cACZH,WAKe,CAAA,qBAAA,EAAA;AALR,gBAAA,UAAA,EAAA,QAAA,CAAA,YAAA;AACN,gBAAA,KAAK,EAAE,IAAC,CAAA,CAAA,CAAA,eAAA,EAAA,sBAAA,CAAA;AACR,gBAAA,KAAK,EAAC,KAAA,CAAA,aAAA,KAAA,IAAA;gBACN,UAAC,EAAA,KAAA,CAAA,aAAA,IAAA,IAAA,CAAA,CAAA,CAAA,eAAA,EAAA,iEAAA,CAAA;;AAED,gBAAA,qBAAc,EAAA,QAAA,CAAA,eAAA;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnFlowNodeEditModal.vue3.js","sources":["../../../src/dialogs/CnFlowNodeEditModal.vue?vue&type=template&id=e107484a&lang.js"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<NcDialog size=\"normal\"\n\t\t:name=\"typeLabel\"\n\t\t@closing=\"cancel\">\n\t\t<div class=\"cn-flow-node-edit\">\n\t\t\t<!-- What this step IS, in the engine's own words. -->\n\t\t\t<p v-if=\"entry && entry.description\" class=\"cn-flow-node-edit__description\">\n\t\t\t\t{{ entry.description }}\n\t\t\t</p>\n\t\t\t<p class=\"cn-flow-node-edit__id\">\n\t\t\t\t{{ node.type }}\n\t\t\t</p>\n\n\t\t\t<NcTextField :modelValue=\"draft.name\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Step name')\"\n\t\t\t\t:placeholder=\"typeLabel\"\n\t\t\t\t:helperText=\"t('nextcloud-vue', 'Shown on the card. Leave empty to show the step type.')\"\n\t\t\t\t@update:modelValue=\"draft.name = $event\" />\n\n\t\t\t<!-- One field per option the ENGINE declares for this step\n\t\t\t (`configKeys`), plus any key already set on the node. The\n\t\t\t widget follows the value: switches for booleans, a number\n\t\t\t field for numbers, a JSON area for structured values. -->\n\t\t\t<div v-for=\"key in formKeys\" :key=\"key\" class=\"cn-flow-node-edit__field\">\n\t\t\t\t<NcCheckboxRadioSwitch v-if=\"widgetFor(key) === 'switch'\"\n\t\t\t\t\t:modelValue=\"draft.config[key] === true\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\">\n\t\t\t\t\t{{ labelFor(key) }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<NcSelect v-else-if=\"widgetFor(key) === 'method'\"\n\t\t\t\t\t:modelValue=\"draft.config[key] || null\"\n\t\t\t\t\t:options=\"HTTP_METHODS\"\n\t\t\t\t\t:inputLabel=\"labelFor(key)\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'GET')\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\" />\n\n\t\t\t\t<!-- A `select` field renders as a picker, never a bare text box.\n\t\t\t\t Its choices come from the URL the OWNING APP declared\n\t\t\t\t (`optionsFrom`) or from the form itself (`options`), for a\n\t\t\t\t short fixed vocabulary with no endpoint of its own. -->\n\t\t\t\t<template v-else-if=\"widgetFor(key) === 'select'\">\n\t\t\t\t\t<NcSelect :modelValue=\"selectedOption(key)\"\n\t\t\t\t\t\tclass=\"cn-flow-node-edit__select\"\n\t\t\t\t\t\t:options=\"selectOptions[key] || []\"\n\t\t\t\t\t\t:inputLabel=\"labelFor(key)\"\n\t\t\t\t\t\t:loading=\"selectLoading[key] === true\"\n\t\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Pick one…')\"\n\t\t\t\t\t\t@update:modelValue=\"setKey(key, $event ? $event.id : '')\" />\n\t\t\t\t\t<!-- NcSelect has no helper text of its own, so the help\n\t\t\t\t\t follows the control, the next thing a screen reader\n\t\t\t\t\t reads after it. -->\n\t\t\t\t\t<p v-if=\"hintFor(key)\"\n\t\t\t\t\t\tclass=\"cn-flow-node-edit__field-help\"\n\t\t\t\t\t\t:data-testid=\"`flow-node-help-${key}`\">\n\t\t\t\t\t\t{{ hintFor(key) }}\n\t\t\t\t\t</p>\n\t\t\t\t</template>\n\n\t\t\t\t<NcTextArea v-else-if=\"widgetFor(key) === 'textarea'\"\n\t\t\t\t\t:modelValue=\"String(draft.config[key] ?? '')\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t:helperText=\"hintFor(key)\"\n\t\t\t\t\trows=\"4\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\" />\n\n\t\t\t\t<NcTextField v-else-if=\"widgetFor(key) === 'number'\"\n\t\t\t\t\t:modelValue=\"String(draft.config[key] ?? '')\"\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t@update:modelValue=\"setNumberKey(key, $event)\" />\n\n\t\t\t\t<NcTextArea v-else-if=\"widgetFor(key) === 'json'\"\n\t\t\t\t\t:modelValue=\"jsonDraftFor(key)\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t:error=\"jsonErrors[key] !== undefined\"\n\t\t\t\t\t:helperText=\"jsonErrors[key] || t('nextcloud-vue', 'A structured value, as JSON.')\"\n\t\t\t\t\trows=\"4\"\n\t\t\t\t\t@update:modelValue=\"setJsonKey(key, $event)\" />\n\n\t\t\t\t<CnCronField v-else-if=\"widgetFor(key) === 'cron'\"\n\t\t\t\t\t:modelValue=\"String(draft.config[key] ?? '')\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\" />\n\n\t\t\t\t<!-- `runAs` is a Nextcloud user id, and the SERVER decides\n\t\t\t\t whether this saver may act as that user — see\n\t\t\t\t FlowTriggerValidator::validateDelegation(). The picker\n\t\t\t\t defaults to the current user, which asserts no delegation\n\t\t\t\t at all; naming anyone else is a request the save may\n\t\t\t\t refuse, and refusing it here would only hide the reason. -->\n\t\t\t\t<!-- WHO the step asks. Users AND groups, because the step has\n\t\t\t\t always been able to ask either and the field could only ever\n\t\t\t\t say one word. Each option carries its own TYPE, so\n\t\t\t\t `{type: 'group', id: 'bezwaar'}` is stored as a group and\n\t\t\t\t nothing has to guess later. -->\n\t\t\t\t<NcSelect v-else-if=\"widgetFor(key) === 'principal'\"\n\t\t\t\t\t:modelValue=\"principalOptions(key)\"\n\t\t\t\t\t:options=\"principalChoices\"\n\t\t\t\t\t:inputLabel=\"labelFor(key)\"\n\t\t\t\t\t:loading=\"principalsLoading\"\n\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\tkeepOpen\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Search people and groups…')\"\n\t\t\t\t\t:data-testid=\"`flow-node-principal-${key}`\"\n\t\t\t\t\t@search=\"searchPrincipals\"\n\t\t\t\t\t@update:modelValue=\"setPrincipals(key, $event)\" />\n\n\t\t\t\t<NcSelect v-else-if=\"widgetFor(key) === 'user'\"\n\t\t\t\t\t:modelValue=\"userOption(key)\"\n\t\t\t\t\t:options=\"userOptions\"\n\t\t\t\t\t:inputLabel=\"labelFor(key)\"\n\t\t\t\t\t:loading=\"usersLoading\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Pick a user…')\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event ? $event.id : '')\" />\n\n\t\t\t\t<NcTextField v-else\n\t\t\t\t\t:modelValue=\"String(draft.config[key] ?? '')\"\n\t\t\t\t\t:label=\"labelFor(key)\"\n\t\t\t\t\t:helperText=\"hintFor(key)\"\n\t\t\t\t\t@update:modelValue=\"setKey(key, $event)\" />\n\t\t\t</div>\n\n\t\t\t<p v-if=\"!formKeys.length\" class=\"cn-flow-node-edit__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This step has no options.') }}\n\t\t\t</p>\n\n\t\t\t<!-- The whole document, for anything the fields above cannot say.\n\t\t\t Collapsed: the fields ARE the interface; this is the escape\n\t\t\t hatch that keeps every present and future key reachable.\n\n\t\t\t HIDDEN when it can reach nothing. A step the engine declares no\n\t\t\t options for, carrying no config, was showing \"This step has no\n\t\t\t options\" and then — as the only thing on the dialog you could\n\t\t\t actually touch — a JSON editor containing `{}`. That reads as\n\t\t\t \"configuring this needs JSON\", which is both wrong and the\n\t\t\t opposite of what the form is for. An escape hatch with nothing\n\t\t\t to escape to is not an advanced feature; it is noise on the\n\t\t\t simplest step in the editor. -->\n\t\t\t<details v-if=\"canEditAsJson\" class=\"cn-flow-node-edit__advanced\">\n\t\t\t\t<summary>{{ t('nextcloud-vue', 'Advanced: edit as JSON') }}</summary>\n\t\t\t\t<NcTextArea :modelValue=\"advancedJson\"\n\t\t\t\t\t:label=\"t('nextcloud-vue', 'Configuration (JSON)')\"\n\t\t\t\t\t:error=\"advancedError !== null\"\n\t\t\t\t\t:helperText=\"advancedError || t('nextcloud-vue', 'The full configuration document. The fields above update along.')\"\n\t\t\t\t\trows=\"8\"\n\t\t\t\t\t@update:modelValue=\"onAdvancedInput\" />\n\t\t\t</details>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton variant=\"error\" @click=\"removeStep\">\n\t\t\t\t{{ t('nextcloud-vue', 'Remove step') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton @click=\"cancel\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" :disabled=\"hasErrors\" @click=\"done\">\n\t\t\t\t{{ t('nextcloud-vue', 'Done') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport {\n\tNcButton,\n\tNcCheckboxRadioSwitch,\n\tNcDialog,\n\tNcSelect,\n\tNcTextArea,\n\tNcTextField,\n} from '@nextcloud/vue'\nimport CnCronField from '../components/CnCronField/CnCronField.vue'\nimport { useFlowStore } from '../composables/useFlowStore.js'\nimport { hasStaticOptions, normaliseSelectOptions } from '../utils/selectOptions.js'\n\n/** The verbs an HTTP-shaped `method` option can take. */\n/**\n * The config keys that name WHO a step asks.\n *\n * These are the fields that carried a bare name and meant a user OR a group.\n * `runAs` is deliberately NOT here: it names the identity the step runs as,\n * which is a different question with a different answer type.\n */\nconst PRINCIPAL_KEYS = [\n\t'assignee',\n\t'candidates',\n\t'candidateUsers',\n\t'candidateGroups',\n\t'candidateRole',\n\t'routingFallback',\n]\n\nconst HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']\n\n/**\n * CnFlowNodeEditModal — edit one flow step through a real form.\n *\n * One field per option the ENGINE declares for the step (the catalogue's\n * `configKeys`), plus any key already present on the node, each rendered with\n * a widget that follows the value: switches for booleans, a number field for\n * numbers, a JSON area for structured values, a method picker for `method`.\n * The catalogue publishes no per-key schema, so the widgets are derived — and\n * an Advanced section keeps the whole document editable, which is what keeps\n * every present and future key reachable without this dialog knowing it.\n *\n * Edits land on a DRAFT: Done commits it to `useFlowStore`, Cancel discards\n * it. Opened by setting `useFlowStore().editingNodeId`; hosted by\n * CnFlowDetail so it exists wherever the canvas does.\n */\nexport default {\n\tname: 'CnFlowNodeEditModal',\n\n\tcomponents: {\n\t\tCnCronField,\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcDialog,\n\t\tNcSelect,\n\t\tNcTextArea,\n\t\tNcTextField,\n\t},\n\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tdata() {\n\t\tconst node = useFlowStore().editingNode || { config: {} }\n\n\t\treturn {\n\t\t\tHTTP_METHODS,\n\n\t\t\t// Everybody a step could ask, and whether that lookup is in\n\t\t\t// flight. Empty is a legitimate resting state: a stored reference\n\t\t\t// still shows, because it is synthesised rather than looked up.\n\t\t\tprincipals: [],\n\t\t\tprincipalsLoading: false,\n\n\t\t\t// Users for a `runAs`-shaped field, and whether that request is in\n\t\t\t// flight. Empty is a legitimate resting state: the picker still\n\t\t\t// offers the current user, so a schedule is authorable even when\n\t\t\t// the user list cannot be read.\n\t\t\tusers: [],\n\t\t\tusersLoading: false,\n\n\t\t\t// The draft the form edits. Committed on Done, discarded on Cancel.\n\t\t\tdraft: {\n\t\t\t\tname: node.name || '',\n\t\t\t\tconfig: JSON.parse(JSON.stringify(node.config || {})),\n\t\t\t},\n\n\t\t\t// Per-key raw text while a JSON-valued field is mid-edit, so typing\n\t\t\t// is not reformatted under the cursor.\n\t\t\tjsonDrafts: {},\n\t\t\tjsonErrors: {},\n\n\t\t\t// Options per select field, loaded from the `optionsFrom` URL the\n\t\t\t// OWNING APP declares for it in the node's configForm.\n\t\t\tselectOptions: {},\n\t\t\tselectLoading: {},\n\n\t\t\tadvancedDraft: null,\n\t\t\tadvancedError: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {object} The node being edited. Falls back to an empty shape\n\t\t * so a dialog outliving its node cannot throw mid-render.\n\t\t */\n\t\tnode() {\n\t\t\treturn this.store.editingNode || { type: '', config: {} }\n\t\t},\n\n\t\t/**\n\t\t * @return {object|null} The catalogue entry for this node's type.\n\t\t */\n\t\tentry() {\n\t\t\treturn this.store.catalogEntry(this.node.type)\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The dialog title: the step's human name.\n\t\t */\n\t\ttypeLabel() {\n\t\t\treturn this.entry ? (this.entry.displayName || this.entry.id) : (this.node.type || '—')\n\t\t},\n\n\t\t/**\n\t\t * The node's per-field declarations from the catalogue's `configForm`\n\t\t * ({key, label, type, help, required, optionsFrom, options}), keyed by config\n\t\t * key. `configKeys` is the degraded form. Either way the node's OWNER\n\t\t * declares the vocabulary — this dialog never invents fields.\n\t\t *\n\t\t * @return {object} key → field declaration.\n\t\t */\n\t\tfieldSpecs() {\n\t\t\tconst specs = {}\n\t\t\tfor (const field of (this.entry?.configForm || [])) {\n\t\t\t\tif (field && field.key) {\n\t\t\t\t\tspecs[field.key] = field\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn specs\n\t\t},\n\n\t\t/**\n\t\t * The keys the form renders: the declared vocabulary first, in its\n\t\t * order — `configForm` beats `configKeys` where both exist, keys only\n\t\t * one names are still rendered — then any key already set that neither\n\t\t * declares. `$`-prefixed keys are authoring annotations and stay in\n\t\t * Advanced.\n\t\t *\n\t\t * @return {Array<string>} The keys.\n\t\t */\n\t\t/**\n\t\t * Whether the raw-JSON escape hatch has anything to reach.\n\t\t *\n\t\t * True when the step declares options, or when the node already carries\n\t\t * config the form does not render. False for a step with neither, where\n\t\t * the hatch would only ever show an empty document.\n\t\t *\n\t\t * @return {boolean} Whether to offer the JSON editor.\n\t\t */\n\t\tcanEditAsJson() {\n\t\t\tif (this.formKeys.length > 0) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\treturn Object.keys(this.draft.config || {}).length > 0\n\t\t},\n\n\t\t/**\n\t\t * The signed-in user's id, or '' when there is no session.\n\t\t *\n\t\t * @return {string} The uid.\n\t\t */\n\t\tcurrentUid() {\n\t\t\treturn String(getCurrentUser()?.uid || '')\n\t\t},\n\n\t\t/**\n\t\t * The users offered for a `runAs` field.\n\t\t *\n\t\t * The current user is always present, and first. Naming yourself asserts\n\t\t * no delegation at all, so it is the one choice the server can never\n\t\t * refuse — which makes it both the right default and the right fallback\n\t\t * when the user list could not be read.\n\t\t *\n\t\t * @return {Array<object>} The options.\n\t\t */\n\t\tuserOptions() {\n\t\t\tconst me = this.currentUid\n\t\t\tconst mine = me === '' ? [] : [{ id: me, label: t('nextcloud-vue', '{uid} (you)', { uid: me }) }]\n\n\t\t\treturn [...mine, ...this.users.filter((user) => user.id !== me)]\n\t\t},\n\n\t\t/**\n\t\t * Everybody the picker can currently offer, users and groups together.\n\t\t *\n\t\t * @return {Array<object>} The options.\n\t\t */\n\t\tprincipalChoices() {\n\t\t\treturn this.principals\n\t\t},\n\n\t\tformKeys() {\n\t\t\tconst fromForm = (this.entry?.configForm || []).map((f) => f.key).filter(Boolean)\n\t\t\tconst declared = [\n\t\t\t\t...fromForm,\n\t\t\t\t...(this.entry?.configKeys || []).filter((k) => !fromForm.includes(k)),\n\t\t\t]\n\t\t\tconst present = Object.keys(this.draft.config).filter((k) => !k.startsWith('$') && !declared.includes(k))\n\n\t\t\treturn [...declared, ...present]\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The draft config as pretty JSON, for Advanced.\n\t\t */\n\t\tadvancedJson() {\n\t\t\tif (this.advancedDraft !== null) {\n\t\t\t\treturn this.advancedDraft\n\t\t\t}\n\n\t\t\treturn JSON.stringify(this.draft.config, null, 2)\n\t\t},\n\n\t\t/**\n\t\t * @return {boolean} Whether any field holds unparseable JSON.\n\t\t */\n\t\thasErrors() {\n\t\t\treturn this.advancedError !== null || Object.keys(this.jsonErrors).length > 0\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// Select fields need their options; everything else is local.\n\t\tfor (const [key, spec] of Object.entries(this.fieldSpecs)) {\n\t\t\tif (hasStaticOptions(spec)) {\n\t\t\t\tthis.selectOptions = { ...this.selectOptions, [key]: normaliseSelectOptions(spec.options) }\n\t\t\t} else if (spec?.type === 'select' && spec?.optionsFrom) {\n\t\t\t\tthis.loadSelectOptions(key, spec)\n\t\t\t}\n\t\t}\n\n\t\tif (this.formKeys.some((key) => this.widgetFor(key) === 'principal')) {\n\t\t\t// An empty search returns the first page, so the picker opens with\n\t\t\t// something in it rather than an empty box the author has to guess\n\t\t\t// how to fill.\n\t\t\tthis.searchPrincipals('')\n\t\t}\n\n\t\tif (this.formKeys.some((key) => this.widgetFor(key) === 'user')) {\n\t\t\tthis.loadUsers()\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Load the users a `runAs` field can name.\n\t\t *\n\t\t * Failure is not surfaced. The picker always offers the current user —\n\t\t * see `userOptions` — so a failed lookup costs the author the ability to\n\t\t * DELEGATE, not the ability to author a schedule at all. Blocking the\n\t\t * dialog on it would be a worse trade.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\t/**\n\t\t * The stored references on a field, as options the select can show.\n\t\t *\n\t\t * 🔴 A REFERENCE THE CURRENT USER CANNOT SEE MUST STILL SHOW. The\n\t\t * autocomplete endpoint answers with what THIS author may look up, and\n\t\t * a delegation is routinely to somebody they cannot. If an unmatched\n\t\t * reference rendered as nothing, opening a step and pressing Done would\n\t\t * silently clear it — a save that deletes the assignment while\n\t\t * appearing to change nothing.\n\t\t *\n\t\t * So every stored reference is synthesised into an option whether or\n\t\t * not the lookup found it, exactly as the single-user picker already\n\t\t * does for an unseen uid.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {Array<object>} The selected options.\n\t\t */\n\t\tprincipalOptions(key) {\n\t\t\treturn this.storedPrincipals(key).map((reference) => {\n\t\t\t\tconst id = `${reference.type}:${reference.id}`\n\t\t\t\tconst known = this.principals.find((option) => option.id === id)\n\n\t\t\t\treturn known || {\n\t\t\t\t\tid,\n\t\t\t\t\ttype: reference.type,\n\t\t\t\t\tvalue: reference.id,\n\t\t\t\t\tlabel: reference.id,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * What a performer field currently holds, as `{type, id}` references.\n\t\t *\n\t\t * Reads both spellings, because both are stored across the fleet: a\n\t\t * bare string (which has always meant a user or a group of that name),\n\t\t * and a typed reference. A bare one is shown under the type the FIELD\n\t\t * NAME implies, so a `candidateGroups` entry does not appear as a\n\t\t * person.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {Array<object>} The references.\n\t\t */\n\t\tstoredPrincipals(key) {\n\t\t\tconst raw = this.draft.config[key]\n\t\t\tconst bareType = key === 'candidateGroups' || key === 'candidateRole' ? 'group' : 'user'\n\t\t\tconst list = Array.isArray(raw) ? raw : (raw === null || raw === undefined || raw === '' ? [] : [raw])\n\n\t\t\treturn list\n\t\t\t\t.map((entry) => {\n\t\t\t\t\tif (typeof entry === 'string') {\n\t\t\t\t\t\treturn entry.trim() === '' ? null : { type: bareType, id: entry.trim() }\n\t\t\t\t\t}\n\t\t\t\t\tif (entry && typeof entry === 'object') {\n\t\t\t\t\t\tconst id = String(entry.id ?? entry.value ?? '').trim()\n\n\t\t\t\t\t\treturn id === '' ? null : { type: String(entry.type || bareType), id }\n\t\t\t\t\t}\n\n\t\t\t\t\treturn null\n\t\t\t\t})\n\t\t\t\t.filter(Boolean)\n\t\t},\n\n\t\t/**\n\t\t * Write the picked references back onto the field.\n\t\t *\n\t\t * Always as typed references, never as bare strings: the whole point is\n\t\t * that the document records which kind of thing was meant, so a save\n\t\t * from this picker is never ambiguous again.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @param {Array<object>} picked What the select handed back.\n\t\t * @return {void}\n\t\t */\n\t\tsetPrincipals(key, picked) {\n\t\t\tconst references = (picked || []).map((option) => ({\n\t\t\t\ttype: option.type || 'user',\n\t\t\t\tid: option.value || String(option.id || '').split(':').slice(1).join(':'),\n\t\t\t})).filter((reference) => reference.id !== '')\n\n\t\t\t// A single-valued field keeps its shape: `assignee` is one\n\t\t\t// performer, and turning it into a list would change what the\n\t\t\t// engine reads.\n\t\t\tif (key === 'assignee' || key === 'routingFallback' || key === 'candidateRole') {\n\t\t\t\tthis.setKey(key, references[0] || '')\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.setKey(key, references)\n\t\t},\n\n\t\t/**\n\t\t * Look people and groups up as the author types.\n\t\t *\n\t\t * 🔑 THE SAME ENDPOINT THE MENTION PICKER USES, with `shareTypes[]` 0\n\t\t * AND 1 — users and groups. OpenRegister publishes no user list and the\n\t\t * OCS user-listing API is admin-only, so this is the only source a\n\t\t * non-admin author can read.\n\t\t *\n\t\t * ⚠️ IT DOES NOT DECIDE WHAT IS VALID. A type this editor cannot search\n\t\t * — a position, a function, a case role — is still a legal reference,\n\t\t * and the server's resolver registry is what says so. That is why a\n\t\t * stored reference is synthesised rather than filtered against these\n\t\t * results.\n\t\t *\n\t\t * @param {string} query What the author typed.\n\t\t * @return {Promise<void>} When the lookup has settled.\n\t\t */\n\t\tasync searchPrincipals(query) {\n\t\t\tthis.principalsLoading = true\n\t\t\ttry {\n\t\t\t\tconst response = await axios.get(\n\t\t\t\t\tgenerateUrl('/ocs/v2.php/core/autocomplete/get'),\n\t\t\t\t\t{\n\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\tsearch: String(query || ''),\n\t\t\t\t\t\t\titemType: '',\n\t\t\t\t\t\t\titemId: '',\n\t\t\t\t\t\t\t'shareTypes[]': [0, 1],\n\t\t\t\t\t\t\tlimit: 50,\n\t\t\t\t\t\t},\n\t\t\t\t\t\theaders: { 'OCS-APIRequest': 'true', Accept: 'application/json' },\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\tconst rows = response?.data?.ocs?.data || []\n\t\t\t\tthis.principals = (Array.isArray(rows) ? rows : [])\n\t\t\t\t\t.map((row) => {\n\t\t\t\t\t\tconst type = row.source === 'groups' ? 'group' : 'user'\n\t\t\t\t\t\tconst id = String(row.id ?? '')\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: `${type}:${id}`,\n\t\t\t\t\t\t\ttype,\n\t\t\t\t\t\t\tvalue: id,\n\t\t\t\t\t\t\tlabel: `${String(row.label ?? id)}${type === 'group' ? this.t('nextcloud-vue', ' (group)') : ''}`,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.filter((option) => option.value !== '')\n\t\t\t} catch {\n\t\t\t\t// A failed lookup must not clear what is already picked: the\n\t\t\t\t// selected options are synthesised from the document, not from\n\t\t\t\t// this list.\n\t\t\t\tthis.principals = []\n\t\t\t} finally {\n\t\t\t\tthis.principalsLoading = false\n\t\t\t}\n\t\t},\n\n\t\tasync loadUsers() {\n\t\t\tthis.usersLoading = true\n\t\t\ttry {\n\t\t\t\t// Nextcloud's OWN autocomplete endpoint — the one the mention\n\t\t\t\t// picker uses. OpenRegister publishes no user list, and the OCS\n\t\t\t\t// user-listing API is admin-only, so this is the only source a\n\t\t\t\t// non-admin author can read. `shareTypes[]=0` restricts it to\n\t\t\t\t// users, keeping groups and circles out of a field that takes a\n\t\t\t\t// single uid.\n\t\t\t\tconst response = await axios.get(\n\t\t\t\t\tgenerateUrl('/ocs/v2.php/core/autocomplete/get'),\n\t\t\t\t\t{\n\t\t\t\t\t\tparams: { search: '', itemType: '', itemId: '', 'shareTypes[]': 0, limit: 50 },\n\t\t\t\t\t\theaders: { 'OCS-APIRequest': 'true', Accept: 'application/json' },\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\tconst rows = response?.data?.ocs?.data || []\n\t\t\t\tthis.users = (Array.isArray(rows) ? rows : [])\n\t\t\t\t\t.filter((row) => row.source === 'users')\n\t\t\t\t\t.map((row) => ({ id: String(row.id ?? ''), label: String(row.label ?? row.id ?? '') }))\n\t\t\t\t\t.filter((row) => row.id !== '')\n\t\t\t} catch {\n\t\t\t\tthis.users = []\n\t\t\t} finally {\n\t\t\t\tthis.usersLoading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The selected user for a key, synthesised when it is not in the list.\n\t\t *\n\t\t * A stored uid the current user cannot see must still SHOW. Dropping it\n\t\t * would make the field look empty and a save would then clear a\n\t\t * delegation the author never touched.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {object|null} The option.\n\t\t */\n\t\tuserOption(key) {\n\t\t\tconst value = String(this.draft.config[key] ?? '')\n\t\t\tif (value === '') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\treturn this.userOptions.find((option) => option.id === value) || { id: value, label: value }\n\t\t},\n\n\t\t/**\n\t\t * Which widget a key gets: the owner's declaration first, the VALUE\n\t\t * second, the key's name last.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {string} 'select' | 'switch' | 'number' | 'textarea' | 'json' | 'method' | 'text'\n\t\t */\n\t\twidgetFor(key) {\n\t\t\tconst spec = this.fieldSpecs[key]\n\t\t\tif (spec?.type === 'select' && (spec?.optionsFrom || hasStaticOptions(spec))) {\n\t\t\t\treturn 'select'\n\t\t\t}\n\t\t\tif (spec?.type === 'boolean') {\n\t\t\t\treturn 'switch'\n\t\t\t}\n\t\t\tif (spec?.type === 'number') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\tif (spec?.type === 'textarea') {\n\t\t\t\treturn 'textarea'\n\t\t\t}\n\n\t\t\t// Keyed on the engine's own declaration where it makes one, and on\n\t\t\t// the key name otherwise. `cron` and `runAs` are the schedule\n\t\t\t// trigger's required vocabulary — a bare text box for either is a\n\t\t\t// schedule that fails at 03:00, with nobody watching.\n\t\t\tif (spec?.type === 'cron' || spec?.format === 'cron' || key === 'cron') {\n\t\t\t\treturn 'cron'\n\t\t\t}\n\t\t\t// 🔴 `runAs` KEEPS THE SINGLE-USER PICKER. It is not a performer:\n\t\t\t// it names the identity the step RUNS AS, the server decides\n\t\t\t// whether this author may delegate to it, and a group cannot be\n\t\t\t// one. Checked BEFORE the principal branch so a step that declares\n\t\t\t// `runAs` as a principal cannot widen it.\n\t\t\tif (key === 'runAs') {\n\t\t\t\treturn 'user'\n\t\t\t}\n\n\t\t\t// Who the step ASKS. Keyed on the engine's own declaration where it\n\t\t\t// makes one, and on the performer key names otherwise — those are\n\t\t\t// the fields that carried a bare name and meant a user OR a group.\n\t\t\tif (spec?.type === 'principal' || PRINCIPAL_KEYS.includes(key)) {\n\t\t\t\treturn 'principal'\n\t\t\t}\n\n\t\t\tif (spec?.type === 'user') {\n\t\t\t\treturn 'user'\n\t\t\t}\n\n\t\t\tconst value = this.draft.config[key]\n\t\t\tif (typeof value === 'boolean') {\n\t\t\t\treturn 'switch'\n\t\t\t}\n\t\t\tif (typeof value === 'number') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\tif (value !== null && value !== undefined && typeof value === 'object') {\n\t\t\t\treturn 'json'\n\t\t\t}\n\t\t\tif (key === 'method') {\n\t\t\t\treturn 'method'\n\t\t\t}\n\n\t\t\treturn 'text'\n\t\t},\n\n\t\t/**\n\t\t * The picker option matching a select field's stored value —\n\t\t * synthesised from the raw value when it is not in the loaded options,\n\t\t * so an existing configuration is never blanked.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {object|null} The selected option.\n\t\t */\n\t\tselectedOption(key) {\n\t\t\tconst value = this.draft.config[key]\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst options = this.selectOptions[key] || []\n\t\t\treturn options.find((o) => o.id === value) || { id: value, label: String(value) }\n\t\t},\n\n\t\t/**\n\t\t * Load the pickable choices for one select field, from the URL its\n\t\t * owning app declared (`optionsFrom`).\n\t\t *\n\t\t * Accepts `{results: [...]}` or a bare array; items as `{id, label}`,\n\t\t * `{value, label}`, or OpenRegister objects (uuid from `@self`, label\n\t\t * from name/title) — the app owns the endpoint, this dialog only has\n\t\t * to read it.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @param {object} spec The field declaration carrying `optionsFrom`.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadSelectOptions(key, spec) {\n\t\t\tthis.selectLoading = { ...this.selectLoading, [key]: true }\n\t\t\ttry {\n\t\t\t\tconst url = String(spec.optionsFrom)\n\t\t\t\tconst response = await axios.get(url.startsWith('/') && !url.startsWith('/apps') && !url.startsWith('/index.php')\n\t\t\t\t\t? generateUrl(url)\n\t\t\t\t\t: url)\n\t\t\t\tconst rows = Array.isArray(response.data) ? response.data : (response.data?.results || [])\n\t\t\t\tconst options = normaliseSelectOptions(rows)\n\t\t\t\tthis.selectOptions = { ...this.selectOptions, [key]: options }\n\t\t\t} catch (error) {\n\t\t\t\t// A picker that could not load degrades to showing the stored\n\t\t\t\t// value; the Advanced editor still reaches everything.\n\t\t\t\t// eslint-disable-next-line no-console -- diagnostic for a failure this code already degrades from\n\t\t\t\tconsole.error(`cn-flow: could not load options for \"${key}\"`, error)\n\t\t\t} finally {\n\t\t\t\tthis.selectLoading = { ...this.selectLoading, [key]: false }\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * A key as a field label: the owner's translated `label` when the\n\t\t * configForm declares one, else `sourceId` → `Source id`.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {string} The label.\n\t\t */\n\t\tlabelFor(key) {\n\t\t\tconst declared = this.fieldSpecs[key]?.label\n\t\t\tif (declared) {\n\t\t\t\treturn declared\n\t\t\t}\n\n\t\t\tconst spaced = key\n\t\t\t\t.replace(/[_-]+/g, ' ')\n\t\t\t\t.replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n\t\t\t\t.toLowerCase()\n\n\t\t\treturn spaced.charAt(0).toUpperCase() + spaced.slice(1)\n\t\t},\n\n\t\t/**\n\t\t * The helper line under a field: the owner's `help` (prefixed when the\n\t\t * field is required), else the built-in hints for keys that earn one.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @return {string|undefined} The helper line.\n\t\t */\n\t\thintFor(key) {\n\t\t\tconst spec = this.fieldSpecs[key]\n\t\t\tif (spec?.help) {\n\t\t\t\treturn spec.required === true\n\t\t\t\t\t? `${this.t('nextcloud-vue', 'Required.')} ${spec.help}`\n\t\t\t\t\t: spec.help\n\t\t\t}\n\t\t\tif (spec?.required === true) {\n\t\t\t\treturn this.t('nextcloud-vue', 'Required.')\n\t\t\t}\n\t\t\tif (key === 'cron') {\n\t\t\t\treturn this.t('nextcloud-vue', 'For example 0 9 * * 1 — 09:00 every Monday.')\n\t\t\t}\n\n\t\t\treturn undefined\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The config key.\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tsetKey(key, value) {\n\t\t\tthis.draft.config = { ...this.draft.config, [key]: value }\n\t\t\tthis.advancedDraft = null\n\t\t\tthis.advancedError = null\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The config key.\n\t\t * @param {string} text The number field's text.\n\t\t * @return {void}\n\t\t */\n\t\tsetNumberKey(key, text) {\n\t\t\tconst parsed = Number(text)\n\t\t\tthis.setKey(key, Number.isFinite(parsed) ? parsed : text)\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The config key.\n\t\t * @return {string} The key's value as JSON text, or its mid-edit draft.\n\t\t */\n\t\tjsonDraftFor(key) {\n\t\t\tif (this.jsonDrafts[key] !== undefined) {\n\t\t\t\treturn this.jsonDrafts[key]\n\t\t\t}\n\n\t\t\treturn JSON.stringify(this.draft.config[key], null, 2)\n\t\t},\n\n\t\t/**\n\t\t * Parse one JSON-valued field, keeping the last valid value on error.\n\t\t *\n\t\t * @param {string} key The config key.\n\t\t * @param {string} text The raw JSON text.\n\t\t * @return {void}\n\t\t */\n\t\tsetJsonKey(key, text) {\n\t\t\tthis.jsonDrafts = { ...this.jsonDrafts, [key]: text }\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(text)\n\t\t\t\tconst rest = { ...this.jsonErrors }\n\t\t\t\tdelete rest[key]\n\t\t\t\tthis.jsonErrors = rest\n\t\t\t\tthis.setKey(key, parsed)\n\t\t\t} catch {\n\t\t\t\tthis.jsonErrors = {\n\t\t\t\t\t...this.jsonErrors,\n\t\t\t\t\t[key]: this.t('nextcloud-vue', 'Not valid JSON, so this option keeps its previous value.'),\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Parse the whole document, keeping the last valid draft on error.\n\t\t *\n\t\t * @param {string} text The raw JSON text.\n\t\t * @return {void}\n\t\t */\n\t\tonAdvancedInput(text) {\n\t\t\tthis.advancedDraft = text\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(text || '{}')\n\t\t\t\tif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\t\t\tthis.advancedError = this.t('nextcloud-vue', 'A step configuration must be a JSON object.')\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis.advancedError = null\n\t\t\t\tthis.draft.config = parsed\n\t\t\t\tthis.jsonDrafts = {}\n\t\t\t\tthis.jsonErrors = {}\n\t\t\t} catch {\n\t\t\t\tthis.advancedError = this.t('nextcloud-vue', 'Not valid JSON, so the configuration keeps its previous value.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Commit the draft and close.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdone() {\n\t\t\tconst id = this.store.editingNodeId\n\t\t\tif (id !== null) {\n\t\t\t\tthis.store.setNodeConfigById(id, this.draft.config)\n\t\t\t\tthis.store.setNodeName(id, this.draft.name)\n\t\t\t}\n\t\t\tthis.store.editingNodeId = null\n\t\t},\n\n\t\t/**\n\t\t * Discard the draft and close.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcancel() {\n\t\t\tthis.store.editingNodeId = null\n\t\t},\n\n\t\t/**\n\t\t * Remove the step entirely and close.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tremoveStep() {\n\t\t\tconst id = this.store.editingNodeId\n\t\t\tthis.store.editingNodeId = null\n\t\t\tif (id !== null) {\n\t\t\t\tthis.store.removeNode(id)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-flow-node-edit {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding-block-end: 8px;\n}\n\n.cn-flow-node-edit__description {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-node-edit__id,\n.cn-flow-node-edit__hint {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Full width, like the text fields around it: at its natural width the\n picker cut every option label in half. */\n.cn-flow-node-edit__select {\n\twidth: 100%;\n}\n\n.cn-flow-node-edit__field-help {\n\tmargin: 4px 0 0;\n\tpadding-inline-start: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-node-edit__advanced summary {\n\tcursor: pointer;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-block-end: 8px;\n}\n</style>\n"],"names":["_createBlock","_createVNode","_createElementVNode","_toDisplayString","_createElementBlock","_Fragment","_renderList","_createTextVNode"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { translate } from '@nextcloud/l10n';
2
2
  import { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue';
3
3
  import { prefixUrl } from '../utils/headers.js';
4
+ import { hasStaticOptions, normaliseSelectOptions } from '../utils/selectOptions.js';
4
5
 
5
6
  /**
6
7
  * The dialog `run-node` opens to collect a directly-invoked flow node's own
@@ -41,7 +42,9 @@ var script = {
41
42
  title: { type: String, default: '' },
42
43
  /**
43
44
  * The node's declared config form — `IFlowNodeConfigForm::configForm()`'s
44
- * shape, unchanged: `[{key, label, type, help?, required?, optionsFrom?}]`.
45
+ * shape, unchanged: `[{key, label, type, help?, required?, optionsFrom?, options?}]`.
46
+ * A `select` takes its choices from `optionsFrom` (a url) or `options`
47
+ * (strings, or `{value, label}` pairs declared in the form itself).
45
48
  *
46
49
  * @type {Array<object>}
47
50
  */
@@ -90,7 +93,9 @@ var script = {
90
93
  }
91
94
  this.values = seed;
92
95
  for (const field of (fields || [])) {
93
- if (field.type === 'select' && field.optionsFrom) {
96
+ if (hasStaticOptions(field)) {
97
+ this.options = { ...this.options, [field.key]: normaliseSelectOptions(field.options, (s) => this.tr(s)) };
98
+ } else if (field.type === 'select' && field.optionsFrom) {
94
99
  this.loadOptions(field);
95
100
  }
96
101
  }
@@ -187,10 +192,7 @@ var script = {
187
192
  const url = prefixUrl(raw);
188
193
  const response = await axios.get(url);
189
194
  const rows = Array.isArray(response.data) ? response.data : (response.data?.results || []);
190
- const opts = rows.map((row) => {
191
- const id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid;
192
- return { id, label: row.label || row.name || row.title || String(id) }
193
- }).filter((o) => o.id !== undefined && o.id !== null && o.id !== '');
195
+ const opts = normaliseSelectOptions(rows);
194
196
  this.options = { ...this.options, [field.key]: opts };
195
197
  } catch (error) {
196
198
  // A picker that fails to load degrades to an empty list rather
@@ -1 +1 @@
1
- {"version":3,"file":"CnRunNodeDialog.vue2.js","sources":["../../../src/dialogs/CnRunNodeDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n -->\n\n<template>\n\t<NcDialog\n\t\t:name=\"tr(title)\"\n\t\tsize=\"normal\"\n\t\t:noClose=\"loading\"\n\t\tdata-testid=\"cn-run-node-dialog\"\n\t\t@closing=\"$emit('close')\">\n\t\t<NcNoteCard v-if=\"loadError\" type=\"error\" data-testid=\"cn-run-node-dialog-error\">\n\t\t\t{{ loadError }}\n\t\t</NcNoteCard>\n\n\t\t<div v-else class=\"cn-run-node-dialog__form\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in fields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\t:data-cn-field=\"field.key\"\n\t\t\t\tclass=\"cn-run-node-dialog__field\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"field.type === 'boolean'\"\n\t\t\t\t\t:modelValue=\"values[field.key] === true\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\">\n\t\t\t\t\t{{ tr(field.label) }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\t:modelValue=\"selectedOption(field)\"\n\t\t\t\t\t:options=\"optionsFor(field)\"\n\t\t\t\t\t:inputLabel=\"tr(field.label)\"\n\t\t\t\t\t:loading=\"optionsLoading[field.key] === true\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Pick one…')\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event ? $event.id : '')\" />\n\n\t\t\t\t<NcTextArea\n\t\t\t\t\tv-else-if=\"field.type === 'textarea'\"\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\trows=\"4\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\" />\n\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else-if=\"field.type === 'number'\"\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\t@update:modelValue=\"setNumberValue(field.key, $event)\" />\n\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\" />\n\t\t\t</div>\n\n\t\t\t<p v-if=\"!fields.length\" class=\"cn-run-node-dialog__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This step has no options.') }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton :disabled=\"loading\" @click=\"$emit('close')\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"!loadError\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"loading || !requiredFieldsFilled\"\n\t\t\t\t@click=\"$emit('confirm', values)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Run') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'\nimport { prefixUrl } from '../utils/headers.js'\n\n/**\n * The dialog `run-node` opens to collect a directly-invoked flow node's own\n * declared config, before POSTing to OpenRegister's\n * `/api/flows/{flowId}/nodes/{nodeId}/run` (manifest-run-node-action / RN-4).\n *\n * WHY THIS IS A NEW, SMALL RENDERER AND NOT `CnFlowNodeEditModal`'S ONE\n * ----------------------------------------------------------------------\n * design.md says \"render it with the SAME field renderer CnFlowSidebar\n * [reads: CnFlowNodeEditModal] uses\" as the INTENT — no new form-rendering\n * VOCABULARY — and this component honours that: the type-to-widget mapping\n * (switch/select/textarea/number/text) and the `optionsFrom` fetch-and-\n * normalise behaviour below are the SAME rules that modal applies. What it\n * does NOT reuse is that modal's component/methods directly, because that\n * renderer solves a materially different problem: it guesses a widget for\n * ANY engine config key via `widgetFor()` heuristics (including keys no\n * `configForm()` ever declared — cron expressions, HTTP methods, principal\n * pickers, a `runAs` identity field, a raw-JSON fallback), because the flow\n * EDITOR must let an author set every key the engine reads whether or not\n * its type ever described one. A run-node CALLER dialog has no such need —\n * it renders ONLY the fields `configForm()` explicitly declares (this\n * interface's whole contract, per its own docblock: \"a field over a key the\n * node ignores looks like it works and changes nothing\") — text / textarea /\n * number / boolean / select, nothing else. Extracting a shared component\n * from the editor's modal is a reasonable follow-up; it was not done here to\n * avoid an invasive refactor of an unrelated, already-shipped surface for a\n * dialog whose actual field vocabulary is a fifth its size.\n *\n * @spec openspec/changes/manifest-run-node-action/specs/manifest-run-node-action/spec.md#requirement-the-nodes-own-config-form-drives-the-dialog-not-a-new-token\n */\nexport default {\n\tname: 'CnRunNodeDialog',\n\n\tcomponents: { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField },\n\n\tprops: {\n\t\t/** Pre-translated dialog title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/**\n\t\t * The node's declared config form — `IFlowNodeConfigForm::configForm()`'s\n\t\t * shape, unchanged: `[{key, label, type, help?, required?, optionsFrom?}]`.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tfields: { type: Array, default: () => [] },\n\t\t/** Whether a run is in flight — disables Cancel/Run and shows a spinner. */\n\t\tloading: { type: Boolean, default: false },\n\t\t/** Set when the node's form could not be resolved; replaces the form with an error and hides Run. */\n\t\tloadError: { type: String, default: '' },\n\t\t/** The consumer's bound `t()`, matching CnActionButtons' own `cnTranslate` inject. */\n\t\ttranslate: { type: Function, default: null },\n\t},\n\n\temits: ['close', 'confirm'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Live field values, keyed by field.key. */\n\t\t\tvalues: {},\n\t\t\t/** Fetched `{id, label}` options per select field.key. */\n\t\t\toptions: {},\n\t\t\t/** In-flight flag per select field.key. */\n\t\t\toptionsLoading: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Every `required: true` field carries a non-empty value. */\n\t\trequiredFieldsFilled() {\n\t\t\treturn this.fields.every((field) => {\n\t\t\t\tif (field.required !== true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tconst value = this.values[field.key]\n\t\t\t\treturn value !== undefined && value !== null && value !== ''\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tfields: {\n\t\t\timmediate: true,\n\t\t\thandler(fields) {\n\t\t\t\tconst seed = {}\n\t\t\t\tfor (const field of (fields || [])) {\n\t\t\t\t\tseed[field.key] = field.type === 'boolean' ? false : ''\n\t\t\t\t}\n\t\t\t\tthis.values = seed\n\t\t\t\tfor (const field of (fields || [])) {\n\t\t\t\t\tif (field.type === 'select' && field.optionsFrom) {\n\t\t\t\t\t\tthis.loadOptions(field)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Resolve a manifest/node-authored UI string through the host translate\n\t\t * function, matching CnActionButtons' own `tr()`.\n\t\t *\n\t\t * @param {string} [value] The English source string.\n\t\t * @return {string|undefined} The translated (or source) string.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\treturn typeof this.translate === 'function' ? this.translate(value) : value\n\t\t},\n\n\t\t/**\n\t\t * Write one field's value.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tsetValue(key, value) {\n\t\t\tthis.values = { ...this.values, [key]: value }\n\t\t},\n\n\t\t/**\n\t\t * Write a number field, coercing an empty string to '' (not 0 — an\n\t\t * empty required-number field must still fail `requiredFieldsFilled`)\n\t\t * and anything else to a JS number.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @param {string} raw The NcTextField string value.\n\t\t * @return {void}\n\t\t */\n\t\tsetNumberValue(key, raw) {\n\t\t\tthis.setValue(key, raw === '' ? '' : Number(raw))\n\t\t},\n\n\t\t/**\n\t\t * The NcSelect-shaped `{id, label}` currently selected for a field, or\n\t\t * null. Falls back to `{id: value, label: String(value)}` when the\n\t\t * stored value is not (yet, or no longer) among the loaded options —\n\t\t * the same graceful-degrade `CnFlowNodeEditModal` applies.\n\t\t *\n\t\t * @param {object} field The field declaration.\n\t\t * @return {object|null}\n\t\t */\n\t\tselectedOption(field) {\n\t\t\tconst value = this.values[field.key]\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst opts = this.options[field.key] || []\n\t\t\treturn opts.find((o) => o.id === value) || { id: value, label: String(value) }\n\t\t},\n\n\t\t/**\n\t\t * The loaded `{id, label}` options for a select field.\n\t\t *\n\t\t * @param {object} field The field declaration.\n\t\t * @return {Array<object>}\n\t\t */\n\t\toptionsFor(field) {\n\t\t\treturn this.options[field.key] || []\n\t\t},\n\n\t\t/**\n\t\t * Fetch and normalise a select field's `optionsFrom` catalogue —\n\t\t * SAME acceptance shape as `CnFlowNodeEditModal`'s own picker (design.md's\n\t\t * point: reuse the FORM CONTRACT, not necessarily the component): a bare\n\t\t * array or `{results: [...]}`; each row read as `{id, label}`,\n\t\t * `{value, label}`, or an OpenRegister object (`@self.uuid`/`uuid`,\n\t\t * `name`/`title`). A row this dialog cannot make sense of is dropped\n\t\t * rather than shown as a blank option.\n\t\t *\n\t\t * @param {object} field The field declaration carrying `optionsFrom`.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions(field) {\n\t\t\tthis.optionsLoading = { ...this.optionsLoading, [field.key]: true }\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }] = await Promise.all([import('@nextcloud/axios')])\n\t\t\t\tconst raw = String(field.optionsFrom)\n\t\t\t\tconst url = prefixUrl(raw)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tconst rows = Array.isArray(response.data) ? response.data : (response.data?.results || [])\n\t\t\t\tconst opts = rows.map((row) => {\n\t\t\t\t\tconst id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid\n\t\t\t\t\treturn { id, label: row.label || row.name || row.title || String(id) }\n\t\t\t\t}).filter((o) => o.id !== undefined && o.id !== null && o.id !== '')\n\t\t\t\tthis.options = { ...this.options, [field.key]: opts }\n\t\t\t} catch (error) {\n\t\t\t\t// A picker that fails to load degrades to an empty list rather\n\t\t\t\t// than blocking the whole dialog — the field just shows no\n\t\t\t\t// choices, which is honest about what happened.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error(`cn-run-node-dialog: could not load options for \"${field.key}\"`, error)\n\t\t\t} finally {\n\t\t\t\tthis.optionsLoading = { ...this.optionsLoading, [field.key]: false }\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-run-node-dialog__form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-run-node-dialog__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnRunNodeDialog.vue2.js","sources":["../../../src/dialogs/CnRunNodeDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n -->\n\n<template>\n\t<NcDialog\n\t\t:name=\"tr(title)\"\n\t\tsize=\"normal\"\n\t\t:noClose=\"loading\"\n\t\tdata-testid=\"cn-run-node-dialog\"\n\t\t@closing=\"$emit('close')\">\n\t\t<NcNoteCard v-if=\"loadError\" type=\"error\" data-testid=\"cn-run-node-dialog-error\">\n\t\t\t{{ loadError }}\n\t\t</NcNoteCard>\n\n\t\t<div v-else class=\"cn-run-node-dialog__form\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in fields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\t:data-cn-field=\"field.key\"\n\t\t\t\tclass=\"cn-run-node-dialog__field\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"field.type === 'boolean'\"\n\t\t\t\t\t:modelValue=\"values[field.key] === true\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\">\n\t\t\t\t\t{{ tr(field.label) }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\t:modelValue=\"selectedOption(field)\"\n\t\t\t\t\t:options=\"optionsFor(field)\"\n\t\t\t\t\t:inputLabel=\"tr(field.label)\"\n\t\t\t\t\t:loading=\"optionsLoading[field.key] === true\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Pick one…')\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event ? $event.id : '')\" />\n\n\t\t\t\t<NcTextArea\n\t\t\t\t\tv-else-if=\"field.type === 'textarea'\"\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\trows=\"4\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\" />\n\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else-if=\"field.type === 'number'\"\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\t@update:modelValue=\"setNumberValue(field.key, $event)\" />\n\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\" />\n\t\t\t</div>\n\n\t\t\t<p v-if=\"!fields.length\" class=\"cn-run-node-dialog__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This step has no options.') }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton :disabled=\"loading\" @click=\"$emit('close')\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"!loadError\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"loading || !requiredFieldsFilled\"\n\t\t\t\t@click=\"$emit('confirm', values)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Run') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'\nimport { prefixUrl } from '../utils/headers.js'\nimport { hasStaticOptions, normaliseSelectOptions } from '../utils/selectOptions.js'\n\n/**\n * The dialog `run-node` opens to collect a directly-invoked flow node's own\n * declared config, before POSTing to OpenRegister's\n * `/api/flows/{flowId}/nodes/{nodeId}/run` (manifest-run-node-action / RN-4).\n *\n * WHY THIS IS A NEW, SMALL RENDERER AND NOT `CnFlowNodeEditModal`'S ONE\n * ----------------------------------------------------------------------\n * design.md says \"render it with the SAME field renderer CnFlowSidebar\n * [reads: CnFlowNodeEditModal] uses\" as the INTENT — no new form-rendering\n * VOCABULARY — and this component honours that: the type-to-widget mapping\n * (switch/select/textarea/number/text) and the `optionsFrom` fetch-and-\n * normalise behaviour below are the SAME rules that modal applies. What it\n * does NOT reuse is that modal's component/methods directly, because that\n * renderer solves a materially different problem: it guesses a widget for\n * ANY engine config key via `widgetFor()` heuristics (including keys no\n * `configForm()` ever declared — cron expressions, HTTP methods, principal\n * pickers, a `runAs` identity field, a raw-JSON fallback), because the flow\n * EDITOR must let an author set every key the engine reads whether or not\n * its type ever described one. A run-node CALLER dialog has no such need —\n * it renders ONLY the fields `configForm()` explicitly declares (this\n * interface's whole contract, per its own docblock: \"a field over a key the\n * node ignores looks like it works and changes nothing\") — text / textarea /\n * number / boolean / select, nothing else. Extracting a shared component\n * from the editor's modal is a reasonable follow-up; it was not done here to\n * avoid an invasive refactor of an unrelated, already-shipped surface for a\n * dialog whose actual field vocabulary is a fifth its size.\n *\n * @spec openspec/changes/manifest-run-node-action/specs/manifest-run-node-action/spec.md#requirement-the-nodes-own-config-form-drives-the-dialog-not-a-new-token\n */\nexport default {\n\tname: 'CnRunNodeDialog',\n\n\tcomponents: { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField },\n\n\tprops: {\n\t\t/** Pre-translated dialog title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/**\n\t\t * The node's declared config form — `IFlowNodeConfigForm::configForm()`'s\n\t\t * shape, unchanged: `[{key, label, type, help?, required?, optionsFrom?, options?}]`.\n\t\t * A `select` takes its choices from `optionsFrom` (a url) or `options`\n\t\t * (strings, or `{value, label}` pairs declared in the form itself).\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tfields: { type: Array, default: () => [] },\n\t\t/** Whether a run is in flight — disables Cancel/Run and shows a spinner. */\n\t\tloading: { type: Boolean, default: false },\n\t\t/** Set when the node's form could not be resolved; replaces the form with an error and hides Run. */\n\t\tloadError: { type: String, default: '' },\n\t\t/** The consumer's bound `t()`, matching CnActionButtons' own `cnTranslate` inject. */\n\t\ttranslate: { type: Function, default: null },\n\t},\n\n\temits: ['close', 'confirm'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Live field values, keyed by field.key. */\n\t\t\tvalues: {},\n\t\t\t/** Fetched `{id, label}` options per select field.key. */\n\t\t\toptions: {},\n\t\t\t/** In-flight flag per select field.key. */\n\t\t\toptionsLoading: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Every `required: true` field carries a non-empty value. */\n\t\trequiredFieldsFilled() {\n\t\t\treturn this.fields.every((field) => {\n\t\t\t\tif (field.required !== true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tconst value = this.values[field.key]\n\t\t\t\treturn value !== undefined && value !== null && value !== ''\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tfields: {\n\t\t\timmediate: true,\n\t\t\thandler(fields) {\n\t\t\t\tconst seed = {}\n\t\t\t\tfor (const field of (fields || [])) {\n\t\t\t\t\tseed[field.key] = field.type === 'boolean' ? false : ''\n\t\t\t\t}\n\t\t\t\tthis.values = seed\n\t\t\t\tfor (const field of (fields || [])) {\n\t\t\t\t\tif (hasStaticOptions(field)) {\n\t\t\t\t\t\tthis.options = { ...this.options, [field.key]: normaliseSelectOptions(field.options, (s) => this.tr(s)) }\n\t\t\t\t\t} else if (field.type === 'select' && field.optionsFrom) {\n\t\t\t\t\t\tthis.loadOptions(field)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Resolve a manifest/node-authored UI string through the host translate\n\t\t * function, matching CnActionButtons' own `tr()`.\n\t\t *\n\t\t * @param {string} [value] The English source string.\n\t\t * @return {string|undefined} The translated (or source) string.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\treturn typeof this.translate === 'function' ? this.translate(value) : value\n\t\t},\n\n\t\t/**\n\t\t * Write one field's value.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tsetValue(key, value) {\n\t\t\tthis.values = { ...this.values, [key]: value }\n\t\t},\n\n\t\t/**\n\t\t * Write a number field, coercing an empty string to '' (not 0 — an\n\t\t * empty required-number field must still fail `requiredFieldsFilled`)\n\t\t * and anything else to a JS number.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @param {string} raw The NcTextField string value.\n\t\t * @return {void}\n\t\t */\n\t\tsetNumberValue(key, raw) {\n\t\t\tthis.setValue(key, raw === '' ? '' : Number(raw))\n\t\t},\n\n\t\t/**\n\t\t * The NcSelect-shaped `{id, label}` currently selected for a field, or\n\t\t * null. Falls back to `{id: value, label: String(value)}` when the\n\t\t * stored value is not (yet, or no longer) among the loaded options —\n\t\t * the same graceful-degrade `CnFlowNodeEditModal` applies.\n\t\t *\n\t\t * @param {object} field The field declaration.\n\t\t * @return {object|null}\n\t\t */\n\t\tselectedOption(field) {\n\t\t\tconst value = this.values[field.key]\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst opts = this.options[field.key] || []\n\t\t\treturn opts.find((o) => o.id === value) || { id: value, label: String(value) }\n\t\t},\n\n\t\t/**\n\t\t * The loaded `{id, label}` options for a select field.\n\t\t *\n\t\t * @param {object} field The field declaration.\n\t\t * @return {Array<object>}\n\t\t */\n\t\toptionsFor(field) {\n\t\t\treturn this.options[field.key] || []\n\t\t},\n\n\t\t/**\n\t\t * Fetch and normalise a select field's `optionsFrom` catalogue —\n\t\t * SAME acceptance shape as `CnFlowNodeEditModal`'s own picker (design.md's\n\t\t * point: reuse the FORM CONTRACT, not necessarily the component): a bare\n\t\t * array or `{results: [...]}`; each row read as `{id, label}`,\n\t\t * `{value, label}`, or an OpenRegister object (`@self.uuid`/`uuid`,\n\t\t * `name`/`title`). A row this dialog cannot make sense of is dropped\n\t\t * rather than shown as a blank option.\n\t\t *\n\t\t * @param {object} field The field declaration carrying `optionsFrom`.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions(field) {\n\t\t\tthis.optionsLoading = { ...this.optionsLoading, [field.key]: true }\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }] = await Promise.all([import('@nextcloud/axios')])\n\t\t\t\tconst raw = String(field.optionsFrom)\n\t\t\t\tconst url = prefixUrl(raw)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tconst rows = Array.isArray(response.data) ? response.data : (response.data?.results || [])\n\t\t\t\tconst opts = normaliseSelectOptions(rows)\n\t\t\t\tthis.options = { ...this.options, [field.key]: opts }\n\t\t\t} catch (error) {\n\t\t\t\t// A picker that fails to load degrades to an empty list rather\n\t\t\t\t// than blocking the whole dialog — the field just shows no\n\t\t\t\t// choices, which is honest about what happened.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error(`cn-run-node-dialog: could not load options for \"${field.key}\"`, error)\n\t\t\t} finally {\n\t\t\t\tthis.optionsLoading = { ...this.optionsLoading, [field.key]: false }\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-run-node-dialog__form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-run-node-dialog__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnRunNodeDialog.vue3.js","sources":["../../../src/dialogs/CnRunNodeDialog.vue?vue&type=template&id=3eb7c55d&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n -->\n\n<template>\n\t<NcDialog\n\t\t:name=\"tr(title)\"\n\t\tsize=\"normal\"\n\t\t:noClose=\"loading\"\n\t\tdata-testid=\"cn-run-node-dialog\"\n\t\t@closing=\"$emit('close')\">\n\t\t<NcNoteCard v-if=\"loadError\" type=\"error\" data-testid=\"cn-run-node-dialog-error\">\n\t\t\t{{ loadError }}\n\t\t</NcNoteCard>\n\n\t\t<div v-else class=\"cn-run-node-dialog__form\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in fields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\t:data-cn-field=\"field.key\"\n\t\t\t\tclass=\"cn-run-node-dialog__field\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"field.type === 'boolean'\"\n\t\t\t\t\t:modelValue=\"values[field.key] === true\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\">\n\t\t\t\t\t{{ tr(field.label) }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\t:modelValue=\"selectedOption(field)\"\n\t\t\t\t\t:options=\"optionsFor(field)\"\n\t\t\t\t\t:inputLabel=\"tr(field.label)\"\n\t\t\t\t\t:loading=\"optionsLoading[field.key] === true\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Pick one…')\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event ? $event.id : '')\" />\n\n\t\t\t\t<NcTextArea\n\t\t\t\t\tv-else-if=\"field.type === 'textarea'\"\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\trows=\"4\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\" />\n\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else-if=\"field.type === 'number'\"\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\t@update:modelValue=\"setNumberValue(field.key, $event)\" />\n\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\" />\n\t\t\t</div>\n\n\t\t\t<p v-if=\"!fields.length\" class=\"cn-run-node-dialog__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This step has no options.') }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton :disabled=\"loading\" @click=\"$emit('close')\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"!loadError\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"loading || !requiredFieldsFilled\"\n\t\t\t\t@click=\"$emit('confirm', values)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Run') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'\nimport { prefixUrl } from '../utils/headers.js'\n\n/**\n * The dialog `run-node` opens to collect a directly-invoked flow node's own\n * declared config, before POSTing to OpenRegister's\n * `/api/flows/{flowId}/nodes/{nodeId}/run` (manifest-run-node-action / RN-4).\n *\n * WHY THIS IS A NEW, SMALL RENDERER AND NOT `CnFlowNodeEditModal`'S ONE\n * ----------------------------------------------------------------------\n * design.md says \"render it with the SAME field renderer CnFlowSidebar\n * [reads: CnFlowNodeEditModal] uses\" as the INTENT — no new form-rendering\n * VOCABULARY — and this component honours that: the type-to-widget mapping\n * (switch/select/textarea/number/text) and the `optionsFrom` fetch-and-\n * normalise behaviour below are the SAME rules that modal applies. What it\n * does NOT reuse is that modal's component/methods directly, because that\n * renderer solves a materially different problem: it guesses a widget for\n * ANY engine config key via `widgetFor()` heuristics (including keys no\n * `configForm()` ever declared — cron expressions, HTTP methods, principal\n * pickers, a `runAs` identity field, a raw-JSON fallback), because the flow\n * EDITOR must let an author set every key the engine reads whether or not\n * its type ever described one. A run-node CALLER dialog has no such need —\n * it renders ONLY the fields `configForm()` explicitly declares (this\n * interface's whole contract, per its own docblock: \"a field over a key the\n * node ignores looks like it works and changes nothing\") — text / textarea /\n * number / boolean / select, nothing else. Extracting a shared component\n * from the editor's modal is a reasonable follow-up; it was not done here to\n * avoid an invasive refactor of an unrelated, already-shipped surface for a\n * dialog whose actual field vocabulary is a fifth its size.\n *\n * @spec openspec/changes/manifest-run-node-action/specs/manifest-run-node-action/spec.md#requirement-the-nodes-own-config-form-drives-the-dialog-not-a-new-token\n */\nexport default {\n\tname: 'CnRunNodeDialog',\n\n\tcomponents: { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField },\n\n\tprops: {\n\t\t/** Pre-translated dialog title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/**\n\t\t * The node's declared config form — `IFlowNodeConfigForm::configForm()`'s\n\t\t * shape, unchanged: `[{key, label, type, help?, required?, optionsFrom?}]`.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tfields: { type: Array, default: () => [] },\n\t\t/** Whether a run is in flight — disables Cancel/Run and shows a spinner. */\n\t\tloading: { type: Boolean, default: false },\n\t\t/** Set when the node's form could not be resolved; replaces the form with an error and hides Run. */\n\t\tloadError: { type: String, default: '' },\n\t\t/** The consumer's bound `t()`, matching CnActionButtons' own `cnTranslate` inject. */\n\t\ttranslate: { type: Function, default: null },\n\t},\n\n\temits: ['close', 'confirm'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Live field values, keyed by field.key. */\n\t\t\tvalues: {},\n\t\t\t/** Fetched `{id, label}` options per select field.key. */\n\t\t\toptions: {},\n\t\t\t/** In-flight flag per select field.key. */\n\t\t\toptionsLoading: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Every `required: true` field carries a non-empty value. */\n\t\trequiredFieldsFilled() {\n\t\t\treturn this.fields.every((field) => {\n\t\t\t\tif (field.required !== true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tconst value = this.values[field.key]\n\t\t\t\treturn value !== undefined && value !== null && value !== ''\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tfields: {\n\t\t\timmediate: true,\n\t\t\thandler(fields) {\n\t\t\t\tconst seed = {}\n\t\t\t\tfor (const field of (fields || [])) {\n\t\t\t\t\tseed[field.key] = field.type === 'boolean' ? false : ''\n\t\t\t\t}\n\t\t\t\tthis.values = seed\n\t\t\t\tfor (const field of (fields || [])) {\n\t\t\t\t\tif (field.type === 'select' && field.optionsFrom) {\n\t\t\t\t\t\tthis.loadOptions(field)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Resolve a manifest/node-authored UI string through the host translate\n\t\t * function, matching CnActionButtons' own `tr()`.\n\t\t *\n\t\t * @param {string} [value] The English source string.\n\t\t * @return {string|undefined} The translated (or source) string.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\treturn typeof this.translate === 'function' ? this.translate(value) : value\n\t\t},\n\n\t\t/**\n\t\t * Write one field's value.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tsetValue(key, value) {\n\t\t\tthis.values = { ...this.values, [key]: value }\n\t\t},\n\n\t\t/**\n\t\t * Write a number field, coercing an empty string to '' (not 0 — an\n\t\t * empty required-number field must still fail `requiredFieldsFilled`)\n\t\t * and anything else to a JS number.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @param {string} raw The NcTextField string value.\n\t\t * @return {void}\n\t\t */\n\t\tsetNumberValue(key, raw) {\n\t\t\tthis.setValue(key, raw === '' ? '' : Number(raw))\n\t\t},\n\n\t\t/**\n\t\t * The NcSelect-shaped `{id, label}` currently selected for a field, or\n\t\t * null. Falls back to `{id: value, label: String(value)}` when the\n\t\t * stored value is not (yet, or no longer) among the loaded options —\n\t\t * the same graceful-degrade `CnFlowNodeEditModal` applies.\n\t\t *\n\t\t * @param {object} field The field declaration.\n\t\t * @return {object|null}\n\t\t */\n\t\tselectedOption(field) {\n\t\t\tconst value = this.values[field.key]\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst opts = this.options[field.key] || []\n\t\t\treturn opts.find((o) => o.id === value) || { id: value, label: String(value) }\n\t\t},\n\n\t\t/**\n\t\t * The loaded `{id, label}` options for a select field.\n\t\t *\n\t\t * @param {object} field The field declaration.\n\t\t * @return {Array<object>}\n\t\t */\n\t\toptionsFor(field) {\n\t\t\treturn this.options[field.key] || []\n\t\t},\n\n\t\t/**\n\t\t * Fetch and normalise a select field's `optionsFrom` catalogue —\n\t\t * SAME acceptance shape as `CnFlowNodeEditModal`'s own picker (design.md's\n\t\t * point: reuse the FORM CONTRACT, not necessarily the component): a bare\n\t\t * array or `{results: [...]}`; each row read as `{id, label}`,\n\t\t * `{value, label}`, or an OpenRegister object (`@self.uuid`/`uuid`,\n\t\t * `name`/`title`). A row this dialog cannot make sense of is dropped\n\t\t * rather than shown as a blank option.\n\t\t *\n\t\t * @param {object} field The field declaration carrying `optionsFrom`.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions(field) {\n\t\t\tthis.optionsLoading = { ...this.optionsLoading, [field.key]: true }\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }] = await Promise.all([import('@nextcloud/axios')])\n\t\t\t\tconst raw = String(field.optionsFrom)\n\t\t\t\tconst url = prefixUrl(raw)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tconst rows = Array.isArray(response.data) ? response.data : (response.data?.results || [])\n\t\t\t\tconst opts = rows.map((row) => {\n\t\t\t\t\tconst id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid\n\t\t\t\t\treturn { id, label: row.label || row.name || row.title || String(id) }\n\t\t\t\t}).filter((o) => o.id !== undefined && o.id !== null && o.id !== '')\n\t\t\t\tthis.options = { ...this.options, [field.key]: opts }\n\t\t\t} catch (error) {\n\t\t\t\t// A picker that fails to load degrades to an empty list rather\n\t\t\t\t// than blocking the whole dialog — the field just shows no\n\t\t\t\t// choices, which is honest about what happened.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error(`cn-run-node-dialog: could not load options for \"${field.key}\"`, error)\n\t\t\t} finally {\n\t\t\t\tthis.optionsLoading = { ...this.optionsLoading, [field.key]: false }\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-run-node-dialog__form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-run-node-dialog__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createVNode","_createBlock","_openBlock","_createElementBlock","_Fragment","_renderList","_toDisplayString"],"mappings":";;;;AAqBc,EAAA,KAAK,EAAC,0BAAiB;;;;;AA+CjB,EAAA,KAAA,EAAA,0BAAA;;;;;;;;;;;;;;IAxDjB,IAAI,EAAE,QAAE,CAAA,EAAA,CAAC,MAAK,CAAA,KAAA,CAAA;QACX,EAAC,QAAO;AACX,IAAA,OAAO,EAAE,MAAE,CAAA,OAAA;;AAEX,IAAA,SAAO,uCAAE,IAAK,CAAA,KAAA,CAAA,OAAA,CAAA,CAAA,CAAA;;AAyDJ,IAAA,OAAO,UAChB,MAEU;MAFVA,WAEU,CAAA,mBAAA,EAAA;AAFA,QAAA,QAAM,EAAA,MAAA,CAAA,OAAA;AAAA,QAAA,OAAA,uCAAA,IAAA,CAAA,KAAA,CAAA,OAAA,CAAA,CAAA,CAAA;;yBAChB,MAAkC;0CAA/B,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,QAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;QAGG,MAAS,CAAA,SAAA;wBADhBC;;AAEA,YAAA,OAAO,EAAC,SAAE;YACT,QAAQ,EAAE,MAAO,CAAA,OAAA,IAAA,CAAK,QAAO,CAAA,oBAAA;YAC7B,OAAK,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,IAAC,CAAA,KAAA,CAAA,SAAA,EAAA,KAAA,CAAA,MAAA,CAAA,CAAA,CAAA;;AACD,YAAA,IAAA,UACP,MAAM;eAAA,MAAA,CAAA,OAAA;gCAANA,WAAM,CAAA,wBAAA,EAAA;;AAAA,oBAAA,IAAA,EAAA,EAAA;;;;;;;;;;;qBAlER,MAEY;OAFT,MAAA,CAAA,SAAA;wBAAHA,WAEY,CAAA,qBAAA,EAAA;;AAFT,YAAA,IAAA,EAAA,OAAA;AAAA,YAAA,aAAA,EAAA,0BAAA;;6BACF,MAAc;8CAAZ,MAAS,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;;;AAGX,WAAAC,SAAA,EAAA,EAAAC,kBAAA,CAkDI,OAlDJ,UAkDI,EAAA;8BAjDJA,kBA4CG,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CA3Ca,MAAM,CAAA,MAAA,EAAA,CAAf,KAAK,KAAA;mCADZF,kBA4CG,CAAA,KAAA,EAAA;gBA1CF,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,eAAY,EAAA,KAAA,CAAA,GAAA;qBACR,EAAC,2BAA2B;;AAE1B,gBAAA,CAAA,KAAK,CAAC,IAAI,KAAA,SAAA;kCADhBF,WAMuB,CAAA,gCAAA,EAAA;;;AAHvB,sBAAA,IAAI,EAAC,QAAE;AACN,sBAAA,qBAAiB,aAAE,QAAQ,CAAA,QAAA,CAAC,KAAK,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;uCAClC,MAAqB;wDAAlB,QAAE,CAAA,EAAA,CAAC,KAAK,CAAC,KAAK,CAAA,CAAA,EAAA,CAAA,YAAA;;;;AAIN,oBAAA,CAAA,KAAK,CAAC,IAAI,KAAA,QAAA;oCADrBA,WAOuB,CAAA,mBAAA,EAAA;;wBALtB,UAAU,EAAE,QAAc,CAAA,cAAA,CAAC,KAAK,CAAA;;AAEhC,wBAAA,UAAQ,EAAA,QAAA,CAAA,EAAA,CAAA,KAAA,CAAA,KAAA,CAAA;AACR,wBAAA,OAAO,EAAE,KAAA,CAAA,cAAc,MAAM,CAAC,GAAG,CAAA,KAAA,IAAA;AACjC,wBAAA,WAAW,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,WAAA,CAAA;wBACd,qBAAiB,EAAA,MAAA,KAAE,QAAG,CAAA,QAAA,CAAA,KAAA,CAAA,GAAA,EAAA,MAAA,GAAA,MAAA,CAAA,EAAA,GAAA,EAAA,CAAA,CAAA;;AAGZ,sBAAA,CAAA,KAAK,CAAC,IAAI,KAAA,UAAA;sCADrBA,WAMY,CAAA,qBAAA,EAAA;;;AAHX,0BAAA,KAAK,EAAE,QAAA,CAAA,EAAE,CAAA,KAAA,CAAA,KAAA,CAAA;AACT,0BAAA,UAAU,EAAE,QAAA,CAAA,EAAE,CAAC,KAAM,CAAA,IAAI,CAAA;AAC1B,0BAAA,IAAI,EAAC,GAAG;AACP,0BAAA,qBAAW,aAAA,QAAA,CAAA,QAAA,CAAA,KAAA,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;AAGD,wBAAA,CAAA,KAAK,CAAC,IAAI,KAAA,QAAA;mDAKE,CAAA,sBAAA,EAAA;;;AAHvB,4BAAA,IAAI,EAAC,QAAK;AACT,4BAAA,KAAI,EAAA,QAAA,CAAA,EAAA,CAAA,KAAA,CAAA,KAAA,CAAA;AACJ,4BAAA,UAAU,EAAE,QAAA,CAAA,EAAE,CAAC,KAAK,CAAC,IAAI,CAAA;AACzB,4BAAA,qBAAiB,aAAE,QAAG,CAAA,cAAA,CAAA,KAAA,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;mDAOtB,CAAA,sBAAA,EAAA;;;AAFA,4BAAA,KAAK,EAAE,QAAA,CAAA,EAAE,CAAC,KAAK,CAAC,KAAI,CAAA;sCACV,EAAE,QAAA,CAAA,EAAE,CAAC,KAAK,CAAC,IAAG,CAAA;AACxB,4BAAA,qBAAA,aAAA,QAAA,CAAA,QAAA,CAAA,KAAA,CAAA,GAAA,EAAA,MAAA,CAAA,CAAA;;;;AAGO,YAAA,CAAA,CAAA,MAAA,CAAA,MAAM,CAAC,MAAA;8BAAjBE,kBAEI,CAAA,GAAA,EAFJ,UAEI,EAAAG,eAAA,CADA,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,2BAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;"}
1
+ {"version":3,"file":"CnRunNodeDialog.vue3.js","sources":["../../../src/dialogs/CnRunNodeDialog.vue?vue&type=template&id=3eb7c55d&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n -->\n\n<template>\n\t<NcDialog\n\t\t:name=\"tr(title)\"\n\t\tsize=\"normal\"\n\t\t:noClose=\"loading\"\n\t\tdata-testid=\"cn-run-node-dialog\"\n\t\t@closing=\"$emit('close')\">\n\t\t<NcNoteCard v-if=\"loadError\" type=\"error\" data-testid=\"cn-run-node-dialog-error\">\n\t\t\t{{ loadError }}\n\t\t</NcNoteCard>\n\n\t\t<div v-else class=\"cn-run-node-dialog__form\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in fields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\t:data-cn-field=\"field.key\"\n\t\t\t\tclass=\"cn-run-node-dialog__field\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"field.type === 'boolean'\"\n\t\t\t\t\t:modelValue=\"values[field.key] === true\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\">\n\t\t\t\t\t{{ tr(field.label) }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\n\t\t\t\t<NcSelect\n\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\t:modelValue=\"selectedOption(field)\"\n\t\t\t\t\t:options=\"optionsFor(field)\"\n\t\t\t\t\t:inputLabel=\"tr(field.label)\"\n\t\t\t\t\t:loading=\"optionsLoading[field.key] === true\"\n\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Pick one…')\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event ? $event.id : '')\" />\n\n\t\t\t\t<NcTextArea\n\t\t\t\t\tv-else-if=\"field.type === 'textarea'\"\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\trows=\"4\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\" />\n\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else-if=\"field.type === 'number'\"\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\t@update:modelValue=\"setNumberValue(field.key, $event)\" />\n\n\t\t\t\t<NcTextField\n\t\t\t\t\tv-else\n\t\t\t\t\t:modelValue=\"String(values[field.key] ?? '')\"\n\t\t\t\t\t:label=\"tr(field.label)\"\n\t\t\t\t\t:helperText=\"tr(field.help)\"\n\t\t\t\t\t@update:modelValue=\"setValue(field.key, $event)\" />\n\t\t\t</div>\n\n\t\t\t<p v-if=\"!fields.length\" class=\"cn-run-node-dialog__hint\">\n\t\t\t\t{{ t('nextcloud-vue', 'This step has no options.') }}\n\t\t\t</p>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton :disabled=\"loading\" @click=\"$emit('close')\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tv-if=\"!loadError\"\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"loading || !requiredFieldsFilled\"\n\t\t\t\t@click=\"$emit('confirm', values)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Run') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'\nimport { prefixUrl } from '../utils/headers.js'\nimport { hasStaticOptions, normaliseSelectOptions } from '../utils/selectOptions.js'\n\n/**\n * The dialog `run-node` opens to collect a directly-invoked flow node's own\n * declared config, before POSTing to OpenRegister's\n * `/api/flows/{flowId}/nodes/{nodeId}/run` (manifest-run-node-action / RN-4).\n *\n * WHY THIS IS A NEW, SMALL RENDERER AND NOT `CnFlowNodeEditModal`'S ONE\n * ----------------------------------------------------------------------\n * design.md says \"render it with the SAME field renderer CnFlowSidebar\n * [reads: CnFlowNodeEditModal] uses\" as the INTENT — no new form-rendering\n * VOCABULARY — and this component honours that: the type-to-widget mapping\n * (switch/select/textarea/number/text) and the `optionsFrom` fetch-and-\n * normalise behaviour below are the SAME rules that modal applies. What it\n * does NOT reuse is that modal's component/methods directly, because that\n * renderer solves a materially different problem: it guesses a widget for\n * ANY engine config key via `widgetFor()` heuristics (including keys no\n * `configForm()` ever declared — cron expressions, HTTP methods, principal\n * pickers, a `runAs` identity field, a raw-JSON fallback), because the flow\n * EDITOR must let an author set every key the engine reads whether or not\n * its type ever described one. A run-node CALLER dialog has no such need —\n * it renders ONLY the fields `configForm()` explicitly declares (this\n * interface's whole contract, per its own docblock: \"a field over a key the\n * node ignores looks like it works and changes nothing\") — text / textarea /\n * number / boolean / select, nothing else. Extracting a shared component\n * from the editor's modal is a reasonable follow-up; it was not done here to\n * avoid an invasive refactor of an unrelated, already-shipped surface for a\n * dialog whose actual field vocabulary is a fifth its size.\n *\n * @spec openspec/changes/manifest-run-node-action/specs/manifest-run-node-action/spec.md#requirement-the-nodes-own-config-form-drives-the-dialog-not-a-new-token\n */\nexport default {\n\tname: 'CnRunNodeDialog',\n\n\tcomponents: { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField },\n\n\tprops: {\n\t\t/** Pre-translated dialog title. */\n\t\ttitle: { type: String, default: '' },\n\t\t/**\n\t\t * The node's declared config form — `IFlowNodeConfigForm::configForm()`'s\n\t\t * shape, unchanged: `[{key, label, type, help?, required?, optionsFrom?, options?}]`.\n\t\t * A `select` takes its choices from `optionsFrom` (a url) or `options`\n\t\t * (strings, or `{value, label}` pairs declared in the form itself).\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tfields: { type: Array, default: () => [] },\n\t\t/** Whether a run is in flight — disables Cancel/Run and shows a spinner. */\n\t\tloading: { type: Boolean, default: false },\n\t\t/** Set when the node's form could not be resolved; replaces the form with an error and hides Run. */\n\t\tloadError: { type: String, default: '' },\n\t\t/** The consumer's bound `t()`, matching CnActionButtons' own `cnTranslate` inject. */\n\t\ttranslate: { type: Function, default: null },\n\t},\n\n\temits: ['close', 'confirm'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Live field values, keyed by field.key. */\n\t\t\tvalues: {},\n\t\t\t/** Fetched `{id, label}` options per select field.key. */\n\t\t\toptions: {},\n\t\t\t/** In-flight flag per select field.key. */\n\t\t\toptionsLoading: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Every `required: true` field carries a non-empty value. */\n\t\trequiredFieldsFilled() {\n\t\t\treturn this.fields.every((field) => {\n\t\t\t\tif (field.required !== true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tconst value = this.values[field.key]\n\t\t\t\treturn value !== undefined && value !== null && value !== ''\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tfields: {\n\t\t\timmediate: true,\n\t\t\thandler(fields) {\n\t\t\t\tconst seed = {}\n\t\t\t\tfor (const field of (fields || [])) {\n\t\t\t\t\tseed[field.key] = field.type === 'boolean' ? false : ''\n\t\t\t\t}\n\t\t\t\tthis.values = seed\n\t\t\t\tfor (const field of (fields || [])) {\n\t\t\t\t\tif (hasStaticOptions(field)) {\n\t\t\t\t\t\tthis.options = { ...this.options, [field.key]: normaliseSelectOptions(field.options, (s) => this.tr(s)) }\n\t\t\t\t\t} else if (field.type === 'select' && field.optionsFrom) {\n\t\t\t\t\t\tthis.loadOptions(field)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Resolve a manifest/node-authored UI string through the host translate\n\t\t * function, matching CnActionButtons' own `tr()`.\n\t\t *\n\t\t * @param {string} [value] The English source string.\n\t\t * @return {string|undefined} The translated (or source) string.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn value\n\t\t\t}\n\t\t\treturn typeof this.translate === 'function' ? this.translate(value) : value\n\t\t},\n\n\t\t/**\n\t\t * Write one field's value.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @param {unknown} value The new value.\n\t\t * @return {void}\n\t\t */\n\t\tsetValue(key, value) {\n\t\t\tthis.values = { ...this.values, [key]: value }\n\t\t},\n\n\t\t/**\n\t\t * Write a number field, coercing an empty string to '' (not 0 — an\n\t\t * empty required-number field must still fail `requiredFieldsFilled`)\n\t\t * and anything else to a JS number.\n\t\t *\n\t\t * @param {string} key The field key.\n\t\t * @param {string} raw The NcTextField string value.\n\t\t * @return {void}\n\t\t */\n\t\tsetNumberValue(key, raw) {\n\t\t\tthis.setValue(key, raw === '' ? '' : Number(raw))\n\t\t},\n\n\t\t/**\n\t\t * The NcSelect-shaped `{id, label}` currently selected for a field, or\n\t\t * null. Falls back to `{id: value, label: String(value)}` when the\n\t\t * stored value is not (yet, or no longer) among the loaded options —\n\t\t * the same graceful-degrade `CnFlowNodeEditModal` applies.\n\t\t *\n\t\t * @param {object} field The field declaration.\n\t\t * @return {object|null}\n\t\t */\n\t\tselectedOption(field) {\n\t\t\tconst value = this.values[field.key]\n\t\t\tif (value === undefined || value === null || value === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst opts = this.options[field.key] || []\n\t\t\treturn opts.find((o) => o.id === value) || { id: value, label: String(value) }\n\t\t},\n\n\t\t/**\n\t\t * The loaded `{id, label}` options for a select field.\n\t\t *\n\t\t * @param {object} field The field declaration.\n\t\t * @return {Array<object>}\n\t\t */\n\t\toptionsFor(field) {\n\t\t\treturn this.options[field.key] || []\n\t\t},\n\n\t\t/**\n\t\t * Fetch and normalise a select field's `optionsFrom` catalogue —\n\t\t * SAME acceptance shape as `CnFlowNodeEditModal`'s own picker (design.md's\n\t\t * point: reuse the FORM CONTRACT, not necessarily the component): a bare\n\t\t * array or `{results: [...]}`; each row read as `{id, label}`,\n\t\t * `{value, label}`, or an OpenRegister object (`@self.uuid`/`uuid`,\n\t\t * `name`/`title`). A row this dialog cannot make sense of is dropped\n\t\t * rather than shown as a blank option.\n\t\t *\n\t\t * @param {object} field The field declaration carrying `optionsFrom`.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions(field) {\n\t\t\tthis.optionsLoading = { ...this.optionsLoading, [field.key]: true }\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }] = await Promise.all([import('@nextcloud/axios')])\n\t\t\t\tconst raw = String(field.optionsFrom)\n\t\t\t\tconst url = prefixUrl(raw)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tconst rows = Array.isArray(response.data) ? response.data : (response.data?.results || [])\n\t\t\t\tconst opts = normaliseSelectOptions(rows)\n\t\t\t\tthis.options = { ...this.options, [field.key]: opts }\n\t\t\t} catch (error) {\n\t\t\t\t// A picker that fails to load degrades to an empty list rather\n\t\t\t\t// than blocking the whole dialog — the field just shows no\n\t\t\t\t// choices, which is honest about what happened.\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error(`cn-run-node-dialog: could not load options for \"${field.key}\"`, error)\n\t\t\t} finally {\n\t\t\t\tthis.optionsLoading = { ...this.optionsLoading, [field.key]: false }\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-run-node-dialog__form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-run-node-dialog__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createVNode","_createBlock","_openBlock","_createElementBlock","_Fragment","_renderList","_toDisplayString"],"mappings":"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"}
package/dist/esm/index.js CHANGED
@@ -142,6 +142,7 @@ export { default as CnFlowRunsWidget } from './components/CnFlowRunsWidget/CnFlo
142
142
  export { default as CnFlowRunsWidgetForm } from './components/CnFlowRunsWidgetForm/CnFlowRunsWidgetForm.vue.js';
143
143
  export { default as CnFlowSidebar } from './components/CnFlowDetail/CnFlowSidebar.vue.js';
144
144
  export { default as CnFlowsPage } from './components/CnFlowsPage/CnFlowsPage.vue.js';
145
+ export { default as CnFlowStepOutcomes } from './components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js';
145
146
  export { default as CnFolderSidebar } from './components/CnFolderSidebar/CnFolderSidebar.vue.js';
146
147
  export { default as CnFolderTree } from './components/CnFolderTree/CnFolderTree.vue.js';
147
148
  export { default as CnFormBuilder } from './components/CnFormBuilder/CnFormBuilder.vue.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1090,7 +1090,21 @@ var translations = {
1090
1090
  "Loading…": "Loading…",
1091
1091
  "A register and schema are required": "A register and schema are required",
1092
1092
  "Change the search or try again.": "Change the search or try again.",
1093
- "Try again later.": "Try again later."
1093
+ "Try again later.": "Try again later.",
1094
+ "Who this step reached": "Who this step reached",
1095
+ "Each list shows its first entries only.": "Each list shows its first entries only.",
1096
+ Delivered: "Delivered",
1097
+ "Opted out": "Opted out",
1098
+ "Not sent, the opt-out check did not answer": "Not sent, the opt-out check did not answer",
1099
+ "Skipped by their notification settings": "Skipped by their notification settings",
1100
+ "Skipped, sending is switched off": "Skipped, sending is switched off",
1101
+ "Held back by the send limit": "Held back by the send limit",
1102
+ "Refused by the step’s address rule": "Refused by the step’s address rule",
1103
+ "Not a known user or group": "Not a known user or group",
1104
+ Failed: "Failed",
1105
+ "external recipients are off": "external recipients are off",
1106
+ "not on the item": "not on the item",
1107
+ "not a valid address": "not a valid address"
1094
1108
  };
1095
1109
  var plurals = {
1096
1110
  "%n year": [
@@ -1 +1 @@
1
- {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1142,7 +1142,21 @@ var translations = {
1142
1142
  "Loading…": "Laden…",
1143
1143
  "A register and schema are required": "Een register en schema zijn verplicht",
1144
1144
  "Change the search or try again.": "Pas de zoekopdracht aan of probeer het opnieuw.",
1145
- "Try again later.": "Probeer het later opnieuw."
1145
+ "Try again later.": "Probeer het later opnieuw.",
1146
+ "Who this step reached": "Wie deze stap bereikte",
1147
+ "Each list shows its first entries only.": "Elke lijst toont alleen de eerste vermeldingen.",
1148
+ Delivered: "Bezorgd",
1149
+ "Opted out": "Afgemeld",
1150
+ "Not sent, the opt-out check did not answer": "Niet verstuurd, de afmeldcontrole gaf geen antwoord",
1151
+ "Skipped by their notification settings": "Overgeslagen door hun meldingsinstellingen",
1152
+ "Skipped, sending is switched off": "Overgeslagen, verzenden staat uit",
1153
+ "Held back by the send limit": "Tegengehouden door de verzendlimiet",
1154
+ "Refused by the step’s address rule": "Geweigerd door de adresregel van de stap",
1155
+ "Not a known user or group": "Geen bekende gebruiker of groep",
1156
+ Failed: "Mislukt",
1157
+ "external recipients are off": "externe ontvangers staan uit",
1158
+ "not on the item": "staat niet op het item",
1159
+ "not a valid address": "geen geldig adres"
1146
1160
  };
1147
1161
  var plurals = {
1148
1162
  "%n year": [
@@ -1 +1 @@
1
- {"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"nl.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -26582,6 +26582,18 @@ a.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name[data-v-75628
26582
26582
  font-size: 0.85em;
26583
26583
  color: var(--color-text-maxcontrast);
26584
26584
  }
26585
+
26586
+ /* Full width, like the text fields around it: at its natural width the
26587
+ picker cut every option label in half. */
26588
+ .cn-flow-node-edit__select[data-v-e107484a] {
26589
+ width: 100%;
26590
+ }
26591
+ .cn-flow-node-edit__field-help[data-v-e107484a] {
26592
+ margin: 4px 0 0;
26593
+ padding-inline-start: 8px;
26594
+ font-size: 0.85em;
26595
+ color: var(--color-text-maxcontrast);
26596
+ }
26585
26597
  .cn-flow-node-edit__advanced summary[data-v-e107484a] {
26586
26598
  cursor: pointer;
26587
26599
  color: var(--color-text-maxcontrast);
@@ -28036,6 +28048,58 @@ to {
28036
28048
  }
28037
28049
 
28038
28050
 
28051
+ .cn-flow-step-outcomes[data-v-5ac5ad4f] {
28052
+ margin: 4px 0 0;
28053
+ font-size: 0.9em;
28054
+ }
28055
+ .cn-flow-step-outcomes__list[data-v-5ac5ad4f] {
28056
+ margin: 0;
28057
+ padding: 0;
28058
+ }
28059
+
28060
+ /* Stacked, name over value: the run sidebar is narrow, and a side-by-side
28061
+ row wrapped mid-list. The host's dt/dd margins and alignment are reset. */
28062
+ .cn-flow-step-outcomes__row[data-v-5ac5ad4f] {
28063
+ display: block;
28064
+ margin-block: 4px;
28065
+ padding-block: 2px;
28066
+ padding-inline-start: 8px;
28067
+ border-inline-start: 3px solid var(--color-border);
28068
+ text-align: start;
28069
+ }
28070
+ .cn-flow-step-outcomes__row--success[data-v-5ac5ad4f] {
28071
+ border-inline-start-color: var(--color-success);
28072
+ }
28073
+ .cn-flow-step-outcomes__row--warning[data-v-5ac5ad4f] {
28074
+ border-inline-start-color: var(--color-warning);
28075
+ }
28076
+ .cn-flow-step-outcomes__row--error[data-v-5ac5ad4f] {
28077
+ border-inline-start-color: var(--color-error);
28078
+ }
28079
+ .cn-flow-step-outcomes__name[data-v-5ac5ad4f] {
28080
+ display: block;
28081
+ white-space: normal;
28082
+ overflow-wrap: anywhere;
28083
+ margin: 0;
28084
+ padding: 0;
28085
+ font-weight: bold;
28086
+ text-align: start;
28087
+ }
28088
+ .cn-flow-step-outcomes__value[data-v-5ac5ad4f] {
28089
+ display: block;
28090
+ white-space: normal;
28091
+ margin: 0;
28092
+ padding: 0;
28093
+ text-align: start;
28094
+ color: var(--color-main-text);
28095
+ overflow-wrap: anywhere;
28096
+ }
28097
+ .cn-flow-step-outcomes__truncated[data-v-5ac5ad4f] {
28098
+ margin: 4px 0 0;
28099
+ color: var(--color-text-maxcontrast);
28100
+ }
28101
+
28102
+
28039
28103
  .cn-run-sidebar[data-v-48e781f0] {
28040
28104
  display: flex;
28041
28105
  flex-direction: column;