|
@@ -0,0 +1,105 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * @node form-trigger
|
|
|
|
|
+ * @name Form
|
|
|
|
|
+ * @category triggers
|
|
|
|
|
+ * @version 1.0.0
|
|
|
|
|
+ * @description Publishes an HTML form at a shareable URL. Submitting it starts the workflow.
|
|
|
|
|
+ * @trigger
|
|
|
|
|
+ * @icon clipboard-list
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+const configSchema = {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Form Title',
|
|
|
|
|
+ description: 'Shown as the heading of the page',
|
|
|
|
|
+ default: 'Untitled form'
|
|
|
|
|
+ },
|
|
|
|
|
+ description: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Description',
|
|
|
|
|
+ description: 'Shown under the heading. Plain text - it is escaped, not rendered as HTML.',
|
|
|
|
|
+ default: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ fields: {
|
|
|
|
|
+ type: 'array',
|
|
|
|
|
+ title: 'Fields',
|
|
|
|
|
+ description: 'The questions on the form, in order.',
|
|
|
|
|
+ default: [],
|
|
|
|
|
+ items: {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ name: { type: 'string', title: 'Name',
|
|
|
|
|
+ description: 'The key the workflow reads, e.g. "image" for $node[\'Form\'].form.image. Letters, digits and underscore, not starting with a digit.' },
|
|
|
|
|
+ label: { type: 'string', title: 'Label', description: 'Shown next to the input' },
|
|
|
|
|
+ type: { type: 'string', title: 'Type',
|
|
|
|
|
+ enum: ['text', 'textarea', 'number', 'select', 'checkbox', 'date', 'file'],
|
|
|
|
|
+ default: 'text' },
|
|
|
|
|
+ required: { type: 'boolean', title: 'Required', default: false },
|
|
|
|
|
+ placeholder: { type: 'string', title: 'Placeholder', default: '' },
|
|
|
|
|
+ options: { type: 'array', title: 'Options', description: 'Choices, for a Select field',
|
|
|
|
|
+ items: { type: 'string' }, default: [] },
|
|
|
|
|
+ accept: { type: 'string', title: 'Accept',
|
|
|
|
|
+ description: 'For a File field, e.g. "image/*". A browser hint only - it is not enforced.',
|
|
|
|
|
+ default: '' },
|
|
|
|
|
+ maxSizeMb: { type: 'number', title: 'Max Size (MB)',
|
|
|
|
|
+ description: 'For a File field. 0 uses the server limit.', default: 0 }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ password: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Password',
|
|
|
|
|
+ format: 'password',
|
|
|
|
|
+ description: 'Leave empty for anyone with the link. This is stored readable in the workflow, so treat it as a gate against a forwarded link, not as a secret.',
|
|
|
|
|
+ default: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ responseMode: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'After Submit',
|
|
|
|
|
+ enum: ['immediate', 'wait'],
|
|
|
|
|
+ description: 'Immediate returns a thank-you page at once and lets the run continue - needed for anything taking more than about 30 seconds. Wait holds the browser until the workflow finishes, so a Respond to Webhook node can answer.',
|
|
|
|
|
+ default: 'immediate'
|
|
|
|
|
+ },
|
|
|
|
|
+ responseMessage: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Thank-you Message',
|
|
|
|
|
+ default: 'Thanks, your answer was received.',
|
|
|
|
|
+ 'x-if': 'responseMode'
|
|
|
|
|
+ },
|
|
|
|
|
+ path: {
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ title: 'Path Filter',
|
|
|
|
|
+ description: 'Optional path suffix. Leave empty to answer at the bare form URL.',
|
|
|
|
|
+ default: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const inputSchema = { type: 'object', properties: {} };
|
|
|
|
|
+
|
|
|
|
|
+const outputSchema = {
|
|
|
|
|
+ type: 'object',
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ form: { type: 'object', description: 'The answers, keyed by field name. A file field holds a binary object.' },
|
|
|
|
|
+ submittedAt: { type: 'number', description: 'Milliseconds since the epoch' },
|
|
|
|
|
+ clientIp: { type: 'string' }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+async function execute(config, input, context) {
|
|
|
|
|
+ // Every value here is built by the webhook controller, which has already
|
|
|
|
|
+ // validated it. Executed directly - from the editor's Run button - there is
|
|
|
|
|
+ // no submission, and an empty form is the honest answer rather than an error.
|
|
|
|
|
+ const triggerData = context.triggerData || {};
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ form: triggerData.form || {},
|
|
|
|
|
+ submittedAt: triggerData.submittedAt || Date.now(),
|
|
|
|
|
+ clientIp: triggerData.clientIp || ''
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports = { configSchema, inputSchema, outputSchema, execute };
|