add heading field

pull/349/head
Pete Matsyburka 1 year ago
parent 21c087cba7
commit 286b8d7067

@ -9,7 +9,7 @@
<div
v-if="isSelected || isDraw"
class="top-0 bottom-0 right-0 left-0 absolute border border-1.5 pointer-events-none"
:class="borderColors[submitterIndex]"
:class="field.type === 'heading' ? '' : borderColors[submitterIndex]"
/>
<div
v-if="field.type === 'cells' && (isSelected || isDraw)"
@ -19,7 +19,7 @@
v-for="(cellW, index) in cells"
:key="index"
class="absolute top-0 bottom-0 border-r"
:class="borderColors[submitterIndex]"
:class="field.type === 'heading' ? '' : borderColors[submitterIndex]"
:style="{ left: (cellW / area.w * 100) + '%' }"
>
<span
@ -31,14 +31,14 @@
</div>
</div>
<div
v-if="field?.type"
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap group-hover:flex group-hover:z-10"
:class="{ 'flex z-10': isNameFocus || isSelected, invisible: !isNameFocus && !isSelected }"
v-if="field?.type && (isSelected || isNameFocus)"
class="absolute bg-white rounded-t border overflow-visible whitespace-nowrap flex z-10"
style="top: -25px; height: 25px"
@mousedown.stop
@pointerdown.stop
>
<FieldSubmitter
v-if="field.type != 'heading'"
v-model="field.submitter_uuid"
class="border-r"
:compact="true"
@ -61,7 +61,7 @@
<span
v-if="field.type !== 'checkbox' || field.name"
ref="name"
:contenteditable="editable && !defaultField"
:contenteditable="editable && !defaultField && field.type !== 'heading'"
dir="auto"
class="pr-1 cursor-text outline-none block"
style="min-width: 2px"
@ -71,11 +71,11 @@
@blur="onNameBlur"
>{{ optionIndexText }} {{ (defaultField ? (field.title || field.name) : field.name) || defaultName }}</span>
<div
v-if="isSettingsFocus || isContenteditable || (isNameFocus && !['checkbox', 'phone'].includes(field.type))"
v-if="isSettingsFocus || (isValueInput && field.type !== 'heading') || (isNameFocus && !['checkbox', 'phone'].includes(field.type))"
class="flex items-center ml-1.5"
>
<input
v-if="!isContenteditable"
v-if="!isValueInput"
:id="`required-checkbox-${field.uuid}`"
v-model="field.required"
type="checkbox"
@ -83,14 +83,14 @@
@mousedown.prevent
>
<label
v-if="!isContenteditable"
v-if="!isValueInput"
:for="`required-checkbox-${field.uuid}`"
class="label text-xs"
@click.prevent="field.required = !field.required"
@mousedown.prevent
>{{ t('required') }}</label>
<input
v-if="isContenteditable"
v-if="isValueInput"
:id="`readonly-checkbox-${field.uuid}`"
type="checkbox"
class="checkbox checkbox-xs no-animation rounded"
@ -99,14 +99,14 @@
@mousedown.prevent
>
<label
v-if="isContenteditable"
v-if="isValueInput"
:for="`readonly-checkbox-${field.uuid}`"
class="label text-xs"
@click.prevent="field.readonly = !(field.readonly ?? true)"
@mousedown.prevent
>{{ t('editable') }}</label>
<span
v-if="field.type !== 'payment' && !isContenteditable"
v-if="field.type !== 'payment' && !isValueInput"
class="dropdown dropdown-end"
@mouseenter="renderDropdown = true"
@touchstart="renderDropdown = true"
@ -157,17 +157,19 @@
</button>
</div>
<div
ref="touchValueTarget"
class="flex items-center h-full w-full"
dir="auto"
:class="[isContenteditable ? 'bg-opacity-50' : 'bg-opacity-80', bgColors[submitterIndex], isDefaultValuePresent || isContenteditable ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
:class="[isValueInput ? 'bg-opacity-50' : 'bg-opacity-80', field.type === 'heading' ? 'bg-gray-50' : bgColors[submitterIndex], isDefaultValuePresent || isValueInput ? (alignClasses[field.preferences?.align] || '') : 'justify-center']"
@click="focusValueInput"
>
<span
v-if="field"
class="flex justify-center items-center space-x-1 h-full"
:class="{ 'w-full h-full': ['cells', 'checkbox'].includes(field.type) }"
class="flex justify-center items-center space-x-1"
:class="{ 'w-full': ['cells', 'checkbox'].includes(field.type), 'h-full': !isValueInput }"
>
<div
v-if="isDefaultValuePresent || isContenteditable"
v-if="isDefaultValuePresent || isValueInput"
:class="{ 'w-full h-full': ['cells', 'checkbox'].includes(field.type), 'text-[1.5vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars }"
>
<div
@ -181,7 +183,7 @@
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
/>
<span
v-else-if="field.type === 'number' && !isContenteditable"
v-else-if="field.type === 'number' && !isValueInput"
class="whitespace-pre-wrap"
>{{ formatNumber(field.default_value, field.preferences?.format) }}</span>
<span
@ -205,13 +207,13 @@
<span
v-else
ref="defaultValue"
:contenteditable="isContenteditable"
:contenteditable="isValueInput"
class="whitespace-pre-wrap outline-none empty:before:content-[attr(placeholder)] before:text-gray-400"
:class="{ 'cursor-text': isContenteditable }"
:class="{ 'cursor-text': isValueInput }"
:placeholder="t('type_value')"
@blur="onDefaultValueBlur"
@paste.prevent="onPaste"
@keydown.enter.prevent="onDefaultValueEnter"
@keydown.enter="onDefaultValueEnter"
>{{ field.default_value }}</span>
</div>
</div>
@ -225,7 +227,7 @@
</span>
</div>
<div
v-if="!isContenteditable"
v-if="!isValueInput"
ref="touchTarget"
class="absolute top-0 bottom-0 right-0 left-0"
:class="isDragged ? 'cursor-grab' : 'cursor-pointer'"
@ -355,6 +357,9 @@ export default {
return this.field?.default_value || this.field?.default_value === 0
}
},
isValueInput () {
return (this.field.type === 'heading' && this.isSelected) || this.isContenteditable
},
modalContainerEl () {
return this.$el.getRootNode().querySelector('#docuseal_modal_container')
},
@ -481,16 +486,7 @@ export default {
if (['text', 'number'].includes(this.field.type)) {
this.isContenteditable = true
this.$nextTick(() => {
this.$refs.defaultValue.focus()
if (this.$refs.defaultValue.innerText.length) {
window.getSelection().collapse(
this.$refs.defaultValue.firstChild,
this.$refs.defaultValue.innerText.length
)
}
})
this.$nextTick(() => this.focusValueInput())
} else if (this.field.type === 'checkbox') {
this.field.readonly = !this.field.readonly
this.field.default_value === true ? delete this.field.default_value : this.field.default_value = true
@ -503,6 +499,18 @@ export default {
this.save()
}
},
focusValueInput () {
if (this.$refs.defaultValue !== document.activeElement) {
this.$refs.defaultValue.focus()
if (this.$refs.defaultValue.innerText.length) {
window.getSelection().collapse(
this.$refs.defaultValue.firstChild,
this.$refs.defaultValue.innerText.length
)
}
}
},
formatNumber (number, format) {
if (format === 'comma') {
return new Intl.NumberFormat('en-US').format(number)
@ -572,6 +580,10 @@ export default {
delete this.field.options
}
if (['heading'].includes(this.field.type)) {
this.field.readonly = true
}
if (['select', 'multiple', 'radio'].includes(this.field.type)) {
this.field.options ||= [{ value: '', uuid: v4() }]
}
@ -633,7 +645,11 @@ export default {
this.save()
},
onDefaultValueEnter (e) {
this.$refs.defaultValue.blur()
if (this.field.type !== 'heading') {
e.preventDefault()
this.$refs.defaultValue.blur()
}
},
onNameEnter (e) {
this.$refs.name.blur()
@ -652,24 +668,8 @@ export default {
this.area.y = (e.offsetY - this.dragFrom.y) / e.target.clientHeight
}
},
startDrag (e) {
this.selectedAreaRef.value = this.area
if (!this.editable) {
return
}
const rect = e.target.getBoundingClientRect()
this.dragFrom = { x: e.clientX - rect.left, y: e.clientY - rect.top }
this.$el.getRootNode().addEventListener('mousemove', this.drag)
this.$el.getRootNode().addEventListener('mouseup', this.stopDrag)
this.$emit('start-drag')
},
startTouchDrag (e) {
if (e.target !== this.$refs.touchTarget) {
if (e.target !== this.$refs.touchTarget && e.target !== this.$refs.touchValueTarget) {
return
}
@ -714,11 +714,13 @@ export default {
this.$emit('stop-drag')
},
startMouseMove (e) {
if (e.target !== this.$refs.touchTarget) {
if (e.target !== this.$refs.touchTarget && e.target !== this.$refs.touchValueTarget) {
return
}
document.activeElement?.blur()
if (document.activeElement !== this.$refs.defaultValue) {
document.activeElement?.blur()
}
e.preventDefault()
@ -728,6 +730,10 @@ export default {
this.selectedAreaRef.value = this.area
if (this.field.type === 'heading') {
this.$nextTick(() => this.focusValueInput())
}
this.dragFrom = { x: rect.left - e.clientX, y: rect.top - e.clientY }
this.$el.getRootNode().addEventListener('mousemove', this.mouseMove)

@ -327,7 +327,7 @@
{{ t('cancel') }}
</button>
<a
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials'].includes(drawField?.type || drawFieldType)"
v-if="!drawField && !drawOption && !['stamp', 'signature', 'initials', 'heading'].includes(drawField?.type || drawFieldType)"
href="#"
class="link block mt-3 text-sm"
@click.prevent="[addField(drawFieldType), drawField = null, drawOption = null, withSelectedFieldType ? '' : drawFieldType = '', showDrawField = false]"
@ -1120,7 +1120,7 @@ export default {
field.options = [{ value: '', uuid: v4() }]
}
if (field.type === 'stamp') {
if (['stamp', 'heading'].includes(field.type)) {
field.readonly = true
}
@ -1204,6 +1204,15 @@ export default {
this.save()
document.activeElement?.blur()
if (field.type === 'heading') {
this.$nextTick(() => {
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
const areaRef = documentRef.pageRefs[area.page].areaRefs.find((ref) => ref.area === this.selectedAreaRef.value)
areaRef.focusValueInput()
})
}
},
addBlankPage () {
this.isLoadingBlankPage = true

@ -14,7 +14,7 @@
<div class="flex items-center p-1 space-x-1">
<FieldType
v-model="field.type"
:editable="editable && !defaultField"
:editable="editable && !defaultField && field.type != 'heading'"
:button-width="20"
:menu-classes="'mt-1.5'"
:menu-style="{ backgroundColor: dropdownBgColor }"
@ -24,7 +24,7 @@
<Contenteditable
ref="name"
:model-value="(defaultField ? (field.title || field.name) : field.name) || defaultName"
:editable="editable && !defaultField"
:editable="editable && !defaultField && field.type != 'heading'"
:icon-inline="true"
:icon-width="18"
:icon-stroke-width="1.6"
@ -96,7 +96,7 @@
@click-description="isShowDescriptionModal = true"
/>
<span
v-else
v-else-if="field.type !== 'heading'"
class="dropdown dropdown-end"
@mouseenter="renderDropdown = true"
@touchstart="renderDropdown = true"
@ -357,9 +357,12 @@ export default {
} else {
const typeIndex = fields.filter((f) => f.type === field.type).indexOf(field)
const suffix = { multiple: this.t('select'), radio: this.t('group') }[field.type] || this.t('field')
return `${this.fieldNames[field.type]} ${suffix} ${typeIndex + 1}`
if (this.field.type === 'heading') {
return `${this.fieldNames[field.type]} ${typeIndex + 1}`
} else {
const suffix = { multiple: this.t('select'), radio: this.t('group') }[field.type] || this.t('field')
return `${this.fieldNames[field.type]} ${suffix} ${typeIndex + 1}`
}
}
},
onNameFocus (e) {
@ -417,6 +420,10 @@ export default {
this.field.options ||= [{ value: '', uuid: v4() }]
}
if (['heading'].includes(this.field.type)) {
this.field.readonly = true
}
(this.field.areas || []).forEach((area) => {
if (this.field.type === 'cells') {
area.cell_w = area.w * 2 / Math.floor(area.w / area.h)

@ -51,7 +51,7 @@
</template>
<script>
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1 } from '@tabler/icons-vue'
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconLetterCaseUpper, IconCreditCard, IconRubberStamp, IconSquareNumber1, IconHeading } from '@tabler/icons-vue'
export default {
name: 'FiledTypeDropdown',
@ -96,6 +96,7 @@ export default {
computed: {
fieldNames () {
return {
heading: this.t('heading'),
text: this.t('text'),
signature: this.t('signature'),
initials: this.t('initials'),
@ -115,6 +116,7 @@ export default {
},
fieldIcons () {
return {
heading: IconHeading,
text: IconTextSize,
signature: IconWritingSign,
initials: IconLetterCaseUpper,
@ -140,7 +142,7 @@ export default {
return acc
}, {})
} else {
return this.fieldIcons
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading'))
}
}
},

@ -277,7 +277,7 @@ export default {
return acc
}, {})
} else {
return this.fieldIcons
return Object.fromEntries(Object.entries(this.fieldIcons).filter(([key]) => key !== 'heading'))
}
},
submitterFields () {

@ -8,6 +8,7 @@ const en = {
uploaded_pdf_contains_form_fields_keep_or_remove_them: 'Uploaded PDF contains form fields. Keep or remove them?',
keep: 'Keep',
left: 'Left',
heading: 'Heading',
validation: 'Validation',
add_blank_page: 'Add blank page',
right: 'Right',

@ -224,6 +224,7 @@ module Submissions
submission.template_fields.filter_map do |field|
next if field['submitter_uuid'] != submitter.uuid
next if field['type'] == 'heading'
submitter_field_counters[field['type']] += 1

@ -48,6 +48,7 @@ module Submitters
submitter_field_counters[field['type']] += 1
next if field['submitter_uuid'] != submitter.uuid
next if field['type'] == 'heading'
field_name =
field['name'].presence || "#{field['type'].titleize} Field #{submitter_field_counters[field['type']]}"

Loading…
Cancel
Save