@conduction/nextcloud-vue 2.71.0 → 2.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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":"CnGraphCanvas.vue3.js","sources":["../../../../src/components/CnGraphCanvas/CnGraphCanvas.vue?vue&type=template&id=1d8bc107&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<div class=\"cn-graph-canvas\" @drop=\"onDrop\" @dragover.prevent>\n\t\t<VueFlow\n\t\t\t:nodes=\"nodes\"\n\t\t\t:edges=\"edges\"\n\t\t\t:minZoom=\"minZoom\"\n\t\t\t:maxZoom=\"maxZoom\"\n\t\t\t:fitViewOnInit=\"fitView\"\n\t\t\t:snapToGrid=\"snapToGrid\"\n\t\t\t:snapGrid=\"snapGrid\"\n\t\t\t:nodesDraggable=\"interactive\"\n\t\t\t:nodesConnectable=\"interactive\"\n\t\t\t:elementsSelectable=\"interactive\"\n\t\t\t:connectionMode=\"connectionMode\"\n\t\t\t@nodesChange=\"onNodesChange\"\n\t\t\t@edgesChange=\"onEdgesChange\"\n\t\t\t@connect=\"onConnect\"\n\t\t\t@nodeClick=\"onNodeClick\"\n\t\t\t@edgeClick=\"onEdgeClick\"\n\t\t\t@paneClick=\"onPaneClick\">\n\t\t\t<!-- Per-type node components, Vue Flow's convention. A host that\n\t\t\t registers `type: 'trigger'` gets `#node-trigger`; anything\n\t\t\t unregistered falls back to CnFlowNode, which carries the\n\t\t\t keyboard contract. -->\n\t\t\t<template #node-default=\"nodeProps\">\n\t\t\t\t<!-- `@connect` routes a KEYBOARD-completed connection into the\n\t\t\t\t same handler as a pointer one, so `onConnect` is the single\n\t\t\t\t place a new edge leaves this component regardless of input.\n\t\t\t\t Without it the keyboard path wrote into Vue Flow's internal\n\t\t\t\t graph and the host never heard about it — the edge drew and\n\t\t\t\t the save dropped it. -->\n\t\t\t\t<CnFlowNode\n\t\t\t\t\tv-bind=\"nodeProps\"\n\t\t\t\t\t:deletable=\"!readOnly\"\n\t\t\t\t\t@connect=\"onConnect\"\n\t\t\t\t\t@remove=\"onNodeRemove\">\n\t\t\t\t\t<template #default=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot node The body of a step, rendered inside the\n\t\t\t\t\t\t focusable node wrapper. Receives `{ node }` with the\n\t\t\t\t\t\t node's `id`, `data` and `selected`. The wrapper — and\n\t\t\t\t\t\t with it the keyboard contract and ARIA state — stays\n\t\t\t\t\t\t the canvas's, so a host cannot accidentally replace a\n\t\t\t\t\t\t focusable node with an inert div. -->\n\t\t\t\t\t\t<slot name=\"node\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowNode>\n\t\t\t</template>\n\n\t\t\t<!-- Per-type edge components, the same convention as nodes above.\n\t\t\t Registered on `default` because that is the type an edge gets\n\t\t\t when nothing else is asked for — which is every edge, now that\n\t\t\t the ROUTER moved off `type` and into `data.lineType`.\n\n\t\t\t That move is the edge half of the lesson the node slot records:\n\t\t\t `type` selects a COMPONENT, and a router is not a component. So\n\t\t\t long as `type` carried `smoothstep`, Vue Flow answered with its\n\t\t\t own built-in edge and no consumer could add anything to a line\n\t\t\t at all. -->\n\t\t\t<template #edge-default=\"edgeProps\">\n\t\t\t\t<CnFlowEdge\n\t\t\t\t\tv-bind=\"edgeProps\"\n\t\t\t\t\t:lineType=\"edgeProps.data && edgeProps.data.lineType\"\n\t\t\t\t\t:animated=\"!(edgeProps.data && edgeProps.data.animated === false)\"\n\t\t\t\t\t:label-aria-label=\"edgeProps.data && edgeProps.data.labelAriaLabel\"\n\t\t\t\t\t@labelMove=\"onEdgeLabelMove\"\n\t\t\t\t\t@labelClick=\"onEdgeLabelClick\"\n\t\t\t\t\t@labelContext=\"onEdgeLabelContext\">\n\t\t\t\t\t<template #label=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-label The chrome of a connection's\n\t\t\t\t\t\t label. Receives `{ edge }` with the edge's `id`,\n\t\t\t\t\t\t `data` and `selected`. Rendered inside a focusable\n\t\t\t\t\t\t control the canvas owns, so render inert content:\n\t\t\t\t\t\t the arrow keys, the focus ring and the ARIA name\n\t\t\t\t\t\t are already handled. An edge whose host renders\n\t\t\t\t\t\t nothing here draws no label control at all. -->\n\t\t\t\t\t\t<slot name=\"edge-label\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #adornment=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-adornment A host's own controls beside a\n\t\t\t\t\t\t connection's label. Receives `{ edge }`. Unlike\n\t\t\t\t\t\t `edge-label` this is NOT inside the label's button,\n\t\t\t\t\t\t so what goes here may be interactive — a replay's\n\t\t\t\t\t\t payload control is the case it exists for. -->\n\t\t\t\t\t\t<slot name=\"edge-adornment\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowEdge>\n\t\t\t</template>\n\n\t\t\t<Background v-if=\"showBackground\" :gap=\"snapGrid[0]\" />\n\t\t\t<MiniMap v-if=\"showMiniMap\" />\n\t\t</VueFlow>\n\n\t\t<!-- OUR OWN CONTROLS, not `@vue-flow/controls`.\n\t\t The library's control bar renders bare <button> elements with an\n\t\t icon and no accessible name, which axe reports as `button-name` at\n\t\t SERIOUS impact — caught by the e2e in this change, not by the unit\n\t\t lane or the gates. Rather than ship a control bar a screen-reader\n\t\t user cannot identify, the buttons are ours and carry real labels. -->\n\t\t<div v-if=\"showControls\" class=\"cn-graph-canvas__controls\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom in')\"\n\t\t\t\t@click=\"zoomIn()\">\n\t\t\t\t+\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom out')\"\n\t\t\t\t@click=\"zoomOut()\">\n\t\t\t\t−\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Fit the whole flow in view')\"\n\t\t\t\t@click=\"fitViewNow()\">\n\t\t\t\t⤢\n\t\t\t</button>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { Background } from '@vue-flow/background'\nimport { useVueFlow, VueFlow } from '@vue-flow/core'\nimport { MiniMap } from '@vue-flow/minimap'\nimport CnFlowEdge from './CnFlowEdge.vue'\nimport CnFlowNode from './CnFlowNode.vue'\n\n/**\n * A graph canvas built on Vue Flow — the library n8n runs.\n *\n * WHY THIS IS NOT A WRAPPER AROUND THE OLD API\n * --------------------------------------------\n * This component used to be ~1,300 hand-written lines re-implementing pan,\n * zoom, drag, connect, resize and hit-testing on a bare SVG, because the fleet\n * was on Vue 2.7 and every maintained canvas library is Vue-3-only (ADR-065\n * §5). ADR-081 removed that constraint and named this library as the reason for\n * the migration.\n *\n * The props therefore MOVED rather than being translated. The graph page was\n * not in production, so there was no consumer to protect, and a shim can only\n * ever expose what the old API could already express — `fitView`, `<MiniMap>`,\n * `connectionMode` and the node resizer would all have stayed unreachable\n * behind props designed for a hand-drawn SVG.\n *\n * `nodeWidth` / `nodeHeight` are gone entirely. They existed only so hand-drawn\n * edges could guess where a node's centre was, and the old docblock admitted\n * the failure mode: \"set them to match what your node slot actually renders, or\n * edges will attach off-centre\". Vue Flow measures the rendered node, so the\n * whole class of bug went with the props.\n *\n * ⚠️ THE CANVAS STILL NEVER MUTATES THE GRAPH. Vue Flow is perfectly willing to\n * own node positions internally; here it does not. Changes are emitted and the\n * host decides what to persist — the same rule the previous implementation\n * held, and the reason a canvas can be dropped into a read-only surface without\n * auditing it first.\n *\n * ⚠️ KEYBOARD OPERATION LIVES IN {@see CnFlowNode}. Vue Flow is pointer-first;\n * the focus/ARIA/key contract is re-implemented in the node component, not\n * inherited from the library. See that file for why the multi-exit case is the\n * one that matters.\n */\nexport default {\n\tname: 'CnGraphCanvas',\n\n\tcomponents: { VueFlow, Background, MiniMap, CnFlowNode, CnFlowEdge },\n\n\tprops: {\n\t\t/** Nodes in Vue Flow's shape: `{ id, type, position: {x,y}, data }`. */\n\t\tnodes: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Edges in Vue Flow's shape: `{ id, source, target }`. */\n\t\tedges: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * When true, every interaction is refused — drag, connect AND select.\n\t\t *\n\t\t * Expressed as three Vue Flow flags below. Missing one produces a canvas\n\t\t * that LOOKS locked and is not, so the three are set together and tested\n\t\t * together.\n\t\t */\n\t\treadOnly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Frame the whole graph on first render. */\n\t\tfitView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Snap dragged nodes to the grid. */\n\t\tsnapToGrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Grid spacing, as Vue Flow's `[x, y]`. */\n\t\tsnapGrid: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [16, 16],\n\t\t},\n\n\t\t/** Minimum zoom. */\n\t\tminZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0.2,\n\t\t},\n\n\t\t/** Maximum zoom. */\n\t\tmaxZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Vue Flow's connection mode — `loose` or `strict`. */\n\t\tconnectionMode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'loose',\n\t\t},\n\n\t\t/** Draw the dotted background. */\n\t\tshowBackground: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the zoom/fit controls. */\n\t\tshowControls: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the minimap. */\n\t\tshowMiniMap: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t'nodes-change',\n\t\t'edges-change',\n\t\t'connect',\n\t\t'node-select',\n\t\t'edge-select',\n\t\t'edge-label-move',\n\t\t'edge-label-click',\n\t\t'edge-label-context',\n\t\t'canvas-click',\n\t\t'canvas-drop',\n\n\t\t'node-remove',\n\n\t\t/**\n\t\t * The canvas zoom changed, by any route — a control here, the host's own\n\t\t * toolbar, the wheel, or a fit-view.\n\t\t *\n\t\t * @type {number}\n\t\t */\n\t\t'zoom-change',\n\t],\n\n\tsetup() {\n\t\t// ⚠️ ALIASED. `fitView` is already a BOOLEAN PROP on this component\n\t\t// (frame the graph on first render), and Vue Flow's composable exports a\n\t\t// FUNCTION of the same name. Returning both puts two `fitView` bindings\n\t\t// on the instance — eslint's `vue/no-dupe-keys` catches it, but the\n\t\t// symptom would have been a fit button that tries to call `true`.\n\t\t// `zoomTo` and `viewport` so a HOST toolbar can drive the zoom and show\n\t\t// the real figure — including one the wheel or a fit-view changed.\n\t\tconst { project, zoomIn, zoomOut, zoomTo, fitView: fitViewNow, viewport } = useVueFlow()\n\n\t\treturn { project, zoomIn, zoomOut, zoomTo, fitViewNow, viewport }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the canvas accepts interaction at all.\n\t\t */\n\t\tinteractive() {\n\t\t\treturn this.readOnly === false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// Reported rather than owned: Vue Flow holds the transform, so a host\n\t\t// that mirrors it in its own toolbar must hear every change, not only\n\t\t// the ones its own buttons caused.\n\t\t'viewport.zoom': {\n\t\t\timmediate: true,\n\t\t\thandler(value) {\n\t\t\t\tthis.$emit('zoom-change', value)\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward Vue Flow's node changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonNodesChange(changes) {\n\t\t\t/**\n\t\t\t * @event nodes-change Vue Flow's node change stream — position,\n\t\t\t * selection, dimensions, removal. The canvas does NOT apply these;\n\t\t\t * the host decides what to persist. Drag frames arrive with\n\t\t\t * `dragging: true`, so a host that writes on every change writes\n\t\t\t * once per animation frame.\n\t\t\t */\n\t\t\tthis.$emit('nodes-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward Vue Flow's edge changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgesChange(changes) {\n\t\t\t/**\n\t\t\t * @event edges-change Vue Flow's edge change stream, same contract\n\t\t\t * as `nodes-change`: reported, never applied here.\n\t\t\t */\n\t\t\tthis.$emit('edges-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward a new connection.\n\t\t *\n\t\t * @param {object} connection The connection.\n\t\t * @return {void}\n\t\t */\n\t\t/**\n\t\t * Pass a node's removal request up to the host.\n\t\t *\n\t\t * @param {string} id The node that asked to be removed.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeRemove(id) {\n\t\t\t/**\n\t\t\t * @event node-remove The focused node should be removed, by id.\n\t\t\t * Raised by Delete/Backspace on a node. The canvas removes\n\t\t\t * nothing itself — the host owns `nodes` and `edges`, and a node\n\t\t\t * dropped here would leave the edges pointing at it behind.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('node-remove', id)\n\t\t},\n\n\t\tonConnect(connection) {\n\t\t\t/**\n\t\t\t * @event connect A new connection was made, by pointer OR by\n\t\t\t * keyboard. Carries Vue Flow's\n\t\t\t * `{ source, target, sourceHandle, targetHandle }`, with the\n\t\t\t * handles reduced to the PORT the host declared — see\n\t\t\t * `portIdOf` below.\n\t\t\t */\n\t\t\tthis.$emit('connect', {\n\t\t\t\t...connection,\n\t\t\t\tsourceHandle: this.portIdOf(connection?.sourceHandle),\n\t\t\t\ttargetHandle: this.portIdOf(connection?.targetHandle),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Reduce a HANDLE id to the PORT id the host declared.\n\t\t *\n\t\t * A node draws more handles than it has ports — one exit is offered on\n\t\t * both the right and the bottom edge, one entry on both the left and the\n\t\t * top — because a line has to be able to leave and arrive on the side\n\t\t * the graph reads best from. Vue Flow keys handles by id, so those\n\t\t * cannot share one, and the side is encoded into the id instead\n\t\t * (`yes__right`). See CnFlowNode's `exitHandles`.\n\t\t *\n\t\t * ⚠️ WHICH MEANS THE ENCODED ID MUST NEVER LEAVE THE CANVAS. A branch\n\t\t * recorded as `yes__bottom` names a port the engine has never heard of,\n\t\t * and a flow whose branch ids do not match its catalogue is one the\n\t\t * engine cannot route — it would take the default exit and the canvas\n\t\t * would keep showing the branch the author drew. So both routes into\n\t\t * this method (pointer via <VueFlow>, keyboard via CnFlowNode) are\n\t\t * stripped here, in the one place every connection passes through.\n\t\t *\n\t\t * An entry handle carries no port of its own — a node has exactly one\n\t\t * inbound port — so it reduces to null rather than to the literal `in`.\n\t\t *\n\t\t * @param {string|null|undefined} handleId The handle id Vue Flow reports.\n\t\t * @return {string|null} The port id, or null.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf('__')\n\t\t\tconst port = cut === -1 ? handleId : handleId.slice(0, cut)\n\n\t\t\treturn port === 'in' ? null : port\n\t\t},\n\n\t\t/**\n\t\t * Forward a node click.\n\t\t *\n\t\t * @param {object} event Vue Flow's node event.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeClick(event) {\n\t\t\t/**\n\t\t\t * @event node-select A node was clicked. Payload is Vue Flow's\n\t\t\t * `{ node }`, so a host tracking bare ids reads `event.node.id`.\n\t\t\t */\n\t\t\tthis.$emit('node-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward an edge click.\n\t\t *\n\t\t * @param {object} event Vue Flow's edge event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeClick(event) {\n\t\t\t/**\n\t\t\t * @event edge-select An edge was clicked.\n\t\t\t */\n\t\t\tthis.$emit('edge-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward a label slid along its line.\n\t\t *\n\t\t * @param {{id: string, labelT: number}} payload The edge and its new\n\t\t * fraction, already clamped.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelMove(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-move A connection's label was moved, by pointer\n\t\t\t * or by keyboard. Carries `{ id, labelT }`. As everywhere else in\n\t\t\t * this component the change is REPORTED, not applied — the host\n\t\t\t * stores `labelT` and feeds it back down.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-move', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward an activated label.\n\t\t *\n\t\t * @param {string} id The edge id.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelClick(id) {\n\t\t\t/**\n\t\t\t * @event edge-label-click A connection's label was activated.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-click', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward a right-click on a label.\n\t\t *\n\t\t * @param {{id: string, event: MouseEvent}} payload The edge and event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelContext(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-context A connection's label was right-clicked.\n\t\t\t * Hosts open the same menu they open for the line itself.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-context', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward a click on the empty pane.\n\t\t *\n\t\t * @param {object} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonPaneClick(event) {\n\t\t\t/**\n\t\t\t * @event canvas-click The empty pane was clicked — hosts use this to\n\t\t\t * clear a selection.\n\t\t\t */\n\t\t\tthis.$emit('canvas-click', event)\n\t\t},\n\n\t\t/**\n\t\t * A palette drop, with the point already converted to canvas space.\n\t\t *\n\t\t * Kept as a Conduction-level event because HTML5 drop plus coordinate\n\t\t * projection is glue every consumer would otherwise write. The\n\t\t * projection is Vue Flow's `project()` rather than the old hand-rolled\n\t\t * `(clientX - rect.left - panOffset.x) / zoom`.\n\t\t *\n\t\t * The canvas does not create the node — the host does, mirroring the\n\t\t * rule that it never mutates positions either.\n\t\t *\n\t\t * @param {DragEvent} event The drop event.\n\t\t * @return {void}\n\t\t */\n\t\tonDrop(event) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst bounds = this.$el.getBoundingClientRect()\n\t\t\tconst position = this.project({\n\t\t\t\tx: event.clientX - bounds.left,\n\t\t\t\ty: event.clientY - bounds.top,\n\t\t\t})\n\n\t\t\t/**\n\t\t\t * @event canvas-drop An HTML5 drop landed on the canvas, with\n\t\t\t * `position` already projected into canvas space and the native\n\t\t\t * `event` alongside so the host can read `dataTransfer`. The canvas\n\t\t\t * never creates the node itself.\n\t\t\t */\n\t\t\tthis.$emit('canvas-drop', { position, event })\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/* Vue Flow's own stylesheet, themed through Nextcloud variables. Imported\n un-scoped because it styles elements Vue Flow renders outside this\n component's scope. */\n@import '@vue-flow/core/dist/style.css';\n@import '@vue-flow/core/dist/theme-default.css';\n\n/* ⚠️ EVERY VUE FLOW PLUGIN SHIPS ITS OWN STYLESHEET, AND ONLY CORE'S WAS HERE.\n\n `@vue-flow/node-resizer` was the one that mattered: its stylesheet is what\n gives a resize control `position: absolute` and a 5px handle. Without it the\n handles CnFlowNode renders have no size and no position, so a node that is\n resizable looks exactly like a node that is not — the component was mounted\n and the affordance was invisible.\n\n The minimap's own sheet is small (a background colour and the grab cursors);\n its geometry comes from `.vue-flow__panel` in core, which was already here.\n Imported for the cursors, and before our own rules so the theme below still\n wins. */\n@import '@vue-flow/node-resizer/dist/style.css';\n@import '@vue-flow/minimap/dist/style.css';\n\n.cn-graph-canvas {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 320px;\n}\n\n/* ⚠️ VUE FLOW'S DEFAULT NODE DRAWS A BOX WE NEVER ASKED FOR.\n\n Every node this canvas renders goes through the `#node-default` slot, so\n Vue Flow wraps our CnFlowNode in `.vue-flow__node-default` — and\n `theme-default.css` gives that wrapper a 1px border, a white background and\n 10px of padding. The result was a box around our box: the wrapper's border,\n then `.cn-flow-node`'s own. Nothing failed and nothing warned; a node just\n quietly had one more outline than it was designed to have.\n\n Stripped, not restyled: `.cn-flow-node` is the node's chrome, and it is the\n ONLY border a node draws. `width` stays 150px because Vue Flow measures the\n wrapper to route edges and place handles — dropping it would move every\n node's footprint, which is a layout change, not a fix for a stray border. */\n.cn-graph-canvas .vue-flow__node-default {\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tbackground: none;\n\n\t/* The handle colour reaches `.cn-flow-node__handle` through this variable.\n\t Vue Flow's own `.vue-flow__node-default .vue-flow__handle` rule outranks\n\t our single-class one, so a themed handle lost to a hard-coded #1a192b on\n\t every node — setting the variable themes it at the source instead of\n\t fighting the specificity. */\n\t--vf-handle: var(--color-primary-element, #0082c9);\n}\n\n/* The selected/focus/hover states re-assert the border and shadow the rule\n above just removed, so they have to be stripped too — otherwise clicking a\n node brought the extra box back. Selection is shown by `.cn-flow-node--selected`,\n and focus by `.cn-flow-node:focus-visible`, both on the node itself. */\n.cn-graph-canvas .vue-flow__node-default.selected,\n.cn-graph-canvas .vue-flow__node-default.selected:hover,\n.cn-graph-canvas .vue-flow__node-default.selectable:hover,\n.cn-graph-canvas .vue-flow__node-default:focus,\n.cn-graph-canvas .vue-flow__node-default:focus-visible {\n\tborder: 0;\n\tbox-shadow: none;\n}\n\n/* THEME, NOT OVERRIDE. Every colour comes from a Nextcloud variable with a\n fallback, so the canvas follows the user's theme — including dark — instead\n of being the one component that ignores it. */\n.cn-graph-canvas .vue-flow__background {\n\tbackground: var(--color-main-background, #fff);\n}\n\n.cn-graph-canvas .vue-flow__edge-path {\n\tstroke: var(--color-border-dark, #888);\n}\n\n.cn-graph-canvas .vue-flow__edge.selected .vue-flow__edge-path {\n\tstroke: var(--color-primary-element, #0082c9);\n}\n\n/* An edge says which way the data flows, and it can only say it with an\n arrowhead — the direction is the one thing a line cannot express on its own.\n\n ⚠️ `!important` IS LOAD-BEARING HERE, not laziness. Vue Flow writes the\n arrowhead's colour as an INLINE style on the polyline it generates, and an\n inline style beats any class selector. The alternative is passing a colour\n through the marker definition in JS, which would hard-code a hex and take\n the arrowhead out of the theme — it would stay dark grey in dark mode while\n the line it terminates went light. Themed here, it follows the user.\n\n `markerUnits` defaults to `strokeWidth`, so the size set alongside this in\n `useFlowStore.canvasEdges` is what keeps the arrow clear of the target\n node's port handle. */\n.cn-graph-canvas .vue-flow__arrowhead polyline,\n.cn-graph-canvas .vue-flow__arrowhead path {\n\tfill: var(--color-text-maxcontrast, #6b6b6b) !important;\n\tstroke: var(--color-text-maxcontrast, #6b6b6b) !important;\n}\n\n.cn-graph-canvas__controls {\n\tposition: absolute;\n\tbottom: 12px;\n\tleft: 12px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tz-index: 5;\n}\n\n.cn-graph-canvas__control {\n\twidth: 28px;\n\theight: 28px;\n\tborder: 1px solid var(--color-border, #d0d0d0);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n\tcursor: pointer;\n}\n\n.cn-graph-canvas__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 1px;\n}\n\n.cn-graph-canvas .vue-flow__minimap {\n\tbackground: var(--color-background-dark, #ededed);\n}\n</style>\n"],"names":["_createElementBlock","_createVNode","_withCtx","_mergeProps","_renderSlot","_createBlock","_openBlock","_createElementVNode"],"mappings":";;;;AA2G2B,EAAA,KAAK,EAAC,2BAAa;;;;;;;;;;;;;uBAlG3CA;AAAG,IAAA,KAAK,EAAC,iBAAQ;AAAA,IAAA,MAAA,0CAAA,QAAA,CAAA,MAAA,IAAA,QAAA,CAAA,MAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAA;AAAA,IAAA,UAAA,0CAAA,MAAA,EAAA,EAAA,CAAA,SAAA,CAAA,CAAA,CAAA;;IACjBC,WAyFS,CAAA,kBAAA,EAAA;AAxFR,MAAA,KAAK,EAAE,MAAK,CAAA,KAAA;AACZ,MAAA,KAAK,EAAE,MAAK,CAAA,KAAA;AACZ,MAAA,OAAO,EAAE,MAAO,CAAA,OAAA;AAChB,MAAA,OAAO,EAAE,MAAO,CAAA,OAAA;AAChB,MAAA,aAAa,EAAE,MAAO,CAAA,OAAA;AACtB,MAAA,UAAU,EAAE,MAAU,CAAA,UAAA;AACtB,MAAA,QAAQ,EAAE,MAAQ,CAAA,QAAA;AAClB,MAAA,cAAc,EAAE,QAAU,CAAA,WAAA;AAC1B,MAAA,gBAAgB,EAAE,QAAQ,CAAA,WAAA;AAC1B,MAAA,kBAAkB,EAAA,QAAA,CAAA,WAAA;AAClB,MAAA,cAAc,EAAE,MAAM,CAAA,cAAA;AACtB,MAAA,aAAW,EAAE,QAAS,CAAA,aAAA;AACtB,MAAA,aAAW,EAAE,QAAU,CAAA,aAAA;AACvB,MAAA,SAAO,EAAE,QAAS,CAAA,SAAA;iBACT,EAAE,QAAW,CAAA,WAAA;iBACb,EAAE,QAAU,CAAA,WAAA;AACjB,MAAA,WAAK,EAAE,QAAU,CAAA,WAAA;;MAKX,cAAY,EAAAC,OAAA,EAqBT,SArBmB,KAAA;mBAqBnB,CAAA,qBAAA,YAbJ,CAaI,SAbK,EAAA;AAChB,UAAA,SAAS,GAAG,MAAQ,CAAA,QAAA;AACf,UAAA,SAAE,EAAE,QAAS,CAAA,SAAA;AACb,UAAA,QAAC,EAAE,QAAY,CAAA,YAAA;;UACV,OAAO,EAAAA,OAAA,WAAW,KAAA;;;;;;MAsBpB,cAAY,EAAAA,OAAA,CACrB,CA2BY,SA5BoB,KAAA;QAChCD,WA2BY,CAAA,qBAAA,EA3BZE,UACQ,CA0BI,SA1BK,EAAA;UAChB,QAAQ,EAAE,SAAS,CAAC,IAAI,IAAI,SAAA,CAAA,IAAA,CAAA,QAAA;UAC5B,QAAQ,EAAA,EAAI,SAAS,CAAC,IAAI,IAAI,SAAG,CAAA,IAAA,CAAA,QAAA,KAAA,KAAA,CAAA;UACjC,kBAAgB,EAAE,SAAS,CAAA,IAAA,IAAA,SAAA,CAAA,IAAA,CAAA,cAAA;AAC3B,UAAA,WAAS,EAAE,QAAe,CAAA,eAAA;AACrB,UAAA,YAAK,EAAE,QAAiB,CAAA,gBAAA;AACxB,UAAA,cAAO,EAAE,QAAkB,CAAA,kBAAA;;UACtB,KAAK,EAAAD,OAAA,CAQf,CAA6C,SARnB,KAAA;AAQ1B,YAAAE,UAAA,CAA6C,6DAAb,SAAS,CAAA,CAAA,CAAA;;UAG/B,SAAS,EAAAF,OAAA,WAAW,KAAA;;;;;;uBAWjC,MAA8C;SAA5B,MAAc,CAAA,cAAA;0BAAhCG,WAA8C,CAAA,qBAAA,EAAA;;AAAX,cAAA,GAAG,EAAE,MAAM,CAAA,QAAA,CAAA,CAAA,CAAA;;;SAC/B,MAAW,CAAA,WAAA;qCAAI,CAAA,kBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;;;;;KASpB,MAAY,CAAA,YAAA;AAArB,SAAAC,SAAA,EAAA,EAAAN,kBAAA,QAAA;UACAO,kBAMQ,CAAA,QAAA,EAAA;AALR,YAAA,IAAA,EAAA,QAAA;AACA,YAAA,KAAK,EAAC,0BAAC;AACN,YAAA,YAAI,EAAA,IAAA,CAAA,CAAA,CAAA,eAAA,EAAA,SAAA,CAAA;AACJ,YAAA,OAAK,uCAAE,MAAI,CAAA,MAAA,EAAA,CAAA,CAAA;aAAA,KAEZ,EAAA,CAAA,cAAA,UAAA,CAAA;UACAA,kBAMQ,CAAA,QAAA,EAAA;AALR,YAAA,IAAA,EAAA,QAAA;AACA,YAAA,KAAK,EAAC,0BAAC;AACN,YAAA,YAAI,EAAA,IAAA,CAAA,CAAA,CAAA,eAAA,EAAA,UAAA,CAAA;AACJ,YAAA,OAAK,uCAAE,MAAI,CAAA,OAAA,EAAA,CAAA,CAAA;aAAA;UAGZA;AACA,YAAA,IAAA,EAAA,QAAA;AACA,YAAA,KAAK,EAAC,0BAAC;AACN,YAAA,YAAE,EAAA,IAAA,CAAA,CAAA,CAAA,eAAA,EAAA,4BAAA,CAAA;AACF,YAAA,OAAC,uCAAA,MAAA,CAAA,UAAA,EAAA,CAAA,CAAA;aAAA;;;;;;;;"}
1
+ {"version":3,"file":"CnGraphCanvas.vue3.js","sources":["../../../../src/components/CnGraphCanvas/CnGraphCanvas.vue?vue&type=template&id=1d8bc107&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<div class=\"cn-graph-canvas\" @drop=\"onDrop\" @dragover.prevent>\n\t\t<VueFlow\n\t\t\t:nodes=\"nodes\"\n\t\t\t:edges=\"edges\"\n\t\t\t:minZoom=\"minZoom\"\n\t\t\t:maxZoom=\"maxZoom\"\n\t\t\t:fitViewOnInit=\"fitView\"\n\t\t\t:snapToGrid=\"snapToGrid\"\n\t\t\t:snapGrid=\"snapGrid\"\n\t\t\t:nodesDraggable=\"interactive\"\n\t\t\t:nodesConnectable=\"interactive\"\n\t\t\t:elementsSelectable=\"interactive\"\n\t\t\t:connectionMode=\"connectionMode\"\n\t\t\t@nodesChange=\"onNodesChange\"\n\t\t\t@edgesChange=\"onEdgesChange\"\n\t\t\t@connect=\"onConnect\"\n\t\t\t@nodeClick=\"onNodeClick\"\n\t\t\t@edgeClick=\"onEdgeClick\"\n\t\t\t@paneClick=\"onPaneClick\">\n\t\t\t<!-- Per-type node components, Vue Flow's convention. A host that\n\t\t\t registers `type: 'trigger'` gets `#node-trigger`; anything\n\t\t\t unregistered falls back to CnFlowNode, which carries the\n\t\t\t keyboard contract. -->\n\t\t\t<template #node-default=\"nodeProps\">\n\t\t\t\t<!-- `@connect` routes a KEYBOARD-completed connection into the\n\t\t\t\t same handler as a pointer one, so `onConnect` is the single\n\t\t\t\t place a new edge leaves this component regardless of input.\n\t\t\t\t Without it the keyboard path wrote into Vue Flow's internal\n\t\t\t\t graph and the host never heard about it — the edge drew and\n\t\t\t\t the save dropped it. -->\n\t\t\t\t<CnFlowNode\n\t\t\t\t\tv-bind=\"nodeProps\"\n\t\t\t\t\t:deletable=\"!readOnly\"\n\t\t\t\t\t@connect=\"onConnect\"\n\t\t\t\t\t@remove=\"onNodeRemove\"\n\t\t\t\t\t@activate=\"onNodeActivate\"\n\t\t\t\t\t@menu=\"onNodeMenu\">\n\t\t\t\t\t<template #default=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot node The body of a step, rendered inside the\n\t\t\t\t\t\t focusable node wrapper. Receives `{ node }` with the\n\t\t\t\t\t\t node's `id`, `data` and `selected`. The wrapper — and\n\t\t\t\t\t\t with it the keyboard contract and ARIA state — stays\n\t\t\t\t\t\t the canvas's, so a host cannot accidentally replace a\n\t\t\t\t\t\t focusable node with an inert div. -->\n\t\t\t\t\t\t<slot name=\"node\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowNode>\n\t\t\t</template>\n\n\t\t\t<!-- Per-type edge components, the same convention as nodes above.\n\t\t\t Registered on `default` because that is the type an edge gets\n\t\t\t when nothing else is asked for — which is every edge, now that\n\t\t\t the ROUTER moved off `type` and into `data.lineType`.\n\n\t\t\t That move is the edge half of the lesson the node slot records:\n\t\t\t `type` selects a COMPONENT, and a router is not a component. So\n\t\t\t long as `type` carried `smoothstep`, Vue Flow answered with its\n\t\t\t own built-in edge and no consumer could add anything to a line\n\t\t\t at all. -->\n\t\t\t<template #edge-default=\"edgeProps\">\n\t\t\t\t<CnFlowEdge\n\t\t\t\t\tv-bind=\"edgeProps\"\n\t\t\t\t\t:lineType=\"edgeProps.data && edgeProps.data.lineType\"\n\t\t\t\t\t:animated=\"!(edgeProps.data && edgeProps.data.animated === false)\"\n\t\t\t\t\t:label-aria-label=\"edgeProps.data && edgeProps.data.labelAriaLabel\"\n\t\t\t\t\t@labelMove=\"onEdgeLabelMove\"\n\t\t\t\t\t@labelClick=\"onEdgeLabelClick\"\n\t\t\t\t\t@labelContext=\"onEdgeLabelContext\">\n\t\t\t\t\t<template #label=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-label The chrome of a connection's\n\t\t\t\t\t\t label. Receives `{ edge }` with the edge's `id`,\n\t\t\t\t\t\t `data` and `selected`. Rendered inside a focusable\n\t\t\t\t\t\t control the canvas owns, so render inert content:\n\t\t\t\t\t\t the arrow keys, the focus ring and the ARIA name\n\t\t\t\t\t\t are already handled. An edge whose host renders\n\t\t\t\t\t\t nothing here draws no label control at all. -->\n\t\t\t\t\t\t<slot name=\"edge-label\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #adornment=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-adornment A host's own controls beside a\n\t\t\t\t\t\t connection's label. Receives `{ edge }`. Unlike\n\t\t\t\t\t\t `edge-label` this is NOT inside the label's button,\n\t\t\t\t\t\t so what goes here may be interactive — a replay's\n\t\t\t\t\t\t payload control is the case it exists for. -->\n\t\t\t\t\t\t<slot name=\"edge-adornment\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowEdge>\n\t\t\t</template>\n\n\t\t\t<Background v-if=\"showBackground\" :gap=\"snapGrid[0]\" />\n\t\t\t<MiniMap v-if=\"showMiniMap\" />\n\t\t</VueFlow>\n\n\t\t<!-- OUR OWN CONTROLS, not `@vue-flow/controls`.\n\t\t The library's control bar renders bare <button> elements with an\n\t\t icon and no accessible name, which axe reports as `button-name` at\n\t\t SERIOUS impact — caught by the e2e in this change, not by the unit\n\t\t lane or the gates. Rather than ship a control bar a screen-reader\n\t\t user cannot identify, the buttons are ours and carry real labels. -->\n\t\t<div v-if=\"showControls\" class=\"cn-graph-canvas__controls\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom in')\"\n\t\t\t\t@click=\"zoomIn()\">\n\t\t\t\t+\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom out')\"\n\t\t\t\t@click=\"zoomOut()\">\n\t\t\t\t−\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Fit the whole flow in view')\"\n\t\t\t\t@click=\"fitViewNow()\">\n\t\t\t\t⤢\n\t\t\t</button>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { Background } from '@vue-flow/background'\nimport { useVueFlow, VueFlow } from '@vue-flow/core'\nimport { MiniMap } from '@vue-flow/minimap'\nimport CnFlowEdge from './CnFlowEdge.vue'\nimport CnFlowNode from './CnFlowNode.vue'\n\n/**\n * A graph canvas built on Vue Flow — the library n8n runs.\n *\n * WHY THIS IS NOT A WRAPPER AROUND THE OLD API\n * --------------------------------------------\n * This component used to be ~1,300 hand-written lines re-implementing pan,\n * zoom, drag, connect, resize and hit-testing on a bare SVG, because the fleet\n * was on Vue 2.7 and every maintained canvas library is Vue-3-only (ADR-065\n * §5). ADR-081 removed that constraint and named this library as the reason for\n * the migration.\n *\n * The props therefore MOVED rather than being translated. The graph page was\n * not in production, so there was no consumer to protect, and a shim can only\n * ever expose what the old API could already express — `fitView`, `<MiniMap>`,\n * `connectionMode` and the node resizer would all have stayed unreachable\n * behind props designed for a hand-drawn SVG.\n *\n * `nodeWidth` / `nodeHeight` are gone entirely. They existed only so hand-drawn\n * edges could guess where a node's centre was, and the old docblock admitted\n * the failure mode: \"set them to match what your node slot actually renders, or\n * edges will attach off-centre\". Vue Flow measures the rendered node, so the\n * whole class of bug went with the props.\n *\n * ⚠️ THE CANVAS STILL NEVER MUTATES THE GRAPH. Vue Flow is perfectly willing to\n * own node positions internally; here it does not. Changes are emitted and the\n * host decides what to persist — the same rule the previous implementation\n * held, and the reason a canvas can be dropped into a read-only surface without\n * auditing it first.\n *\n * ⚠️ KEYBOARD OPERATION LIVES IN {@see CnFlowNode}. Vue Flow is pointer-first;\n * the focus/ARIA/key contract is re-implemented in the node component, not\n * inherited from the library. See that file for why the multi-exit case is the\n * one that matters.\n */\nexport default {\n\tname: 'CnGraphCanvas',\n\n\tcomponents: { VueFlow, Background, MiniMap, CnFlowNode, CnFlowEdge },\n\n\tprops: {\n\t\t/** Nodes in Vue Flow's shape: `{ id, type, position: {x,y}, data }`. */\n\t\tnodes: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Edges in Vue Flow's shape: `{ id, source, target }`. */\n\t\tedges: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * When true, every interaction is refused — drag, connect AND select.\n\t\t *\n\t\t * Expressed as three Vue Flow flags below. Missing one produces a canvas\n\t\t * that LOOKS locked and is not, so the three are set together and tested\n\t\t * together.\n\t\t */\n\t\treadOnly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Frame the whole graph on first render. */\n\t\tfitView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Snap dragged nodes to the grid. */\n\t\tsnapToGrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Grid spacing, as Vue Flow's `[x, y]`. */\n\t\tsnapGrid: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [16, 16],\n\t\t},\n\n\t\t/** Minimum zoom. */\n\t\tminZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0.2,\n\t\t},\n\n\t\t/** Maximum zoom. */\n\t\tmaxZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Vue Flow's connection mode — `loose` or `strict`. */\n\t\tconnectionMode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'loose',\n\t\t},\n\n\t\t/** Draw the dotted background. */\n\t\tshowBackground: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the zoom/fit controls. */\n\t\tshowControls: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the minimap. */\n\t\tshowMiniMap: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t'nodes-change',\n\t\t'edges-change',\n\t\t'connect',\n\t\t'node-select',\n\t\t'edge-select',\n\t\t'edge-label-move',\n\t\t'edge-label-click',\n\t\t'edge-label-context',\n\t\t'canvas-click',\n\t\t'canvas-drop',\n\n\t\t'node-remove',\n\t\t'node-activate',\n\t\t'node-menu',\n\n\t\t/**\n\t\t * The canvas zoom changed, by any route — a control here, the host's own\n\t\t * toolbar, the wheel, or a fit-view.\n\t\t *\n\t\t * @type {number}\n\t\t */\n\t\t'zoom-change',\n\t],\n\n\tsetup() {\n\t\t// ⚠️ ALIASED. `fitView` is already a BOOLEAN PROP on this component\n\t\t// (frame the graph on first render), and Vue Flow's composable exports a\n\t\t// FUNCTION of the same name. Returning both puts two `fitView` bindings\n\t\t// on the instance — eslint's `vue/no-dupe-keys` catches it, but the\n\t\t// symptom would have been a fit button that tries to call `true`.\n\t\t// `zoomTo` and `viewport` so a HOST toolbar can drive the zoom and show\n\t\t// the real figure — including one the wheel or a fit-view changed.\n\t\tconst { project, zoomIn, zoomOut, zoomTo, fitView: fitViewNow, viewport } = useVueFlow()\n\n\t\treturn { project, zoomIn, zoomOut, zoomTo, fitViewNow, viewport }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the canvas accepts interaction at all.\n\t\t */\n\t\tinteractive() {\n\t\t\treturn this.readOnly === false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// Reported rather than owned: Vue Flow holds the transform, so a host\n\t\t// that mirrors it in its own toolbar must hear every change, not only\n\t\t// the ones its own buttons caused.\n\t\t'viewport.zoom': {\n\t\t\timmediate: true,\n\t\t\thandler(value) {\n\t\t\t\tthis.$emit('zoom-change', value)\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward Vue Flow's node changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonNodesChange(changes) {\n\t\t\t/**\n\t\t\t * @event nodes-change Vue Flow's node change stream — position,\n\t\t\t * selection, dimensions, removal. The canvas does NOT apply these;\n\t\t\t * the host decides what to persist. Drag frames arrive with\n\t\t\t * `dragging: true`, so a host that writes on every change writes\n\t\t\t * once per animation frame.\n\t\t\t */\n\t\t\tthis.$emit('nodes-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward Vue Flow's edge changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgesChange(changes) {\n\t\t\t/**\n\t\t\t * @event edges-change Vue Flow's edge change stream, same contract\n\t\t\t * as `nodes-change`: reported, never applied here.\n\t\t\t */\n\t\t\tthis.$emit('edges-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward a new connection.\n\t\t *\n\t\t * @param {object} connection The connection.\n\t\t * @return {void}\n\t\t */\n\t\t/**\n\t\t * Pass a node's removal request up to the host.\n\t\t *\n\t\t * @param {string} id The node that asked to be removed.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeRemove(id) {\n\t\t\t/**\n\t\t\t * @event node-remove The focused node should be removed, by id.\n\t\t\t * Raised by Delete/Backspace on a node. The canvas removes\n\t\t\t * nothing itself — the host owns `nodes` and `edges`, and a node\n\t\t\t * dropped here would leave the edges pointing at it behind.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('node-remove', id)\n\t\t},\n\n\t\tonConnect(connection) {\n\t\t\t/**\n\t\t\t * @event connect A new connection was made, by pointer OR by\n\t\t\t * keyboard. Carries Vue Flow's\n\t\t\t * `{ source, target, sourceHandle, targetHandle }`, with the\n\t\t\t * handles reduced to the PORT the host declared — see\n\t\t\t * `portIdOf` below.\n\t\t\t */\n\t\t\tthis.$emit('connect', {\n\t\t\t\t...connection,\n\t\t\t\tsourceHandle: this.portIdOf(connection?.sourceHandle),\n\t\t\t\ttargetHandle: this.portIdOf(connection?.targetHandle),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Reduce a HANDLE id to the PORT id the host declared.\n\t\t *\n\t\t * A node draws more handles than it has ports — one exit is offered on\n\t\t * both the right and the bottom edge, one entry on both the left and the\n\t\t * top — because a line has to be able to leave and arrive on the side\n\t\t * the graph reads best from. Vue Flow keys handles by id, so those\n\t\t * cannot share one, and the side is encoded into the id instead\n\t\t * (`yes__right`). See CnFlowNode's `exitHandles`.\n\t\t *\n\t\t * ⚠️ WHICH MEANS THE ENCODED ID MUST NEVER LEAVE THE CANVAS. A branch\n\t\t * recorded as `yes__bottom` names a port the engine has never heard of,\n\t\t * and a flow whose branch ids do not match its catalogue is one the\n\t\t * engine cannot route — it would take the default exit and the canvas\n\t\t * would keep showing the branch the author drew. So both routes into\n\t\t * this method (pointer via <VueFlow>, keyboard via CnFlowNode) are\n\t\t * stripped here, in the one place every connection passes through.\n\t\t *\n\t\t * An entry handle carries no port of its own — a node has exactly one\n\t\t * inbound port — so it reduces to null rather than to the literal `in`.\n\t\t *\n\t\t * @param {string|null|undefined} handleId The handle id Vue Flow reports.\n\t\t * @return {string|null} The port id, or null.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf('__')\n\t\t\tconst port = cut === -1 ? handleId : handleId.slice(0, cut)\n\n\t\t\treturn port === 'in' ? null : port\n\t\t},\n\n\t\t/**\n\t\t * Forward Enter or Space on a node: the keyboard double-click.\n\t\t *\n\t\t * @param {string} id The node id.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeActivate(id) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event node-activate Enter or Space on a node. Payload is the id.\n\t\t\t */\n\t\t\tthis.$emit('node-activate', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward Shift+F10 or the menu key on a node: the keyboard click menu.\n\t\t *\n\t\t * @param {object} request `{ id, clientX, clientY }`, a point at the node.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeMenu(request) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event node-menu Shift+F10 or the menu key on a node. Payload is\n\t\t\t * `{ id, clientX, clientY }`.\n\t\t\t */\n\t\t\tthis.$emit('node-menu', request)\n\t\t},\n\n\t\t/**\n\t\t * Forward a node click.\n\t\t *\n\t\t * @param {object} event Vue Flow's node event.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeClick(event) {\n\t\t\t/**\n\t\t\t * @event node-select A node was clicked. Payload is Vue Flow's\n\t\t\t * `{ node }`, so a host tracking bare ids reads `event.node.id`.\n\t\t\t */\n\t\t\tthis.$emit('node-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward an edge click.\n\t\t *\n\t\t * @param {object} event Vue Flow's edge event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeClick(event) {\n\t\t\t/**\n\t\t\t * @event edge-select An edge was clicked.\n\t\t\t */\n\t\t\tthis.$emit('edge-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward a label slid along its line.\n\t\t *\n\t\t * @param {{id: string, labelT: number}} payload The edge and its new\n\t\t * fraction, already clamped.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelMove(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-move A connection's label was moved, by pointer\n\t\t\t * or by keyboard. Carries `{ id, labelT }`. As everywhere else in\n\t\t\t * this component the change is REPORTED, not applied — the host\n\t\t\t * stores `labelT` and feeds it back down.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-move', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward an activated label.\n\t\t *\n\t\t * @param {string} id The edge id.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelClick(id) {\n\t\t\t/**\n\t\t\t * @event edge-label-click A connection's label was activated.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-click', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward a right-click on a label.\n\t\t *\n\t\t * @param {{id: string, event: MouseEvent}} payload The edge and event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelContext(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-context A connection's label was right-clicked.\n\t\t\t * Hosts open the same menu they open for the line itself.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-context', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward a click on the empty pane.\n\t\t *\n\t\t * @param {object} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonPaneClick(event) {\n\t\t\t/**\n\t\t\t * @event canvas-click The empty pane was clicked — hosts use this to\n\t\t\t * clear a selection.\n\t\t\t */\n\t\t\tthis.$emit('canvas-click', event)\n\t\t},\n\n\t\t/**\n\t\t * A palette drop, with the point already converted to canvas space.\n\t\t *\n\t\t * Kept as a Conduction-level event because HTML5 drop plus coordinate\n\t\t * projection is glue every consumer would otherwise write. The\n\t\t * projection is Vue Flow's `project()` rather than the old hand-rolled\n\t\t * `(clientX - rect.left - panOffset.x) / zoom`.\n\t\t *\n\t\t * The canvas does not create the node — the host does, mirroring the\n\t\t * rule that it never mutates positions either.\n\t\t *\n\t\t * @param {DragEvent} event The drop event.\n\t\t * @return {void}\n\t\t */\n\t\tonDrop(event) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst bounds = this.$el.getBoundingClientRect()\n\t\t\tconst position = this.project({\n\t\t\t\tx: event.clientX - bounds.left,\n\t\t\t\ty: event.clientY - bounds.top,\n\t\t\t})\n\n\t\t\t/**\n\t\t\t * @event canvas-drop An HTML5 drop landed on the canvas, with\n\t\t\t * `position` already projected into canvas space and the native\n\t\t\t * `event` alongside so the host can read `dataTransfer`. The canvas\n\t\t\t * never creates the node itself.\n\t\t\t */\n\t\t\tthis.$emit('canvas-drop', { position, event })\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/* Vue Flow's own stylesheet, themed through Nextcloud variables. Imported\n un-scoped because it styles elements Vue Flow renders outside this\n component's scope. */\n@import '@vue-flow/core/dist/style.css';\n@import '@vue-flow/core/dist/theme-default.css';\n\n/* ⚠️ EVERY VUE FLOW PLUGIN SHIPS ITS OWN STYLESHEET, AND ONLY CORE'S WAS HERE.\n\n `@vue-flow/node-resizer` was the one that mattered: its stylesheet is what\n gives a resize control `position: absolute` and a 5px handle. Without it the\n handles CnFlowNode renders have no size and no position, so a node that is\n resizable looks exactly like a node that is not — the component was mounted\n and the affordance was invisible.\n\n The minimap's own sheet is small (a background colour and the grab cursors);\n its geometry comes from `.vue-flow__panel` in core, which was already here.\n Imported for the cursors, and before our own rules so the theme below still\n wins. */\n@import '@vue-flow/node-resizer/dist/style.css';\n@import '@vue-flow/minimap/dist/style.css';\n\n.cn-graph-canvas {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 320px;\n}\n\n/* ⚠️ VUE FLOW'S DEFAULT NODE DRAWS A BOX WE NEVER ASKED FOR.\n\n Every node this canvas renders goes through the `#node-default` slot, so\n Vue Flow wraps our CnFlowNode in `.vue-flow__node-default` — and\n `theme-default.css` gives that wrapper a 1px border, a white background and\n 10px of padding. The result was a box around our box: the wrapper's border,\n then `.cn-flow-node`'s own. Nothing failed and nothing warned; a node just\n quietly had one more outline than it was designed to have.\n\n Stripped, not restyled: `.cn-flow-node` is the node's chrome, and it is the\n ONLY border a node draws. `width` stays 150px because Vue Flow measures the\n wrapper to route edges and place handles — dropping it would move every\n node's footprint, which is a layout change, not a fix for a stray border. */\n.cn-graph-canvas .vue-flow__node-default {\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tbackground: none;\n\n\t/* The handle colour reaches `.cn-flow-node__handle` through this variable.\n\t Vue Flow's own `.vue-flow__node-default .vue-flow__handle` rule outranks\n\t our single-class one, so a themed handle lost to a hard-coded #1a192b on\n\t every node — setting the variable themes it at the source instead of\n\t fighting the specificity. */\n\t--vf-handle: var(--color-primary-element, #0082c9);\n}\n\n/* The selected/focus/hover states re-assert the border and shadow the rule\n above just removed, so they have to be stripped too — otherwise clicking a\n node brought the extra box back. Selection is shown by `.cn-flow-node--selected`,\n and focus by `.cn-flow-node:focus-visible`, both on the node itself. */\n.cn-graph-canvas .vue-flow__node-default.selected,\n.cn-graph-canvas .vue-flow__node-default.selected:hover,\n.cn-graph-canvas .vue-flow__node-default.selectable:hover,\n.cn-graph-canvas .vue-flow__node-default:focus,\n.cn-graph-canvas .vue-flow__node-default:focus-visible {\n\tborder: 0;\n\tbox-shadow: none;\n}\n\n/* THEME, NOT OVERRIDE. Every colour comes from a Nextcloud variable with a\n fallback, so the canvas follows the user's theme — including dark — instead\n of being the one component that ignores it. */\n.cn-graph-canvas .vue-flow__background {\n\tbackground: var(--color-main-background, #fff);\n}\n\n.cn-graph-canvas .vue-flow__edge-path {\n\tstroke: var(--color-border-dark, #888);\n}\n\n.cn-graph-canvas .vue-flow__edge.selected .vue-flow__edge-path {\n\tstroke: var(--color-primary-element, #0082c9);\n}\n\n/* An edge says which way the data flows, and it can only say it with an\n arrowhead — the direction is the one thing a line cannot express on its own.\n\n ⚠️ `!important` IS LOAD-BEARING HERE, not laziness. Vue Flow writes the\n arrowhead's colour as an INLINE style on the polyline it generates, and an\n inline style beats any class selector. The alternative is passing a colour\n through the marker definition in JS, which would hard-code a hex and take\n the arrowhead out of the theme — it would stay dark grey in dark mode while\n the line it terminates went light. Themed here, it follows the user.\n\n `markerUnits` defaults to `strokeWidth`, so the size set alongside this in\n `useFlowStore.canvasEdges` is what keeps the arrow clear of the target\n node's port handle. */\n.cn-graph-canvas .vue-flow__arrowhead polyline,\n.cn-graph-canvas .vue-flow__arrowhead path {\n\tfill: var(--color-text-maxcontrast, #6b6b6b) !important;\n\tstroke: var(--color-text-maxcontrast, #6b6b6b) !important;\n}\n\n.cn-graph-canvas__controls {\n\tposition: absolute;\n\tbottom: 12px;\n\tleft: 12px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tz-index: 5;\n}\n\n.cn-graph-canvas__control {\n\twidth: 28px;\n\theight: 28px;\n\tborder: 1px solid var(--color-border, #d0d0d0);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n\tcursor: pointer;\n}\n\n.cn-graph-canvas__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 1px;\n}\n\n.cn-graph-canvas .vue-flow__minimap {\n\tbackground: var(--color-background-dark, #ededed);\n}\n</style>\n"],"names":["_createElementBlock","_createVNode","_withCtx","_mergeProps","_renderSlot","_createBlock","_openBlock","_createElementVNode"],"mappings":"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"}
@@ -4,6 +4,7 @@ import { generateUrl } from '@nextcloud/router';
4
4
  import { NcButton, NcCheckboxRadioSwitch, NcDialog, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue';
5
5
  import __sfc_main from '../components/CnCronField/CnCronField.vue.js';
6
6
  import { useFlowStore } from '../composables/useFlowStore.js';
7
+ import { hasStaticOptions, normaliseSelectOptions } from '../utils/selectOptions.js';
7
8
 
8
9
  /** The verbs an HTTP-shaped `method` option can take. */
9
10
  /**
@@ -121,7 +122,7 @@ var script = {
121
122
 
122
123
  /**
123
124
  * The node's per-field declarations from the catalogue's `configForm`
124
- * ({key, label, type, help, required, optionsFrom}), keyed by config
125
+ * ({key, label, type, help, required, optionsFrom, options}), keyed by config
125
126
  * key. `configKeys` is the degraded form. Either way the node's OWNER
126
127
  * declares the vocabulary — this dialog never invents fields.
127
128
  *
@@ -232,7 +233,9 @@ var script = {
232
233
  created() {
233
234
  // Select fields need their options; everything else is local.
234
235
  for (const [key, spec] of Object.entries(this.fieldSpecs)) {
235
- if (spec?.type === 'select' && spec?.optionsFrom) {
236
+ if (hasStaticOptions(spec)) {
237
+ this.selectOptions = { ...this.selectOptions, [key]: normaliseSelectOptions(spec.options) };
238
+ } else if (spec?.type === 'select' && spec?.optionsFrom) {
236
239
  this.loadSelectOptions(key, spec);
237
240
  }
238
241
  }
@@ -468,7 +471,7 @@ var script = {
468
471
  */
469
472
  widgetFor(key) {
470
473
  const spec = this.fieldSpecs[key];
471
- if (spec?.type === 'select' && spec?.optionsFrom) {
474
+ if (spec?.type === 'select' && (spec?.optionsFrom || hasStaticOptions(spec))) {
472
475
  return 'select'
473
476
  }
474
477
  if (spec?.type === 'boolean') {
@@ -564,13 +567,7 @@ var script = {
564
567
  ? generateUrl(url)
565
568
  : url);
566
569
  const rows = Array.isArray(response.data) ? response.data : (response.data?.results || []);
567
- const options = rows.map((row) => {
568
- const id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid;
569
- return {
570
- id,
571
- label: row.label || row.name || row.title || String(id),
572
- }
573
- }).filter((o) => o.id !== undefined && o.id !== null && o.id !== '');
570
+ const options = normaliseSelectOptions(rows);
574
571
  this.selectOptions = { ...this.selectOptions, [key]: options };
575
572
  } catch (error) {
576
573
  // A picker that could not load degrades to showing the stored
@@ -1 +1 @@
1
- {"version":3,"file":"CnFlowNodeEditModal.vue2.js","sources":["../../../src/dialogs/CnFlowNodeEditModal.vue"],"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":["CnCronField","axios"],"mappings":"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"}
1
+ {"version":3,"file":"CnFlowNodeEditModal.vue2.js","sources":["../../../src/dialogs/CnFlowNodeEditModal.vue"],"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":["CnCronField","axios"],"mappings":";;;;;;;;AAyLA;AACA;AACC;AACA;;;;;AAKD,MAAM,iBAAiB;CACtB,UAAU;CACV,YAAY;AACZ,CAAA,gBAAgB;AAChB,CAAA,iBAAiB;AACjB,CAAA,eAAe;AACf,CAAA,iBAAiB;AAClB,EAAA;;AAEA,MAAM,YAAW,GAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAA;;AAEhF;;AAEC;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,qBAAqB;;CAE3B,UAAU,EAAE;eACXA,UAAW;AACX,EAAA,QAAQ;AACR,EAAA,qBAAqB;AACrB,EAAA,QAAQ;AACR,EAAA,QAAQ;EACR,UAAU;EACV,WAAW;EACX;;AAED,CAAA,KAAK,GAAG;EACP,OAAO,EAAE,KAAK,EAAE,YAAY,EAAG,EAAA;EAC/B;;AAED,CAAA,IAAI,GAAG;EACN,MAAM,OAAO,YAAY,EAAE,CAAC,WAAU,IAAK,EAAE,MAAM,EAAE,EAAG,GAAA;;AAExD,EAAA,OAAO;GACN,YAAY;;;;;GAKZ,UAAU,EAAE,EAAE;GACd,iBAAiB,EAAE,KAAK;;;;;;AAMxB,GAAA,KAAK,EAAE,EAAE;AACT,GAAA,YAAY,EAAE,KAAK;;;AAGnB,GAAA,KAAK,EAAE;AACN,IAAA,IAAI,EAAE,IAAI,CAAC,IAAG,IAAK,EAAE;IACrB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAK,IAAK,EAAE,CAAC,CAAC;IACrD;;;AAGD;GACA,UAAU,EAAE,EAAE;GACd,UAAU,EAAE,EAAE;;;AAGd;AACA,GAAA,aAAa,EAAE,EAAE;AACjB,GAAA,aAAa,EAAE,EAAE;;AAEjB,GAAA,aAAa,EAAE,IAAI;AACnB,GAAA,aAAa,EAAE,IAAI;AACpB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;;;AAIA,EAAA,IAAI,GAAG;GACN,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAG,EAAA;GACxD;;AAED;;;AAGA,EAAA,KAAK,GAAG;AACP,GAAA,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA;GAC7C;;AAED;;;EAGA,SAAS,GAAG;AACX,GAAA,OAAO,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,WAAU,IAAK,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,CAAC,IAAK,IAAG,GAAG,CAAA;GACtF;;AAED;;AAEC;;;AAGA;AACA;;EAED,UAAU,GAAG;GACZ,MAAM,KAAM,GAAE,GAAC;AACf,GAAA,KAAK,MAAM,UAAU,IAAI,CAAC,KAAK,EAAE,UAAW,IAAG,EAAE,GAAG;AACnD,IAAA,IAAI,SAAS,KAAK,CAAC,GAAG,EAAE;AACvB,KAAA,KAAK,CAAC,KAAK,CAAC,GAAG,CAAA,GAAI,MAAI;AACxB,KAAA;AACD,IAAA;;GAEA,OAAO,KAAI;GACX;;AAED;;;;;AAKC;AACA;AACA;;AAED;;AAEC;;;AAGA;AACA;AACA;;AAED,EAAA,aAAa,GAAG;GACf,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAK,GAAI,CAAC,EAAE;AAC7B,IAAA,OAAO,IAAG;AACX,IAAA;;GAEA,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAO,IAAG,EAAE,CAAC,CAAC,MAAK,GAAI,CAAA;GACrD;;AAED;;AAEC;;;EAGD,UAAU,GAAG;AACZ,GAAA,OAAO,MAAM,CAAC,cAAc,EAAE,EAAE,GAAE,IAAK,EAAE,CAAA;GACzC;;AAED;AACC;AACA;;;;AAIA;AACA;AACA;;EAED,WAAW,GAAG;GACb,MAAM,EAAG,GAAE,IAAI,CAAC,WAAS;AACzB,GAAA,MAAM,IAAK,GAAE,EAAG,KAAI,EAAG,GAAE,EAAC,GAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,eAAe,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,EAAG,EAAC,GAAG,EAAA;;GAEhG,OAAO,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAG,KAAI,EAAE,CAAC,CAAA;GAC/D;;AAED;;AAEC;AACA;;AAED,EAAA,gBAAgB,GAAG;AAClB,GAAA,OAAO,IAAI,CAAC,UAAS;GACrB;;EAED,QAAQ,GAAG;AACV,GAAA,MAAM,QAAS,GAAE,CAAC,IAAI,CAAC,KAAK,EAAE,UAAW,IAAG,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,EAAA;AAChF,GAAA,MAAM,QAAO,GAAI;IAChB,GAAG,QAAQ;AACX,IAAA,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,UAAS,IAAK,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACvE,KAAA;GACA,MAAM,UAAU,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAA;;GAExG,OAAO,CAAC,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAA;GAC/B;;AAED;;;AAGA,EAAA,YAAY,GAAG;GACd,IAAI,IAAI,CAAC,aAAc,KAAI,IAAI,EAAE;IAChC,OAAO,IAAI,CAAC,aAAY;AACzB,IAAA;;AAEA,GAAA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;GAChD;;AAED;;;EAGA,SAAS,GAAG;AACX,GAAA,OAAO,IAAI,CAAC,aAAc,KAAI,IAAG,IAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,SAAS,CAAA;GAC5E;EACD;;AAED,CAAA,OAAO,GAAG;;EAET,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAA,IAAK,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;GAC1D,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE;AAC3B,IAAA,IAAI,CAAC,gBAAgB,EAAE,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,GAAG,GAAG,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAE,GAAA;IACzF,MAAK,IAAI,IAAI,EAAE,SAAS,QAAS,IAAG,IAAI,EAAE,WAAW,EAAE;AACxD,IAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAA;AACjC,IAAA;AACD,GAAA;;AAEA,EAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAA,KAAM,WAAW,CAAC,EAAE;;;;GAIrE,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAA;AACzB,GAAA;;EAEA,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAA,KAAM,MAAM,CAAC,EAAE;AAChE,GAAA,IAAI,CAAC,SAAS,GAAC;AAChB,GAAA;EACA;;AAED,CAAA,OAAO,EAAE;AACR;AACC;AACA;;;;;AAKA;;;AAGD;;AAEC;;;;;;;AAOA;;;AAGA;AACA;AACA;AACA;;AAED,EAAA,gBAAgB,CAAC,GAAG,EAAE;GACrB,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,KAAK;AACpD,IAAA,MAAM,EAAC,GAAI,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC,EAAA;IAC7C,MAAM,KAAM,GAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,EAAC,KAAM,EAAE,EAAA;;IAE/D,OAAO,KAAM,IAAG;AACf,KAAA,EAAE;AACF,KAAA,IAAI,EAAE,SAAS,CAAC,IAAI;AACpB,KAAA,KAAK,EAAE,SAAS,CAAC,EAAE;AACnB,KAAA,KAAK,EAAE,SAAS,CAAC,EAAE;AACpB,KAAA;IACA,CAAA;GACD;;AAED;;AAEC;;;;;AAKA;AACA;AACA;AACA;;AAED,EAAA,gBAAgB,CAAC,GAAG,EAAE;AACrB,GAAA,MAAM,GAAI,GAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAA;AACjC,GAAA,MAAM,QAAO,GAAI,GAAI,KAAI,iBAAgB,IAAK,GAAI,KAAI,eAAc,GAAI,UAAU,OAAK;AACvF,GAAA,MAAM,IAAG,GAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAE,GAAE,OAAO,GAAE,KAAM,IAAK,IAAG,GAAI,KAAI,SAAU,IAAG,GAAI,KAAI,EAAG,GAAE,EAAG,GAAE,CAAC,GAAG,CAAC,EAAA;;AAErG,GAAA,OAAO,IAAG;AACT,KAAC,GAAG,CAAC,CAAC,KAAK,KAAK;KACf,IAAI,OAAO,UAAU,QAAQ,EAAE;MAC9B,OAAO,KAAK,CAAC,IAAI,EAAG,KAAI,EAAC,GAAI,IAAK,GAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,EAAG,EAAA;AACxE,MAAA;AACA,KAAA,IAAI,KAAM,IAAG,OAAO,KAAM,KAAI,QAAQ,EAAE;MACvC,MAAM,EAAG,GAAE,MAAM,CAAC,KAAK,CAAC,EAAC,IAAK,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,GAAC;;MAEtD,OAAO,EAAG,KAAI,EAAC,GAAI,IAAG,GAAI,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAG,IAAK,QAAQ,CAAC,EAAE,EAAG,EAAA;AACtE,MAAA;;AAEA,KAAA,OAAO,IAAG;KACV,CAAA;AACD,KAAC,MAAM,CAAC,OAAO,CAAA;GAChB;;AAED;AACC;AACA;;;AAGA;AACA;AACA;;;;EAID,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE;AAC1B,GAAA,MAAM,UAAW,GAAE,CAAC,MAAO,IAAG,EAAE,EAAE,GAAG,CAAC,CAAC,MAAM,MAAM;IAClD,IAAI,EAAE,MAAM,CAAC,QAAQ,MAAM;AAC3B,IAAA,EAAE,EAAE,MAAM,CAAC,KAAM,IAAG,MAAM,CAAC,MAAM,CAAC,EAAG,IAAG,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;AAC1E,IAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,EAAC,KAAM,EAAE,EAAA;;;;;AAK7C,GAAA,IAAI,GAAI,KAAI,UAAW,IAAG,GAAI,KAAI,iBAAgB,IAAK,QAAQ,eAAe,EAAE;AAC/E,IAAA,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAA,IAAK,EAAE,EAAA;;IAEpC,MAAK;AACN,IAAA;;GAEA,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAA;GAC3B;;AAED;AACC;AACA;;;;;AAKA;;;;;AAKA;AACA;AACA;;;EAGD,MAAM,gBAAgB,CAAC,KAAK,EAAE;GAC7B,IAAI,CAAC,iBAAkB,GAAE,KAAG;AAC5B,GAAA,IAAI;IACH,MAAM,QAAS,GAAE,MAAMC,cAAK,CAAC,GAAG;KAC/B,WAAW,CAAC,mCAAmC,CAAC;AAChD,KAAA;AACC,MAAA,MAAM,EAAE;OACP,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;OAC3B,QAAQ,EAAE,EAAE;AACZ,OAAA,MAAM,EAAE,EAAE;AACV,OAAA,cAAc,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACtB,OAAA,KAAK,EAAE,EAAE;OACT;MACD,OAAO,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAiB,EAAG;MACjE;AACF,MAAA;;AAEA,IAAA,MAAM,IAAG,GAAI,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,IAAK,IAAG,GAAC;AAC3C,IAAA,IAAI,CAAC,UAAW,GAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAA,GAAI,IAAK,GAAE,EAAE;MAChD,GAAG,CAAC,CAAC,GAAG,KAAK;AACb,MAAA,MAAM,IAAK,GAAE,GAAG,CAAC,WAAW,QAAO,GAAI,OAAQ,GAAE,OAAK;MACtD,MAAM,EAAG,GAAE,MAAM,CAAC,GAAG,CAAC,EAAG,IAAG,EAAE,EAAA;;AAE9B,MAAA,OAAO;AACN,OAAA,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;OACnB,IAAI;AACJ,OAAA,KAAK,EAAE,EAAE;AACT,OAAA,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,KAAM,IAAG,EAAE,CAAC,CAAC,EAAE,IAAG,KAAM,OAAQ,GAAE,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,UAAU,CAAA,GAAI,EAAE,CAAC,CAAC;AAClG,OAAA;MACA,CAAA;AACD,MAAC,MAAM,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,KAAM,KAAI,EAAE,EAAA;AACzC,IAAA,CAAE,MAAM;;;;AAIP,IAAA,IAAI,CAAC,UAAS,GAAI,GAAC;AACpB,IAAA,SAAU;IACT,IAAI,CAAC,iBAAgB,GAAI,MAAI;AAC9B,IAAA;GACA;;AAED,EAAA,MAAM,SAAS,GAAG;AACjB,GAAA,IAAI,CAAC,eAAe,KAAG;AACvB,GAAA,IAAI;;;;;;;IAOH,MAAM,QAAS,GAAE,MAAMA,cAAK,CAAC,GAAG;KAC/B,WAAW,CAAC,mCAAmC,CAAC;AAChD,KAAA;AACC,MAAA,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI;MAC9E,OAAO,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAiB,EAAG;MACjE;AACF,MAAA;;AAEA,IAAA,MAAM,IAAG,GAAI,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,IAAK,IAAG,GAAC;AAC3C,IAAA,IAAI,CAAC,KAAM,GAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAA,GAAI,IAAK,GAAE,EAAE;AAC5C,MAAC,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,MAAO,KAAI,OAAO,CAAA;AACvC,MAAC,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAG,IAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAM,IAAG,GAAG,CAAC,EAAC,IAAK,EAAE,CAAA,EAAG,CAAC,CAAA;MACrF,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,OAAO,EAAE,EAAA;AAChC,IAAA,CAAE,MAAM;IACP,IAAI,CAAC,KAAM,GAAE,GAAC;AACf,IAAA,SAAU;IACT,IAAI,CAAC,YAAW,GAAI,MAAI;AACzB,IAAA;GACA;;AAED;;AAEC;;;AAGA;AACA;AACA;AACA;;AAED,EAAA,UAAU,CAAC,GAAG,EAAE;AACf,GAAA,MAAM,KAAI,GAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,EAAE,EAAA;AACjD,GAAA,IAAI,KAAM,KAAI,EAAE,EAAE;AACjB,IAAA,OAAO,IAAG;AACX,IAAA;;AAEA,GAAA,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,OAAO,KAAK,CAAA,IAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAM,EAAA;GAC3F;;AAED;;;AAGC;AACA;AACA;;AAED,EAAA,SAAS,CAAC,GAAG,EAAE;GACd,MAAM,IAAK,GAAE,IAAI,CAAC,UAAU,CAAC,GAAG,EAAA;AAChC,GAAA,IAAI,IAAI,EAAE,IAAK,KAAI,aAAa,IAAI,EAAE,WAAU,IAAK,gBAAgB,CAAC,IAAI,CAAC,CAAC,EAAE;IAC7E,OAAO,QAAO;AACf,IAAA;GACA,IAAI,IAAI,EAAE,IAAG,KAAM,SAAS,EAAE;IAC7B,OAAO,QAAO;AACf,IAAA;GACA,IAAI,IAAI,EAAE,SAAS,QAAQ,EAAE;IAC5B,OAAO,QAAO;AACf,IAAA;GACA,IAAI,IAAI,EAAE,IAAG,KAAM,UAAU,EAAE;AAC9B,IAAA,OAAO,UAAS;AACjB,IAAA;;;;;AAKA;GACA,IAAI,IAAI,EAAE,IAAK,KAAI,MAAK,IAAK,IAAI,EAAE,MAAO,KAAI,MAAK,IAAK,GAAI,KAAI,MAAM,EAAE;IACvE,OAAO,MAAK;AACb,IAAA;;;;;AAKA;AACA,GAAA,IAAI,QAAQ,OAAO,EAAE;IACpB,OAAO,MAAK;AACb,IAAA;;;;;GAKA,IAAI,IAAI,EAAE,IAAK,KAAI,WAAU,IAAK,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC/D,IAAA,OAAO,WAAU;AAClB,IAAA;;GAEA,IAAI,IAAI,EAAE,IAAK,KAAI,MAAM,EAAE;IAC1B,OAAO,MAAK;AACb,IAAA;;AAEA,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAA;GACnC,IAAI,OAAO,UAAU,SAAS,EAAE;IAC/B,OAAO,QAAO;AACf,IAAA;GACA,IAAI,OAAO,UAAU,QAAQ,EAAE;IAC9B,OAAO,QAAO;AACf,IAAA;GACA,IAAI,KAAI,KAAM,QAAQ,KAAM,KAAI,SAAQ,IAAK,OAAO,KAAM,KAAI,QAAQ,EAAE;IACvE,OAAO,MAAK;AACb,IAAA;AACA,GAAA,IAAI,GAAE,KAAM,QAAQ,EAAE;IACrB,OAAO,QAAO;AACf,IAAA;;GAEA,OAAO,MAAK;GACZ;;AAED;;;AAGC;AACA;AACA;AACA;;AAED,EAAA,cAAc,CAAC,GAAG,EAAE;AACnB,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAA;GACnC,IAAI,KAAI,KAAM,SAAQ,IAAK,UAAU,IAAK,IAAG,KAAI,KAAM,EAAE,EAAE;AAC1D,IAAA,OAAO,IAAG;AACX,IAAA;;AAEA,GAAA,MAAM,UAAU,IAAI,CAAC,aAAa,CAAC,GAAG,CAAE,IAAG,GAAC;AAC5C,GAAA,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAG,KAAI,KAAK,CAAE,IAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAE,EAAA;GAChF;;AAED;;AAEC;AACA;;;;AAIA;AACA;AACA;;;;AAID,EAAA,MAAM,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE;GAClC,IAAI,CAAC,aAAc,GAAE,EAAE,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,GAAG,GAAG,IAAK,GAAA;AAC1D,GAAA,IAAI;AACH,IAAA,MAAM,GAAI,GAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAA;IACnC,MAAM,WAAW,MAAMA,cAAK,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAA,IAAK,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAA,IAAK,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAA;AAC/G,OAAE,WAAW,CAAC,GAAG,CAAA;OACf,GAAG,EAAA;AACN,IAAA,MAAM,IAAG,GAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAG,IAAK,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,EAAA;AACzF,IAAA,MAAM,UAAU,sBAAsB,CAAC,IAAI,EAAA;IAC3C,IAAI,CAAC,aAAY,GAAI,EAAE,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,GAAG,GAAG,OAAQ,GAAA;IAC5D,CAAA,OAAO,KAAK,EAAE;;;AAGf;IACA,OAAO,CAAC,KAAK,CAAC,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAA;AACpE,IAAA,SAAU;IACT,IAAI,CAAC,aAAc,GAAE,EAAE,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,GAAG,GAAG,KAAM,GAAA;AAC5D,IAAA;GACA;;AAED;;;AAGC;AACA;;;EAGD,QAAQ,CAAC,GAAG,EAAE;AACb,GAAA,MAAM,QAAS,GAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,MAAI;GAC3C,IAAI,QAAQ,EAAE;IACb,OAAO,QAAO;AACf,IAAA;;AAEA,GAAA,MAAM,MAAK,GAAI,GAAE;AAChB,KAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAA;AACtB,KAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAA;KACrC,WAAW,GAAC;;GAEd,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAC,GAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;GACtD;;AAED;;;AAGC;AACA;AACA;;EAED,OAAO,CAAC,GAAG,EAAE;GACZ,MAAM,IAAK,GAAE,IAAI,CAAC,UAAU,CAAC,GAAG,EAAA;AAChC,GAAA,IAAI,IAAI,EAAE,IAAI,EAAE;IACf,OAAO,IAAI,CAAC,QAAO,KAAM,IAAG;OACzB,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;AACvD,OAAE,IAAI,CAAC,IAAG;AACZ,IAAA;GACA,IAAI,IAAI,EAAE,QAAO,KAAM,IAAI,EAAE;AAC5B,IAAA,OAAO,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,WAAW,CAAA;AAC3C,IAAA;AACA,GAAA,IAAI,QAAQ,MAAM,EAAE;AACnB,IAAA,OAAO,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,6CAA6C,CAAA;AAC7E,IAAA;;AAEA,GAAA,OAAO,SAAQ;GACf;;AAED;AACC;AACA;;;AAGD,EAAA,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE;GAClB,IAAI,CAAC,KAAK,CAAC,MAAO,GAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,GAAG,KAAM,GAAA;GACzD,IAAI,CAAC,gBAAgB,KAAG;GACxB,IAAI,CAAC,gBAAgB,KAAG;GACxB;;AAED;AACC;AACA;;;EAGD,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE;GACvB,MAAM,MAAK,GAAI,MAAM,CAAC,IAAI,EAAA;GAC1B,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,SAAS,IAAI,EAAA;GACxD;;AAED;AACC;;;AAGD,EAAA,YAAY,CAAC,GAAG,EAAE;AACjB,GAAA,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAE,KAAI,SAAS,EAAE;IACvC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAA;AAC3B,IAAA;;GAEA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;GACrD;;AAED;;AAEC;AACA;AACA;;;AAGD,EAAA,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE;GACrB,IAAI,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,GAAG,IAAK,GAAA;AACpD,GAAA,IAAI;IACH,MAAM,MAAK,GAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAA;IAC9B,MAAM,IAAK,GAAE,EAAE,GAAG,IAAI,CAAC,UAAW,GAAA;AAClC,IAAA,OAAO,IAAI,CAAC,GAAG,EAAA;AACf,IAAA,IAAI,CAAC,aAAa,KAAG;IACrB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAA;AACxB,IAAA,CAAE,MAAM;AACP,IAAA,IAAI,CAAC,aAAa;AACjB,KAAA,GAAG,IAAI,CAAC,UAAU;AAClB,KAAA,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,0DAA0D,CAAC;AAC3F,MAAA;AACD,IAAA;GACA;;AAED;;AAEC;AACA;;;AAGD,EAAA,eAAe,CAAC,IAAI,EAAE;GACrB,IAAI,CAAC,gBAAgB,KAAG;AACxB,GAAA,IAAI;AACH,IAAA,MAAM,MAAK,GAAI,IAAI,CAAC,KAAK,CAAC,IAAG,IAAK,IAAI,EAAA;AACtC,IAAA,IAAI,MAAK,KAAM,IAAK,IAAG,OAAO,WAAW,QAAS,IAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;AAC3E,KAAA,IAAI,CAAC,aAAc,GAAE,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,6CAA6C,EAAA;KAC1F,MAAK;AACN,KAAA;;IAEA,IAAI,CAAC,gBAAgB,KAAG;IACxB,IAAI,CAAC,KAAK,CAAC,MAAK,GAAI,OAAK;AACzB,IAAA,IAAI,CAAC,UAAS,GAAI,GAAC;AACnB,IAAA,IAAI,CAAC,UAAS,GAAI,GAAC;AACpB,IAAA,CAAE,MAAM;IACP,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,gEAAgE,EAAA;AAC9G,IAAA;GACA;;AAED;;AAEC;;;AAGD,EAAA,IAAI,GAAG;AACN,GAAA,MAAM,EAAC,GAAI,IAAI,CAAC,KAAK,CAAC,cAAY;GAClC,IAAI,EAAG,KAAI,IAAI,EAAE;IAChB,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAA;AAClD,IAAA,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAA;AAC3C,IAAA;GACA,IAAI,CAAC,KAAK,CAAC,aAAc,GAAE,KAAG;GAC9B;;AAED;;AAEC;;;AAGD,EAAA,MAAM,GAAG;GACR,IAAI,CAAC,KAAK,CAAC,aAAc,GAAE,KAAG;GAC9B;;AAED;AACC;AACA;;;EAGD,UAAU,GAAG;AACZ,GAAA,MAAM,EAAC,GAAI,IAAI,CAAC,KAAK,CAAC,cAAY;GAClC,IAAI,CAAC,KAAK,CAAC,aAAc,GAAE,KAAG;GAC9B,IAAI,EAAG,KAAI,IAAI,EAAE;IAChB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,EAAA;AACzB,IAAA;GACA;EACD;AACF;;;;"}
@@ -6,11 +6,12 @@ const _hoisted_2 = {
6
6
  class: "cn-flow-node-edit__description"
7
7
  };
8
8
  const _hoisted_3 = { class: "cn-flow-node-edit__id" };
9
- const _hoisted_4 = {
9
+ const _hoisted_4 = ["data-testid"];
10
+ const _hoisted_5 = {
10
11
  key: 1,
11
12
  class: "cn-flow-node-edit__hint"
12
13
  };
13
- const _hoisted_5 = {
14
+ const _hoisted_6 = {
14
15
  key: 2,
15
16
  class: "cn-flow-node-edit__advanced"
16
17
  };
@@ -96,15 +97,24 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
96
97
  "onUpdate:modelValue": $event => ($options.setKey(key, $event))
97
98
  }, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel", "placeholder", "onUpdate:modelValue"]))
98
99
  : ($options.widgetFor(key) === 'select')
99
- ? (openBlock(), createBlock(_component_NcSelect, {
100
- key: 2,
101
- modelValue: $options.selectedOption(key),
102
- options: $data.selectOptions[key] || [],
103
- inputLabel: $options.labelFor(key),
104
- loading: $data.selectLoading[key] === true,
105
- placeholder: _ctx.t('nextcloud-vue', 'Pick one…'),
106
- "onUpdate:modelValue": $event => ($options.setKey(key, $event ? $event.id : ''))
107
- }, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel", "loading", "placeholder", "onUpdate:modelValue"]))
100
+ ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
101
+ createVNode(_component_NcSelect, {
102
+ modelValue: $options.selectedOption(key),
103
+ class: "cn-flow-node-edit__select",
104
+ options: $data.selectOptions[key] || [],
105
+ inputLabel: $options.labelFor(key),
106
+ loading: $data.selectLoading[key] === true,
107
+ placeholder: _ctx.t('nextcloud-vue', 'Pick one…'),
108
+ "onUpdate:modelValue": $event => ($options.setKey(key, $event ? $event.id : ''))
109
+ }, null, 8 /* PROPS */, ["modelValue", "options", "inputLabel", "loading", "placeholder", "onUpdate:modelValue"]),
110
+ ($options.hintFor(key))
111
+ ? (openBlock(), createElementBlock("p", {
112
+ key: 0,
113
+ class: "cn-flow-node-edit__field-help",
114
+ "data-testid": `flow-node-help-${key}`
115
+ }, toDisplayString($options.hintFor(key)), 9 /* TEXT, PROPS */, _hoisted_4))
116
+ : createCommentVNode("v-if", true)
117
+ ], 64 /* STABLE_FRAGMENT */))
108
118
  : ($options.widgetFor(key) === 'textarea')
109
119
  ? (openBlock(), createBlock(_component_NcTextArea, {
110
120
  key: 3,
@@ -173,10 +183,10 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
173
183
  ]))
174
184
  }), 128 /* KEYED_FRAGMENT */)),
175
185
  (!$options.formKeys.length)
176
- ? (openBlock(), createElementBlock("p", _hoisted_4, toDisplayString(_ctx.t('nextcloud-vue', 'This step has no options.')), 1 /* TEXT */))
186
+ ? (openBlock(), createElementBlock("p", _hoisted_5, toDisplayString(_ctx.t('nextcloud-vue', 'This step has no options.')), 1 /* TEXT */))
177
187
  : createCommentVNode("v-if", true),
178
188
  ($options.canEditAsJson)
179
- ? (openBlock(), createElementBlock("details", _hoisted_5, [
189
+ ? (openBlock(), createElementBlock("details", _hoisted_6, [
180
190
  createElementVNode("summary", null, toDisplayString(_ctx.t('nextcloud-vue', 'Advanced: edit as JSON')), 1 /* TEXT */),
181
191
  createVNode(_component_NcTextArea, {
182
192
  modelValue: $options.advancedJson,