From a650228a6797353874475e0f9261c9cae75a61ca Mon Sep 17 00:00:00 2001 From: Julien Nahum Date: Thu, 1 Feb 2024 18:21:30 +0100 Subject: [PATCH] Auto-resize iframes, fix custom code, fix create form initial properties --- .../forms/components/FormFieldsEditor.vue | 92 +++++-------------- .../forms/create/CreateFormBaseModal.vue | 6 +- .../components/pages/forms/show/EmbedCode.vue | 59 ++++++------ client/composables/forms/initForm.js | 30 +++++- client/lib/utils.js | 16 ++++ client/pages/forms/[slug]/index.vue | 20 ++-- client/pages/forms/create/guest.vue | 2 +- client/pages/forms/create/index.vue | 2 +- client/public/widgets/iframeResize.min.js | 8 ++ .../iframeResizer.contentWindow.min.js | 9 ++ 10 files changed, 132 insertions(+), 112 deletions(-) create mode 100644 client/public/widgets/iframeResize.min.js create mode 100644 client/public/widgets/iframeResizer.contentWindow.min.js diff --git a/client/components/open/forms/components/FormFieldsEditor.vue b/client/components/open/forms/components/FormFieldsEditor.vue index 99e9200..046bb53 100644 --- a/client/components/open/forms/components/FormFieldsEditor.vue +++ b/client/components/open/forms/components/FormFieldsEditor.vue @@ -98,7 +98,7 @@ - + @@ -138,7 +138,7 @@ - + @@ -172,7 +172,7 @@ import clonedeep from 'clone-deep' import EditableDiv from '~/components/global/EditableDiv.vue' import VButton from '~/components/global/VButton.vue' -draggable.compatConfig = { MODE: 3 } +draggable.compatConfig = {MODE: 3} export default { name: 'FormFieldsEditor', components: { @@ -182,7 +182,7 @@ export default { EditableDiv }, - setup () { + setup() { const workingFormStore = useWorkingFormStore() return { route: useRoute(), @@ -191,21 +191,21 @@ export default { } }, - data () { + data() { return { removing: null } }, - mounted () { + mounted() { this.init() }, methods: { - onChangeName (field, newName) { + onChangeName(field, newName) { field.name = newName }, - toggleHidden (field) { + toggleHidden(field) { field.hidden = !field.hidden if (field.hidden) { field.required = false @@ -214,69 +214,27 @@ export default { field.generates_auto_increment_id = false } }, - toggleRequired (field) { + toggleRequired(field) { field.required = !field.required if (field.required) { field.hidden = false } }, - getDefaultFields () { - return [ - { - name: 'Name', - type: 'text', - hidden: false, - required: true, - id: this.generateUUID() - }, - { - name: 'Email', - type: 'email', - hidden: false, - id: this.generateUUID() - }, - { - name: 'Message', - type: 'text', - hidden: false, - multi_lines: true, - id: this.generateUUID() - } - ] - }, - init () { - if (this.route.name === 'forms-create' || this.route.name === 'forms-create-guest') { // Set Default fields - if (!this.form.properties || this.form.properties.length===0) { - this.form.properties = this.getDefaultFields() - } - } else { - this.form.properties = this.form.properties.map((field) => { - // Add more field properties - field.placeholder = field.placeholder || null - field.prefill = field.prefill || null - field.help = field.help || null - field.help_position = field.help_position || 'below_input' - - return field - }) + init() { + if (!this.form.properties) { + return } - }, - generateUUID () { - let d = new Date().getTime()// Timestamp - let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0// Time in microseconds since page-load or 0 if unsupported - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - let r = Math.random() * 16// random number between 0 and 16 - if (d > 0) { // Use timestamp until depleted - r = (d + r) % 16 | 0 - d = Math.floor(d / 16) - } else { // Use microseconds since page-load if supported - r = (d2 + r) % 16 | 0 - d2 = Math.floor(d2 / 16) - } - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16) + this.form.properties = this.form.properties.map((field) => { + // Add more field properties + field.placeholder = field.placeholder || null + field.prefill = field.prefill || null + field.help = field.help || null + field.help_position = field.help_position || 'below_input' + + return field }) }, - formatType (field) { + formatType(field) { let type = field.type.replace('_', ' ') if (!type.startsWith('nf')) { type = type + ' Input' @@ -288,17 +246,17 @@ export default { } return type }, - editOptions (index) { + editOptions(index) { this.workingFormStore.openSettingsForField(index) }, - removeBlock (blockIndex) { + removeBlock(blockIndex) { this.form.properties.splice(blockIndex, 1) this.closeSidebar() }, - closeSidebar () { + closeSidebar() { this.workingFormStore.closeEditFieldSidebar() }, - openAddFieldSidebar () { + openAddFieldSidebar() { this.workingFormStore.openAddFieldSidebar(null) } } diff --git a/client/components/pages/forms/create/CreateFormBaseModal.vue b/client/components/pages/forms/create/CreateFormBaseModal.vue index dc1496d..66478db 100644 --- a/client/components/pages/forms/create/CreateFormBaseModal.vue +++ b/client/components/pages/forms/create/CreateFormBaseModal.vue @@ -27,7 +27,7 @@
-
@@ -41,7 +41,7 @@

@@ -71,7 +71,7 @@
- + diff --git a/client/components/pages/forms/show/EmbedCode.vue b/client/components/pages/forms/show/EmbedCode.vue index 11c843b..24bf4a3 100644 --- a/client/components/pages/forms/show/EmbedCode.vue +++ b/client/components/pages/forms/show/EmbedCode.vue @@ -5,7 +5,7 @@ - + - \ No newline at end of file diff --git a/client/composables/forms/initForm.js b/client/composables/forms/initForm.js index 70068ff..5694d72 100644 --- a/client/composables/forms/initForm.js +++ b/client/composables/forms/initForm.js @@ -1,11 +1,12 @@ +import {generateUUID} from "~/lib/utils.js"; -export const initForm = (defaultValue = {}) => { +export const initForm = (defaultValue = {}, withDefaultProperties = false) => { return useForm({ title: 'My Form', description: null, visibility: 'public', workspace_id: null, - properties: [], + properties: withDefaultProperties ? getDefaultProperties() :[], notifies: false, slack_notifies: false, @@ -52,3 +53,28 @@ export const initForm = (defaultValue = {}) => { ...defaultValue }) } + +function getDefaultProperties () { + return [ + { + name: 'Name', + type: 'text', + hidden: false, + required: true, + id: generateUUID() + }, + { + name: 'Email', + type: 'email', + hidden: false, + id: generateUUID() + }, + { + name: 'Message', + type: 'text', + hidden: false, + multi_lines: true, + id: generateUUID() + } + ] +} diff --git a/client/lib/utils.js b/client/lib/utils.js index 7d3df6f..f5ddf80 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -13,6 +13,22 @@ export const hash = (str, seed = 0) => { return 4294967296 * (2097151 & h2) + (h1 >>> 0); } +export const generateUUID = () => { + let d = new Date().getTime()// Timestamp + let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0// Time in microseconds since page-load or 0 if unsupported + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + let r = Math.random() * 16// random number between 0 and 16 + if (d > 0) { // Use timestamp until depleted + r = (d + r) % 16 | 0 + d = Math.floor(d / 16) + } else { // Use microseconds since page-load if supported + r = (d2 + r) % 16 | 0 + d2 = Math.floor(d2 / 16) + } + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16) + }) +} + /* * Url and domain related utils */ diff --git a/client/pages/forms/[slug]/index.vue b/client/pages/forms/[slug]/index.vue index 25fe79b..6475df9 100644 --- a/client/pages/forms/[slug]/index.vue +++ b/client/pages/forms/[slug]/index.vue @@ -111,13 +111,6 @@ const loadForm = async (setup=false) => { // Adapt page to form: colors, custom code etc handleDarkMode(form.value.dark_mode) handleTransparentMode(form.value.transparent_background) - - if (process.server) return - if (form.value.custom_code) { - const scriptEl = document.createRange().createContextualFragment(form.value.custom_code) - document.head.append(scriptEl) - } - if (!isIframe) focusOnFirstFormElement() } await loadForm(true) @@ -127,6 +120,14 @@ onMounted(() => { if (form.value) { handleDarkMode(form.value?.dark_mode) handleTransparentMode(form.value?.transparent_background) + + if (process.client) { + if (form.value.custom_code) { + const scriptEl = document.createRange().createContextualFragment(form.value.custom_code) + document.head.append(scriptEl) + } + if (!isIframe) focusOnFirstFormElement() + } } }) @@ -165,6 +166,9 @@ useHead({ return titleChunk } return titleChunk ? `${titleChunk} - OpnForm` : 'OpnForm'; - } + }, + ... form.value.custom_code ? { + script: [ { src: '/widgets/iframeResizer.contentWindow.min.js' } ] + } : {} }) diff --git a/client/pages/forms/create/guest.vue b/client/pages/forms/create/guest.vue index 677f672..8700691 100644 --- a/client/pages/forms/create/guest.vue +++ b/client/pages/forms/create/guest.vue @@ -75,7 +75,7 @@ onMounted(() => { is_pro: false }]) - form.value = initForm() + form.value = initForm({}, true) if (route.query.template !== undefined && route.query.template) { const template = templatesStore.getByKey(route.query.template) if (template && template.structure) { diff --git a/client/pages/forms/create/index.vue b/client/pages/forms/create/index.vue index aff4797..d19d797 100644 --- a/client/pages/forms/create/index.vue +++ b/client/pages/forms/create/index.vue @@ -92,7 +92,7 @@ onMounted(() => { formStore.loadAll(workspace.value.id) } - form.value = initForm({workspace_id: workspace.value?.id}) + form.value = initForm({workspace_id: workspace.value?.id}, true) formInitialHash.value = hash(JSON.stringify(form.value.data())) if (route.query.template !== undefined && route.query.template) { const template = templatesStore.getByKey(route.query.template) diff --git a/client/public/widgets/iframeResize.min.js b/client/public/widgets/iframeResize.min.js new file mode 100644 index 0000000..4ad7495 --- /dev/null +++ b/client/public/widgets/iframeResize.min.js @@ -0,0 +1,8 @@ +/*! iFrame Resizer (iframeSizer.min.js ) - v4.3.9 - 2023-11-10 + * Desc: Force cross domain iframes to size to content. + * Requires: iframeResizer.contentWindow.min.js to be loaded into the target frame. + * Copyright: (c) 2023 David J. Bradshaw - dave@bradshaw.net + * License: MIT + */ +!function(d){var c,u,a,v,x,I,M,r,f,k,i,l,z;function m(){return window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver}function F(e,n,i){e.addEventListener(n,i,!1)}function B(e,n,i){e.removeEventListener(n,i,!1)}function p(e){return x+"["+(n="Host page: "+(e=e),n=window.top!==window.self?window.parentIFrame&&window.parentIFrame.getId?window.parentIFrame.getId()+": "+e:"Nested host page: "+e:n)+"]";var n}function t(e){return k[e]?k[e].log:u}function O(e,n){o("log",e,n,t(e))}function E(e,n){o("info",e,n,t(e))}function R(e,n){o("warn",e,n,!0)}function o(e,n,i,t){!0===t&&"object"==typeof window.console&&console[e](p(n),i)}function w(e){function i(){t("Height"),t("Width"),P(function(){H(w),C(b),l("onResized",w)},w,"init")}function n(){var e=p.slice(I).split(":"),n=e[1]?parseInt(e[1],10):0,i=k[e[0]]&&k[e[0]].iframe,t=getComputedStyle(i);return{iframe:i,id:e[0],height:n+function(e){if("border-box"!==e.boxSizing)return 0;var n=e.paddingTop?parseInt(e.paddingTop,10):0,e=e.paddingBottom?parseInt(e.paddingBottom,10):0;return n+e}(t)+function(e){if("border-box"!==e.boxSizing)return 0;var n=e.borderTopWidth?parseInt(e.borderTopWidth,10):0,e=e.borderBottomWidth?parseInt(e.borderBottomWidth,10):0;return n+e}(t),width:e[2],type:e[3]}}function t(e){var n=Number(k[b]["max"+e]),i=Number(k[b]["min"+e]),e=e.toLowerCase(),t=Number(w[e]);O(b,"Checking "+e+" is in range "+i+"-"+n),tk[r]["max"+e])throw new Error("Value for min"+e+" can not be greater than max"+e)}}function h(e,n){null===i&&(i=setTimeout(function(){i=null,e()},n))}function e(){"hidden"!==document.visibilityState&&(O("document","Trigger event: Visibility change"),h(function(){b("Tab Visible","resize")},16))}function b(i,t){Object.keys(k).forEach(function(e){var n;k[n=e]&&"parent"===k[n].resizeFrom&&k[n].autoResize&&!k[n].firstRun&&A(i,t,k[e].iframe,e)})}function y(){F(window,"message",w),F(window,"resize",function(){var e;O("window","Trigger event: "+(e="resize")),h(function(){b("Window "+e,"resize")},16)}),F(document,"visibilitychange",e),F(document,"-webkit-visibilitychange",e)}function n(){function t(e,n){if(n){if(!n.tagName)throw new TypeError("Object is not a valid DOM element");if("IFRAME"!==n.tagName.toUpperCase())throw new TypeError("Expected