diff --git a/README.md b/README.md
index d97620c0..9e6b2f04 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|Heroku|Railway|
|:--:|:---:|
-| [
](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [
](https://railway.app/template/IGoDnc?referralCode=ruU7JR)|
+| [
](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [
](https://railway.com/deploy/IGoDnc?referralCode=ruU7JR)|
|**DigitalOcean**|**Render**|
| [
](https://cloud.digitalocean.com/apps/new?repo=https://github.com/docusealco/docuseal-digitalocean/tree/master&refcode=421d50f53990) | [
](https://render.com/deploy?repo=https://github.com/docusealco/docuseal-render)
diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb
index f28bf5e1..8f6d77fc 100644
--- a/app/controllers/api/submitters_controller.rb
+++ b/app/controllers/api/submitters_controller.rb
@@ -40,6 +40,10 @@ module Api
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_content
end
+ if @submitter.declined_at?
+ return render json: { error: 'Submitter has already declined the submission.' }, status: :unprocessable_content
+ end
+
submission = @submitter.submission
role = submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
diff --git a/app/javascript/submission_form/initials_step.vue b/app/javascript/submission_form/initials_step.vue
index 814b9c12..8a53d532 100644
--- a/app/javascript/submission_form/initials_step.vue
+++ b/app/javascript/submission_form/initials_step.vue
@@ -359,7 +359,9 @@ export default {
},
initTextInitial () {
if (this.submitter.name) {
- this.$refs.textInput.value = this.submitter.name.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() || '').join('')
+ const parts = this.submitter.name.trim().split(/\s+/)
+
+ this.$refs.textInput.value = (parts.length > 1 ? [parts[0], parts[parts.length - 1]] : parts).map((part) => part[0]?.toUpperCase() || '').join('')
}
if (this.$refs.textInput.value) {
diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue
index 218a182f..5338fc07 100644
--- a/app/javascript/template_builder/builder.vue
+++ b/app/javascript/template_builder/builder.vue
@@ -364,7 +364,14 @@
:document="dynamicDocuments.find((dynamicDocument) => dynamicDocument.uuid === document.uuid)"
:selected-submitter="selectedSubmitter"
:drag-field="dragField"
+ :draw-field="drawField"
+ :draw-field-type="drawFieldType"
+ :draw-custom-field="drawCustomField"
+ :draw-option="drawOption"
@update="onDynamicDocumentUpdate"
+ @draw="clearDrawField"
+ @add-custom-field="addCustomField"
+ @set-draw="[drawField = $event.field, drawOption = $event.option]"
/>
{
+ this.documentRefs.forEach((documentRef) => {
+ if (documentRef.isDynamic && documentRef.document.uuid === area.attachment_uuid) {
+ documentRef.removeArea(area)
+ }
+ })
+ })
+ }
+ },
+ onRemoveSubmitter (submitter) {
+ if (this.dynamicDocuments.length) {
+ this.template.fields.forEach((field) => {
+ if (field.submitter_uuid === submitter.uuid) {
+ field.areas?.forEach((area) => {
+ this.documentRefs.forEach((documentRef) => {
+ if (documentRef.isDynamic && documentRef.document.uuid === area.attachment_uuid) {
+ documentRef.removeArea(area)
+ }
+ })
+ })
+ }
+ })
+ }
+ },
toggleSelectMode () {
this.isSelectModeRef.value = !this.isSelectModeRef.value
@@ -2512,60 +2547,80 @@ export default {
this.save()
},
- onDocumentRemove (item) {
- if (window.confirm(this.t('are_you_sure_'))) {
- this.template.schema.splice(this.template.schema.indexOf(item), 1)
+ removeAreasByAttachmentUuid (attachmentUuid) {
+ const removedFieldUuids = []
- const removedFieldUuids = []
+ this.selectedAreasRef.value = this.selectedAreasRef.value.filter((area) => area.attachment_uuid !== attachmentUuid)
- this.template.fields.forEach((field) => {
- [...(field.areas || [])].forEach((area) => {
- if (area.attachment_uuid === item.attachment_uuid) {
- field.areas.splice(field.areas.indexOf(area), 1)
+ this.template.fields.forEach((field) => {
+ [...(field.areas || [])].forEach((area) => {
+ if (area.attachment_uuid === attachmentUuid) {
+ field.areas.splice(field.areas.indexOf(area), 1)
- removedFieldUuids.push(field.uuid)
- }
- })
+ removedFieldUuids.push(field.uuid)
+ }
})
+ })
- this.template.fields = this.template.fields.reduce((acc, f) => {
- if (removedFieldUuids.includes(f.uuid) && !f.areas?.length) {
- this.removeFieldConditions(f)
- } else {
- acc.push(f)
- }
+ this.template.fields = this.template.fields.reduce((acc, field) => {
+ if (removedFieldUuids.includes(field.uuid) && !field.areas?.length) {
+ this.removeFieldConditions(field)
+ } else {
+ acc.push(field)
+ }
+
+ return acc
+ }, [])
+ },
+ onDocumentRemove (item) {
+ if (window.confirm(this.t('are_you_sure_'))) {
+ this.template.schema.splice(this.template.schema.indexOf(item), 1)
- return acc
- }, [])
+ this.removeAreasByAttachmentUuid(item.attachment_uuid)
this.save()
}
},
onDocumentReplace (data) {
const { replaceSchemaItem, schema, documents } = data
+ const isReplacingDynamicDocument = !!replaceSchemaItem.dynamic
// eslint-disable-next-line camelcase
const { google_drive_file_id, dynamic, ...cleanedReplaceSchemaItem } = replaceSchemaItem
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...cleanedReplaceSchemaItem, ...schema[0] })
this.template.documents.push(...documents)
+ if (isReplacingDynamicDocument) {
+ this.removeAreasByAttachmentUuid(replaceSchemaItem.attachment_uuid)
+
+ const dynamicDocumentIndex = this.dynamicDocuments.findIndex((doc) => doc.uuid === replaceSchemaItem.attachment_uuid)
+
+ if (dynamicDocumentIndex !== -1) {
+ this.dynamicDocuments.splice(dynamicDocumentIndex, 1)
+ }
+ }
+
if (data.fields) {
this.template.fields = data.fields
- const removedFieldUuids = []
+ if (isReplacingDynamicDocument) {
+ this.removeAreasByAttachmentUuid(replaceSchemaItem.attachment_uuid)
+ } else {
+ const removedFieldUuids = []
- this.template.fields.forEach((field) => {
- [...(field.areas || [])].forEach((area) => {
- if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
- field.areas.splice(field.areas.indexOf(area), 1)
+ this.template.fields.forEach((field) => {
+ [...(field.areas || [])].forEach((area) => {
+ if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
+ field.areas.splice(field.areas.indexOf(area), 1)
- removedFieldUuids.push(field.uuid)
- }
+ removedFieldUuids.push(field.uuid)
+ }
+ })
})
- })
- this.template.fields =
- this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
+ this.template.fields =
+ this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
+ }
}
if (data.submitters) {
@@ -2576,13 +2631,15 @@ export default {
}
}
- this.template.fields.forEach((field) => {
- (field.areas || []).forEach((area) => {
- if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
- area.attachment_uuid = schema[0].attachment_uuid
- }
+ if (!isReplacingDynamicDocument) {
+ this.template.fields.forEach((field) => {
+ (field.areas || []).forEach((area) => {
+ if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
+ area.attachment_uuid = schema[0].attachment_uuid
+ }
+ })
})
- })
+ }
if (this.onUpload) {
this.onUpload(this.template)
@@ -2694,9 +2751,13 @@ export default {
scrollToArea (area) {
const documentRef = this.documentRefs.find((a) => a.document.uuid === area.attachment_uuid)
- documentRef.scrollToArea(area)
+ if (documentRef.isDynamic) {
+ this.selectedAreasRef.value = []
+ } else {
+ this.selectedAreasRef.value = [area]
+ }
- this.selectedAreasRef.value = [area]
+ documentRef.scrollToArea(area)
},
baseFetch (path, options = {}) {
return fetch(this.baseUrl + path, {
@@ -2942,15 +3003,15 @@ export default {
}
})
- this.reconcileDynamicFields()
+ this.save()
},
rebuildVariablesSchema ({ disable = true } = {}) {
const parsed = {}
- const dynamicDocumentRef = this.documentRefs.find((e) => e.mergeSchemaProperties)
+ const dynamicDocumentRef = this.documentRefs.find((e) => e.isDynamic)
this.documentRefs.forEach((ref) => {
- if (ref.updateVariablesSchema) {
+ if (ref.isDynamic) {
ref.updateVariablesSchema()
}
})
@@ -2964,70 +3025,8 @@ export default {
if (!this.template.variables_schema) {
this.template.variables_schema = parsed
} else {
- this.syncVariablesSchema(this.template.variables_schema, parsed, { disable })
- }
- },
- syncVariablesSchema (existing, parsed, { disable = true } = {}) {
- for (const key of Object.keys(parsed)) {
- if (!existing[key]) {
- existing[key] = parsed[key]
- }
+ dynamicDocumentRef.syncVariablesSchema(this.template.variables_schema, parsed, { disable })
}
-
- for (const key of Object.keys(existing)) {
- if (!parsed[key]) {
- if (disable) {
- existing[key].disabled = true
- } else {
- delete existing[key]
- }
- } else {
- delete existing[key].disabled
-
- if (!existing[key].form_type) {
- existing[key].type = parsed[key].type
- }
-
- if (parsed[key].items) {
- if (!existing[key].items) {
- existing[key].items = parsed[key].items
- } else if (existing[key].items.properties && parsed[key].items.properties) {
- this.syncVariablesSchema(existing[key].items.properties, parsed[key].items.properties, { disable })
- } else if (!existing[key].items.properties && !parsed[key].items.properties) {
- existing[key].items.type = parsed[key].items.type
- }
- }
-
- if (existing[key].properties && parsed[key].properties) {
- this.syncVariablesSchema(existing[key].properties, parsed[key].properties, { disable })
- }
- }
- }
- },
- reconcileDynamicFields () {
- const dynamicFieldUuids = new Set()
-
- this.dynamicDocuments.forEach((doc) => {
- const body = doc.body || ''
- const uuidRegex = /uuid="([^"]+)"/g
- let match
-
- while ((match = uuidRegex.exec(body)) !== null) {
- dynamicFieldUuids.add(match[1])
- }
- })
-
- const toRemove = this.template.fields.filter((field) => {
- if (field.areas && field.areas.length > 0) return false
-
- return field.uuid && !dynamicFieldUuids.has(field.uuid)
- })
-
- toRemove.forEach((field) => {
- this.template.fields.splice(this.template.fields.indexOf(field), 1)
- })
-
- this.save()
}
}
}
diff --git a/app/javascript/template_builder/dynamic_area.vue b/app/javascript/template_builder/dynamic_area.vue
index 8a4cb89e..53fb2566 100644
--- a/app/javascript/template_builder/dynamic_area.vue
+++ b/app/javascript/template_builder/dynamic_area.vue
@@ -5,7 +5,6 @@
:draggable="editable"
:style="[nodeStyle]"
@mousedown="selectArea"
- @click.stop
@dragstart="onDragStart"
@contextmenu.prevent.stop="onContextMenu"
>
@@ -25,7 +24,14 @@
{{ field.default_value }}
+ >
+
+ {{ t('signing_date') }}
+
+
+ {{ field.default_value }}
+
+