Form Editor v2.5 (#599)
* Form Editor v2.5 * Remove log debug --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
parent
97c4b9db5b
commit
8a1282f4b0
|
|
@ -19,6 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
:style="inputStyle"
|
||||||
class="flex flex-col w-full items-center justify-center transition-colors duration-40"
|
class="flex flex-col w-full items-center justify-center transition-colors duration-40"
|
||||||
:class="[
|
:class="[
|
||||||
{'!cursor-not-allowed':disabled, 'cursor-pointer':!disabled,
|
{'!cursor-not-allowed':disabled, 'cursor-pointer':!disabled,
|
||||||
|
|
@ -29,12 +30,18 @@
|
||||||
theme.fileInput.spacing.horizontal,
|
theme.fileInput.spacing.horizontal,
|
||||||
theme.fileInput.spacing.vertical,
|
theme.fileInput.spacing.vertical,
|
||||||
theme.fileInput.fontSize,
|
theme.fileInput.fontSize,
|
||||||
theme.fileInput.minHeight
|
theme.fileInput.minHeight,
|
||||||
|
{'border-red-500 border-2':hasError},
|
||||||
|
'focus:outline-none focus:ring-2'
|
||||||
]"
|
]"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
:aria-label="multiple ? 'Choose files or drag here' : 'Choose a file or drag here'"
|
||||||
@dragover.prevent="uploadDragoverEvent=true"
|
@dragover.prevent="uploadDragoverEvent=true"
|
||||||
@dragleave.prevent="uploadDragoverEvent=false"
|
@dragleave.prevent="uploadDragoverEvent=false"
|
||||||
@drop.prevent="onUploadDropEvent"
|
@drop.prevent="onUploadDropEvent"
|
||||||
@click="openFileUpload"
|
@click="openFileUpload"
|
||||||
|
@keydown.enter.prevent="openFileUpload"
|
||||||
>
|
>
|
||||||
<div class="flex w-full items-center justify-center">
|
<div class="flex w-full items-center justify-center">
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
'!cursor-not-allowed !bg-gray-200': disabled,
|
'!cursor-not-allowed !bg-gray-200': disabled,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
class="resize-y"
|
class="resize-y block"
|
||||||
:name="name"
|
:name="name"
|
||||||
:style="inputStyle"
|
:style="inputStyle"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
|
|
@ -63,7 +63,6 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
...inputProps,
|
...inputProps,
|
||||||
maxCharLimit: {type: Number, required: false, default: null},
|
maxCharLimit: {type: Number, required: false, default: null},
|
||||||
showCharLimit: {type: Boolean, required: false, default: false},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
:maxlength="maxCharLimit"
|
:maxlength="maxCharLimit"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
@keydown.enter.prevent="onEnterPress"
|
@keydown.enter.prevent="onEnterPress"
|
||||||
|
@focus="onFocus"
|
||||||
|
@blur="onBlur"
|
||||||
>
|
>
|
||||||
|
|
||||||
<template
|
<template
|
||||||
|
|
@ -74,7 +76,6 @@ export default {
|
||||||
max: {type: Number, required: false, default: null},
|
max: {type: Number, required: false, default: null},
|
||||||
autocomplete: {type: [Boolean, String, Object], default: null},
|
autocomplete: {type: [Boolean, String, Object], default: null},
|
||||||
maxCharLimit: {type: Number, required: false, default: null},
|
maxCharLimit: {type: Number, required: false, default: null},
|
||||||
showCharLimit: {type: Boolean, required: false, default: false},
|
|
||||||
pattern: {type: String, default: null},
|
pattern: {type: String, default: null},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
class="input-label"
|
class="input-label"
|
||||||
:class="[
|
:class="[
|
||||||
theme.default.label,
|
theme.default.label,
|
||||||
{ 'uppercase text-xs': uppercaseLabels, 'text-sm': !uppercaseLabels },
|
{ 'uppercase text-xs': uppercaseLabels, 'text-sm/none': !uppercaseLabels },
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<slot>
|
<slot>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@
|
||||||
<has-error
|
<has-error
|
||||||
v-if="hasValidation && form"
|
v-if="hasValidation && form"
|
||||||
:form="form"
|
:form="form"
|
||||||
:field="name"
|
:field-id="name"
|
||||||
|
:field-name="label"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@
|
||||||
:class="[
|
:class="[
|
||||||
theme.SelectInput.input,
|
theme.SelectInput.input,
|
||||||
theme.SelectInput.borderRadius,
|
theme.SelectInput.borderRadius,
|
||||||
{ '!ring-red-500 !ring-2 !border-transparent': hasError, '!cursor-not-allowed dark:!bg-gray-600 !bg-gray-200': disabled },
|
{
|
||||||
|
'!ring-red-500 !ring-2 !border-transparent': hasError,
|
||||||
|
'!cursor-not-allowed dark:!bg-gray-600 !bg-gray-200': disabled,
|
||||||
|
'focus-within:ring-2 focus-within:ring-opacity-100 focus-within:border-transparent': !hasError
|
||||||
|
},
|
||||||
inputClass
|
inputClass
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
|
@ -19,12 +23,14 @@
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
aria-expanded="true"
|
aria-expanded="true"
|
||||||
aria-labelledby="listbox-label"
|
aria-labelledby="listbox-label"
|
||||||
class="cursor-pointer w-full flex-grow relative"
|
class="cursor-pointer w-full flex-grow relative focus:outline-none"
|
||||||
:class="[
|
:class="[
|
||||||
theme.SelectInput.spacing.horizontal,
|
theme.SelectInput.spacing.horizontal,
|
||||||
theme.SelectInput.spacing.vertical
|
theme.SelectInput.spacing.vertical
|
||||||
]"
|
]"
|
||||||
@click="toggleDropdown"
|
@click="toggleDropdown"
|
||||||
|
@focus="onFocus"
|
||||||
|
@blur="onBlur"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
|
|
@ -237,12 +243,13 @@ export default {
|
||||||
allowCreation: {type: Boolean, default: false},
|
allowCreation: {type: Boolean, default: false},
|
||||||
disabled: {type: Boolean, default: false}
|
disabled: {type: Boolean, default: false}
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'update-options'],
|
emits: ['update:modelValue', 'update-options', 'focus', 'blur'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
defaultValue: this.modelValue ?? null
|
defaultValue: this.modelValue ?? null,
|
||||||
|
isFocused: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -311,11 +318,26 @@ export default {
|
||||||
}
|
}
|
||||||
return this.modelValue === value
|
return this.modelValue === value
|
||||||
},
|
},
|
||||||
|
onFocus(event) {
|
||||||
|
this.isFocused = true
|
||||||
|
this.$emit('focus', event)
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlur(event) {
|
||||||
|
this.isFocused = false
|
||||||
|
this.$emit('blur', event)
|
||||||
|
},
|
||||||
|
|
||||||
toggleDropdown() {
|
toggleDropdown() {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
this.isOpen = false
|
this.isOpen = false
|
||||||
} else {
|
} else {
|
||||||
this.isOpen = !this.isOpen
|
this.isOpen = !this.isOpen
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.onFocus()
|
||||||
|
} else {
|
||||||
|
this.onBlur()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!this.isOpen) {
|
if (!this.isOpen) {
|
||||||
this.searchTerm = ''
|
this.searchTerm = ''
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,14 @@ export function useFormInput(props, context, options = {}) {
|
||||||
return wrapperProps
|
return wrapperProps
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onFocus = (event) => {
|
||||||
|
context.emit('focus', event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBlur = (event) => {
|
||||||
|
context.emit('blur', event)
|
||||||
|
}
|
||||||
|
|
||||||
// Watch for changes in props.modelValue and update the local content
|
// Watch for changes in props.modelValue and update the local content
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
|
|
@ -104,5 +112,7 @@ export function useFormInput(props, context, options = {}) {
|
||||||
hasValidation,
|
hasValidation,
|
||||||
hasError,
|
hasError,
|
||||||
inputWrapperProps,
|
inputWrapperProps,
|
||||||
|
onFocus,
|
||||||
|
onBlur,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div
|
<div
|
||||||
v-if="errorMessage"
|
v-if="errorMessage"
|
||||||
class="has-error text-sm text-red-500 -bottom-3"
|
class="has-error text-xs text-red-500 mt-1"
|
||||||
v-html="errorMessage"
|
v-html="errorMessage"
|
||||||
/>
|
/>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
@ -10,42 +10,57 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "HasError",
|
name: 'HasError',
|
||||||
props: {
|
props: {
|
||||||
form: {
|
form: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
field: {
|
fieldId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
fieldName: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
errorMessage() {
|
errorMessage() {
|
||||||
if (!this.form || !this.form.errors || !this.form.errors.any())
|
if (!this.form.errors || !this.form.errors.any())
|
||||||
return null
|
return null
|
||||||
const subErrorsKeys = Object.keys(this.form.errors.all()).filter(
|
const subErrorsKeys = Object.keys(this.form.errors.all()).filter(
|
||||||
(key) => {
|
(key) => {
|
||||||
return key.startsWith(this.field) && key !== this.field
|
return key.startsWith(this.fieldId) && key !== this.fieldId
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
const baseError =
|
let baseError
|
||||||
this.form.errors.get(this.field) ??
|
= this.form.errors.get(this.fieldId)
|
||||||
(subErrorsKeys.length ? "This field has some errors:" : null)
|
?? (subErrorsKeys.length ? 'This field has some errors:' : null)
|
||||||
// If no error and no sub errors, return
|
// If no error and no sub errors, return
|
||||||
if (!baseError) return null
|
if (!baseError)
|
||||||
|
return null
|
||||||
|
|
||||||
return `<p class="text-red-500">${baseError}</p><ul class="list-disc list-inside">${subErrorsKeys.map(
|
// Check if baseError starts with "The {field.name} field" and replace if necessary
|
||||||
(key) => {
|
if (baseError.startsWith(`The ${this.fieldName} field`)) {
|
||||||
return "<li>" + this.getSubError(key) + "</li>"
|
baseError = baseError.replace(`The ${this.fieldName} field`, 'This field')
|
||||||
},
|
}
|
||||||
)}</ul>`
|
|
||||||
|
const coreError = `<p class='text-red-500'>${baseError}</p>`
|
||||||
|
if (subErrorsKeys.length) {
|
||||||
|
return coreError + `<ul class='list-disc list-inside'>${subErrorsKeys.map(
|
||||||
|
(key) => {
|
||||||
|
return `<li>${this.getSubError(key)}</li>`
|
||||||
|
},
|
||||||
|
)}</ul>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return coreError
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getSubError(subErrorKey) {
|
getSubError(subErrorKey) {
|
||||||
return this.form.errors.get(subErrorKey).replace(subErrorKey, "item")
|
return this.form.errors.get(subErrorKey).replace(subErrorKey, 'item')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@
|
||||||
v-if="!editing"
|
v-if="!editing"
|
||||||
:content="content"
|
:content="content"
|
||||||
>
|
>
|
||||||
<label class="cursor-pointer truncate w-full">
|
{{ content }}
|
||||||
{{ content }}
|
|
||||||
</label>
|
|
||||||
</slot>
|
</slot>
|
||||||
<div
|
<div
|
||||||
v-if="editing"
|
v-if="editing"
|
||||||
|
|
@ -55,7 +53,11 @@ const divHeight = ref(0)
|
||||||
const parentRef = ref(null) // Ref for parent element
|
const parentRef = ref(null) // Ref for parent element
|
||||||
const editInputRef = ref(null) // Ref for edit input element
|
const editInputRef = ref(null) // Ref for edit input element
|
||||||
|
|
||||||
const startEditing = () => {
|
const startEditing = (event) => {
|
||||||
|
if (editing.value || (event.type === 'keydown' && event.target !== parentRef.value)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (parentRef.value) {
|
if (parentRef.value) {
|
||||||
divHeight.value = parentRef.value.offsetHeight
|
divHeight.value = parentRef.value.offsetHeight
|
||||||
editing.value = true
|
editing.value = true
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,19 @@
|
||||||
:href="getFontUrl"
|
:href="getFontUrl"
|
||||||
>
|
>
|
||||||
|
|
||||||
<h1
|
<template v-if="!isHideTitle">
|
||||||
v-if="!isHideTitle"
|
<EditableTag
|
||||||
class="mb-4 px-2"
|
v-if="adminPreview"
|
||||||
:class="{'mt-4':isEmbedPopup}"
|
v-model="form.title"
|
||||||
v-text="form.title"
|
element="h1"
|
||||||
/>
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
<h1
|
||||||
|
v-else
|
||||||
|
class="mb-2 px-2"
|
||||||
|
v-text="form.title"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div v-if="isPublicFormPage && form.is_password_protected">
|
<div v-if="isPublicFormPage && form.is_password_protected">
|
||||||
<p class="form-description mb-4 text-gray-700 dark:text-gray-300 px-2">
|
<p class="form-description mb-4 text-gray-700 dark:text-gray-300 px-2">
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@
|
||||||
/>
|
/>
|
||||||
<has-error
|
<has-error
|
||||||
:form="dataForm"
|
:form="dataForm"
|
||||||
field="h-captcha-response"
|
field-id="h-captcha-response"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="adminPreview"
|
v-if="adminPreview"
|
||||||
class="absolute translate-y-full lg:translate-y-0 -bottom-1.5 left-1/2 -translate-x-1/2 lg:-translate-x-full lg:-left-1 lg:top-1 lg:bottom-0 hidden group-hover/nffield:block"
|
class="absolute translate-y-full lg:translate-y-0 -bottom-1 left-1/2 -translate-x-1/2 lg:-translate-x-full lg:-left-1 lg:top-1 lg:bottom-0 hidden group-hover/nffield:block"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex lg:flex-col bg-gray-100 dark:bg-gray-800 border rounded-md"
|
class="flex lg:flex-col bg-gray-100 dark:bg-gray-800 border rounded-md"
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
v-if="field.type === 'nf-text' && field.content"
|
v-if="field.type === 'nf-text' && field.content"
|
||||||
:id="field.id"
|
:id="field.id"
|
||||||
:key="field.id"
|
:key="field.id"
|
||||||
class="nf-text w-full mb-3"
|
class="nf-text w-full my-1.5"
|
||||||
:class="[getFieldAlignClasses(field)]"
|
:class="[getFieldAlignClasses(field)]"
|
||||||
v-html="field.content"
|
v-html="field.content"
|
||||||
/>
|
/>
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
v-if="field.type === 'nf-code' && field.content"
|
v-if="field.type === 'nf-code' && field.content"
|
||||||
:id="field.id"
|
:id="field.id"
|
||||||
:key="field.id"
|
:key="field.id"
|
||||||
class="nf-code w-full px-2 mb-3"
|
class="nf-code w-full px-2 my-1.5"
|
||||||
v-html="field.content"
|
v-html="field.content"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|
@ -91,6 +91,7 @@
|
||||||
:key="field.id"
|
:key="field.id"
|
||||||
class="my-4 w-full px-2"
|
class="my-4 w-full px-2"
|
||||||
:class="[getFieldAlignClasses(field)]"
|
:class="[getFieldAlignClasses(field)]"
|
||||||
|
@dblclick="editFieldOptions"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!field.image_block"
|
v-if="!field.image_block"
|
||||||
|
|
@ -115,7 +116,7 @@
|
||||||
class="hidden group-hover/nffield:flex translate-x-full absolute right-0 top-0 h-full w-5 flex-col justify-center pl-1 pt-3"
|
class="hidden group-hover/nffield:flex translate-x-full absolute right-0 top-0 h-full w-5 flex-col justify-center pl-1 pt-3"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-center bg-gray-100 dark:bg-gray-800 border rounded-md h-12 text-gray-500 dark:text-gray-400 dark:border-gray-500 cursor-grab handle"
|
class="flex items-center bg-gray-100 dark:bg-gray-800 border rounded-md h-12 text-gray-500 dark:text-gray-400 dark:border-gray-500 cursor-grab handle min-h-[40px]"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="clarity:drag-handle-line"
|
name="clarity:drag-handle-line"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,13 @@
|
||||||
id="form-editor"
|
id="form-editor"
|
||||||
class="relative flex w-full flex-col grow max-h-screen"
|
class="relative flex w-full flex-col grow max-h-screen"
|
||||||
>
|
>
|
||||||
|
<!-- Loading overlay -->
|
||||||
|
<div
|
||||||
|
v-if="updateFormLoading"
|
||||||
|
class="absolute inset-0 bg-white bg-opacity-70 z-50 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<loader class="h-6 w-6 text-blue-500" />
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="border-b bg-white md:hidden fixed inset-0 w-full z-50 flex flex-col items-center justify-center"
|
class="border-b bg-white md:hidden fixed inset-0 w-full z-50 flex flex-col items-center justify-center"
|
||||||
>
|
>
|
||||||
|
|
@ -175,6 +182,7 @@ export default {
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$emit("mounted")
|
this.$emit("mounted")
|
||||||
|
this.workingFormStore.activeTab = 0
|
||||||
useAmplitude().logEvent('form_editor_viewed')
|
useAmplitude().logEvent('form_editor_viewed')
|
||||||
this.appStore.hideNavbar()
|
this.appStore.hideNavbar()
|
||||||
if (!this.isEdit) {
|
if (!this.isEdit) {
|
||||||
|
|
@ -292,6 +300,8 @@ export default {
|
||||||
slug: this.createdFormSlug,
|
slug: this.createdFormSlug,
|
||||||
new_form: response.users_first_form,
|
new_form: response.users_first_form,
|
||||||
},
|
},
|
||||||
|
}).then(() => {
|
||||||
|
this.updateFormLoading = false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
@ -304,8 +314,6 @@ export default {
|
||||||
)
|
)
|
||||||
captureException(error)
|
captureException(error)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.updateFormLoading = false
|
this.updateFormLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full border-b p-2 flex gap-x-3 items-center bg-white">
|
<div class="w-full border-b p-2 flex gap-x-2 items-center bg-white">
|
||||||
<a
|
<a
|
||||||
v-if="backButton"
|
v-if="backButton"
|
||||||
href="#"
|
href="#"
|
||||||
|
|
@ -13,38 +13,39 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
<EditableTag
|
<UTabs
|
||||||
v-model="form.title"
|
id="form-editor-navbar-tabs"
|
||||||
element="h3"
|
v-model="activeTab"
|
||||||
class="font-medium py-1 text-md w-48 text-gray-500 truncate"
|
:items="[
|
||||||
|
{ label: 'Build' },
|
||||||
|
{ label: 'Design'},
|
||||||
|
{ label: 'Settings'}
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UBadge
|
|
||||||
v-if="form.visibility == 'draft'"
|
|
||||||
color="yellow"
|
|
||||||
variant="soft"
|
|
||||||
label="Draft"
|
|
||||||
/>
|
|
||||||
<UBadge
|
|
||||||
v-else-if="form.visibility == 'closed'"
|
|
||||||
color="gray"
|
|
||||||
variant="soft"
|
|
||||||
label="Closed"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UndoRedo />
|
|
||||||
|
|
||||||
<div class="flex-grow flex justify-center">
|
<div class="flex-grow flex justify-center">
|
||||||
<UTabs
|
<EditableTag
|
||||||
v-model="activeTab"
|
id="form-editor-title"
|
||||||
:items="[
|
v-model="form.title"
|
||||||
{ label: 'Build' },
|
element="h3"
|
||||||
{ label: 'Design'},
|
class="font-medium py-1 text-md w-48 text-gray-500 truncate form-editor-title"
|
||||||
{ label: 'Settings'}
|
/>
|
||||||
]"
|
<UBadge
|
||||||
|
v-if="form.visibility == 'draft'"
|
||||||
|
color="yellow"
|
||||||
|
variant="soft"
|
||||||
|
label="Draft"
|
||||||
|
/>
|
||||||
|
<UBadge
|
||||||
|
v-else-if="form.visibility == 'closed'"
|
||||||
|
color="gray"
|
||||||
|
variant="soft"
|
||||||
|
label="Closed"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<UndoRedo />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex items-stretch gap-x-2"
|
class="flex items-stretch gap-x-2"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
<UTooltip :text="element.hidden ? 'Show Block' : 'Hide Block'">
|
<UTooltip :text="element.hidden ? 'Show Block' : 'Hide Block'">
|
||||||
<button
|
<button
|
||||||
class="hidden cursor-pointer rounded p-1 transition-colors hover:bg-nt-blue-lighter items-center justify-center"
|
class="hidden !cursor-pointer rounded p-1 transition-colors hover:bg-nt-blue-lighter items-center justify-center"
|
||||||
:class="{
|
:class="{
|
||||||
'text-gray-300 hover:text-blue-500 md:group-hover:flex': !element.hidden,
|
'text-gray-300 hover:text-blue-500 md:group-hover:flex': !element.hidden,
|
||||||
'text-gray-300 hover:text-gray-500 md:flex': element.hidden,
|
'text-gray-300 hover:text-gray-500 md:flex': element.hidden,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-2 px-4">
|
<div class="py-2 px-4">
|
||||||
<p class="text-sm font-medium my-2">
|
<p class="text-gray-500 text-xs font-medium my-2">
|
||||||
Input Blocks
|
Input Blocks
|
||||||
</p>
|
</p>
|
||||||
<draggable
|
<draggable
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
>
|
>
|
||||||
<template #item="{element}">
|
<template #item="{element}">
|
||||||
<div
|
<div
|
||||||
class="flex hover:bg-gray-50 rounded-md items-center gap-2 p-2"
|
class="flex hover:bg-gray-50 rounded-md items-center gap-2 p-2 group"
|
||||||
role="button"
|
role="button"
|
||||||
@click.prevent="addBlock(element.name)"
|
@click.prevent="addBlock(element.name)"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="bg-red-500 rounded-full w-2.5 h-2.5" />
|
<div class="bg-red-500 rounded-full w-2.5 h-2.5" />
|
||||||
<div class="bg-yellow-500 rounded-full w-2.5 h-2.5" />
|
<div class="bg-yellow-500 rounded-full w-2.5 h-2.5" />
|
||||||
<div class="bg-green-500 rounded-full w-2.5 h-2.5" />
|
<div class="bg-green-500 rounded-full w-2.5 h-2.5" />
|
||||||
<p class="text-sm text-gray-500/70 text-sm ml-4">
|
<p class="text-sm text-gray-500/70 text-sm ml-4 select-none">
|
||||||
Form Preview
|
Form Preview
|
||||||
</p>
|
</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@
|
||||||
<has-error
|
<has-error
|
||||||
v-if="hasValidation"
|
v-if="hasValidation"
|
||||||
:form="form"
|
:form="form"
|
||||||
:field="name"
|
:field-id="name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="field.type == 'nf-text'"
|
v-if="field.type == 'nf-text'"
|
||||||
class="border-t py-2"
|
class="border-t mt-4"
|
||||||
>
|
>
|
||||||
<rich-text-area-input
|
<rich-text-area-input
|
||||||
class="mx-4"
|
class="mx-4"
|
||||||
|
|
@ -85,7 +85,7 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type == 'nf-image'"
|
v-else-if="field.type == 'nf-image'"
|
||||||
class="border-t py-2"
|
class="border-t mt-4"
|
||||||
>
|
>
|
||||||
<image-input
|
<image-input
|
||||||
name="image_block"
|
name="image_block"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
:class="[
|
:class="[
|
||||||
option.class ? (typeof option.class === 'function' ? option.class(isSelected(option.name)) : option.class) : {},
|
option.class ? (typeof option.class === 'function' ? option.class(isSelected(option.name)) : option.class) : {},
|
||||||
{
|
{
|
||||||
'border-blue-500': isSelected(option.name),
|
'border-blue-500 bg-blue-50': isSelected(option.name),
|
||||||
'hover:bg-gray-100 border-gray-300': !isSelected(option.name)
|
'hover:bg-gray-100 border-gray-300': !isSelected(option.name)
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ export const themes = {
|
||||||
default: {
|
default: {
|
||||||
default: {
|
default: {
|
||||||
wrapper: {
|
wrapper: {
|
||||||
sm: 'relative mb-2',
|
sm: 'relative my-1',
|
||||||
md: 'relative mb-3',
|
md: 'relative my-1.5',
|
||||||
lg: 'relative mb-3',
|
lg: 'relative my-1.5',
|
||||||
},
|
},
|
||||||
label: 'text-gray-700 dark:text-gray-300 font-medium',
|
label: 'text-gray-700 dark:text-gray-300 font-medium',
|
||||||
input:
|
input:
|
||||||
|
|
@ -156,9 +156,9 @@ export const themes = {
|
||||||
simple: {
|
simple: {
|
||||||
default: {
|
default: {
|
||||||
wrapper: {
|
wrapper: {
|
||||||
sm: 'relative mb-2',
|
sm: 'relative my-1',
|
||||||
md: 'relative mb-3',
|
md: 'relative my-1.5',
|
||||||
lg: 'relative mb-3',
|
lg: 'relative my-1.5',
|
||||||
},
|
},
|
||||||
label: 'text-gray-700 dark:text-gray-300 font-medium',
|
label: 'text-gray-700 dark:text-gray-300 font-medium',
|
||||||
input:
|
input:
|
||||||
|
|
@ -301,9 +301,9 @@ export const themes = {
|
||||||
notion: {
|
notion: {
|
||||||
default: {
|
default: {
|
||||||
wrapper: {
|
wrapper: {
|
||||||
sm: 'relative mb-2',
|
sm: 'relative my-1',
|
||||||
md: 'relative mb-3',
|
md: 'relative my-1.5',
|
||||||
lg: 'relative mb-3',
|
lg: 'relative my-1.5',
|
||||||
},
|
},
|
||||||
label: 'text-gray-900 dark:text-gray-100 mb-1 block mt-4',
|
label: 'text-gray-900 dark:text-gray-100 mb-1 block mt-4',
|
||||||
input:
|
input:
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ body.dark * {
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-3xl sm:text-4xl font-semibold;
|
@apply text-2xl sm:text-3xl font-extrabold;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,14 @@ export const useWorkingFormStore = defineStore("working_form", {
|
||||||
},
|
},
|
||||||
|
|
||||||
prefillDefault(data) {
|
prefillDefault(data) {
|
||||||
|
// If a field already has this name, we need to make it unique with a number at the end
|
||||||
|
let baseName = data.name
|
||||||
|
let counter = 1
|
||||||
|
while (this.content.properties.some(prop => prop.name === data.name)) {
|
||||||
|
counter++
|
||||||
|
data.name = `${baseName} ${counter}`
|
||||||
|
}
|
||||||
|
|
||||||
if (data.type === "nf-text") {
|
if (data.type === "nf-text") {
|
||||||
data.content = "<p>This is a text block.</p>"
|
data.content = "<p>This is a text block.</p>"
|
||||||
} else if (data.type === "nf-page-break") {
|
} else if (data.type === "nf-page-break") {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue