mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5439c63ce7 | ||
|
|
b78287242b | ||
|
|
b869ae38b3 | ||
|
|
4a320e9ba6 | ||
|
|
0025928a35 | ||
|
|
c227a1988a | ||
|
|
3ade45f541 | ||
|
|
17da8bde80 | ||
|
|
59d9a59769 | ||
|
|
7210badde9 | ||
|
|
21659a73f3 | ||
|
|
c0e49ebeea | ||
|
|
2d24eac4a3 | ||
|
|
14c3c8181b | ||
|
|
036f6049a2 | ||
|
|
236e4cb35f | ||
|
|
ee77cb0982 | ||
|
|
d474ad05e5 | ||
|
|
dbf1fa37c5 | ||
|
|
7444bb8b68 | ||
|
|
f7132445a1 | ||
|
|
0127dd738e |
No files matched your search
@@ -25,7 +25,7 @@ module Api
|
|||||||
|
|
||||||
def paginate(relation)
|
def paginate(relation)
|
||||||
result = relation.order(id: :desc)
|
result = relation.order(id: :desc)
|
||||||
.limit([params[:limit] || DEFAULT_LIMIT, MAX_LIMIT].min)
|
.limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min)
|
||||||
|
|
||||||
result = result.where('id < ?', params[:after]) if params[:after].present?
|
result = result.where('id < ?', params[:after]) if params[:after].present?
|
||||||
result = result.where('id > ?', params[:before]) if params[:before].present?
|
result = result.where('id > ?', params[:before]) if params[:before].present?
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ module Api
|
|||||||
submissions = Submissions.search(@submissions, params[:q])
|
submissions = Submissions.search(@submissions, params[:q])
|
||||||
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
|
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
|
||||||
|
|
||||||
submissions = paginate(submissions.preload(:created_by_user, :template, :submitters))
|
submissions = paginate(submissions.preload(:created_by_user, :template, :submitters,
|
||||||
|
audit_trail_attachment: :blob))
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
data: submissions.as_json(serialize_params),
|
data: submissions.as_json(serialize_params),
|
||||||
@@ -93,6 +94,7 @@ module Api
|
|||||||
def serialize_params
|
def serialize_params
|
||||||
{
|
{
|
||||||
only: %i[id source submitters_order created_at updated_at],
|
only: %i[id source submitters_order created_at updated_at],
|
||||||
|
methods: %i[audit_log_url],
|
||||||
include: {
|
include: {
|
||||||
submitters: { only: %i[id slug uuid name email phone
|
submitters: { only: %i[id slug uuid name email phone
|
||||||
completed_at opened_at sent_at
|
completed_at opened_at sent_at
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ module Api
|
|||||||
templates = Templates.search(@templates, params[:q])
|
templates = Templates.search(@templates, params[:q])
|
||||||
|
|
||||||
templates = params[:archived] ? templates.archived : templates.active
|
templates = params[:archived] ? templates.archived : templates.active
|
||||||
|
templates = templates.where(application_key: params[:application_key]) if params[:application_key].present?
|
||||||
|
|
||||||
templates = paginate(templates.preload(:author, documents_attachments: :blob))
|
templates = paginate(templates.preload(:author, documents_attachments: :blob))
|
||||||
|
|
||||||
@@ -51,11 +52,13 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
def template_params
|
def template_params
|
||||||
params.require(:template).permit(:name,
|
params.require(:template).permit(
|
||||||
schema: [%i[attachment_uuid name]],
|
:name,
|
||||||
submitters: [%i[name uuid]],
|
schema: [%i[attachment_uuid name]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type, :required,
|
submitters: [%i[name uuid]],
|
||||||
{ options: [], areas: [%i[x y w h cell_w attachment_uuid page]] }]])
|
fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value,
|
||||||
|
{ options: [], areas: [%i[x y w h cell_w attachment_uuid page]] }]]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -47,7 +47,7 @@ class StartFormController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def submitter_params
|
def submitter_params
|
||||||
params.require(:submitter).permit(:email, :phone).tap do |attrs|
|
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
|
||||||
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
attrs[:email] = Submissions.normalize_email(attrs[:email])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -513,9 +513,15 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
this.submittedValues = JSON.parse(JSON.stringify(this.values))
|
this.submittedValues = JSON.parse(JSON.stringify(this.values))
|
||||||
|
|
||||||
|
this.fields.forEach((field) => {
|
||||||
|
if (field.default_value && !field.readonly) {
|
||||||
|
this.values[field.uuid] = field.default_value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (this.goToLast) {
|
if (this.goToLast) {
|
||||||
const requiredEmptyStepIndex = this.stepFields.indexOf(this.stepFields.find((fields) => fields.some((f) => f.required && !this.values[f.uuid])))
|
const requiredEmptyStepIndex = this.stepFields.indexOf(this.stepFields.find((fields) => fields.some((f) => f.required && !this.submittedValues[f.uuid])))
|
||||||
const lastFilledStepIndex = this.stepFields.indexOf([...this.stepFields].reverse().find((fields) => fields.some((f) => !!this.values[f.uuid]))) + 1
|
const lastFilledStepIndex = this.stepFields.indexOf([...this.stepFields].reverse().find((fields) => fields.some((f) => !!this.submittedValues[f.uuid]))) + 1
|
||||||
|
|
||||||
const indexesList = [this.stepFields.length - 1]
|
const indexesList = [this.stepFields.length - 1]
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
:style="{ left: (cellW / area.w * 100) + '%' }"
|
:style="{ left: (cellW / area.w * 100) + '%' }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="index === 0"
|
v-if="index === 0 && editable"
|
||||||
class="h-2.5 w-2.5 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-ew-resize z-10"
|
class="h-2.5 w-2.5 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-ew-resize z-10"
|
||||||
style="left: -4px"
|
style="left: -4px"
|
||||||
@mousedown.stop="startResizeCell"
|
@mousedown.stop="startResizeCell"
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
v-model="field.submitter_uuid"
|
v-model="field.submitter_uuid"
|
||||||
class="border-r"
|
class="border-r"
|
||||||
:compact="true"
|
:compact="true"
|
||||||
|
:editable="editable"
|
||||||
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px]'"
|
:menu-classes="'dropdown-content bg-white menu menu-xs p-2 shadow rounded-box w-52 rounded-t-none -left-[1px]'"
|
||||||
:submitters="template.submitters"
|
:submitters="template.submitters"
|
||||||
@update:model-value="save"
|
@update:model-value="save"
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
<FieldType
|
<FieldType
|
||||||
v-model="field.type"
|
v-model="field.type"
|
||||||
:button-width="27"
|
:button-width="27"
|
||||||
|
:editable="editable"
|
||||||
:button-classes="'px-1'"
|
:button-classes="'px-1'"
|
||||||
:menu-classes="'bg-white rounded-t-none'"
|
:menu-classes="'bg-white rounded-t-none'"
|
||||||
@update:model-value="[maybeUpdateOptions(), save()]"
|
@update:model-value="[maybeUpdateOptions(), save()]"
|
||||||
@@ -58,7 +60,7 @@
|
|||||||
<span
|
<span
|
||||||
v-if="field.type !== 'checkbox' || field.name"
|
v-if="field.type !== 'checkbox' || field.name"
|
||||||
ref="name"
|
ref="name"
|
||||||
contenteditable
|
:contenteditable="editable"
|
||||||
class="pr-1 cursor-text outline-none block"
|
class="pr-1 cursor-text outline-none block"
|
||||||
style="min-width: 2px"
|
style="min-width: 2px"
|
||||||
@keydown.enter.prevent="onNameEnter"
|
@keydown.enter.prevent="onNameEnter"
|
||||||
@@ -84,7 +86,7 @@
|
|||||||
>Required</label>
|
>Required</label>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else-if="editable"
|
||||||
class="pr-1"
|
class="pr-1"
|
||||||
title="Remove"
|
title="Remove"
|
||||||
@click.prevent="$emit('remove')"
|
@click.prevent="$emit('remove')"
|
||||||
@@ -93,18 +95,27 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="opacity-50 flex items-center justify-center h-full w-full"
|
class="flex items-center h-full w-full"
|
||||||
:class="bgColors[submitterIndex]"
|
:class="[bgColors[submitterIndex], field?.default_value ? '' : 'justify-center']"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="field"
|
v-if="field"
|
||||||
class="flex justify-center items-center space-x-1 h-full"
|
class="flex justify-center items-center space-x-1 h-full"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="field?.default_value"
|
||||||
|
class="text-[1.5vw] lg:text-base"
|
||||||
|
>
|
||||||
|
<div class="flex items-center px-0.5">
|
||||||
|
<span class="whitespace-pre-wrap">{{ field.default_value }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<component
|
<component
|
||||||
:is="fieldIcons[field.type]"
|
:is="fieldIcons[field.type]"
|
||||||
|
v-else
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
class="max-h-10"
|
class="max-h-10 opacity-50"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,7 +124,7 @@
|
|||||||
class="absolute top-0 bottom-0 right-0 left-0 cursor-pointer"
|
class="absolute top-0 bottom-0 right-0 left-0 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
v-if="field?.type"
|
v-if="field?.type && editable"
|
||||||
class="h-4 w-4 md:h-2.5 md:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
class="h-4 w-4 md:h-2.5 md:w-2.5 -right-1 rounded-full -bottom-1 border-gray-400 bg-white shadow-md border absolute cursor-nwse-resize"
|
||||||
@mousedown.stop="startResize"
|
@mousedown.stop="startResize"
|
||||||
@touchstart="startTouchResize"
|
@touchstart="startTouchResize"
|
||||||
@@ -145,6 +156,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -185,30 +201,30 @@ export default {
|
|||||||
},
|
},
|
||||||
borderColors () {
|
borderColors () {
|
||||||
return [
|
return [
|
||||||
'border-red-500',
|
'border-red-500/50',
|
||||||
'border-sky-500',
|
'border-sky-500/50',
|
||||||
'border-emerald-500',
|
'border-emerald-500/50',
|
||||||
'border-yellow-300',
|
'border-yellow-300/50',
|
||||||
'border-purple-600',
|
'border-purple-600/50',
|
||||||
'border-pink-500',
|
'border-pink-500/50',
|
||||||
'border-cyan-500',
|
'border-cyan-500/50',
|
||||||
'border-orange-500',
|
'border-orange-500/50',
|
||||||
'border-lime-500',
|
'border-lime-500/50',
|
||||||
'border-indigo-500'
|
'border-indigo-500/50'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
bgColors () {
|
bgColors () {
|
||||||
return [
|
return [
|
||||||
'bg-red-100',
|
'bg-red-100/50',
|
||||||
'bg-sky-100',
|
'bg-sky-100/50',
|
||||||
'bg-emerald-100',
|
'bg-emerald-100/50',
|
||||||
'bg-yellow-100',
|
'bg-yellow-100/50',
|
||||||
'bg-purple-100',
|
'bg-purple-100/50',
|
||||||
'bg-pink-100',
|
'bg-pink-100/50',
|
||||||
'bg-cyan-100',
|
'bg-cyan-100/50',
|
||||||
'bg-orange-100',
|
'bg-orange-100/50',
|
||||||
'bg-lime-100',
|
'bg-lime-100/50',
|
||||||
'bg-indigo-100'
|
'bg-indigo-100/50'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
isSelected () {
|
isSelected () {
|
||||||
@@ -262,6 +278,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
maybeUpdateOptions () {
|
maybeUpdateOptions () {
|
||||||
|
delete this.field.default_value
|
||||||
|
|
||||||
if (!['radio', 'multiple', 'select'].includes(this.field.type)) {
|
if (!['radio', 'multiple', 'select'].includes(this.field.type)) {
|
||||||
delete this.field.options
|
delete this.field.options
|
||||||
}
|
}
|
||||||
@@ -313,6 +331,10 @@ export default {
|
|||||||
startDrag (e) {
|
startDrag (e) {
|
||||||
this.selectedAreaRef.value = this.area
|
this.selectedAreaRef.value = this.area
|
||||||
|
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const rect = e.target.getBoundingClientRect()
|
const rect = e.target.getBoundingClientRect()
|
||||||
|
|
||||||
this.dragFrom = { x: e.clientX - rect.left, y: e.clientY - rect.top }
|
this.dragFrom = { x: e.clientX - rect.left, y: e.clientY - rect.top }
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<Contenteditable
|
<Contenteditable
|
||||||
:model-value="template.name"
|
:model-value="template.name"
|
||||||
|
:editable="editable"
|
||||||
class="text-3xl font-semibold focus:text-clip"
|
class="text-3xl font-semibold focus:text-clip"
|
||||||
:icon-stroke-width="2.3"
|
:icon-stroke-width="2.3"
|
||||||
@update:model-value="updateName"
|
@update:model-value="updateName"
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:document="sortedDocuments[index]"
|
:document="sortedDocuments[index]"
|
||||||
:accept-file-types="acceptFileTypes"
|
:accept-file-types="acceptFileTypes"
|
||||||
|
:editable="editable"
|
||||||
:template="template"
|
:template="template"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
@scroll-to="scrollIntoDocument(item)"
|
@scroll-to="scrollIntoDocument(item)"
|
||||||
@@ -90,7 +92,7 @@
|
|||||||
:class="{ 'bg-base-100': withStickySubmitters }"
|
:class="{ 'bg-base-100': withStickySubmitters }"
|
||||||
>
|
>
|
||||||
<Upload
|
<Upload
|
||||||
v-if="sortedDocuments.length"
|
v-if="sortedDocuments.length && editable"
|
||||||
:accept-file-types="acceptFileTypes"
|
:accept-file-types="acceptFileTypes"
|
||||||
:template-id="template.id"
|
:template-id="template.id"
|
||||||
:is-direct-upload="isDirectUpload"
|
:is-direct-upload="isDirectUpload"
|
||||||
@@ -122,12 +124,13 @@
|
|||||||
:document="document"
|
:document="document"
|
||||||
:is-drag="!!dragFieldType"
|
:is-drag="!!dragFieldType"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
|
:editable="editable"
|
||||||
@draw="onDraw"
|
@draw="onDraw"
|
||||||
@drop-field="onDropfield"
|
@drop-field="onDropfield"
|
||||||
@remove-area="removeArea"
|
@remove-area="removeArea"
|
||||||
/>
|
/>
|
||||||
<DocumentControls
|
<DocumentControls
|
||||||
v-if="isBreakpointLg"
|
v-if="isBreakpointLg && editable"
|
||||||
:with-arrows="template.schema.length > 1"
|
:with-arrows="template.schema.length > 1"
|
||||||
:item="template.schema.find((item) => item.attachment_uuid === document.uuid)"
|
:item="template.schema.find((item) => item.attachment_uuid === document.uuid)"
|
||||||
:document="document"
|
:document="document"
|
||||||
@@ -142,7 +145,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
v-if="sortedDocuments.length && isBreakpointLg"
|
v-if="sortedDocuments.length && isBreakpointLg && editable"
|
||||||
class="pb-4"
|
class="pb-4"
|
||||||
>
|
>
|
||||||
<Upload
|
<Upload
|
||||||
@@ -153,34 +156,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<MobileDrawField
|
||||||
v-if="drawField"
|
v-if="drawField && isBreakpointLg"
|
||||||
class="fixed text-center w-full left-1/2 bottom-0 transform -translate-x-1/2"
|
:draw-field="drawField"
|
||||||
>
|
:fields="template.fields"
|
||||||
<span
|
:submitters="template.submitters"
|
||||||
class="rounded bg-base-200 px-4 py-2 rounded-full inline-flex space-x-2 mx-auto items-center mb-4 z-20 md:hidden"
|
:selected-submitter="selectedSubmitter"
|
||||||
>
|
class="md:hidden"
|
||||||
<component
|
:editable="editable"
|
||||||
:is="fieldIcons[drawField.type]"
|
@cancel="drawField = null"
|
||||||
:width="20"
|
@change-submitter="[selectedSubmitter = $event, drawField.submitter_uuid = $event.uuid]"
|
||||||
:height="20"
|
/>
|
||||||
class="inline"
|
|
||||||
:stroke-width="1.6"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
Draw {{ fieldNames[drawField.type] }} Field
|
|
||||||
</span>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
class="link block text-center"
|
|
||||||
@click.prevent="drawField = null"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<FieldType
|
<FieldType
|
||||||
v-if="sortedDocuments.length && !drawField"
|
v-if="sortedDocuments.length && !drawField && editable"
|
||||||
class="dropdown-top dropdown-end fixed bottom-4 right-4 z-10 md:hidden"
|
class="dropdown-top dropdown-end fixed bottom-4 right-4 z-10 md:hidden"
|
||||||
:model-value="''"
|
:model-value="''"
|
||||||
@update:model-value="startFieldDraw($event)"
|
@update:model-value="startFieldDraw($event)"
|
||||||
@@ -231,6 +219,7 @@
|
|||||||
:submitters="template.submitters"
|
:submitters="template.submitters"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
:with-sticky-submitters="withStickySubmitters"
|
:with-sticky-submitters="withStickySubmitters"
|
||||||
|
:editable="editable"
|
||||||
@set-draw="drawField = $event"
|
@set-draw="drawField = $event"
|
||||||
@set-drag="dragFieldType = $event"
|
@set-drag="dragFieldType = $event"
|
||||||
@change-submitter="selectedSubmitter = $event"
|
@change-submitter="selectedSubmitter = $event"
|
||||||
@@ -247,6 +236,7 @@
|
|||||||
import Upload from './upload'
|
import Upload from './upload'
|
||||||
import Dropzone from './dropzone'
|
import Dropzone from './dropzone'
|
||||||
import Fields from './fields'
|
import Fields from './fields'
|
||||||
|
import MobileDrawField from './mobile_draw_field'
|
||||||
import Document from './document'
|
import Document from './document'
|
||||||
import Logo from './logo'
|
import Logo from './logo'
|
||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
@@ -263,6 +253,7 @@ export default {
|
|||||||
Upload,
|
Upload,
|
||||||
Document,
|
Document,
|
||||||
Fields,
|
Fields,
|
||||||
|
MobileDrawField,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
FieldType,
|
FieldType,
|
||||||
IconX,
|
IconX,
|
||||||
@@ -300,6 +291,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
acceptFileTypes: {
|
acceptFileTypes: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
ref="contenteditable"
|
ref="contenteditable"
|
||||||
contenteditable
|
:contenteditable="editable"
|
||||||
style="min-width: 2px"
|
style="min-width: 2px"
|
||||||
:class="iconInline ? 'inline' : 'block'"
|
:class="iconInline ? 'inline' : 'block'"
|
||||||
class="peer outline-none focus:block"
|
class="peer outline-none focus:block"
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
*
|
*
|
||||||
</span>
|
</span>
|
||||||
<IconPencil
|
<IconPencil
|
||||||
|
v-if="editable"
|
||||||
class="cursor-pointer flex-none opacity-0 group-hover/contenteditable-container:opacity-100 group-hover/contenteditable:opacity-100 align-middle peer-focus:hidden"
|
class="cursor-pointer flex-none opacity-0 group-hover/contenteditable-container:opacity-100 group-hover/contenteditable:opacity-100 align-middle peer-focus:hidden"
|
||||||
:style="iconInline ? {} : { right: -(1.1 * iconWidth) + 'px' }"
|
:style="iconInline ? {} : { right: -(1.1 * iconWidth) + 'px' }"
|
||||||
title="Edit"
|
title="Edit"
|
||||||
@@ -69,6 +70,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
iconStrokeWidth: {
|
iconStrokeWidth: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
:key="image.id"
|
:key="image.id"
|
||||||
:ref="setPageRefs"
|
:ref="setPageRefs"
|
||||||
:number="index"
|
:number="index"
|
||||||
|
:editable="editable"
|
||||||
:areas="areasIndex[index]"
|
:areas="areasIndex[index]"
|
||||||
:is-drag="isDrag"
|
:is-drag="isDrag"
|
||||||
:draw-field="drawField"
|
:draw-field="drawField"
|
||||||
@@ -38,6 +39,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
drawField: {
|
drawField: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="border border-base-300 rounded rounded-tr-none relative group"
|
class="border border-base-300 rounded rounded-tr-none relative group"
|
||||||
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between relative group/contenteditable-container">
|
<div class="flex items-center justify-between relative group/contenteditable-container">
|
||||||
<div
|
<div
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
<div class="flex items-center p-1 space-x-1">
|
<div class="flex items-center p-1 space-x-1">
|
||||||
<FieldType
|
<FieldType
|
||||||
v-model="field.type"
|
v-model="field.type"
|
||||||
|
:editable="editable"
|
||||||
:button-width="20"
|
:button-width="20"
|
||||||
@update:model-value="[maybeUpdateOptions(), save()]"
|
@update:model-value="[maybeUpdateOptions(), save()]"
|
||||||
@click="scrollToFirstArea"
|
@click="scrollToFirstArea"
|
||||||
@@ -20,6 +22,7 @@
|
|||||||
<Contenteditable
|
<Contenteditable
|
||||||
ref="name"
|
ref="name"
|
||||||
:model-value="field.name || defaultName"
|
:model-value="field.name || defaultName"
|
||||||
|
:editable="editable"
|
||||||
:icon-inline="true"
|
:icon-inline="true"
|
||||||
:icon-width="18"
|
:icon-width="18"
|
||||||
:icon-stroke-width="1.6"
|
:icon-stroke-width="1.6"
|
||||||
@@ -46,19 +49,29 @@
|
|||||||
>Required</label>
|
>Required</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else-if="editable"
|
||||||
class="flex items-center space-x-1"
|
class="flex items-center space-x-1"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
v-if="field && !field.areas.length"
|
||||||
|
title="Draw"
|
||||||
|
class="relative cursor-pointer text-transparent group-hover:text-base-content"
|
||||||
|
@click="$emit('set-draw', field)"
|
||||||
|
>
|
||||||
|
<IconNewSection
|
||||||
|
:width="18"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
<span
|
<span
|
||||||
v-if="field.areas?.length"
|
|
||||||
class="dropdown dropdown-end"
|
class="dropdown dropdown-end"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
title="Areas"
|
title="Settings"
|
||||||
class="cursor-pointer text-transparent group-hover:text-base-content"
|
class="cursor-pointer text-transparent group-hover:text-base-content"
|
||||||
>
|
>
|
||||||
<IconShape
|
<IconSettings
|
||||||
:width="18"
|
:width="18"
|
||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
@@ -66,8 +79,57 @@
|
|||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52 z-10"
|
class="mt-1.5 dropdown-content menu menu-xs p-2 shadow bg-base-100 rounded-box w-52 z-10"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart.prevent.stop
|
||||||
@click="closeDropdown"
|
@click="closeDropdown"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="field.type === 'text'"
|
||||||
|
class="py-1.5 px-1 relative"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="field.default_value"
|
||||||
|
type="text"
|
||||||
|
placeholder="Default value"
|
||||||
|
class="input input-bordered input-xs w-full max-w-xs h-7 !outline-0"
|
||||||
|
@blur="save"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
v-if="field.default_value"
|
||||||
|
:style="{ backgroundColor: backgroundColor }"
|
||||||
|
class="absolute -top-1 left-2.5 px-1 h-4"
|
||||||
|
style="font-size: 8px"
|
||||||
|
>
|
||||||
|
Default value
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<li @click.stop>
|
||||||
|
<label class="cursor-pointer py-1.5">
|
||||||
|
<input
|
||||||
|
v-model="field.required"
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-xs"
|
||||||
|
@update:model-value="save"
|
||||||
|
>
|
||||||
|
<span class="label-text">Required</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="field.type === 'text'"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<label class="cursor-pointer py-1.5">
|
||||||
|
<input
|
||||||
|
v-model="field.readonly"
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-xs"
|
||||||
|
@update:model-value="save"
|
||||||
|
>
|
||||||
|
<span class="label-text">Read-only</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<hr class="pb-0.5 mt-0.5">
|
||||||
<li
|
<li
|
||||||
v-for="(area, index) in field.areas || []"
|
v-for="(area, index) in field.areas || []"
|
||||||
:key="index"
|
:key="index"
|
||||||
@@ -113,18 +175,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
v-else
|
class="relative text-transparent group-hover:text-base-content pr-1"
|
||||||
title="Areas"
|
|
||||||
class="relative cursor-pointer text-transparent group-hover:text-base-content"
|
|
||||||
@click="$emit('set-draw', field)"
|
|
||||||
>
|
|
||||||
<IconShape
|
|
||||||
:width="18"
|
|
||||||
:stroke-width="1.6"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="relative text-transparent group-hover:text-base-content"
|
|
||||||
title="Remove"
|
title="Remove"
|
||||||
@click="$emit('remove', field)"
|
@click="$emit('remove', field)"
|
||||||
>
|
>
|
||||||
@@ -133,24 +184,6 @@
|
|||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div class="flex flex-col pr-1 text-transparent group-hover:text-base-content">
|
|
||||||
<button
|
|
||||||
title="Up"
|
|
||||||
class="relative"
|
|
||||||
style="font-size: 10px; margin-bottom: -2px"
|
|
||||||
@click="$emit('move-up')"
|
|
||||||
>
|
|
||||||
▲
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
title="Down"
|
|
||||||
class="relative"
|
|
||||||
style="font-size: 10px; margin-top: -2px"
|
|
||||||
@click="$emit('move-down')"
|
|
||||||
>
|
|
||||||
▼
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -194,26 +227,32 @@
|
|||||||
<script>
|
<script>
|
||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
import FieldType from './field_type'
|
import FieldType from './field_type'
|
||||||
import { IconShape, IconNewSection, IconTrashX, IconCopy } from '@tabler/icons-vue'
|
import { IconShape, IconNewSection, IconTrashX, IconCopy, IconSettings } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplateField',
|
name: 'TemplateField',
|
||||||
components: {
|
components: {
|
||||||
Contenteditable,
|
Contenteditable,
|
||||||
|
IconSettings,
|
||||||
IconShape,
|
IconShape,
|
||||||
IconNewSection,
|
IconNewSection,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
FieldType
|
FieldType
|
||||||
},
|
},
|
||||||
inject: ['template', 'save'],
|
inject: ['template', 'save', 'backgroundColor'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['set-draw', 'remove', 'move-up', 'move-down', 'scroll-to'],
|
emits: ['set-draw', 'remove', 'scroll-to'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isNameFocus: false
|
isNameFocus: false
|
||||||
@@ -266,6 +305,8 @@ export default {
|
|||||||
document.activeElement.blur()
|
document.activeElement.blur()
|
||||||
},
|
},
|
||||||
maybeUpdateOptions () {
|
maybeUpdateOptions () {
|
||||||
|
delete this.field.default_value
|
||||||
|
|
||||||
if (!['radio', 'multiple', 'select'].includes(this.field.type)) {
|
if (!['radio', 'multiple', 'select'].includes(this.field.type)) {
|
||||||
delete this.field.options
|
delete this.field.options
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,91 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dropdown">
|
<div v-if="mobileView">
|
||||||
|
<div class="flex space-x-2 items-end">
|
||||||
|
<div class="group/contenteditable-container bg-base-100 rounded-md p-2 border border-base-300 w-full flex justify-between items-end">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span
|
||||||
|
class="w-3 h-3 flex-shrink-0 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>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown dropdown-top dropdown-end">
|
||||||
|
<label
|
||||||
|
tabindex="0"
|
||||||
|
class="bg-base-100 cursor-pointer rounded-md p-2 border border-base-300 w-full flex justify-center"
|
||||||
|
>
|
||||||
|
<IconChevronUp
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<ul
|
||||||
|
v-if="editable"
|
||||||
|
tabindex="0"
|
||||||
|
class="rounded-md min-w-max mb-2"
|
||||||
|
:class="menuClasses"
|
||||||
|
@click="closeDropdown"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="(submitter, index) in submitters"
|
||||||
|
:key="submitter.uuid"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="flex px-2 group justify-between items-center"
|
||||||
|
:class="{ 'active': submitter === selectedSubmitter }"
|
||||||
|
@click.prevent="selectSubmitter(submitter)"
|
||||||
|
>
|
||||||
|
<span class="py-1 flex items-center">
|
||||||
|
<span
|
||||||
|
class="rounded-full w-3 h-3 ml-1 mr-3"
|
||||||
|
:class="colors[index]"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{ submitter.name }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="submitters.length > 1 && editable"
|
||||||
|
class="px-2"
|
||||||
|
@click.stop="remove(submitter)"
|
||||||
|
>
|
||||||
|
<IconTrashX :width="18" />
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li v-if="submitters.length < 10 && editable">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="flex px-2"
|
||||||
|
@click.prevent="addSubmitter"
|
||||||
|
>
|
||||||
|
<IconUserPlus
|
||||||
|
:width="20"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
<span class="py-1">
|
||||||
|
Add Submitter
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="dropdown"
|
||||||
|
>
|
||||||
<label
|
<label
|
||||||
v-if="compact"
|
v-if="compact"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -25,6 +111,7 @@
|
|||||||
v-model="selectedSubmitter.name"
|
v-model="selectedSubmitter.name"
|
||||||
class="cursor-text"
|
class="cursor-text"
|
||||||
:icon-inline="true"
|
:icon-inline="true"
|
||||||
|
:editable="editable"
|
||||||
:select-on-edit-click="true"
|
:select-on-edit-click="true"
|
||||||
:icon-width="18"
|
:icon-width="18"
|
||||||
@update:model-value="$emit('name-change', selectedSubmitter)"
|
@update:model-value="$emit('name-change', selectedSubmitter)"
|
||||||
@@ -38,6 +125,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
|
v-if="editable || !compact"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
:class="menuClasses"
|
:class="menuClasses"
|
||||||
@click="closeDropdown"
|
@click="closeDropdown"
|
||||||
@@ -62,7 +150,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
v-if="!compact && submitters.length > 1"
|
v-if="!compact && submitters.length > 1 && editable"
|
||||||
class="hidden group-hover:block px-2"
|
class="hidden group-hover:block px-2"
|
||||||
@click.stop="remove(submitter)"
|
@click.stop="remove(submitter)"
|
||||||
>
|
>
|
||||||
@@ -70,7 +158,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="submitters.length < 10">
|
<li v-if="submitters.length < 10 && editable">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
class="flex px-2"
|
class="flex px-2"
|
||||||
@@ -90,7 +178,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconUserPlus, IconTrashX, IconPlus } from '@tabler/icons-vue'
|
import { IconUserPlus, IconTrashX, IconPlus, IconChevronUp } from '@tabler/icons-vue'
|
||||||
import Contenteditable from './contenteditable'
|
import Contenteditable from './contenteditable'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
|
|
||||||
@@ -100,18 +188,29 @@ export default {
|
|||||||
IconUserPlus,
|
IconUserPlus,
|
||||||
Contenteditable,
|
Contenteditable,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconTrashX
|
IconTrashX,
|
||||||
|
IconChevronUp
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
submitters: {
|
submitters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
compact: {
|
compact: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
mobileView: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</slot>
|
</slot>
|
||||||
<ul
|
<ul
|
||||||
|
v-if="editable"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10 mb-3"
|
class="dropdown-content menu menu-xs p-2 shadow rounded-box w-52 z-10 mb-3"
|
||||||
:class="menuClasses"
|
:class="menuClasses"
|
||||||
@@ -65,6 +66,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
buttonWidth: {
|
buttonWidth: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -5,26 +5,37 @@
|
|||||||
class="w-full rounded-lg"
|
class="w-full rounded-lg"
|
||||||
:class="{ 'bg-base-100': withStickySubmitters }"
|
:class="{ 'bg-base-100': withStickySubmitters }"
|
||||||
:submitters="submitters"
|
:submitters="submitters"
|
||||||
|
:editable="editable"
|
||||||
@new-submitter="save"
|
@new-submitter="save"
|
||||||
@remove="removeSubmitter"
|
@remove="removeSubmitter"
|
||||||
@name-change="save"
|
@name-change="save"
|
||||||
@update:model-value="$emit('change-submitter', submitters.find((s) => s.uuid === $event))"
|
@update:model-value="$emit('change-submitter', submitters.find((s) => s.uuid === $event))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-1 mt-2">
|
<div
|
||||||
|
class="mb-1 mt-2"
|
||||||
|
@dragover.prevent="onFieldDragover"
|
||||||
|
@drop="save"
|
||||||
|
>
|
||||||
<Field
|
<Field
|
||||||
v-for="field in submitterFields"
|
v-for="field in submitterFields"
|
||||||
:key="field.uuid"
|
:key="field.uuid"
|
||||||
|
:data-uuid="field.uuid"
|
||||||
:field="field"
|
:field="field"
|
||||||
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
:type-index="fields.filter((f) => f.type === field.type).indexOf(field)"
|
||||||
|
:editable="editable && !dragField"
|
||||||
|
:draggable="editable"
|
||||||
|
@dragstart="dragField = field"
|
||||||
|
@dragend="dragField = null"
|
||||||
@remove="removeField"
|
@remove="removeField"
|
||||||
@move-up="move(field, -1)"
|
|
||||||
@move-down="move(field, 1)"
|
|
||||||
@scroll-to="$emit('scroll-to-area', $event)"
|
@scroll-to="$emit('scroll-to-area', $event)"
|
||||||
@set-draw="$emit('set-draw', $event)"
|
@set-draw="$emit('set-draw', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 gap-1 pb-2">
|
<div
|
||||||
|
v-if="editable"
|
||||||
|
class="grid grid-cols-3 gap-1 pb-2"
|
||||||
|
>
|
||||||
<template
|
<template
|
||||||
v-for="(icon, type) in fieldIcons"
|
v-for="(icon, type) in fieldIcons"
|
||||||
:key="type"
|
:key="type"
|
||||||
@@ -100,7 +111,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="fields.length < 4"
|
v-if="fields.length < 4 && editable"
|
||||||
class="text-xs p-2 border border-base-200 rounded"
|
class="text-xs p-2 border border-base-200 rounded"
|
||||||
>
|
>
|
||||||
<ul class="list-disc list-outside ml-3">
|
<ul class="list-disc list-outside ml-3">
|
||||||
@@ -137,6 +148,11 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
withStickySubmitters: {
|
withStickySubmitters: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -152,6 +168,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
emits: ['set-draw', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
dragField: null
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fieldNames: FieldType.computed.fieldNames,
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldIcons: FieldType.computed.fieldIcons,
|
fieldIcons: FieldType.computed.fieldIcons,
|
||||||
@@ -163,6 +184,24 @@ export default {
|
|||||||
onDragstart (fieldType) {
|
onDragstart (fieldType) {
|
||||||
this.$emit('set-drag', fieldType)
|
this.$emit('set-drag', fieldType)
|
||||||
},
|
},
|
||||||
|
onFieldDragover (e) {
|
||||||
|
const targetFieldUuid = e.target.closest('[data-uuid]')?.dataset?.uuid
|
||||||
|
|
||||||
|
if (this.dragField && targetFieldUuid && this.dragField.uuid !== targetFieldUuid) {
|
||||||
|
const field = this.fields.find((f) => f.uuid === targetFieldUuid)
|
||||||
|
|
||||||
|
const currentIndex = this.fields.indexOf(this.dragField)
|
||||||
|
const targetIndex = this.fields.indexOf(field)
|
||||||
|
|
||||||
|
if (currentIndex < targetIndex) {
|
||||||
|
this.fields.splice(targetIndex + 1, 0, this.dragField)
|
||||||
|
this.fields.splice(currentIndex, 1)
|
||||||
|
} else {
|
||||||
|
this.fields.splice(targetIndex, 0, this.dragField)
|
||||||
|
this.fields.splice(currentIndex + 1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
removeSubmitter (submitter) {
|
removeSubmitter (submitter) {
|
||||||
[...this.fields].forEach((field) => {
|
[...this.fields].forEach((field) => {
|
||||||
if (field.submitter_uuid === submitter.uuid) {
|
if (field.submitter_uuid === submitter.uuid) {
|
||||||
@@ -178,26 +217,6 @@ export default {
|
|||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
move (field, direction) {
|
|
||||||
const currentIndex = this.submitterFields.indexOf(field)
|
|
||||||
const fieldsIndex = this.fields.indexOf(field)
|
|
||||||
|
|
||||||
this.fields.splice(fieldsIndex, 1)
|
|
||||||
|
|
||||||
if (currentIndex + direction > this.submitterFields.length) {
|
|
||||||
const firstIndex = this.fields.indexOf(this.submitterFields[0])
|
|
||||||
|
|
||||||
this.fields.splice(firstIndex, 0, field)
|
|
||||||
} else if (currentIndex + direction < 0) {
|
|
||||||
const lastIndex = this.fields.indexOf(this.submitterFields[this.submitterFields.length - 1])
|
|
||||||
|
|
||||||
this.fields.splice(lastIndex + 1, 0, field)
|
|
||||||
} else {
|
|
||||||
this.fields.splice(fieldsIndex + direction, 0, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.save()
|
|
||||||
},
|
|
||||||
removeField (field) {
|
removeField (field) {
|
||||||
this.fields.splice(this.fields.indexOf(field), 1)
|
this.fields.splice(this.fields.indexOf(field), 1)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div class="fixed text-center w-full bottom-0 pr-6 mb-4">
|
||||||
|
<span class="w-full bg-base-200 px-4 py-2 rounded-md inline-flex space-x-2 mx-auto items-center justify-between mb-2 z-20 md:hidden">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<component
|
||||||
|
:is="fieldIcons[drawField.type]"
|
||||||
|
:width="20"
|
||||||
|
:height="20"
|
||||||
|
class="inline"
|
||||||
|
:stroke-width="1.6"
|
||||||
|
/>
|
||||||
|
<span> Draw {{ fieldNames[drawField.type] }} Field </span>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="link block text-center"
|
||||||
|
@click.prevent="$emit('cancel')"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<FieldSubmitter
|
||||||
|
:model-value="selectedSubmitter.uuid"
|
||||||
|
:submitters="submitters"
|
||||||
|
:editable="editable"
|
||||||
|
:mobile-view="true"
|
||||||
|
@new-submitter="save"
|
||||||
|
@remove="removeSubmitter"
|
||||||
|
@name-change="save"
|
||||||
|
@update:model-value="$emit('change-submitter', submitters.find((s) => s.uuid === $event))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Field from './field'
|
||||||
|
import FieldType from './field_type'
|
||||||
|
import FieldSubmitter from './field_submitter'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MobileDrawField',
|
||||||
|
components: {
|
||||||
|
Field,
|
||||||
|
FieldSubmitter
|
||||||
|
},
|
||||||
|
inject: ['save'],
|
||||||
|
props: {
|
||||||
|
drawField: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
submitters: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selectedSubmitter: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['change-submitter', 'cancel'],
|
||||||
|
computed: {
|
||||||
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
|
fieldIcons: FieldType.computed.fieldIcons
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeSubmitter (submitter) {
|
||||||
|
[...this.fields].forEach((field) => {
|
||||||
|
if (field.submitter_uuid === submitter.uuid) {
|
||||||
|
this.removeField(field)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.submitters.splice(this.submitters.indexOf(submitter), 1)
|
||||||
|
|
||||||
|
if (this.selectedSubmitter === submitter) {
|
||||||
|
this.$emit('change-submitter', this.submitters[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
|
removeField (field) {
|
||||||
|
this.fields.splice(this.fields.indexOf(field), 1)
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
:ref="setAreaRefs"
|
:ref="setAreaRefs"
|
||||||
:area="item.area"
|
:area="item.area"
|
||||||
:field="item.field"
|
:field="item.field"
|
||||||
|
:editable="editable"
|
||||||
@start-resize="resizeDirection = $event"
|
@start-resize="resizeDirection = $event"
|
||||||
@stop-resize="resizeDirection = null"
|
@stop-resize="resizeDirection = null"
|
||||||
@start-drag="isMove = true"
|
@start-drag="isMove = true"
|
||||||
@@ -77,6 +78,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
isDrag: {
|
isDrag: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -141,6 +147,10 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.showMask = true
|
this.showMask = true
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1"
|
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1"
|
||||||
@click="$emit('scroll-to', item)"
|
@click="$emit('scroll-to', item)"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between w-full">
|
<div
|
||||||
|
v-if="editable"
|
||||||
|
class="flex justify-between w-full"
|
||||||
|
>
|
||||||
<div style="width: 26px" />
|
<div style="width: 26px" />
|
||||||
<div class="">
|
<div class="">
|
||||||
<ReplaceButton
|
<ReplaceButton
|
||||||
@@ -95,6 +98,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
acceptFileTypes: {
|
acceptFileTypes: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
|
|||||||
@@ -10,19 +10,18 @@ class ProcessSubmitterCompletionJob < ApplicationJob
|
|||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(submitter)
|
Submissions::EnsureResultGenerated.call(submitter)
|
||||||
|
|
||||||
if submitter.account.encrypted_configs.exists?(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
if is_all_completed && submitter.completed_at == submitter.submission.submitters.maximum(:completed_at)
|
||||||
SendFormCompletedWebhookRequestJob.perform_later(submitter)
|
Submissions::GenerateAuditTrail.call(submitter.submission)
|
||||||
|
|
||||||
|
enqueue_completed_emails(submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
return unless is_all_completed
|
return if Accounts.load_webhook_configs(submitter.account).blank?
|
||||||
return if submitter.completed_at != submitter.submission.submitters.maximum(:completed_at)
|
|
||||||
|
|
||||||
Submissions::GenerateAuditTrail.call(submitter.submission)
|
SendFormCompletedWebhookRequestJob.perform_later(submitter)
|
||||||
|
|
||||||
enqueue_emails(submitter)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_emails(submitter)
|
def enqueue_completed_emails(submitter)
|
||||||
user = submitter.submission.created_by_user || submitter.template.author
|
user = submitter.submission.created_by_user || submitter.template.author
|
||||||
|
|
||||||
if submitter.template.account.users.exists?(id: user.id)
|
if submitter.template.account.users.exists?(id: user.id)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
# fk_rails_... (user_id => users.id)
|
# fk_rails_... (user_id => users.id)
|
||||||
#
|
#
|
||||||
class AccessToken < ApplicationRecord
|
class AccessToken < ApplicationRecord
|
||||||
TOKEN_LENGTH = 22
|
TOKEN_LENGTH = 43
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class Submission < ApplicationRecord
|
|||||||
|
|
||||||
scope :active, -> { where(deleted_at: nil) }
|
scope :active, -> { where(deleted_at: nil) }
|
||||||
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).distinct }
|
scope :pending, -> { joins(:submitters).where(submitters: { completed_at: nil }).distinct }
|
||||||
scope :completed, -> { left_joins(:submitters).where.not(submitters: { completed_at: nil }).distinct }
|
scope :completed, -> { where.not(id: pending.select(:submission_id)) }
|
||||||
|
|
||||||
enum :source, {
|
enum :source, {
|
||||||
invite: 'invite',
|
invite: 'invite',
|
||||||
@@ -62,4 +62,9 @@ class Submission < ApplicationRecord
|
|||||||
random: 'random',
|
random: 'random',
|
||||||
preserved: 'preserved'
|
preserved: 'preserved'
|
||||||
}, scope: false, prefix: true
|
}, scope: false, prefix: true
|
||||||
|
|
||||||
|
def audit_trail_url
|
||||||
|
audit_trail&.url
|
||||||
|
end
|
||||||
|
alias audit_log_url audit_trail_url
|
||||||
end
|
end
|
||||||
+14
-13
@@ -4,19 +4,20 @@
|
|||||||
#
|
#
|
||||||
# Table name: templates
|
# Table name: templates
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# deleted_at :datetime
|
# application_key :string
|
||||||
# fields :text not null
|
# deleted_at :datetime
|
||||||
# name :string not null
|
# fields :text not null
|
||||||
# schema :text not null
|
# name :string not null
|
||||||
# slug :string not null
|
# schema :text not null
|
||||||
# source :text not null
|
# slug :string not null
|
||||||
# submitters :text not null
|
# source :text not null
|
||||||
# created_at :datetime not null
|
# submitters :text not null
|
||||||
# updated_at :datetime not null
|
# created_at :datetime not null
|
||||||
# account_id :bigint not null
|
# updated_at :datetime not null
|
||||||
# author_id :bigint not null
|
# account_id :bigint not null
|
||||||
# folder_id :bigint not null
|
# author_id :bigint not null
|
||||||
|
# folder_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
|
<a href="<%= root_path %>" class="text-2xl font-bold items-center flex space-x-2">
|
||||||
<%= render 'shared/title' %>
|
<%= render 'shared/title' %>
|
||||||
</a>
|
</a>
|
||||||
<%= render 'shared/github' %>
|
<span class="hidden sm:inline">
|
||||||
|
<%= render 'shared/github' %>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% if signed_in? %>
|
<% if signed_in? %>
|
||||||
<div class="space-x-4 flex items-center">
|
<div class="space-x-4 flex items-center">
|
||||||
@@ -16,13 +18,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="flex items-center justify-center space-x-4 mr-1">
|
<div class="flex items-center justify-center space-x-4 mr-1">
|
||||||
<% if can?(:manage, EncryptedConfig) && !can?(:manage, :tenants) %>
|
<%= render 'shared/navbar_buttons' %>
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, class: 'hidden md:inline-flex items-center font-medium text-lg', data: { prefetch: false } do %>
|
|
||||||
Console
|
|
||||||
<span class="badge badge-warning ml-1">New</span>
|
|
||||||
<% end %>
|
|
||||||
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
|
|
||||||
<% end %>
|
|
||||||
<%= link_to 'Settings', settings_profile_index_path, class: 'hidden md:inline-flex font-medium text-lg' %>
|
<%= link_to 'Settings', settings_profile_index_path, class: 'hidden md:inline-flex font-medium text-lg' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -75,7 +71,8 @@
|
|||||||
<% if Docuseal.multitenant? && !request.path.in?([registration_path, new_registration_path]) %>
|
<% if Docuseal.multitenant? && !request.path.in?([registration_path, new_registration_path]) %>
|
||||||
<%= link_to registration_path, class: 'btn btn-neutral btn-sm btn-outline' do %>
|
<%= link_to registration_path, class: 'btn btn-neutral btn-sm btn-outline' do %>
|
||||||
<span class="flex items-center justify-center space-x-1">
|
<span class="flex items-center justify-center space-x-1">
|
||||||
<span>Create free Account</span>
|
<span class="hidden sm:block">Create free Account</span>
|
||||||
|
<span class="md:hidden">Sign Up</span>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'hidden md:inline-flex btn btn-warning btn-sm', data: { prefetch: false } do %>
|
||||||
|
Upgrade
|
||||||
|
<% end %>
|
||||||
|
<span class="hidden md:inline-flex h-3 border-r border-base-content"></span>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<div class="block w-full md:w-52 flex-none">
|
<div class="block w-full md:w-52 flex-none">
|
||||||
<menu-active>
|
<menu-active>
|
||||||
<ul class="menu px-0">
|
<ul class="menu px-0">
|
||||||
<li class="menu-title py-0"><span>Settings</span></li>
|
<li class="menu-title py-0 !bg-transparent mb-3 -mt-5"><a href="<%= '/' %>" class="!bg-transparent !text-neutral font-medium">← Back</a></li>
|
||||||
|
<li class="menu-title py-0 !bg-transparent"><span class="!bg-transparent">Settings</span></li>
|
||||||
<li></li>
|
<li></li>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'Profile', settings_profile_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Profile', settings_profile_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
@@ -70,14 +71,14 @@
|
|||||||
<li>
|
<li>
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/plans") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||||
Plans
|
Plans
|
||||||
|
<span class="badge badge-warning">New</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
|
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path : Docuseal::CONSOLE_URL, class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||||
Console
|
Console
|
||||||
<span class="badge badge-warning">New</span>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4', onsubmit: 'event.submitter.disabled = true' } do |f| %>
|
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4', onsubmit: 'event.submitter.disabled = true' } do |f| %>
|
||||||
<div class="form-control !mt-0">
|
<div class="form-control !mt-0">
|
||||||
<%= f.label :email, class: 'label' %>
|
<%= f.label :email, class: 'label' %>
|
||||||
<%= f.email_field :email, value: current_user&.email, required: true, class: 'base-input', placeholder: 'Provide your email to start' %>
|
<%= f.email_field :email, value: current_user&.email || params[:email], required: true, class: 'base-input', placeholder: 'Provide your email to start' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.button button_title(title: 'Start', disabled_with: 'Starting'), class: 'base-button' %>
|
<%= f.button button_title(title: 'Start', disabled_with: 'Starting'), class: 'base-button' %>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<% if @submitter.submission.template.submitters.size == 1 %>
|
<% if @submitter.submission.template.submitters.size == 1 %>
|
||||||
<div class="divider">OR</div>
|
<div class="divider">OR</div>
|
||||||
<div>
|
<div>
|
||||||
<%= button_to button_title(title: 'Resubmit', disabled_with: 'Resubmit', icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone }, resubmit: true }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
<%= button_to button_title(title: 'Resubmit', disabled_with: 'Resubmit', icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@submitter.submission.template.slug), params: { submitter: { email: @submitter.email, phone: @submitter.phone, name: @submitter.name }, resubmit: true }, method: :put, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'white-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<%= render 'submissions/annotation', annot: %>
|
<%= render 'submissions/annotation', annot: %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
||||||
<% value = values[field['uuid']] %>
|
<% value = values[field['uuid']].presence || (field['readonly'] ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submitter.submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||||
<% next if value.blank? %>
|
<% next if value.blank? %>
|
||||||
<% next if !field['readonly'] && field['submitter_uuid'] == @submitter.uuid %>
|
<% next if !field['readonly'] && field['submitter_uuid'] == @submitter.uuid %>
|
||||||
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submitter.submission.template.account.locale %>
|
<%= render 'submissions/value', area:, field:, attachments_index:, value:, locale: @submitter.submission.template.account.locale %>
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
<%= hidden_field_tag :base_template_id, @base_template.id %>
|
<%= hidden_field_tag :base_template_id, @base_template.id %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control mt-6">
|
<div class="form-control mt-6">
|
||||||
<%= f.text_field :name, required: true, placeholder: 'Template Name', class: 'base-input' %>
|
<%= f.text_field :name, required: true, placeholder: 'Document Template Name', class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 mb-4 flex items-center justify-between">
|
<div class="mt-3 mb-4 flex items-center justify-between">
|
||||||
<a href="#" onclick="[event.preventDefault(), window.folder_name.focus()]">
|
<a href="#" onclick="[event.preventDefault(), window.folder_name.focus()]">
|
||||||
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||||
</a>
|
</a>
|
||||||
<folder-autocomplete class="flex justify-between w-full">
|
<folder-autocomplete class="flex justify-between w-full">
|
||||||
<input id="folder_name" type="text" class="w-64 outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.name || TemplateFolder::DEFAULT_NAME %>" onblur="window.folder_name.value = window.folder_name.value || 'Default'" autocomplete="off">
|
<input id="folder_name" placeholder="Folder Name" type="text" class="w-64 outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.name || TemplateFolder::DEFAULT_NAME %>" onblur="window.folder_name.value = window.folder_name.value || 'Default'" autocomplete="off">
|
||||||
<a href="#" onclick="[event.preventDefault(), window.folder_name.value = '', window.folder_name.focus()]" class="link peer-focus:hidden mr-1.5">
|
<a href="#" onclick="[event.preventDefault(), window.folder_name.value = '', window.folder_name.focus()]" class="link peer-focus:hidden mr-1.5">
|
||||||
Change Folder
|
Change Folder
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AddApplicationIdToSubmitters < ActiveRecord::Migration[7.0]
|
class AddApplicationKeyToSubmitters < ActiveRecord::Migration[7.0]
|
||||||
def change
|
def change
|
||||||
add_column :submitters, :application_key, :string
|
add_column :submitters, :application_key, :string
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddApplicationKeyToTemplates < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :templates, :application_key, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
+2
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2023_10_17_213639) do
|
ActiveRecord::Schema[7.0].define(version: 2023_11_02_171817) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
@@ -173,6 +173,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_17_213639) do
|
|||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.text "source", null: false
|
t.text "source", null: false
|
||||||
t.bigint "folder_id", null: false
|
t.bigint "folder_id", null: false
|
||||||
|
t.string "application_key"
|
||||||
t.index ["account_id"], name: "index_templates_on_account_id"
|
t.index ["account_id"], name: "index_templates_on_account_id"
|
||||||
t.index ["author_id"], name: "index_templates_on_author_id"
|
t.index ["author_id"], name: "index_templates_on_author_id"
|
||||||
t.index ["folder_id"], name: "index_templates_on_folder_id"
|
t.index ["folder_id"], name: "index_templates_on_folder_id"
|
||||||
|
|||||||
+6
-2
@@ -47,9 +47,13 @@ module Accounts
|
|||||||
end
|
end
|
||||||
|
|
||||||
def load_webhook_configs(account)
|
def load_webhook_configs(account)
|
||||||
EncryptedConfig.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY) unless Docuseal.multitenant?
|
configs = account.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
||||||
|
|
||||||
account.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
unless Docuseal.multitenant?
|
||||||
|
configs ||= Account.order(:id).first.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
||||||
|
end
|
||||||
|
|
||||||
|
configs
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_signing_pkcs(account)
|
def load_signing_pkcs(account)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ module Submissions
|
|||||||
else
|
else
|
||||||
'Verified'
|
'Verified'
|
||||||
end
|
end
|
||||||
|
UNVERIFIED_TEXT = 'Unverified'
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
@@ -151,10 +152,10 @@ module Submissions
|
|||||||
composer.document.layout.formatted_text_box(
|
composer.document.layout.formatted_text_box(
|
||||||
[
|
[
|
||||||
submitter.email && {
|
submitter.email && {
|
||||||
text: "Email verification: #{click_email_event ? VERIFIED_TEXT : 'Unverififed'}\n"
|
text: "Email verification: #{click_email_event ? VERIFIED_TEXT : UNVERIFIED_TEXT}\n"
|
||||||
},
|
},
|
||||||
submitter.phone && {
|
submitter.phone && {
|
||||||
text: "Phone verification: #{is_phone_verified ? VERIFIED_TEXT : 'Unverififed'}\n"
|
text: "Phone verification: #{is_phone_verified ? VERIFIED_TEXT : UNVERIFIED_TEXT}\n"
|
||||||
},
|
},
|
||||||
completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" },
|
completed_event.data['ip'] && { text: "IP: #{completed_event.data['ip']}\n" },
|
||||||
completed_event.data['sid'] && { text: "Session ID: #{completed_event.data['sid']}\n" },
|
completed_event.data['sid'] && { text: "Session ID: #{completed_event.data['sid']}\n" },
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ module Submitters
|
|||||||
.except('uuid', 'values', 'slug')
|
.except('uuid', 'values', 'slug')
|
||||||
.merge('values' => values,
|
.merge('values' => values,
|
||||||
'documents' => documents,
|
'documents' => documents,
|
||||||
|
'audit_log_url' => submitter.submission.audit_log_url,
|
||||||
'role' => submitter_name)
|
'role' => submitter_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ module Submitters
|
|||||||
module SubmitValues
|
module SubmitValues
|
||||||
ValidationError = Class.new(StandardError)
|
ValidationError = Class.new(StandardError)
|
||||||
|
|
||||||
|
VARIABLE_REGEXP = /\{\{?(\w+)\}\}?/
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(submitter, params, request)
|
def call(submitter, params, request)
|
||||||
@@ -36,6 +38,7 @@ module Submitters
|
|||||||
submitter.completed_at = Time.current
|
submitter.completed_at = Time.current
|
||||||
submitter.ip = request.remote_ip
|
submitter.ip = request.remote_ip
|
||||||
submitter.ua = request.user_agent
|
submitter.ua = request.user_agent
|
||||||
|
submitter.values = merge_default_values(submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
@@ -69,6 +72,56 @@ module Submitters
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def merge_default_values(submitter)
|
||||||
|
default_values = submitter.submission.template_fields.each_with_object({}) do |field, acc|
|
||||||
|
next if field['submitter_uuid'] != submitter.uuid
|
||||||
|
|
||||||
|
value = field['default_value']
|
||||||
|
|
||||||
|
next if value.blank?
|
||||||
|
|
||||||
|
acc[field['uuid']] = template_default_value_for_submitter(value, submitter, with_time: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
default_values.compact_blank.merge(submitter.values)
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_default_value_for_submitter(value, submitter, with_time: false)
|
||||||
|
return if value.blank?
|
||||||
|
|
||||||
|
role = submitter.submission.template_submitters.find { |e| e['uuid'] == submitter.uuid }['name']
|
||||||
|
|
||||||
|
replace_default_variables(value,
|
||||||
|
submitter.attributes.merge('role' => role),
|
||||||
|
submitter.submission.template,
|
||||||
|
with_time:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_default_variables(value, attrs, template, with_time: false)
|
||||||
|
return if value.blank?
|
||||||
|
|
||||||
|
value.gsub(VARIABLE_REGEXP) do |e|
|
||||||
|
case key = ::Regexp.last_match(1)
|
||||||
|
when 'time'
|
||||||
|
if with_time
|
||||||
|
I18n.l(Time.current.in_time_zone(template.account.timezone), format: :long, locale: template.account.locale)
|
||||||
|
else
|
||||||
|
e
|
||||||
|
end
|
||||||
|
when 'date'
|
||||||
|
if with_time
|
||||||
|
I18n.l(Time.current.in_time_zone(template.account.timezone).to_date)
|
||||||
|
else
|
||||||
|
e
|
||||||
|
end
|
||||||
|
when 'role', 'email', 'phone', 'name'
|
||||||
|
attrs[key] || e
|
||||||
|
else
|
||||||
|
e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate_value!(_value, _field, _params, _submitter, _request)
|
def validate_value!(_value, _field, _params, _submitter, _request)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in new issue
Block a user