UNPKG

svelte-craftcms-headless-formie

Version:

Svelte Package for using Craft CMS with the Formie Plugin in headless configuration

293 lines (292 loc) 10.3 kB
// // Credit to Dave Stockley (magicspon) // https://github.com/magicspon // import { gql } from 'graphql-tag'; // @ts-expect-error no ts support import FormDataJson from 'form-data-json-convert'; import { flatMap, isPlainObject } from 'lodash-es'; // import { base64 } from '@sveu/browser'; export const getFormFieldMeta = (form) => { const allRows = flatMap(form.pages, 'rows'); const allFields = flatMap(allRows, 'rowFields'); const fields = flatMap(allFields, ({ handle, inputTypeName }) => { return { handle, inputTypeName }; }); return fields; }; function createMutationHandle(form) { return `save_${form.handle}_Submission`; } function createMutationTypes(form) { const types = getFormFieldMeta(form).map(({ handle, inputTypeName }) => { return `$${handle}: ${inputTypeName}`; }); // Add in any captcha tokens generated when we queried the form. form.captchas.forEach((captcha) => { types.push(`$${captcha.handle}: FormieCaptchaInput`); }); return types.join(', '); } function createMutationValues(form, siteId) { const values = flatMap(getFormFieldMeta(form), 'handle').map((key) => { return `${key}: $${key}`; }); // Add in any captcha tokens generated when we queried the form. form.captchas.forEach((captcha) => { values.push(`${captcha.handle}: $${captcha.handle}`); }); // for multi site / multilanguage sites if (siteId) { values.push(`siteId: ${siteId}`); } return values.join(', '); } export const getFormMutation = (form, siteId) => { const mutationTypes = createMutationTypes(form); const mutationHandle = createMutationHandle(form); const mutationValues = createMutationValues(form, siteId); return gql ` mutation FormMutation(${mutationTypes}) { submission: ${mutationHandle}(${mutationValues}) { id } } `; }; export const getMutationVariables = async (form, el) => { const formData = new FormData(el); const object = FormDataJson.toJson(el); const mutationTypes = getFormFieldMeta(form); // eslint-disable-next-line @typescript-eslint/no-explicit-any function groupFields(object, fieldName) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const groupedFields = {}; Object.keys(object).forEach((key) => { if (key.startsWith(`${fieldName}-`)) { const subField = key.replace(`${fieldName}-`, ''); groupedFields[subField] = object[key]; delete object[key]; } }); return groupedFields; } const getBase64 = (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = (error) => reject(error); }); }; for (const info of mutationTypes) { const { handle, inputTypeName } = info; let value = object[handle]; // Check for grouped fields and process them if (inputTypeName.includes('FormieGroupInput')) { object[handle] = groupFields(object, handle); } // Fix FileUploadInput if (inputTypeName === '[FileUploadInput]') { const fileInputValues = formData.getAll(info.handle); const transformedValues = []; if (fileInputValues.length && fileInputValues[0].size) { // per default, an empty input field has always a length. but its first elem will be empty await Promise.all(fileInputValues.map(async (file) => { if (!file.size) return; try { const base64Value = await getBase64(file); // Wait for the base64 conversion if (!base64Value) return; transformedValues.push({ fileData: base64Value, filename: file.name }); // Assign the base64 string to the object } catch (error) { console.error('Error converting file to base64:', error); } })); } object[info.handle] = transformedValues; } // Neue Logik in getMutationVariables if (inputTypeName.includes('FormieRepeaterInput')) { object[info.handle] = { rows: [] }; const rowKeys = Object.keys(object).filter((key) => key.startsWith(`${info.handle}-rows`)); const groupedRows = rowKeys.reduce((acc, key) => { const rowMatch = key.match(/-(\d+)-(.+)$/); if (rowMatch) { const [_, rowIndex, fieldName] = rowMatch; console.log(_); // @ts-expect-error works fine if (!acc[rowIndex]) acc[rowIndex] = {}; // @ts-expect-error works fine acc[rowIndex][fieldName] = object[key]; delete object[key]; } return acc; }, []); object[info.handle].rows = groupedRows; } // Fix Name subfields else if (inputTypeName.endsWith('FormieNameInput') || inputTypeName.endsWith('FormieAddressInput')) { object[info.handle] = {}; const keys = Object.keys(object).filter((key) => key.startsWith(info.handle + '-')); keys.forEach((key) => { object[info.handle][key.replace(info.handle + '-', '')] = object[key]; delete object[key]; }); } else if (typeof value === 'undefined') { continue; } // Fix up any objects that look like arrays else if (isPlainObject(value)) { if (typeof value[0] !== 'undefined') { value = Object.values(value); object[info.handle] = value; } } else if (info.inputTypeName === 'Int') { value = parseInt(object[info.handle], 10); object[info.handle] = value; } else if (info.inputTypeName === '[Int]') { if (isPlainObject(value)) { value = Object.values(value); } // eslint-disable-next-line @typescript-eslint/no-explicit-any value = value.map((item) => { return parseInt(item, 10); }); object[info.handle] = value; } else if (info.inputTypeName === 'Number') { value = Number(object[info.handle]); object[info.handle] = value; } else if (info.inputTypeName === '[Number]') { if (isPlainObject(value)) { value = Object.values(value); } // eslint-disable-next-line @typescript-eslint/no-explicit-any value = value.map((item) => { return Number(item); }); object[info.handle] = value; } else { object[info.handle] = value; } } // Add in any captcha tokens generated when we queried the form. form.captchas.forEach((captcha) => { object[captcha.handle] = { name: captcha.name, value: captcha.value }; }); return object; }; export const allowedFileTypes = { all: { result: '*', key: '' }, word: { result: '.doc, .docx', key: 'word' }, image: { result: 'image/*', key: 'image' }, pdf: { result: '.pdf', key: 'pdf' }, audio: { result: 'audio/*', key: 'audio' }, captionsSubtitles: { result: '.vtt, .srt', key: 'captionsSubtitles' }, json: { result: 'application/json', key: 'json' }, excel: { result: '.xls, .xlsx', key: 'excel' }, compressed: { result: '.zip, .rar, .7zip', key: 'compressed' }, javascript: { result: '.js', key: 'javascript' }, text: { result: '.txt, .rtf', key: 'text' }, powerpoint: { result: '.ppt, .pptx, .pps, .ppsx', key: 'powerpoint' }, video: { result: 'video/*', key: 'video' } }; export const getAllowedFileTypes = (input) => { if (input.length === 0) return '*'; const fileTypes = input.map((type) => { let result = ''; switch (type) { case allowedFileTypes.word.key: result = allowedFileTypes.word.result; break; case allowedFileTypes.image.key: result = allowedFileTypes.image.result; break; case allowedFileTypes.pdf.key: result = allowedFileTypes.pdf.result; break; case allowedFileTypes.audio.key: result = allowedFileTypes.audio.result; break; case allowedFileTypes.captionsSubtitles.key: result = allowedFileTypes.captionsSubtitles.result; break; case allowedFileTypes.json.key: result = allowedFileTypes.json.result; break; case allowedFileTypes.excel.key: result = allowedFileTypes.excel.result; break; case allowedFileTypes.compressed.key: result = allowedFileTypes.compressed.result; break; case allowedFileTypes.javascript.key: result = allowedFileTypes.javascript.result; break; case allowedFileTypes.text.key: result = allowedFileTypes.text.result; break; case allowedFileTypes.powerpoint.key: result = allowedFileTypes.powerpoint.result; break; case allowedFileTypes.video.key: result = allowedFileTypes.video.result; break; } return result; }); return fileTypes.join(', '); };