adding first self editable text-field for prefills portion

pull/150/merge^2
iozeey 2 years ago
parent 15fe135ce7
commit d809bf6684

@ -53,7 +53,7 @@ module Api
def template_params
params.require(:template).permit(
:name,
:name, values: {},
schema: [%i[attachment_uuid name]],
submitters: [%i[name uuid]],
fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value,

@ -10,6 +10,7 @@ window.customElements.define('submission-form', class extends HTMLElement {
this.app = createApp(Form, {
submitter: JSON.parse(this.dataset.submitter),
templateValues: JSON.parse(this.dataset.templateValues),
authenticityToken: this.dataset.authenticityToken,
canSendEmail: this.dataset.canSendEmail === 'true',
isDirectUpload: this.dataset.isDirectUpload === 'true',

@ -21,20 +21,19 @@
/>
</span>
</div>
<div
v-else
class="flex items-center px-0.5"
>
<span v-if="Array.isArray(modelValue)">
{{ modelValue.join(', ') }}
</span>
<span v-else-if="field.type === 'date'">
{{ formattedDate }}
</span>
<span v-else>
{{ modelValue }}
</span>
</div>
</div>
<!-- show myText prefill with stored value -->
<div
v-else-if="field.type === 'my_text'"
class="flex absolute"
:style="{ ...computedStyle, backgroundColor: 'white' }"
:class="{ 'cursor-default ': !submittable, 'border ': submittable, 'z-0 ': isActive && submittable, 'bg-opacity-100 ': (isActive || isValueSet) && submittable }"
>
<span
style="border-width: 2px; --tw-bg-opacity: 1; --tw-border-opacity: 0.2;"
class="!text-2xl w-full h-full"
v-text="showLocalText"
/>
</div>
<div
@ -248,12 +247,20 @@ export default {
area: {
type: Object,
required: true
},
templateValues: {
type: Object,
required: false,
default () {
return {}
}
}
},
emits: ['update:model-value'],
data () {
return {
textOverflowChars: 0
textOverflowChars: 0,
showLocalText: ''
}
},
computed: {
@ -271,7 +278,8 @@ export default {
radio: 'Radio',
multiple: 'Multiple Select',
phone: 'Phone',
redact: 'redact'
redact: 'Redact',
my_text: 'My Text'
}
},
fieldIcons () {
@ -345,6 +353,15 @@ export default {
}
},
mounted () {
if (this.field.type === 'my_text') {
const fieldUuid = this.field.uuid
if (this.templateValues && this.templateValues[fieldUuid]) {
this.showLocalText = this.templateValues[fieldUuid]
} else {
this.showLocalText = ''
}
}
if (this.field.type === 'text' && this.$refs.textContainer) {
this.$nextTick(() => {
this.textOverflowChars = this.$refs.textContainer.scrollHeight > this.$refs.textContainer.clientHeight ? this.modelValue.length : 0

@ -25,6 +25,7 @@
:with-label="withLabel"
:is-value-set="step.some((f) => f.uuid in values)"
:attachments-index="attachmentsIndex"
:template-values="templateValues"
@click="$emit('focus-step', step)"
/>
</Teleport>
@ -66,6 +67,13 @@ export default {
type: Array,
required: false,
default: () => []
},
templateValues: {
type: Object,
required: false,
default () {
return {}
}
}
},
emits: ['focus-step'],

@ -6,6 +6,7 @@
:attachments-index="attachmentsIndex"
:with-label="!isAnonymousChecboxes"
:current-step="currentStepFields"
:template-values="templateValues"
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
/>
<button
@ -65,6 +66,9 @@
@focus="$refs.areas.scrollIntoField(currentField)"
/>
</div>
<div v-if="['my_text'].includes(currentField.type)">
<!-- do nothing on this side just chill for now -->
</div>
<DateStep
v-else-if="currentField.type === 'date'"
:key="currentField.uuid"
@ -375,6 +379,10 @@ export default {
type: Object,
required: true
},
templateValues: {
type: Object,
required: true
},
canSendEmail: {
type: Boolean,
required: false,

@ -39,6 +39,7 @@
@pointerdown.stop
>
<FieldSubmitter
v-if="(field.type !== 'my_text')"
v-model="field.submitter_uuid"
class="border-r"
:compact="true"
@ -49,6 +50,7 @@
@click="selectedAreaRef.value = area"
/>
<FieldType
v-if="(field.type !== 'my_text')"
v-model="field.type"
:button-width="27"
:editable="editable"
@ -68,7 +70,7 @@
@blur="onNameBlur"
>{{ optionIndexText }} {{ field.name || defaultName }}</span>
<div
v-if="isNameFocus && !['checkbox', 'phone', 'redact'].includes(field.type)"
v-if="isNameFocus && !['checkbox', 'phone', 'redact', 'my_text'].includes(field.type)"
class="flex items-center ml-1.5"
>
<input
@ -110,6 +112,23 @@
/>
</span>
</div>
<!-- adding editable textarea for prefills -->
<div
v-else-if="field.type === 'my_text'"
class="flex items-center justify-center h-full w-full"
style="background-color: rgb(185, 185, 185);"
>
<textarea
:id="field.uuid"
ref="textarea"
:value="myLocalText"
style="border-width: 2px; --tw-bg-opacity: 1; --tw-border-opacity: 0.2;"
class="!text-2xl w-full h-full"
:placeholder="`type here`"
:name="`values[${field.uuid}]`"
@input="makeMyText"
/>
</div>
<div
v-else
class="flex items-center h-full w-full"
@ -140,6 +159,7 @@
</span>
</div>
<div
v-if="field.type !== 'my_text'"
ref="touchTarget"
class="absolute top-0 bottom-0 right-0 left-0 cursor-pointer"
/>
@ -156,7 +176,7 @@
import FieldSubmitter from './field_submitter'
import FieldType from './field_type'
import Field from './field'
import { IconX } from '@tabler/icons-vue'
import { IconX, IconWriting } from '@tabler/icons-vue'
import { v4 } from 'uuid'
export default {
@ -164,7 +184,8 @@ export default {
components: {
FieldType,
FieldSubmitter,
IconX
IconX,
IconWriting
},
inject: ['template', 'selectedAreaRef', 'save'],
props: {
@ -188,12 +209,13 @@ export default {
default: null
}
},
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove'],
emits: ['start-resize', 'stop-resize', 'start-drag', 'stop-drag', 'remove', 'update:myText'],
data () {
return {
isResize: false,
isDragged: false,
isNameFocus: false,
myLocalText: '',
textOverflowChars: 0,
dragFrom: { x: 0, y: 0 }
}
@ -278,6 +300,15 @@ export default {
}
},
mounted () {
if (this.field.type === 'my_text') {
const fieldUuid = this.field.uuid
if (this.template.values && this.template.values[fieldUuid]) {
this.myLocalText = this.template.values[fieldUuid]
} else {
this.myLocalText = ''
}
}
if (this.field.type === 'text' && this.field.default_value && this.$refs.textContainer && (this.textOverflowChars === 0 || (this.textOverflowChars - 4) > this.field.default_value)) {
this.$nextTick(() => {
this.textOverflowChars = this.$el.clientHeight < this.$refs.textContainer.clientHeight ? this.field.default_value.length : 0
@ -285,6 +316,15 @@ export default {
}
},
methods: {
makeMyText (e) {
this.myLocalText = e.target.value ? e.target.value : this.myLocalText
this.sendSaveText(
{ [this.field.uuid]: e.target.value }
)
},
sendSaveText (event) {
this.$emit('update:myText', event)
},
onNameFocus (e) {
this.selectedAreaRef.value = this.area

@ -134,6 +134,7 @@
@draw="onDraw"
@drop-field="onDropfield"
@remove-area="removeArea"
@update:my-text="updateMyText"
/>
<DocumentControls
v-if="isBreakpointLg && editable"
@ -426,6 +427,11 @@ export default {
this.documentRefs = []
},
methods: {
updateMyText (values) {
const existingValues = this.template.values || {}
const updatedValues = { ...existingValues, ...values }
this.template.values = updatedValues
},
startFieldDraw (type) {
const field = {
name: '',
@ -435,7 +441,9 @@ export default {
submitter_uuid: this.selectedSubmitter.uuid,
type
}
if (['redact', 'my_text'].includes(type)) {
field.required = 'false'
}
if (['select', 'multiple', 'radio'].includes(type)) {
field.options = [{ value: '', uuid: v4() }]
}
@ -611,7 +619,7 @@ export default {
...this.dragField
}
if (['redact'].includes(field.type)) {
if (['redact', 'my_text'].includes(field.type)) {
field.required = 'false'
}
if (['select', 'multiple', 'radio'].includes(field.type)) {
@ -785,7 +793,8 @@ export default {
name: this.template.name,
schema: this.template.schema,
submitters: this.template.submitters,
fields: this.template.fields
fields: this.template.fields,
values: this.template.values
}
}),
headers: { 'Content-Type': 'application/json' }

@ -14,6 +14,7 @@
@drop-field="$emit('drop-field', {...$event, attachment_uuid: document.uuid })"
@remove-area="$emit('remove-area', $event)"
@draw="$emit('draw', {...$event, attachment_uuid: document.uuid })"
@update:my-text="$emit('update:myText', $event)"
/>
</div>
</template>
@ -60,7 +61,7 @@ export default {
default: false
}
},
emits: ['draw', 'drop-field', 'remove-area'],
emits: ['draw', 'drop-field', 'remove-area', 'update:myText'],
data () {
return {
pageRefs: []

@ -31,7 +31,7 @@
/>
</div>
<div
v-if="isNameFocus && !['redact'].includes(field.type)"
v-if="isNameFocus && !['redact', 'my_text'].includes(field.type)"
class="flex items-center relative"
>
<template v-if="field.type != 'phone'">

@ -97,33 +97,64 @@
:class="colors[submitters.indexOf(selectedSubmitter)]"
/>
</label>
<label
<!-- adding button to show and hide prefills -->
<div
v-else
tabindex="0"
class="cursor-pointer group/contenteditable-container rounded-md p-2 border border-base-300 w-full flex justify-between"
>
<div class="flex items-center space-x-2">
<span
class="w-3 h-3 rounded-full"
:class="colors[submitters.indexOf(selectedSubmitter)]"
/>
<Contenteditable
v-model="selectedSubmitter.name"
class="cursor-text"
:icon-inline="true"
:editable="editable"
:select-on-edit-click="true"
:icon-width="18"
@update:model-value="$emit('name-change', selectedSubmitter)"
/>
</div>
<span class="flex items-center">
<IconPlus
width="18"
height="18"
/>
</span>
</label>
<label
v-if="!showNewFields"
class="cursor-pointer rounded-md p-2 border border-base-300 w-full flex justify-between"
@click="$emit('add-prefills')"
>
<div class="flex items-center space-x-2">
<span
style="background-color: grey;"
class="w-3 h-3 rounded-full"
/>
<div class="items-center space-x-2">Show Prefills</div>
</div>
</label>
<label
v-else
class="cursor-pointer rounded-md p-2 border border-base-300 w-full flex justify-between"
@click="$emit('add-prefills')"
>
<div class="flex items-center space-x-2">
<span
style="background-color: grey;"
class="w-3 h-3 rounded-full"
/>
<div class="items-center space-x-2">Hide Prefills</div>
</div>
</label>
<label
tabindex="0"
class="cursor-pointer group/contenteditable-container rounded-md p-2 border border-base-300 w-full flex justify-between"
>
<div class="flex items-center space-x-2">
<span
class="w-3 h-3 rounded-full"
:class="colors[submitters.indexOf(selectedSubmitter)]"
/>
<Contenteditable
v-model="selectedSubmitter.name"
class="cursor-text"
:icon-inline="true"
:editable="editable"
:select-on-edit-click="true"
:icon-width="18"
@update:model-value="$emit('name-change', selectedSubmitter)"
/>
</div>
<span class="flex items-center">
<IconPlus
width="18"
height="18"
/>
</span>
</label>
</div>
<ul
v-if="editable || !compact"
tabindex="0"
@ -192,6 +223,11 @@ export default {
IconChevronUp
},
props: {
showNewFields: {
type: Boolean,
required: false,
default: false
},
submitters: {
type: Array,
required: true
@ -221,7 +257,7 @@ export default {
default: 'dropdown-content menu p-2 shadow bg-base-100 rounded-box w-full z-10'
}
},
emits: ['update:model-value', 'remove', 'new-submitter', 'name-change'],
emits: ['update:model-value', 'remove', 'new-submitter', 'name-change', 'add-prefills'],
computed: {
colors () {
return [

@ -46,7 +46,7 @@
</template>
<script>
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconBarrierBlock, IconLetterCaseUpper } from '@tabler/icons-vue'
import { IconTextSize, IconWritingSign, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconColumns3, IconPhoneCheck, IconBarrierBlock, IconLetterCaseUpper, IconTextResize } from '@tabler/icons-vue'
export default {
name: 'FiledTypeDropdown',
inject: ['withPhone'],
@ -92,7 +92,8 @@ export default {
radio: 'Radio',
cells: 'Cells',
phone: 'Phone',
redact: 'redact'
redact: 'Redact',
my_text: 'My_Text'
}
},
fieldIcons () {
@ -109,7 +110,8 @@ export default {
multiple: IconChecks,
radio: IconCircleDot,
phone: IconPhoneCheck,
redact: IconBarrierBlock
redact: IconBarrierBlock,
my_text: IconTextResize
}
}
},

@ -6,10 +6,12 @@
:class="{ 'bg-base-100': withStickySubmitters }"
:submitters="submitters"
:editable="editable"
:show-new-fields="showNewFields"
@new-submitter="save"
@remove="removeSubmitter"
@name-change="save"
@update:model-value="$emit('change-submitter', submitters.find((s) => s.uuid === $event))"
@add-prefills="toggleNewFields"
/>
</div>
<div
@ -62,49 +64,85 @@
</template>
</div>
<div
v-if="editable"
v-if="editable && !showNewFields"
class="grid grid-cols-3 gap-1 pb-2"
>
<template
v-for="(icon, type) in fieldIcons"
:key="type"
>
<button
v-if="withPhone || type != 'phone'"
draggable="true"
class="flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
:style="{ backgroundColor: backgroundColor }"
@dragstart="onDragstart({ type: type })"
@dragend="$emit('drag-end')"
@click="addField(type)"
<div
v-if="!['redact', 'my_text'].includes(type)"
>
<div class="w-0 absolute left-0">
<IconDrag class="cursor-grab" />
</div>
<div class="flex items-center flex-col px-2 py-2">
<component :is="icon" />
<span class="text-xs mt-1">
{{ fieldNames[type] }}
</span>
<button
v-if="withPhone || type != 'phone'"
draggable="true"
class="flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
:style="{ backgroundColor }"
@dragstart="onDragstart({ type: type })"
@dragend="$emit('drag-end')"
@click="addField(type)"
>
<div class="w-0 absolute left-0">
<IconDrag class="cursor-grab" />
</div>
<div class="flex items-center flex-col px-2 py-2">
<component :is="icon" />
<span class="text-xs mt-1">
{{ fieldNames[type] }}
</span>
</div>
</button>
<div
v-else
class="tooltip tooltip-bottom-end flex"
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
>
<a
href="https://www.docuseal.co/pricing"
target="_blank"
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
:style="{ backgroundColor: backgroundColor }"
>
<div class="w-0 absolute left-0">
<IconLock
width="18"
height="18"
stroke-width="1.5"
/>
</div>
<div class="flex items-center flex-col px-2 py-2">
<component :is="icon" />
<span class="text-xs mt-1">
{{ fieldNames[type] }}
</span>
</div>
</a>
</div>
</button>
</div>
</template>
</div>
<div
v-else-if="editable && showNewFields"
class="grid grid-cols-3 gap-1 pb-2"
>
<template
v-for="(icon, type) in fieldIcons"
:key="type"
>
<div
v-else
class="tooltip tooltip-bottom-end flex"
data-tip="Unlock SMS-verified phone number field with paid plan. Use text field for phone numbers without verification."
v-if="['redact', 'my_text'].includes(type)"
>
<a
href="https://www.docuseal.co/pricing"
target="_blank"
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
:style="{ backgroundColor: backgroundColor }"
<button
draggable="true"
class="flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
:style="{ backgroundColor }"
@dragstart="onDragstart({ type: type })"
@dragend="$emit('drag-end')"
@click="addField(type)"
>
<div class="w-0 absolute left-0">
<IconLock
width="18"
height="18"
stroke-width="1.5"
/>
<IconDrag class="cursor-grab" />
</div>
<div class="flex items-center flex-col px-2 py-2">
<component :is="icon" />
@ -112,7 +150,7 @@
{{ fieldNames[type] }}
</span>
</div>
</a>
</button>
</div>
</template>
</div>
@ -184,7 +222,8 @@ export default {
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
data () {
return {
dragField: null
dragField: null,
showNewFields: false
}
},
computed: {
@ -200,6 +239,9 @@ export default {
}
},
methods: {
toggleNewFields () {
this.showNewFields = !this.showNewFields
},
onDragstart (field) {
this.$emit('set-drag', field)
},
@ -250,7 +292,7 @@ export default {
submitter_uuid: this.selectedSubmitter.uuid,
type
}
if (['redact'].includes(type)) {
if (['redact', 'my_text'].includes(type)) {
field.required = 'false'
}
if (['select', 'multiple', 'radio'].includes(type)) {

@ -28,12 +28,14 @@
@start-drag="isMove = true"
@stop-drag="isMove = false"
@remove="$emit('remove-area', item.area)"
@update:my-text="$emit('update:myText', $event)"
/>
<FieldArea
v-if="newArea"
:is-draw="true"
:field="{ submitter_uuid: selectedSubmitter.uuid, type: drawField?.type || 'text' }"
:area="newArea"
@update:my-text="$emit('update:myText', $event)"
/>
</div>
<div
@ -93,7 +95,7 @@ export default {
required: true
}
},
emits: ['draw', 'drop-field', 'remove-area'],
emits: ['draw', 'drop-field', 'remove-area', 'update:myText'],
data () {
return {
areaRefs: [],

@ -13,6 +13,7 @@
# slug :string not null
# source :text not null
# submitters :text not null
# values :text
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
@ -50,6 +51,7 @@ class Template < ApplicationRecord
serialize :fields, JSON
serialize :schema, JSON
serialize :submitters, JSON
serialize :values, JSON
has_many_attached :documents

@ -1,4 +1,5 @@
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
<% completed_button_params = submitter.submission.template.account.account_configs.find_by(key: AccountConfig::FORM_COMPLETED_BUTTON_KEY)&.value || {} %>
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-completed-button="<%= completed_button_params.to_json %>" data-go-to-last="<%= submitter.opened_at? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-authenticity-token="<%= form_authenticity_token %>" data-values="<%= submitter.values.to_json %>"></submission-form>
<% templateValues = submitter.submission.template.values %>
<submission-form data-template-values="<%= templateValues.to_json %>" data-is-demo="<%= Docuseal.demo? %>" data-completed-button="<%= completed_button_params.to_json %>" data-go-to-last="<%= submitter.opened_at? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-authenticity-token="<%= form_authenticity_token %>" data-values="<%= submitter.values.to_json %>"></submission-form>

@ -0,0 +1,5 @@
class AddValuesToTemplates < ActiveRecord::Migration[7.0]
def change
add_column :templates, :values, :text
end
end

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_11_19_222105) do
ActiveRecord::Schema[7.0].define(version: 2023_11_30_132129) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -77,7 +77,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_11_19_222105) do
t.string "event_name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["submitter_id", "event_name"], name: "index_document_generation_events_on_submitter_id_and_event_name", unique: true, where: "((event_name)::text = ANY ((ARRAY['start'::character varying, 'complete'::character varying])::text[]))"
t.index ["submitter_id", "event_name"], name: "index_document_generation_events_on_submitter_id_and_event_name", unique: true, where: "((event_name)::text = ANY (ARRAY[('start'::character varying)::text, ('complete'::character varying)::text]))"
t.index ["submitter_id"], name: "index_document_generation_events_on_submitter_id"
end
@ -176,6 +176,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_11_19_222105) do
t.text "source", null: false
t.bigint "folder_id", null: false
t.string "application_key"
t.text "values"
t.index ["account_id"], name: "index_templates_on_account_id"
t.index ["author_id"], name: "index_templates_on_author_id"
t.index ["folder_id"], name: "index_templates_on_folder_id"

Loading…
Cancel
Save