Auto-resize iframes, fix custom code, fix create form initial properties

This commit is contained in:
Julien Nahum 2024-02-01 18:21:30 +01:00
parent de3e2d69c0
commit a650228a67
10 changed files with 132 additions and 112 deletions

View File

@ -98,7 +98,7 @@
</g> </g>
<defs> <defs>
<clipPath id="clip0_1027_7292"> <clipPath id="clip0_1027_7292">
<rect width="24" height="24" fill="white" /> <rect width="24" height="24" fill="white"/>
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
@ -138,7 +138,7 @@
</g> </g>
<defs> <defs>
<clipPath id="clip0_1027_7210"> <clipPath id="clip0_1027_7210">
<rect width="24" height="24" fill="white" /> <rect width="24" height="24" fill="white"/>
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
@ -172,7 +172,7 @@ import clonedeep from 'clone-deep'
import EditableDiv from '~/components/global/EditableDiv.vue' import EditableDiv from '~/components/global/EditableDiv.vue'
import VButton from '~/components/global/VButton.vue' import VButton from '~/components/global/VButton.vue'
draggable.compatConfig = { MODE: 3 } draggable.compatConfig = {MODE: 3}
export default { export default {
name: 'FormFieldsEditor', name: 'FormFieldsEditor',
components: { components: {
@ -182,7 +182,7 @@ export default {
EditableDiv EditableDiv
}, },
setup () { setup() {
const workingFormStore = useWorkingFormStore() const workingFormStore = useWorkingFormStore()
return { return {
route: useRoute(), route: useRoute(),
@ -191,21 +191,21 @@ export default {
} }
}, },
data () { data() {
return { return {
removing: null removing: null
} }
}, },
mounted () { mounted() {
this.init() this.init()
}, },
methods: { methods: {
onChangeName (field, newName) { onChangeName(field, newName) {
field.name = newName field.name = newName
}, },
toggleHidden (field) { toggleHidden(field) {
field.hidden = !field.hidden field.hidden = !field.hidden
if (field.hidden) { if (field.hidden) {
field.required = false field.required = false
@ -214,42 +214,16 @@ export default {
field.generates_auto_increment_id = false field.generates_auto_increment_id = false
} }
}, },
toggleRequired (field) { toggleRequired(field) {
field.required = !field.required field.required = !field.required
if (field.required) { if (field.required) {
field.hidden = false field.hidden = false
} }
}, },
getDefaultFields () { init() {
return [ if (!this.form.properties) {
{ 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) => { this.form.properties = this.form.properties.map((field) => {
// Add more field properties // Add more field properties
field.placeholder = field.placeholder || null field.placeholder = field.placeholder || null
@ -259,24 +233,8 @@ export default {
return field return field
}) })
}
}, },
generateUUID () { formatType(field) {
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)
})
},
formatType (field) {
let type = field.type.replace('_', ' ') let type = field.type.replace('_', ' ')
if (!type.startsWith('nf')) { if (!type.startsWith('nf')) {
type = type + ' Input' type = type + ' Input'
@ -288,17 +246,17 @@ export default {
} }
return type return type
}, },
editOptions (index) { editOptions(index) {
this.workingFormStore.openSettingsForField(index) this.workingFormStore.openSettingsForField(index)
}, },
removeBlock (blockIndex) { removeBlock(blockIndex) {
this.form.properties.splice(blockIndex, 1) this.form.properties.splice(blockIndex, 1)
this.closeSidebar() this.closeSidebar()
}, },
closeSidebar () { closeSidebar() {
this.workingFormStore.closeEditFieldSidebar() this.workingFormStore.closeEditFieldSidebar()
}, },
openAddFieldSidebar () { openAddFieldSidebar() {
this.workingFormStore.openAddFieldSidebar(null) this.workingFormStore.openAddFieldSidebar(null)
} }
} }

View File

@ -27,7 +27,7 @@
</template> </template>
</template> </template>
<div v-if="state=='default'" class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-8"> <div v-if="state=='default'" class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-8">
<div v-track.select_form_base="{base:'contact-form'}" <div v-track.select_form_base="{base:'contact-form'}" role="button"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" @click="$emit('close')" class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" @click="$emit('close')"
> >
<div class="p-4"> <div class="p-4">
@ -41,7 +41,7 @@
</p> </p>
</div> </div>
<div v-if="aiFeaturesEnabled" v-track.select_form_base="{base:'ai'}" <div v-if="aiFeaturesEnabled" v-track.select_form_base="{base:'ai'}"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" @click="state='ai'" class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" role="button" @click="state='ai'"
> >
<div class="p-4 relative"> <div class="p-4 relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
@ -71,7 +71,7 @@
</div> </div>
</div> </div>
<div v-else-if="state=='ai'"> <div v-else-if="state=='ai'">
<a class="absolute top-4 left-4" href="#" @click.prevent="state='default'"> <a class="absolute top-4 left-4" href="#" role="button" @click.prevent="state='default'">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 inline -mt-1"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 inline -mt-1">
<path fill-rule="evenodd" d="M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z" clip-rule="evenodd" />
</svg> </svg>

View File

@ -18,38 +18,37 @@
<script> <script>
import CopyContent from '../../../open/forms/components/CopyContent.vue' import CopyContent from '../../../open/forms/components/CopyContent.vue'
import {appUrl} from "~/lib/utils.js";
export default { export default {
name: 'EmbedCode', name: 'EmbedCode',
components: { CopyContent }, components: {CopyContent},
props: { props: {
form: { type: Object, required: true }, form: {type: Object, required: true},
extraQueryParam: { type: String, default: '' } extraQueryParam: {type: String, default: ''}
}, },
data: () => ({ data: () => ({
autoresizeIframe: false
}), }),
computed: { computed: {
embedCode() { embedCode() {
const share_url = (this.extraQueryParam) ? this.form.share_url + "?" + this.extraQueryParam : this.form.share_url + this.extraQueryParam return `
return '<iframe style="border:none;width:100%;" height="' + this.formHeight + 'px" src="' + share_url + '"></iframe>' <script type="text/javascript" src="${appUrl('/widgets/iframeResize.min.js')}"><\/script>
${this.iframeCode}
<script type="text/javascript">iFrameResize({log: false, checkOrigin: false}, "#${this.iframeId}");<\/script>
`
}, },
formHeight() { iframeCode() {
let height = 200 const share_url = (this.extraQueryParam) ? this.form.share_url + "?" + this.extraQueryParam : this.form.share_url + this.extraQueryParam
if (!this.form.hide_title && !this.extraQueryParam) { return '<iframe style="border:none;width:100%;" frameborder="0" width="100%" frameborder="0" id="' + this.iframeId + '" src="' + share_url + '"></iframe>'
height += 60 },
} iframeId() {
height += this.form.properties.filter((property) => { return 'form-' + this.form.slug
return !property.hidden
}).length * 70
return height
} }
}, },
methods: {} methods: {}
} }
</script> </script>

View File

@ -1,11 +1,12 @@
import {generateUUID} from "~/lib/utils.js";
export const initForm = (defaultValue = {}) => { export const initForm = (defaultValue = {}, withDefaultProperties = false) => {
return useForm({ return useForm({
title: 'My Form', title: 'My Form',
description: null, description: null,
visibility: 'public', visibility: 'public',
workspace_id: null, workspace_id: null,
properties: [], properties: withDefaultProperties ? getDefaultProperties() :[],
notifies: false, notifies: false,
slack_notifies: false, slack_notifies: false,
@ -52,3 +53,28 @@ export const initForm = (defaultValue = {}) => {
...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()
}
]
}

16
client/lib/utils.js vendored
View File

@ -13,6 +13,22 @@ export const hash = (str, seed = 0) => {
return 4294967296 * (2097151 & h2) + (h1 >>> 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 * Url and domain related utils
*/ */

View File

@ -111,13 +111,6 @@ const loadForm = async (setup=false) => {
// Adapt page to form: colors, custom code etc // Adapt page to form: colors, custom code etc
handleDarkMode(form.value.dark_mode) handleDarkMode(form.value.dark_mode)
handleTransparentMode(form.value.transparent_background) 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) await loadForm(true)
@ -127,6 +120,14 @@ onMounted(() => {
if (form.value) { if (form.value) {
handleDarkMode(form.value?.dark_mode) handleDarkMode(form.value?.dark_mode)
handleTransparentMode(form.value?.transparent_background) 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
} }
return titleChunk ? `${titleChunk} - OpnForm` : 'OpnForm'; return titleChunk ? `${titleChunk} - OpnForm` : 'OpnForm';
} },
... form.value.custom_code ? {
script: [ { src: '/widgets/iframeResizer.contentWindow.min.js' } ]
} : {}
}) })
</script> </script>

View File

@ -75,7 +75,7 @@ onMounted(() => {
is_pro: false is_pro: false
}]) }])
form.value = initForm() form.value = initForm({}, true)
if (route.query.template !== undefined && route.query.template) { if (route.query.template !== undefined && route.query.template) {
const template = templatesStore.getByKey(route.query.template) const template = templatesStore.getByKey(route.query.template)
if (template && template.structure) { if (template && template.structure) {

View File

@ -92,7 +92,7 @@ onMounted(() => {
formStore.loadAll(workspace.value.id) 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())) formInitialHash.value = hash(JSON.stringify(form.value.data()))
if (route.query.template !== undefined && route.query.template) { if (route.query.template !== undefined && route.query.template) {
const template = templatesStore.getByKey(route.query.template) const template = templatesStore.getByKey(route.query.template)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long