Merge from docusealco/wip
This commit is contained in:
@@ -64,7 +64,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
|
|
||||||
|Heroku|Railway|
|
|Heroku|Railway|
|
||||||
|:--:|:---:|
|
|:--:|:---:|
|
||||||
| [<img alt="Deploy on Heroku" src="https://www.herokucdn.com/deploy/button.svg" height="40">](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [<img alt="Deploy on Railway" src="https://railway.app/button.svg" height="40">](https://railway.app/template/IGoDnc?referralCode=ruU7JR)|
|
| [<img alt="Deploy on Heroku" src="https://www.herokucdn.com/deploy/button.svg" height="40">](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [<img alt="Deploy on Railway" src="https://railway.app/button.svg" height="40">](https://railway.com/deploy/IGoDnc?referralCode=ruU7JR)|
|
||||||
|**DigitalOcean**|**Render**|
|
|**DigitalOcean**|**Render**|
|
||||||
| [<img alt="Deploy on DigitalOcean" src="https://www.deploytodo.com/do-btn-blue.svg" height="40">](https://cloud.digitalocean.com/apps/new?repo=https://github.com/docusealco/docuseal-digitalocean/tree/master&refcode=421d50f53990) | [<img alt="Deploy to Render" src="https://render.com/images/deploy-to-render-button.svg" height="40">](https://render.com/deploy?repo=https://github.com/docusealco/docuseal-render)
|
| [<img alt="Deploy on DigitalOcean" src="https://www.deploytodo.com/do-btn-blue.svg" height="40">](https://cloud.digitalocean.com/apps/new?repo=https://github.com/docusealco/docuseal-digitalocean/tree/master&refcode=421d50f53990) | [<img alt="Deploy to Render" src="https://render.com/images/deploy-to-render-button.svg" height="40">](https://render.com/deploy?repo=https://github.com/docusealco/docuseal-render)
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ module Api
|
|||||||
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_content
|
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if @submitter.declined_at?
|
||||||
|
return render json: { error: 'Submitter has already declined the submission.' }, status: :unprocessable_content
|
||||||
|
end
|
||||||
|
|
||||||
submission = @submitter.submission
|
submission = @submitter.submission
|
||||||
role = submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
|
role = submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
|
||||||
|
|
||||||
|
|||||||
@@ -359,7 +359,9 @@ export default {
|
|||||||
},
|
},
|
||||||
initTextInitial () {
|
initTextInitial () {
|
||||||
if (this.submitter.name) {
|
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) {
|
if (this.$refs.textInput.value) {
|
||||||
|
|||||||
@@ -364,7 +364,14 @@
|
|||||||
:document="dynamicDocuments.find((dynamicDocument) => dynamicDocument.uuid === document.uuid)"
|
:document="dynamicDocuments.find((dynamicDocument) => dynamicDocument.uuid === document.uuid)"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
:drag-field="dragField"
|
:drag-field="dragField"
|
||||||
|
:draw-field="drawField"
|
||||||
|
:draw-field-type="drawFieldType"
|
||||||
|
:draw-custom-field="drawCustomField"
|
||||||
|
:draw-option="drawOption"
|
||||||
@update="onDynamicDocumentUpdate"
|
@update="onDynamicDocumentUpdate"
|
||||||
|
@draw="clearDrawField"
|
||||||
|
@add-custom-field="addCustomField"
|
||||||
|
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
||||||
/>
|
/>
|
||||||
<Document
|
<Document
|
||||||
v-else
|
v-else
|
||||||
@@ -509,6 +516,8 @@
|
|||||||
:show-tour-start-form="showTourStartForm"
|
:show-tour-start-form="showTourStartForm"
|
||||||
@add-field="addField"
|
@add-field="addField"
|
||||||
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
@set-draw="[drawField = $event.field, drawOption = $event.option]"
|
||||||
|
@remove-field="onRemoveField"
|
||||||
|
@remove-submitter="onRemoveSubmitter"
|
||||||
@select-submitter="selectedSubmitter = $event"
|
@select-submitter="selectedSubmitter = $event"
|
||||||
@set-draw-type="[drawFieldType = $event, showDrawField = true]"
|
@set-draw-type="[drawFieldType = $event, showDrawField = true]"
|
||||||
@set-draw-custom-field="[drawCustomField = $event, showDrawField = true]"
|
@set-draw-custom-field="[drawCustomField = $event, showDrawField = true]"
|
||||||
@@ -1155,6 +1164,32 @@ export default {
|
|||||||
this.drawCustomField = null
|
this.drawCustomField = null
|
||||||
this.showDrawField = false
|
this.showDrawField = false
|
||||||
},
|
},
|
||||||
|
onRemoveField (field) {
|
||||||
|
if (this.dynamicDocuments.length) {
|
||||||
|
field.areas?.forEach((area) => {
|
||||||
|
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 () {
|
toggleSelectMode () {
|
||||||
this.isSelectModeRef.value = !this.isSelectModeRef.value
|
this.isSelectModeRef.value = !this.isSelectModeRef.value
|
||||||
|
|
||||||
@@ -2512,15 +2547,14 @@ export default {
|
|||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
onDocumentRemove (item) {
|
removeAreasByAttachmentUuid (attachmentUuid) {
|
||||||
if (window.confirm(this.t('are_you_sure_'))) {
|
|
||||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
|
||||||
|
|
||||||
const removedFieldUuids = []
|
const removedFieldUuids = []
|
||||||
|
|
||||||
|
this.selectedAreasRef.value = this.selectedAreasRef.value.filter((area) => area.attachment_uuid !== attachmentUuid)
|
||||||
|
|
||||||
this.template.fields.forEach((field) => {
|
this.template.fields.forEach((field) => {
|
||||||
[...(field.areas || [])].forEach((area) => {
|
[...(field.areas || [])].forEach((area) => {
|
||||||
if (area.attachment_uuid === item.attachment_uuid) {
|
if (area.attachment_uuid === attachmentUuid) {
|
||||||
field.areas.splice(field.areas.indexOf(area), 1)
|
field.areas.splice(field.areas.indexOf(area), 1)
|
||||||
|
|
||||||
removedFieldUuids.push(field.uuid)
|
removedFieldUuids.push(field.uuid)
|
||||||
@@ -2528,30 +2562,50 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.template.fields = this.template.fields.reduce((acc, f) => {
|
this.template.fields = this.template.fields.reduce((acc, field) => {
|
||||||
if (removedFieldUuids.includes(f.uuid) && !f.areas?.length) {
|
if (removedFieldUuids.includes(field.uuid) && !field.areas?.length) {
|
||||||
this.removeFieldConditions(f)
|
this.removeFieldConditions(field)
|
||||||
} else {
|
} else {
|
||||||
acc.push(f)
|
acc.push(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
|
},
|
||||||
|
onDocumentRemove (item) {
|
||||||
|
if (window.confirm(this.t('are_you_sure_'))) {
|
||||||
|
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||||
|
|
||||||
|
this.removeAreasByAttachmentUuid(item.attachment_uuid)
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDocumentReplace (data) {
|
onDocumentReplace (data) {
|
||||||
const { replaceSchemaItem, schema, documents } = data
|
const { replaceSchemaItem, schema, documents } = data
|
||||||
|
const isReplacingDynamicDocument = !!replaceSchemaItem.dynamic
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
const { google_drive_file_id, dynamic, ...cleanedReplaceSchemaItem } = replaceSchemaItem
|
const { google_drive_file_id, dynamic, ...cleanedReplaceSchemaItem } = replaceSchemaItem
|
||||||
|
|
||||||
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...cleanedReplaceSchemaItem, ...schema[0] })
|
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, { ...cleanedReplaceSchemaItem, ...schema[0] })
|
||||||
this.template.documents.push(...documents)
|
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) {
|
if (data.fields) {
|
||||||
this.template.fields = data.fields
|
this.template.fields = data.fields
|
||||||
|
|
||||||
|
if (isReplacingDynamicDocument) {
|
||||||
|
this.removeAreasByAttachmentUuid(replaceSchemaItem.attachment_uuid)
|
||||||
|
} else {
|
||||||
const removedFieldUuids = []
|
const removedFieldUuids = []
|
||||||
|
|
||||||
this.template.fields.forEach((field) => {
|
this.template.fields.forEach((field) => {
|
||||||
@@ -2567,6 +2621,7 @@ export default {
|
|||||||
this.template.fields =
|
this.template.fields =
|
||||||
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
|
this.template.fields.filter((f) => !removedFieldUuids.includes(f.uuid) || f.areas?.length)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.submitters) {
|
if (data.submitters) {
|
||||||
this.template.submitters = data.submitters
|
this.template.submitters = data.submitters
|
||||||
@@ -2576,6 +2631,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isReplacingDynamicDocument) {
|
||||||
this.template.fields.forEach((field) => {
|
this.template.fields.forEach((field) => {
|
||||||
(field.areas || []).forEach((area) => {
|
(field.areas || []).forEach((area) => {
|
||||||
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
|
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
|
||||||
@@ -2583,6 +2639,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (this.onUpload) {
|
if (this.onUpload) {
|
||||||
this.onUpload(this.template)
|
this.onUpload(this.template)
|
||||||
@@ -2694,9 +2751,13 @@ export default {
|
|||||||
scrollToArea (area) {
|
scrollToArea (area) {
|
||||||
const documentRef = this.documentRefs.find((a) => a.document.uuid === area.attachment_uuid)
|
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 = {}) {
|
baseFetch (path, options = {}) {
|
||||||
return fetch(this.baseUrl + path, {
|
return fetch(this.baseUrl + path, {
|
||||||
@@ -2942,15 +3003,15 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.reconcileDynamicFields()
|
this.save()
|
||||||
},
|
},
|
||||||
rebuildVariablesSchema ({ disable = true } = {}) {
|
rebuildVariablesSchema ({ disable = true } = {}) {
|
||||||
const parsed = {}
|
const parsed = {}
|
||||||
|
|
||||||
const dynamicDocumentRef = this.documentRefs.find((e) => e.mergeSchemaProperties)
|
const dynamicDocumentRef = this.documentRefs.find((e) => e.isDynamic)
|
||||||
|
|
||||||
this.documentRefs.forEach((ref) => {
|
this.documentRefs.forEach((ref) => {
|
||||||
if (ref.updateVariablesSchema) {
|
if (ref.isDynamic) {
|
||||||
ref.updateVariablesSchema()
|
ref.updateVariablesSchema()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -2964,70 +3025,8 @@ export default {
|
|||||||
if (!this.template.variables_schema) {
|
if (!this.template.variables_schema) {
|
||||||
this.template.variables_schema = parsed
|
this.template.variables_schema = parsed
|
||||||
} else {
|
} else {
|
||||||
this.syncVariablesSchema(this.template.variables_schema, parsed, { disable })
|
dynamicDocumentRef.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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
:draggable="editable"
|
:draggable="editable"
|
||||||
:style="[nodeStyle]"
|
:style="[nodeStyle]"
|
||||||
@mousedown="selectArea"
|
@mousedown="selectArea"
|
||||||
@click.stop
|
|
||||||
@dragstart="onDragStart"
|
@dragstart="onDragStart"
|
||||||
@contextmenu.prevent.stop="onContextMenu"
|
@contextmenu.prevent.stop="onContextMenu"
|
||||||
>
|
>
|
||||||
@@ -25,7 +24,14 @@
|
|||||||
<span
|
<span
|
||||||
v-if="field?.default_value"
|
v-if="field?.default_value"
|
||||||
class="text-xs overflow-hidden text-ellipsis whitespace-nowrap pr-1 font-normal pl-0.5"
|
class="text-xs overflow-hidden text-ellipsis whitespace-nowrap pr-1 font-normal pl-0.5"
|
||||||
>{{ field.default_value }}</span>
|
>
|
||||||
|
<template v-if="field.default_value === '{{date}}'">
|
||||||
|
{{ t('signing_date') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ field.default_value }}
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="field && !iconOnlyField"
|
v-else-if="field && !iconOnlyField"
|
||||||
class="text-xs overflow-hidden text-ellipsis whitespace-nowrap pr-1 opacity-70 font-normal pl-0.5"
|
class="text-xs overflow-hidden text-ellipsis whitespace-nowrap pr-1 opacity-70 font-normal pl-0.5"
|
||||||
@@ -227,6 +233,7 @@ export default {
|
|||||||
this.onAreaDragStart()
|
this.onAreaDragStart()
|
||||||
},
|
},
|
||||||
onContextMenu (e) {
|
onContextMenu (e) {
|
||||||
|
this.selectArea()
|
||||||
this.onAreaContextMenu(this.area, e)
|
this.onAreaContextMenu(this.area, e)
|
||||||
},
|
},
|
||||||
onResizeStart (e) {
|
onResizeStart (e) {
|
||||||
|
|||||||
@@ -29,18 +29,28 @@
|
|||||||
:container="$refs.container"
|
:container="$refs.container"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
:section="section"
|
:section="section"
|
||||||
|
:section-refs="sectionRefs"
|
||||||
:container-width="containerWidth"
|
:container-width="containerWidth"
|
||||||
:attachments-index="attachmentsIndex"
|
:attachments-index="attachmentsIndex"
|
||||||
:selected-submitter="selectedSubmitter"
|
:selected-submitter="selectedSubmitter"
|
||||||
:drag-field="dragField"
|
:drag-field="dragField"
|
||||||
|
:draw-field="drawField"
|
||||||
|
:draw-field-type="drawFieldType"
|
||||||
|
:draw-custom-field="drawCustomField"
|
||||||
|
:render-html-for-save-ref="renderHtmlForSaveRef"
|
||||||
|
:draw-option="drawOption"
|
||||||
:attachment-uuid="document.uuid"
|
:attachment-uuid="document.uuid"
|
||||||
@update="onSectionUpdate(section, $event)"
|
@update="onSectionUpdate(section, $event)"
|
||||||
|
@draw="$emit('draw', $event)"
|
||||||
|
@add-custom-field="$emit('add-custom-field', $event)"
|
||||||
|
@set-draw="$emit('set-draw', $event)"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { ref } from 'vue'
|
||||||
import DynamicSection from './dynamic_section.vue'
|
import DynamicSection from './dynamic_section.vue'
|
||||||
import { dynamicStylesheet, tiptapStylesheet } from './dynamic_editor.js'
|
import { dynamicStylesheet, tiptapStylesheet } from './dynamic_editor.js'
|
||||||
import { buildVariablesSchema, mergeSchemaProperties } from './dynamic_variables_schema.js'
|
import { buildVariablesSchema, mergeSchemaProperties } from './dynamic_variables_schema.js'
|
||||||
@@ -70,9 +80,29 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
drawField: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
drawFieldType: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
drawCustomField: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
drawOption: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['update'],
|
emits: ['update', 'draw', 'set-draw', 'add-custom-field'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
containerWidth: 1040,
|
containerWidth: 1040,
|
||||||
@@ -81,6 +111,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
renderHtmlForSaveRef: () => ref(false),
|
||||||
|
isDynamic () {
|
||||||
|
return true
|
||||||
|
},
|
||||||
attachmentsIndex () {
|
attachmentsIndex () {
|
||||||
return (this.document.attachments || []).reduce((acc, att) => {
|
return (this.document.attachments || []).reduce((acc, att) => {
|
||||||
acc[att.uuid] = att.url
|
acc[att.uuid] = att.url
|
||||||
@@ -135,6 +169,15 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
mergeSchemaProperties,
|
mergeSchemaProperties,
|
||||||
|
removeArea (area) {
|
||||||
|
this.sectionRefs.forEach((sectionRef) => {
|
||||||
|
const pos = sectionRef.findAreaNodePos(area.uuid)
|
||||||
|
|
||||||
|
if (pos !== -1) {
|
||||||
|
sectionRef.editor.chain().focus().deleteRange({ from: pos, to: pos + 1 }).run()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
setSectionRefs (ref) {
|
setSectionRefs (ref) {
|
||||||
if (ref) {
|
if (ref) {
|
||||||
this.sectionRefs.push(ref)
|
this.sectionRefs.push(ref)
|
||||||
@@ -150,11 +193,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollToArea (area) {
|
scrollToArea (area) {
|
||||||
|
this.sectionRefs.forEach(({ editor }) => editor.commands.setNodeSelection(0))
|
||||||
|
|
||||||
this.sectionRefs.forEach(({ editor }) => {
|
this.sectionRefs.forEach(({ editor }) => {
|
||||||
const el = editor.view.dom.querySelector(`[data-area-uuid="${area.uuid}"]`)
|
const el = editor.view.dom.querySelector(`[data-area-uuid="${area.uuid}"]`)
|
||||||
|
|
||||||
if (el) {
|
if (el) {
|
||||||
editor.chain().focus().setNodeSelection(editor.view.posAtDOM(el, 0)).run()
|
editor.commands.setNodeSelection(editor.view.posAtDOM(el, 0))
|
||||||
|
|
||||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||||
}
|
}
|
||||||
@@ -176,7 +221,7 @@ export default {
|
|||||||
const target = this.bodyDom.getElementById(section.id)
|
const target = this.bodyDom.getElementById(section.id)
|
||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
target.innerHTML = editor.getHTML()
|
target.innerHTML = this.getHtmlForSave(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.document.body = this.bodyDom.body.innerHTML
|
this.document.body = this.bodyDom.body.innerHTML
|
||||||
@@ -200,7 +245,7 @@ export default {
|
|||||||
this.sectionRefs.forEach(({ section, editor }) => {
|
this.sectionRefs.forEach(({ section, editor }) => {
|
||||||
const target = this.bodyDom.getElementById(section.id)
|
const target = this.bodyDom.getElementById(section.id)
|
||||||
|
|
||||||
target.innerHTML = editor.getHTML()
|
target.innerHTML = this.getHtmlForSave(editor)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.document.body = this.bodyDom.body.innerHTML
|
this.document.body = this.bodyDom.body.innerHTML
|
||||||
@@ -209,6 +254,15 @@ export default {
|
|||||||
|
|
||||||
this.$emit('update', this.document)
|
this.$emit('update', this.document)
|
||||||
},
|
},
|
||||||
|
getHtmlForSave (editor) {
|
||||||
|
this.renderHtmlForSaveRef.value = true
|
||||||
|
|
||||||
|
const result = editor.getHTML()
|
||||||
|
|
||||||
|
this.renderHtmlForSaveRef.value = false
|
||||||
|
|
||||||
|
return result
|
||||||
|
},
|
||||||
saveBody () {
|
saveBody () {
|
||||||
clearTimeout(this.saveTimer)
|
clearTimeout(this.saveTimer)
|
||||||
|
|
||||||
@@ -219,6 +273,43 @@ export default {
|
|||||||
body: JSON.stringify({ body: this.bodyDom.body.innerHTML }),
|
body: JSON.stringify({ body: this.bodyDom.body.innerHTML }),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
syncVariablesSchema (existing, parsed, { disable = true } = {}) {
|
||||||
|
for (const key of Object.keys(parsed)) {
|
||||||
|
if (!existing[key]) {
|
||||||
|
existing[key] = parsed[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ tiptapStylesheet.replaceSync(
|
|||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
-webkit-font-variant-ligatures: none;
|
-webkit-font-variant-ligatures: none;
|
||||||
font-variant-ligatures: none;
|
font-variant-ligatures: none;
|
||||||
font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
|
font-feature-settings: "liga" 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror [contenteditable="false"] {
|
.ProseMirror [contenteditable="false"] {
|
||||||
@@ -89,7 +89,7 @@ img.ProseMirror-separator {
|
|||||||
.ProseMirror-focused .ProseMirror-gapcursor {
|
.ProseMirror-focused .ProseMirror-gapcursor {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.variable-highlight {
|
dynamic-variable {
|
||||||
background-color: #fef3c7;
|
background-color: #fef3c7;
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
@@ -533,7 +533,7 @@ function buildDecorations (doc) {
|
|||||||
const from = pos + match.index
|
const from = pos + match.index
|
||||||
const to = from + match[0].length
|
const to = from + match[0].length
|
||||||
|
|
||||||
decorations.push(Decoration.inline(from, to, { class: 'variable-highlight' }))
|
decorations.push(Decoration.inline(from, to, { nodeName: 'dynamic-variable' }))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -584,7 +584,7 @@ const VariableHighlight = Extension.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function buildEditor ({ dynamicAreaProps, attachmentsIndex, onFieldDrop, onFieldDestroy, editorOptions }) {
|
export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlForSaveRef, onFieldDrop, onFieldDestroy, editorOptions }) {
|
||||||
const FieldNode = Node.create({
|
const FieldNode = Node.create({
|
||||||
name: 'fieldNode',
|
name: 'fieldNode',
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -617,11 +617,32 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, onFieldDrop,
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
renderHTML ({ node }) {
|
renderHTML ({ node }) {
|
||||||
return ['dynamic-field', {
|
const attrs = {
|
||||||
uuid: node.attrs.uuid,
|
uuid: node.attrs.uuid,
|
||||||
'area-uuid': node.attrs.areaUuid,
|
'area-uuid': node.attrs.areaUuid,
|
||||||
style: `width: ${node.attrs.width}; height: ${node.attrs.height}; display: ${node.attrs.display}; vertical-align: ${node.attrs.verticalAlign};`
|
style: `width: ${node.attrs.width}; height: ${node.attrs.height}; display: ${node.attrs.display}; vertical-align: ${node.attrs.verticalAlign};`
|
||||||
}]
|
}
|
||||||
|
|
||||||
|
if (!renderHtmlForSaveRef.value) {
|
||||||
|
const fieldArea = dynamicAreaProps.findFieldArea(node.attrs.areaUuid)
|
||||||
|
|
||||||
|
if (fieldArea?.field && fieldArea?.area) {
|
||||||
|
const field = JSON.parse(JSON.stringify(fieldArea.field))
|
||||||
|
const area = JSON.parse(JSON.stringify(fieldArea.area))
|
||||||
|
|
||||||
|
delete field.areas
|
||||||
|
delete field.uuid
|
||||||
|
delete field.submitter_uuid
|
||||||
|
delete area.uuid
|
||||||
|
delete area.attachment_uuid
|
||||||
|
|
||||||
|
attrs['data-field'] = JSON.stringify(field)
|
||||||
|
attrs['data-area'] = JSON.stringify(area)
|
||||||
|
attrs['data-template-id'] = dynamicAreaProps.template.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['dynamic-field', attrs]
|
||||||
},
|
},
|
||||||
addNodeView () {
|
addNodeView () {
|
||||||
return ({ node, getPos, editor }) => {
|
return ({ node, getPos, editor }) => {
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative bg-white select-none mb-4 before:border before:rounded before:top-0 before:bottom-0 before:left-0 before:right-0 before:absolute"
|
class="relative bg-white select-none mb-4 before:border before:rounded before:top-0 before:bottom-0 before:left-0 before:right-0 before:absolute"
|
||||||
|
:class="{ 'cursor-crosshair': isDrawMode && editable }"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="isDrawMode && editable && cursorHighlightCoords"
|
||||||
|
class="absolute pointer-events-none z-10 bg-black"
|
||||||
|
:style="{ width: '1px', height: cursorHighlightCoords.height + 'px', left: cursorHighlightCoords.x + 'px', top: cursorHighlightCoords.y + 'px' }"
|
||||||
|
/>
|
||||||
<div :style="{ zoom: containerWidth / sectionWidthPx }">
|
<div :style="{ zoom: containerWidth / sectionWidthPx }">
|
||||||
<section
|
<section
|
||||||
:id="section.id"
|
:id="section.id"
|
||||||
ref="editorElement"
|
ref="editorElement"
|
||||||
|
dir="auto"
|
||||||
:class="section.classList.value"
|
:class="section.classList.value"
|
||||||
:style="section.style.cssText"
|
:style="section.style.cssText"
|
||||||
/>
|
/>
|
||||||
@@ -44,7 +51,10 @@
|
|||||||
:field="contextMenuField"
|
:field="contextMenuField"
|
||||||
:with-copy-to-all-pages="false"
|
:with-copy-to-all-pages="false"
|
||||||
@close="closeContextMenu"
|
@close="closeContextMenu"
|
||||||
|
@copy="onContextMenuCopy"
|
||||||
@delete="onContextMenuDelete"
|
@delete="onContextMenuDelete"
|
||||||
|
@add-custom-field="$emit('add-custom-field', $event)"
|
||||||
|
@set-draw="$emit('set-draw', $event)"
|
||||||
@save="save"
|
@save="save"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@@ -53,6 +63,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { shallowRef } from 'vue'
|
import { shallowRef } from 'vue'
|
||||||
|
import { DOMSerializer, Fragment } from '@tiptap/pm/model'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import FieldContextMenu from './field_context_menu.vue'
|
import FieldContextMenu from './field_context_menu.vue'
|
||||||
import AreaTitle from './area_title.vue'
|
import AreaTitle from './area_title.vue'
|
||||||
@@ -72,6 +83,11 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
sectionRefs: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
editable: {
|
editable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -100,19 +116,45 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
drawField: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
drawFieldType: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
drawCustomField: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
renderHtmlForSaveRef: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
drawOption: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
attachmentUuid: {
|
attachmentUuid: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['update'],
|
emits: ['update', 'draw', 'set-draw', 'add-custom-field'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isAreaDrag: false,
|
isAreaDrag: false,
|
||||||
areaToolbarCoords: null,
|
areaToolbarCoords: null,
|
||||||
dynamicMenuCoords: null,
|
dynamicMenuCoords: null,
|
||||||
contextMenu: null
|
contextMenu: null,
|
||||||
|
cursorHighlightCoords: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -140,6 +182,7 @@ export default {
|
|||||||
verification: { width: '150px', height: '80px' },
|
verification: { width: '150px', height: '80px' },
|
||||||
image: { width: '200px', height: '100px' },
|
image: { width: '200px', height: '100px' },
|
||||||
date: { width: '100px', height: this.defaultHeight },
|
date: { width: '100px', height: this.defaultHeight },
|
||||||
|
datenow: { width: '100px', height: this.defaultHeight },
|
||||||
text: { width: '120px', height: this.defaultHeight },
|
text: { width: '120px', height: this.defaultHeight },
|
||||||
cells: { width: '120px', height: this.defaultHeight },
|
cells: { width: '120px', height: this.defaultHeight },
|
||||||
file: { width: '120px', height: this.defaultHeight },
|
file: { width: '120px', height: this.defaultHeight },
|
||||||
@@ -164,6 +207,9 @@ export default {
|
|||||||
isDraggingField () {
|
isDraggingField () {
|
||||||
return !!(this.fieldsDragFieldRef?.value || this.customDragFieldRef?.value || this.dragField)
|
return !!(this.fieldsDragFieldRef?.value || this.customDragFieldRef?.value || this.dragField)
|
||||||
},
|
},
|
||||||
|
isDrawMode () {
|
||||||
|
return !!(this.drawField || this.drawCustomField || this.drawFieldType)
|
||||||
|
},
|
||||||
selectedArea () {
|
selectedArea () {
|
||||||
return this.selectedAreasRef.value[0]
|
return this.selectedAreasRef.value[0]
|
||||||
},
|
},
|
||||||
@@ -192,15 +238,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.initEditor()
|
|
||||||
},
|
|
||||||
beforeUnmount () {
|
|
||||||
if (this.editor) {
|
|
||||||
this.editor.destroy()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async initEditor () {
|
|
||||||
this.editorRef.value = buildEditor({
|
this.editorRef.value = buildEditor({
|
||||||
dynamicAreaProps: {
|
dynamicAreaProps: {
|
||||||
template: this.template,
|
template: this.template,
|
||||||
@@ -216,6 +253,7 @@ export default {
|
|||||||
attachmentsIndex: this.attachmentsIndex,
|
attachmentsIndex: this.attachmentsIndex,
|
||||||
onFieldDrop: this.onFieldDrop,
|
onFieldDrop: this.onFieldDrop,
|
||||||
onFieldDestroy: this.onFieldDestroy,
|
onFieldDestroy: this.onFieldDestroy,
|
||||||
|
renderHtmlForSaveRef: this.renderHtmlForSaveRef,
|
||||||
editorOptions: {
|
editorOptions: {
|
||||||
element: this.$refs.editorElement,
|
element: this.$refs.editorElement,
|
||||||
editable: this.editable,
|
editable: this.editable,
|
||||||
@@ -225,12 +263,96 @@ export default {
|
|||||||
onBlur: () => { this.dynamicMenuCoords = null }
|
onBlur: () => { this.dynamicMenuCoords = null }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.editor.view.dom.addEventListener('paste', this.onEditorPaste, true)
|
||||||
|
this.editor.view.dom.addEventListener('pointerdown', this.onEditorPointerDown, true)
|
||||||
|
this.editor.view.dom.addEventListener('mousemove', this.onEditorMouseMove)
|
||||||
|
this.editor.view.dom.addEventListener('mouseleave', this.onEditorMouseLeave)
|
||||||
|
this.editor.view.dom.addEventListener('keydown', this.onEditorKeyDown)
|
||||||
},
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.view.dom.removeEventListener('paste', this.onEditorPaste, true)
|
||||||
|
this.editor.view.dom.removeEventListener('pointerdown', this.onEditorPointerDown, true)
|
||||||
|
this.editor.view.dom.removeEventListener('mousemove', this.onEditorMouseMove)
|
||||||
|
this.editor.view.dom.removeEventListener('mouseleave', this.onEditorMouseLeave)
|
||||||
|
this.editor.view.dom.removeEventListener('keydown', this.onEditorKeyDown)
|
||||||
|
this.editor.destroy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
findAreaNodePos (areaUuid) {
|
findAreaNodePos (areaUuid) {
|
||||||
const el = this.editor.view.dom.querySelector(`[data-area-uuid="${areaUuid}"]`)
|
const el = this.editor.view.dom.querySelector(`[data-area-uuid="${areaUuid}"]`)
|
||||||
|
|
||||||
return this.editor.view.posAtDOM(el, 0)
|
return this.editor.view.posAtDOM(el, 0)
|
||||||
},
|
},
|
||||||
|
getFieldInsertIndex (pos) {
|
||||||
|
const view = this.editor.view
|
||||||
|
const fields = this.template.fields || []
|
||||||
|
|
||||||
|
if (!fields.length) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousField = null
|
||||||
|
|
||||||
|
view.state.doc.nodesBetween(0, pos, (node, nodePos) => {
|
||||||
|
if (node.type.name !== 'fieldNode') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodePos + node.nodeSize <= pos) {
|
||||||
|
previousField = this.fieldAreaIndex[node.attrs.areaUuid]?.field || previousField
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!previousField) {
|
||||||
|
previousField = this.getPreviousSectionField()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!previousField) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousFieldIndex = fields.indexOf(previousField)
|
||||||
|
|
||||||
|
return previousFieldIndex === -1 ? fields.length : previousFieldIndex + 1
|
||||||
|
},
|
||||||
|
insertFieldInTemplate (field, index) {
|
||||||
|
const currentFieldIndex = this.template.fields.indexOf(field)
|
||||||
|
|
||||||
|
if (currentFieldIndex !== -1) {
|
||||||
|
return currentFieldIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
this.template.fields.splice(index, 0, field)
|
||||||
|
|
||||||
|
return index
|
||||||
|
},
|
||||||
|
getLastFieldInSection (sectionRef) {
|
||||||
|
let lastField = null
|
||||||
|
|
||||||
|
sectionRef.editor.state.doc.descendants((node) => {
|
||||||
|
if (node.type.name === 'fieldNode') {
|
||||||
|
lastField = this.fieldAreaIndex[node.attrs.areaUuid]?.field || lastField
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return lastField
|
||||||
|
},
|
||||||
|
getPreviousSectionField () {
|
||||||
|
const sectionIndex = this.sectionRefs.indexOf(this)
|
||||||
|
|
||||||
|
for (let index = sectionIndex - 1; index >= 0; index -= 1) {
|
||||||
|
const previousField = this.getLastFieldInSection(this.sectionRefs[index])
|
||||||
|
|
||||||
|
if (previousField) {
|
||||||
|
return previousField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
},
|
||||||
removeArea (area) {
|
removeArea (area) {
|
||||||
const { field } = this.fieldAreaIndex[area.uuid]
|
const { field } = this.fieldAreaIndex[area.uuid]
|
||||||
const areaIndex = field.areas.indexOf(area)
|
const areaIndex = field.areas.indexOf(area)
|
||||||
@@ -361,6 +483,220 @@ export default {
|
|||||||
closeContextMenu () {
|
closeContextMenu () {
|
||||||
this.contextMenu = null
|
this.contextMenu = null
|
||||||
},
|
},
|
||||||
|
onEditorMouseLeave () {
|
||||||
|
this.cursorHighlightCoords = null
|
||||||
|
},
|
||||||
|
onEditorKeyDown (event) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.editor.chain().setNodeSelection(0).blur().run()
|
||||||
|
this.deselectArea()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEditorMouseMove (event) {
|
||||||
|
if (!this.isDrawMode || !this.editable) {
|
||||||
|
this.cursorHighlightCoords = null
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = this.editor?.view
|
||||||
|
|
||||||
|
if (!view) return
|
||||||
|
|
||||||
|
const pos = view.posAtCoords({ left: event.clientX, top: event.clientY })
|
||||||
|
|
||||||
|
if (!pos) {
|
||||||
|
this.cursorHighlightCoords = null
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const coords = view.coordsAtPos(pos.pos)
|
||||||
|
const outerRect = this.$el.getBoundingClientRect()
|
||||||
|
const lineHeight = coords.bottom - coords.top
|
||||||
|
|
||||||
|
this.cursorHighlightCoords = {
|
||||||
|
x: coords.left - outerRect.left,
|
||||||
|
y: coords.top - outerRect.top,
|
||||||
|
height: lineHeight
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFieldNode (areaUuid) {
|
||||||
|
const pos = this.findAreaNodePos(areaUuid)
|
||||||
|
|
||||||
|
return this.editor.state.doc.nodeAt(pos)
|
||||||
|
},
|
||||||
|
serializeFieldNodeHtml (areaUuid) {
|
||||||
|
const node = this.getFieldNode(areaUuid)
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const serializer = DOMSerializer.fromSchema(this.editor.state.schema)
|
||||||
|
const container = document.createElement('div')
|
||||||
|
|
||||||
|
container.appendChild(serializer.serializeFragment(Fragment.from(node)))
|
||||||
|
|
||||||
|
return container.innerHTML
|
||||||
|
},
|
||||||
|
async writeHtmlToClipboard ({ html, text }, clipboardData = null) {
|
||||||
|
if (clipboardData) {
|
||||||
|
clipboardData.setData('text/html', html)
|
||||||
|
clipboardData.setData('text/plain', text || html)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigator.clipboard?.write && window.ClipboardItem) {
|
||||||
|
await navigator.clipboard.write([
|
||||||
|
new ClipboardItem({
|
||||||
|
'text/html': new Blob([html], { type: 'text/html' }),
|
||||||
|
'text/plain': new Blob([text || html], { type: 'text/plain' })
|
||||||
|
})
|
||||||
|
])
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigator.clipboard?.writeText) {
|
||||||
|
await navigator.clipboard.writeText(text || html)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
async copyFieldAreaToClipboard (areaUuid, clipboardData = null) {
|
||||||
|
const html = this.serializeFieldNodeHtml(areaUuid)
|
||||||
|
|
||||||
|
if (!html) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.writeHtmlToClipboard({ html, text: html }, clipboardData)
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to copy dynamic field:', e)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onContextMenuCopy () {
|
||||||
|
await this.copyFieldAreaToClipboard(this.contextMenu.areaUuid)
|
||||||
|
|
||||||
|
this.closeContextMenu()
|
||||||
|
},
|
||||||
|
buildCopiedField (payload) {
|
||||||
|
const field = JSON.parse(JSON.stringify(payload.field))
|
||||||
|
const area = {
|
||||||
|
...JSON.parse(JSON.stringify(payload.area)),
|
||||||
|
uuid: v4(),
|
||||||
|
attachment_uuid: this.attachmentUuid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.templateId !== this.template.id) {
|
||||||
|
delete field.conditions
|
||||||
|
|
||||||
|
if (field.preferences) {
|
||||||
|
delete field.preferences.formula
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newField = {
|
||||||
|
...field,
|
||||||
|
uuid: v4(),
|
||||||
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
|
areas: [area]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['radio', 'multiple'].includes(field.type) && field.options?.length) {
|
||||||
|
const oldOptionUuid = area.option_uuid
|
||||||
|
const optionsMap = {}
|
||||||
|
|
||||||
|
newField.options = field.options.map((opt) => {
|
||||||
|
const newUuid = v4()
|
||||||
|
optionsMap[opt.uuid] = newUuid
|
||||||
|
|
||||||
|
return { ...opt, uuid: newUuid }
|
||||||
|
})
|
||||||
|
|
||||||
|
area.option_uuid = optionsMap[oldOptionUuid] || newField.options[0]?.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
return { field: newField, area }
|
||||||
|
},
|
||||||
|
onEditorPaste (event) {
|
||||||
|
const clipboardData = event.clipboardData
|
||||||
|
|
||||||
|
if (!clipboardData) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = clipboardData.getData('text/html')
|
||||||
|
const text = clipboardData.getData('text/plain')
|
||||||
|
const clipboardHtml = html || (text.includes('<dynamic-field') ? text : '')
|
||||||
|
|
||||||
|
if (!clipboardHtml || !clipboardHtml.includes('data-field=')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement('div')
|
||||||
|
|
||||||
|
container.innerHTML = clipboardHtml
|
||||||
|
|
||||||
|
const fieldNodes = [...container.querySelectorAll('dynamic-field[data-field][data-area]')]
|
||||||
|
|
||||||
|
if (!fieldNodes.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const { selection } = this.editor.state
|
||||||
|
const { from, to } = selection
|
||||||
|
let insertIndex = this.getFieldInsertIndex(selection.node?.type.name === 'fieldNode' ? to : from)
|
||||||
|
|
||||||
|
let lastArea = null
|
||||||
|
|
||||||
|
fieldNodes.forEach((fieldNode) => {
|
||||||
|
const fieldValue = fieldNode.dataset.field
|
||||||
|
const areaValue = fieldNode.dataset.area
|
||||||
|
const templateId = fieldNode.dataset.templateId
|
||||||
|
|
||||||
|
if (!fieldValue || !areaValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { field, area } = this.buildCopiedField({
|
||||||
|
field: JSON.parse(fieldValue),
|
||||||
|
area: JSON.parse(areaValue),
|
||||||
|
templateId: Number(templateId)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.insertFieldInTemplate(field, insertIndex)
|
||||||
|
insertIndex += 1
|
||||||
|
|
||||||
|
fieldNode.setAttribute('uuid', field.uuid)
|
||||||
|
fieldNode.setAttribute('area-uuid', area.uuid)
|
||||||
|
fieldNode.removeAttribute('data-field')
|
||||||
|
fieldNode.removeAttribute('data-area')
|
||||||
|
fieldNode.removeAttribute('data-template-id')
|
||||||
|
|
||||||
|
lastArea = area
|
||||||
|
})
|
||||||
|
|
||||||
|
this.editor.chain().focus().insertContentAt({ from, to }, container.innerHTML).run()
|
||||||
|
|
||||||
|
if (lastArea) {
|
||||||
|
this.editor.commands.setNodeSelection(this.findAreaNodePos(lastArea.uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeContextMenu()
|
||||||
|
this.save()
|
||||||
|
},
|
||||||
onContextMenuDelete () {
|
onContextMenuDelete () {
|
||||||
const menu = this.contextMenu
|
const menu = this.contextMenu
|
||||||
const fieldArea = this.fieldAreaIndex[menu.areaUuid]
|
const fieldArea = this.fieldAreaIndex[menu.areaUuid]
|
||||||
@@ -398,44 +734,100 @@ export default {
|
|||||||
|
|
||||||
if (!pos) return false
|
if (!pos) return false
|
||||||
|
|
||||||
const fieldType = draggedField.type || 'text'
|
this.insertFieldAtRange({
|
||||||
const dims = this.defaultSizes[fieldType] || this.defaultSizes.text
|
sourceField: draggedField,
|
||||||
const areaUuid = v4()
|
existingField: this.fieldsDragFieldRef?.value,
|
||||||
|
from: pos.pos
|
||||||
const existingField = this.fieldsDragFieldRef?.value
|
|
||||||
|
|
||||||
if (existingField) {
|
|
||||||
if (!this.template.fields.includes(existingField)) {
|
|
||||||
this.template.fields.push(existingField)
|
|
||||||
}
|
|
||||||
|
|
||||||
existingField.areas = existingField.areas || []
|
|
||||||
existingField.areas.push({ uuid: areaUuid, attachment_uuid: this.attachmentUuid })
|
|
||||||
|
|
||||||
const nodeType = view.state.schema.nodes.fieldNode
|
|
||||||
const fieldNode = nodeType.create({
|
|
||||||
uuid: existingField.uuid,
|
|
||||||
areaUuid,
|
|
||||||
width: dims.width,
|
|
||||||
height: dims.height
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const tr = view.state.tr.insert(pos.pos, fieldNode)
|
this.fieldsDragFieldRef.value = null
|
||||||
|
this.customDragFieldRef.value = null
|
||||||
|
|
||||||
view.dispatch(tr)
|
return true
|
||||||
} else {
|
},
|
||||||
|
onEditorPointerDown (event) {
|
||||||
|
if (!this.isDrawMode || !this.editable || this.isDraggingField) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.button === 2) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = this.editor?.view
|
||||||
|
|
||||||
|
if (!view) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = view.state.selection
|
||||||
|
const isTextRangeSelection = !selection.empty && !selection.node
|
||||||
|
|
||||||
|
let from = selection.from
|
||||||
|
let to = selection.to
|
||||||
|
|
||||||
|
if (!isTextRangeSelection) {
|
||||||
|
const pos = view.posAtCoords({ left: event.clientX, top: event.clientY })
|
||||||
|
|
||||||
|
if (!pos) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from = pos.pos
|
||||||
|
to = pos.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceField = this.drawField || this.drawCustomField || { type: this.drawFieldType }
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
if (this.drawOption && this.drawField) {
|
||||||
|
const areaWithoutOption = this.drawField.areas?.find((a) => !a.option_uuid)
|
||||||
|
|
||||||
|
if (areaWithoutOption && !this.drawField.areas.find((a) => a.option_uuid === this.drawField.options[0].uuid)) {
|
||||||
|
areaWithoutOption.option_uuid = this.drawField.options[0].uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const inserted = this.insertFieldAtRange({
|
||||||
|
sourceField,
|
||||||
|
existingField: this.drawField,
|
||||||
|
optionUuid: this.drawOption?.uuid,
|
||||||
|
from,
|
||||||
|
to
|
||||||
|
})
|
||||||
|
|
||||||
|
if (inserted) {
|
||||||
|
this.$emit('draw', inserted)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildFieldArea ({ optionUuid = null } = {}) {
|
||||||
|
const area = {
|
||||||
|
uuid: v4(),
|
||||||
|
attachment_uuid: this.attachmentUuid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionUuid) {
|
||||||
|
area.option_uuid = optionUuid
|
||||||
|
}
|
||||||
|
|
||||||
|
return area
|
||||||
|
},
|
||||||
|
buildNewField (sourceField, area) {
|
||||||
|
const fieldType = sourceField?.type || 'text'
|
||||||
const newField = {
|
const newField = {
|
||||||
name: draggedField.name || '',
|
name: sourceField?.name || '',
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
required: fieldType !== 'checkbox',
|
required: fieldType !== 'checkbox',
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
type: fieldType,
|
type: fieldType,
|
||||||
areas: [{ uuid: areaUuid, attachment_uuid: this.attachmentUuid }]
|
areas: [area]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['select', 'multiple', 'radio'].includes(fieldType)) {
|
if (['select', 'multiple', 'radio'].includes(fieldType)) {
|
||||||
if (draggedField.options?.length) {
|
if (sourceField?.options?.length) {
|
||||||
newField.options = draggedField.options.map((opt) => ({
|
newField.options = sourceField.options.map((opt) => ({
|
||||||
value: typeof opt === 'string' ? opt : opt.value,
|
value: typeof opt === 'string' ? opt : opt.value,
|
||||||
uuid: v4()
|
uuid: v4()
|
||||||
}))
|
}))
|
||||||
@@ -458,29 +850,51 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.template.fields.push(newField)
|
return newField
|
||||||
|
},
|
||||||
|
insertFieldAtRange ({ sourceField, existingField = null, optionUuid = null, from, to = from }) {
|
||||||
|
if (!sourceField) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldType = sourceField.type || existingField?.type || 'text'
|
||||||
|
const dims = this.defaultSizes[fieldType]
|
||||||
|
const area = this.buildFieldArea({ optionUuid })
|
||||||
|
const insertIndex = this.getFieldInsertIndex(from)
|
||||||
|
|
||||||
|
let field = existingField
|
||||||
|
const view = this.editor.view
|
||||||
|
|
||||||
|
if (field) {
|
||||||
|
if (!this.template.fields.includes(field)) {
|
||||||
|
this.insertFieldInTemplate(field, insertIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
field.areas = field.areas || []
|
||||||
|
field.areas.push(area)
|
||||||
|
} else {
|
||||||
|
field = this.buildNewField(sourceField, area)
|
||||||
|
this.insertFieldInTemplate(field, insertIndex)
|
||||||
|
}
|
||||||
|
|
||||||
const nodeType = view.state.schema.nodes.fieldNode
|
const nodeType = view.state.schema.nodes.fieldNode
|
||||||
const fieldNode = nodeType.create({
|
const fieldNode = nodeType.create({
|
||||||
uuid: newField.uuid,
|
uuid: field.uuid,
|
||||||
areaUuid,
|
areaUuid: area.uuid,
|
||||||
width: dims.width,
|
width: dims.width,
|
||||||
height: dims.height
|
height: dims.height
|
||||||
})
|
})
|
||||||
|
|
||||||
const tr = view.state.tr.insert(pos.pos, fieldNode)
|
const tr = from !== to
|
||||||
|
? view.state.tr.replaceWith(from, to, fieldNode)
|
||||||
|
: view.state.tr.insert(from, fieldNode)
|
||||||
|
|
||||||
view.dispatch(tr)
|
view.dispatch(tr)
|
||||||
}
|
|
||||||
|
|
||||||
this.fieldsDragFieldRef.value = null
|
|
||||||
this.customDragFieldRef.value = null
|
|
||||||
|
|
||||||
this.editor.chain().focus().setNodeSelection(pos.pos).run()
|
|
||||||
|
|
||||||
|
this.editor.chain().focus().setNodeSelection(from).run()
|
||||||
this.save()
|
this.save()
|
||||||
|
|
||||||
return true
|
return { area, field, pos: from }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,28 +22,30 @@
|
|||||||
class="border-base-300"
|
class="border-base-300"
|
||||||
>
|
>
|
||||||
<label class="peer flex items-center py-1.5 cursor-pointer select-none">
|
<label class="peer flex items-center py-1.5 cursor-pointer select-none">
|
||||||
|
<span class="w-5 flex justify-center items-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="hidden peer"
|
class="hidden peer"
|
||||||
checked
|
checked
|
||||||
>
|
>
|
||||||
<IconChevronDown
|
<IconChevronDown
|
||||||
class="hidden peer-checked:block"
|
class="hidden peer-checked:block ml-0.5"
|
||||||
:width="14"
|
:width="14"
|
||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
<IconChevronRight
|
<IconChevronRight
|
||||||
class="block peer-checked:hidden"
|
class="block peer-checked:hidden ml-0.5"
|
||||||
:width="14"
|
:width="14"
|
||||||
:stroke-width="1.6"
|
:stroke-width="1.6"
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
<span class="ml-1">{{ key }}</span>
|
<span class="ml-1">{{ key }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="node.type === 'array'"
|
v-if="node.type === 'array'"
|
||||||
class="text-xs bg-base-200 rounded px-1 ml-1"
|
class="text-xs bg-base-200 rounded px-1 ml-1"
|
||||||
>{{ t('list') }}</span>
|
>{{ t('list') }}</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="hidden peer-has-[:checked]:block pl-3.5">
|
<div class="hidden peer-has-[:checked]:block pl-5">
|
||||||
<template
|
<template
|
||||||
v-for="[varNode, varPath] in nestedVariables(node, key)"
|
v-for="[varNode, varPath] in nestedVariables(node, key)"
|
||||||
:key="varPath"
|
:key="varPath"
|
||||||
@@ -101,7 +103,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isGroup (node) {
|
isGroup (node) {
|
||||||
return (node.type === 'object' && node.properties) || (node.type === 'array' && node.items?.properties)
|
return (node.type === 'object' && node.properties) || (node.type === 'array' && node.items?.type === 'object' && node.items?.properties)
|
||||||
},
|
},
|
||||||
nestedVariables (node, groupKey) {
|
nestedVariables (node, groupKey) {
|
||||||
const properties = node.type === 'array' ? node.items?.properties : node.properties
|
const properties = node.type === 'array' ? node.items?.properties : node.properties
|
||||||
|
|||||||
@@ -353,7 +353,26 @@ function extractConditionVariables (node, acc = []) {
|
|||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IRREGULAR_PLURALS = {
|
||||||
|
people: 'person',
|
||||||
|
men: 'man',
|
||||||
|
women: 'woman',
|
||||||
|
children: 'child',
|
||||||
|
teeth: 'tooth',
|
||||||
|
feet: 'foot',
|
||||||
|
mice: 'mouse',
|
||||||
|
geese: 'goose',
|
||||||
|
oxen: 'ox',
|
||||||
|
criteria: 'criterion',
|
||||||
|
phenomena: 'phenomenon',
|
||||||
|
alumni: 'alumnus',
|
||||||
|
data: 'datum',
|
||||||
|
media: 'medium'
|
||||||
|
}
|
||||||
|
|
||||||
function singularize (word) {
|
function singularize (word) {
|
||||||
|
if (IRREGULAR_PLURALS[word]) return IRREGULAR_PLURALS[word]
|
||||||
|
|
||||||
if (word.endsWith('ies')) return word.slice(0, -3) + 'y'
|
if (word.endsWith('ies')) return word.slice(0, -3) + 'y'
|
||||||
if (word.endsWith('ches') || word.endsWith('shes')) return word.slice(0, -2)
|
if (word.endsWith('ches') || word.endsWith('shes')) return word.slice(0, -2)
|
||||||
if (word.endsWith('ses') || word.endsWith('xes') || word.endsWith('zes')) return word.slice(0, -2)
|
if (word.endsWith('ses') || word.endsWith('xes') || word.endsWith('zes')) return word.slice(0, -2)
|
||||||
@@ -513,6 +532,12 @@ function processOperators (operators, propertiesHash = {}, parentProperties = {}
|
|||||||
|
|
||||||
assignNestedSchema(propertiesHash, parentProperties, op.variableName, { type: 'array', items: itemProperties })
|
assignNestedSchema(propertiesHash, parentProperties, op.variableName, { type: 'array', items: itemProperties })
|
||||||
processOperators(op.children, propertiesHash, { ...parentProperties, [singularKey]: itemProperties })
|
processOperators(op.children, propertiesHash, { ...parentProperties, [singularKey]: itemProperties })
|
||||||
|
|
||||||
|
if (itemProperties.type === 'object' && itemProperties.properties && Object.keys(itemProperties.properties).length === 0) {
|
||||||
|
delete itemProperties.properties
|
||||||
|
itemProperties.type = 'string'
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-draw-custom-field', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter', 'set-drag-placeholder', 'select-submitter', 'rebuild-variables-schema'],
|
emits: ['add-field', 'set-draw', 'set-draw-type', 'set-draw-custom-field', 'set-drag', 'drag-end', 'scroll-to-area', 'change-submitter', 'set-drag-placeholder', 'select-submitter', 'rebuild-variables-schema', 'remove-field', 'remove-submitter'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
fieldPagesLoaded: null,
|
fieldPagesLoaded: null,
|
||||||
@@ -805,6 +805,8 @@ export default {
|
|||||||
this.$emit('change-submitter', this.submitters[0])
|
this.$emit('change-submitter', this.submitters[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$emit('remove-submitter', submitter)
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
removeField (field, save = true) {
|
removeField (field, save = true) {
|
||||||
@@ -830,6 +832,8 @@ export default {
|
|||||||
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(area), 1)
|
this.selectedAreasRef.value.splice(this.selectedAreasRef.value.indexOf(area), 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.$emit('remove-field', field)
|
||||||
|
|
||||||
if (save) {
|
if (save) {
|
||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,13 +274,16 @@ export default {
|
|||||||
makeDynamic () {
|
makeDynamic () {
|
||||||
this.isMakeDynamicLoading = true
|
this.isMakeDynamicLoading = true
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
this.baseFetch(`/templates/${this.template.id}/dynamic_documents`, {
|
this.baseFetch(`/templates/${this.template.id}/dynamic_documents`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ uuid: this.document.uuid }),
|
body: JSON.stringify({ uuid: this.document.uuid }),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
}).then(async (resp) => {
|
}),
|
||||||
|
import(/* webpackChunkName: "dynamic-editor" */ './dynamic_document')
|
||||||
|
]).then(async ([resp, _]) => {
|
||||||
const dynamicDocument = await resp.json()
|
const dynamicDocument = await resp.json()
|
||||||
|
|
||||||
this.template.schema.find((item) => item.attachment_uuid === dynamicDocument.uuid).dynamic = true
|
this.template.schema.find((item) => item.attachment_uuid === dynamicDocument.uuid).dynamic = true
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class Submission < ApplicationRecord
|
|||||||
|
|
||||||
dynamic_count = template_schema&.count { |e| e['dynamic'] }.to_i
|
dynamic_count = template_schema&.count { |e| e['dynamic'] }.to_i
|
||||||
|
|
||||||
if template.variables_schema.blank?
|
if variables_schema.blank?
|
||||||
if dynamic_count > 0
|
if dynamic_count > 0
|
||||||
if dynamic_count == template_schema.size
|
if dynamic_count == template_schema.size
|
||||||
template_schema_dynamic_document_attachments
|
template_schema_dynamic_document_attachments
|
||||||
|
|||||||
@@ -131,6 +131,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if !Docuseal.multitenant? || can?(:manage, :disable_decline) %>
|
||||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DECLINE_KEY) %>
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DECLINE_KEY) %>
|
||||||
<% if can?(:manage, account_config) %>
|
<% if can?(:manage, account_config) %>
|
||||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
@@ -148,6 +149,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORM_PREFILL_SIGNATURE_KEY) %>
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORM_PREFILL_SIGNATURE_KEY) %>
|
||||||
<% if can?(:manage, account_config) %>
|
<% if can?(:manage, account_config) %>
|
||||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
|
|||||||
@@ -8,18 +8,20 @@
|
|||||||
<% variables_form = render 'variables_form', schema: @template.variables_schema if @template.variables_schema.present? && @template.variables_schema.any? { |_, v| !v['disabled'] } %>
|
<% variables_form = render 'variables_form', schema: @template.variables_schema if @template.variables_schema.present? && @template.variables_schema.any? { |_, v| !v['disabled'] } %>
|
||||||
<%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %>
|
<%= render 'shared/turbo_modal_large', title: params[:selfsign] ? t('add_recipients') : t('add_new_recipients') do %>
|
||||||
<% options = [only_detailed ? nil : [t('via_email'), 'email'], only_detailed ? nil : [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], with_list ? [t('upload_list'), 'list'] : nil].compact %>
|
<% options = [only_detailed ? nil : [t('via_email'), 'email'], only_detailed ? nil : [t('via_phone'), 'phone'], [t('detailed'), 'detailed'], with_list ? [t('upload_list'), 'list'] : nil].compact %>
|
||||||
|
<% if options.size > 1 %>
|
||||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 mt-4 block">
|
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="relative text-center px-2 mt-4 block">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<% options.each_with_index do |(label, value), index| %>
|
<% options.each_with_index do |(label, value), index| %>
|
||||||
<%= content_tag(value == 'list' ? 'span' : 'toggle-cookies', data: { value:, key: 'add_recipients_tab' }) do %>
|
<%= content_tag(value == 'list' ? 'span' : 'toggle-cookies', data: { value:, key: 'add_recipients_tab' }) do %>
|
||||||
<%= radio_button_tag 'option', value, value == (only_detailed ? 'detailed' : default_tab), class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
<%= radio_button_tag 'option', value, value == (only_detailed ? 'detailed' : default_tab), class: 'peer hidden', data: { action: 'change:toggle-visible#trigger' } %>
|
||||||
<label for="option_<%= value %>" class="block bg-base-200 md:min-w-[112px] text-sm font-semibold whitespace-nowrap py-1.5 px-4 peer-checked:bg-base-300 <%= 'hidden sm:inline-block' if value == 'list' %> <%= 'rounded-l-3xl' if index.zero? %> <%= 'rounded-r-3xl sm:rounded-r-none' if value == 'detailed' %> <%= 'rounded-r-3xl' if index == options.size - 1 %>">
|
<label for="option_<%= value %>" class="block bg-base-200 md:min-w-[112px] text-sm font-semibold whitespace-nowrap py-1.5 px-4 peer-checked:bg-base-300 <%= 'hidden sm:inline-block' if value == 'list' %> <%= 'rounded-l-3xl' if index.zero? %> <%= 'rounded-r-3xl sm:rounded-r-none' if value == 'detailed' && index != options.size - 1 %> <%= 'rounded-r-3xl' if index == options.size - 1 %>">
|
||||||
<%= label %>
|
<%= label %>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</toggle-visible>
|
</toggle-visible>
|
||||||
|
<% end %>
|
||||||
<div class="px-5 mb-5 mt-4">
|
<div class="px-5 mb-5 mt-4">
|
||||||
<% unless only_detailed %>
|
<% unless only_detailed %>
|
||||||
<div id="email" class="<%= 'hidden' if default_tab != 'email' %>">
|
<div id="email" class="<%= 'hidden' if default_tab != 'email' %>">
|
||||||
|
|||||||
@@ -85,9 +85,9 @@
|
|||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<h2 id="log" class="text-3xl font-bold"><%= t('events_log') %></h2>
|
<h2 id="log" class="text-3xl font-bold"><%= t('events_log') %></h2>
|
||||||
<div class="tabs border-b mt-4">
|
<div class="tabs border-b mt-4">
|
||||||
<%= link_to t('all'), url_for(params: request.query_parameters.except('status')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status].blank? ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
<%= link_to t('all'), url_for(params: request.query_parameters.except('status', 'page')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status].blank? ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
||||||
<%= link_to t('succeeded'), url_for(params: request.query_parameters.merge('status' => 'success')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'success' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
<%= link_to t('succeeded'), url_for(params: request.query_parameters.except('page').merge('status' => 'success')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'success' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
||||||
<%= link_to t('failed'), url_for(params: request.query_parameters.merge('status' => 'error')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'error' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
<%= link_to t('failed'), url_for(params: request.query_parameters.except('page').merge('status' => 'error')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'error' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
|
||||||
</div>
|
</div>
|
||||||
<% if @webhook_events.present? %>
|
<% if @webhook_events.present? %>
|
||||||
<div class="divide-y divide-base-300 rounded-lg">
|
<div class="divide-y divide-base-300 rounded-lg">
|
||||||
|
|||||||
@@ -883,6 +883,13 @@ en: &en
|
|||||||
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} email address is awaiting confirmation. Follow the link in the email to confirm."
|
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} email address is awaiting confirmation. Follow the link in the email to confirm."
|
||||||
please_confirm_your_email_address_using_the_link_below_: 'Please confirm your email address using the link below:'
|
please_confirm_your_email_address_using_the_link_below_: 'Please confirm your email address using the link below:'
|
||||||
confirm_email: Confirm email
|
confirm_email: Confirm email
|
||||||
|
please_verify_your_email_address_to_continue: Please verify your email address to continue.
|
||||||
|
verification_code_sent_click_link_or_enter_code: A verification code has been sent to your email. Click the email link or enter the one time code to confirm your email.
|
||||||
|
use_otp_code_to_verify_email_or_click_link_below_html: 'Use <b>%{code}</b> code to verify your email or click the link below:'
|
||||||
|
verify_your_email: Verify your email
|
||||||
|
your_email_has_been_confirmed: Your email has been confirmed.
|
||||||
|
invalid_or_expired_verification_code: Invalid or expired verification code.
|
||||||
|
didnt_receive_an_email: Did not receive an email?
|
||||||
unconfirmed: Unconfirmed
|
unconfirmed: Unconfirmed
|
||||||
you_requested_to_reset_your_password_use_the_link_below_to_continue: You requested to reset your password. Use the link below to continue
|
you_requested_to_reset_your_password_use_the_link_below_to_continue: You requested to reset your password. Use the link below to continue
|
||||||
if_you_didnt_request_this_you_can_ignore_this_email: "If you didn't request this, please ignore this email."
|
if_you_didnt_request_this_you_can_ignore_this_email: "If you didn't request this, please ignore this email."
|
||||||
@@ -1900,6 +1907,13 @@ es: &es
|
|||||||
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} está pendiente de confirmación. Sigue el enlace en el correo para confirmarla."
|
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} está pendiente de confirmación. Sigue el enlace en el correo para confirmarla."
|
||||||
please_confirm_your_email_address_using_the_link_below_: 'Por favor, confirma tu dirección de correo electrónico utilizando el enlace a continuación:'
|
please_confirm_your_email_address_using_the_link_below_: 'Por favor, confirma tu dirección de correo electrónico utilizando el enlace a continuación:'
|
||||||
confirm_email: Confirmar correo
|
confirm_email: Confirmar correo
|
||||||
|
please_verify_your_email_address_to_continue: Por favor, verifica tu dirección de correo electrónico para continuar.
|
||||||
|
verification_code_sent_click_link_or_enter_code: Se ha enviado un código de verificación a tu correo. Puedes hacer clic en el enlace del correo o ingresar el código a continuación.
|
||||||
|
use_otp_code_to_verify_email_or_click_link_below_html: 'Usa el código <b>%{code}</b> para verificar tu correo electrónico o haz clic en el enlace a continuación:'
|
||||||
|
verify_your_email: Verificar tu correo electrónico
|
||||||
|
your_email_has_been_confirmed: Tu correo electrónico ha sido confirmado.
|
||||||
|
invalid_or_expired_verification_code: Código de verificación inválido o expirado.
|
||||||
|
didnt_receive_an_email: "¿No recibiste un correo electrónico?"
|
||||||
unconfirmed: No confirmado
|
unconfirmed: No confirmado
|
||||||
you_requested_to_reset_your_password_use_the_link_below_to_continue: Solicitaste restablecer tu contraseña. Usa el enlace a continuación para continuar.
|
you_requested_to_reset_your_password_use_the_link_below_to_continue: Solicitaste restablecer tu contraseña. Usa el enlace a continuación para continuar.
|
||||||
if_you_didnt_request_this_you_can_ignore_this_email: "Si no solicitaste esto, puedes ignorar este correo electrónico."
|
if_you_didnt_request_this_you_can_ignore_this_email: "Si no solicitaste esto, puedes ignorar este correo electrónico."
|
||||||
@@ -2918,6 +2932,13 @@ it: &it
|
|||||||
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} è in attesa di conferma. Segui il link nell'email per confermare."
|
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} è in attesa di conferma. Segui il link nell'email per confermare."
|
||||||
please_confirm_your_email_address_using_the_link_below_: 'Conferma il tuo indirizzo email utilizzando il link qui sotto:'
|
please_confirm_your_email_address_using_the_link_below_: 'Conferma il tuo indirizzo email utilizzando il link qui sotto:'
|
||||||
confirm_email: Conferma email
|
confirm_email: Conferma email
|
||||||
|
please_verify_your_email_address_to_continue: Verifica il tuo indirizzo email per continuare.
|
||||||
|
verification_code_sent_click_link_or_enter_code: "È stato inviato un codice di verifica alla tua email. Puoi cliccare il link nell'email o inserire il codice qui sotto."
|
||||||
|
use_otp_code_to_verify_email_or_click_link_below_html: 'Usa il codice <b>%{code}</b> per verificare la tua email o clicca il link qui sotto:'
|
||||||
|
verify_your_email: Verifica la tua email
|
||||||
|
your_email_has_been_confirmed: La tua email è stata confermata.
|
||||||
|
invalid_or_expired_verification_code: Codice di verifica non valido o scaduto.
|
||||||
|
didnt_receive_an_email: Non hai ricevuto un'email?
|
||||||
unconfirmed: Non confermato
|
unconfirmed: Non confermato
|
||||||
you_requested_to_reset_your_password_use_the_link_below_to_continue: Hai richiesto di reimpostare la tua password. Usa il link qui sotto per continuare.
|
you_requested_to_reset_your_password_use_the_link_below_to_continue: Hai richiesto di reimpostare la tua password. Usa il link qui sotto per continuare.
|
||||||
if_you_didnt_request_this_you_can_ignore_this_email: "Se non hai richiesto questo, puoi ignorare questa email."
|
if_you_didnt_request_this_you_can_ignore_this_email: "Se non hai richiesto questo, puoi ignorare questa email."
|
||||||
@@ -3932,6 +3953,13 @@ fr: &fr
|
|||||||
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} est en attente de confirmation. Suivez le lien dans l'e-mail pour la confirmer."
|
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} est en attente de confirmation. Suivez le lien dans l'e-mail pour la confirmer."
|
||||||
please_confirm_your_email_address_using_the_link_below_: 'Veuillez confirmer votre adresse e-mail en utilisant le lien ci-dessous :'
|
please_confirm_your_email_address_using_the_link_below_: 'Veuillez confirmer votre adresse e-mail en utilisant le lien ci-dessous :'
|
||||||
confirm_email: "Confirmer l'e-mail"
|
confirm_email: "Confirmer l'e-mail"
|
||||||
|
please_verify_your_email_address_to_continue: "Veuillez vérifier votre adresse e-mail pour continuer."
|
||||||
|
verification_code_sent_click_link_or_enter_code: "Un code de vérification a été envoyé à votre adresse e-mail. Vous pouvez cliquer sur le lien dans l'e-mail ou saisir le code ci-dessous."
|
||||||
|
use_otp_code_to_verify_email_or_click_link_below_html: "Utilisez le code <b>%{code}</b> pour vérifier votre e-mail ou cliquez sur le lien ci-dessous :"
|
||||||
|
verify_your_email: "Vérifier votre e-mail"
|
||||||
|
your_email_has_been_confirmed: "Votre adresse e-mail a été confirmée."
|
||||||
|
invalid_or_expired_verification_code: "Code de vérification invalide ou expiré."
|
||||||
|
didnt_receive_an_email: "Vous n'avez pas reçu d'e-mail ?"
|
||||||
unconfirmed: Non confirmé
|
unconfirmed: Non confirmé
|
||||||
you_requested_to_reset_your_password_use_the_link_below_to_continue: Vous avez demandé à réinitialiser votre mot de passe. Utilisez le lien ci-dessous pour continuer.
|
you_requested_to_reset_your_password_use_the_link_below_to_continue: Vous avez demandé à réinitialiser votre mot de passe. Utilisez le lien ci-dessous pour continuer.
|
||||||
if_you_didnt_request_this_you_can_ignore_this_email: "Si vous n'avez pas fait cette demande, veuillez ignorer cet e-mail."
|
if_you_didnt_request_this_you_can_ignore_this_email: "Si vous n'avez pas fait cette demande, veuillez ignorer cet e-mail."
|
||||||
@@ -4949,6 +4977,13 @@ pt: &pt
|
|||||||
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} está aguardando confirmação. Siga o link enviado para esse endereço de e-mail para confirmar."
|
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} está aguardando confirmação. Siga o link enviado para esse endereço de e-mail para confirmar."
|
||||||
please_confirm_your_email_address_using_the_link_below_: 'Por favor, confirme seu endereço de e-mail usando o link abaixo:'
|
please_confirm_your_email_address_using_the_link_below_: 'Por favor, confirme seu endereço de e-mail usando o link abaixo:'
|
||||||
confirm_email: Confirmar e-mail
|
confirm_email: Confirmar e-mail
|
||||||
|
please_verify_your_email_address_to_continue: Verifique seu endereço de e-mail para continuar.
|
||||||
|
verification_code_sent_click_link_or_enter_code: Um código de verificação foi enviado para seu e-mail. Você pode clicar no link do e-mail ou inserir o código abaixo.
|
||||||
|
use_otp_code_to_verify_email_or_click_link_below_html: 'Use o código <b>%{code}</b> para verificar seu e-mail ou clique no link abaixo:'
|
||||||
|
verify_your_email: Verificar seu e-mail
|
||||||
|
your_email_has_been_confirmed: Seu e-mail foi confirmado.
|
||||||
|
invalid_or_expired_verification_code: Código de verificação inválido ou expirado.
|
||||||
|
didnt_receive_an_email: Não recebeu um e-mail?
|
||||||
unconfirmed: Não confirmado
|
unconfirmed: Não confirmado
|
||||||
you_requested_to_reset_your_password_use_the_link_below_to_continue: Você solicitou a redefinição da sua senha. Use o link abaixo para continuar.
|
you_requested_to_reset_your_password_use_the_link_below_to_continue: Você solicitou a redefinição da sua senha. Use o link abaixo para continuar.
|
||||||
if_you_didnt_request_this_you_can_ignore_this_email: "Se você não solicitou isso, pode ignorar este e-mail."
|
if_you_didnt_request_this_you_can_ignore_this_email: "Se você não solicitou isso, pode ignorar este e-mail."
|
||||||
@@ -5966,6 +6001,13 @@ de: &de
|
|||||||
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} wartet auf Bestätigung. Folgen Sie dem Link in der E-Mail, um sie zu bestätigen."
|
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} wartet auf Bestätigung. Folgen Sie dem Link in der E-Mail, um sie zu bestätigen."
|
||||||
please_confirm_your_email_address_using_the_link_below_: 'Bitte bestätigen Sie Ihre E-Mail-Adresse über den folgenden Link:'
|
please_confirm_your_email_address_using_the_link_below_: 'Bitte bestätigen Sie Ihre E-Mail-Adresse über den folgenden Link:'
|
||||||
confirm_email: E-Mail bestätigen
|
confirm_email: E-Mail bestätigen
|
||||||
|
please_verify_your_email_address_to_continue: Bitte bestätigen Sie Ihre E-Mail-Adresse, um fortzufahren.
|
||||||
|
verification_code_sent_click_link_or_enter_code: Ein Verifizierungscode wurde an Ihre E-Mail gesendet. Sie können auf den Link in der E-Mail klicken oder den Code unten eingeben.
|
||||||
|
use_otp_code_to_verify_email_or_click_link_below_html: 'Verwenden Sie den Code <b>%{code}</b>, um Ihre E-Mail zu bestätigen, oder klicken Sie auf den Link unten:'
|
||||||
|
verify_your_email: E-Mail bestätigen
|
||||||
|
your_email_has_been_confirmed: Ihre E-Mail-Adresse wurde bestätigt.
|
||||||
|
invalid_or_expired_verification_code: Ungültiger oder abgelaufener Verifizierungscode.
|
||||||
|
didnt_receive_an_email: Keine E-Mail erhalten?
|
||||||
unconfirmed: Unbestätigt
|
unconfirmed: Unbestätigt
|
||||||
you_requested_to_reset_your_password_use_the_link_below_to_continue: Sie haben angefordert, Ihr Passwort zurückzusetzen. Verwenden Sie den untenstehenden Link, um fortzufahren.
|
you_requested_to_reset_your_password_use_the_link_below_to_continue: Sie haben angefordert, Ihr Passwort zurückzusetzen. Verwenden Sie den untenstehenden Link, um fortzufahren.
|
||||||
if_you_didnt_request_this_you_can_ignore_this_email: "Wenn Sie dies nicht angefordert haben, können Sie diese E-Mail ignorieren."
|
if_you_didnt_request_this_you_can_ignore_this_email: "Wenn Sie dies nicht angefordert haben, können Sie diese E-Mail ignorieren."
|
||||||
@@ -7368,6 +7410,13 @@ nl: &nl
|
|||||||
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} wacht op bevestiging. Volg de link in de e-mail om te bevestigen."
|
email_address_is_awaiting_confirmation_follow_the_link_in_the_email_to_confirm: "%{email} wacht op bevestiging. Volg de link in de e-mail om te bevestigen."
|
||||||
please_confirm_your_email_address_using_the_link_below_: 'Bevestig je e-mailadres via de onderstaande link:'
|
please_confirm_your_email_address_using_the_link_below_: 'Bevestig je e-mailadres via de onderstaande link:'
|
||||||
confirm_email: E-mailadres bevestigen
|
confirm_email: E-mailadres bevestigen
|
||||||
|
please_verify_your_email_address_to_continue: Bevestig je e-mailadres om door te gaan.
|
||||||
|
verification_code_sent_click_link_or_enter_code: Er is een verificatiecode naar je e-mail gestuurd. Je kunt op de link in de e-mail klikken of de code hieronder invoeren.
|
||||||
|
use_otp_code_to_verify_email_or_click_link_below_html: 'Gebruik code <b>%{code}</b> om je e-mail te verifiëren of klik op de link hieronder:'
|
||||||
|
verify_your_email: Je e-mail verifiëren
|
||||||
|
your_email_has_been_confirmed: Je e-mailadres is bevestigd.
|
||||||
|
invalid_or_expired_verification_code: Ongeldige of verlopen verificatiecode.
|
||||||
|
didnt_receive_an_email: Geen e-mail ontvangen?
|
||||||
unconfirmed: Onbevestigd
|
unconfirmed: Onbevestigd
|
||||||
you_requested_to_reset_your_password_use_the_link_below_to_continue: Je hebt gevraagd je wachtwoord te resetten. Gebruik de onderstaande link om verder te gaan.
|
you_requested_to_reset_your_password_use_the_link_below_to_continue: Je hebt gevraagd je wachtwoord te resetten. Gebruik de onderstaande link om verder te gaan.
|
||||||
if_you_didnt_request_this_you_can_ignore_this_email: "Als je dit niet hebt aangevraagd, kun je deze e-mail negeren."
|
if_you_didnt_request_this_you_can_ignore_this_email: "Als je dit niet hebt aangevraagd, kun je deze e-mail negeren."
|
||||||
|
|||||||
+1
-13
@@ -3,8 +3,6 @@
|
|||||||
module Submissions
|
module Submissions
|
||||||
DEFAULT_SUBMITTERS_ORDER = 'random'
|
DEFAULT_SUBMITTERS_ORDER = 'random'
|
||||||
|
|
||||||
PRELOAD_ALL_PAGES_AMOUNT = 200
|
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def search(current_user, submissions, keyword, search_values: false, search_template: false)
|
def search(current_user, submissions, keyword, search_values: false, search_template: false)
|
||||||
@@ -81,19 +79,9 @@ module Submissions
|
|||||||
def preload_with_pages(submission)
|
def preload_with_pages(submission)
|
||||||
ActiveRecord::Associations::Preloader.new(
|
ActiveRecord::Associations::Preloader.new(
|
||||||
records: submission.schema_documents,
|
records: submission.schema_documents,
|
||||||
associations: [:blob]
|
associations: [:blob, { preview_images_attachments: :blob }]
|
||||||
).call
|
).call
|
||||||
|
|
||||||
total_pages =
|
|
||||||
submission.schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
|
|
||||||
|
|
||||||
if total_pages < PRELOAD_ALL_PAGES_AMOUNT
|
|
||||||
ActiveRecord::Associations::Preloader.new(
|
|
||||||
records: submission.schema_documents,
|
|
||||||
associations: [{ preview_images_attachments: :blob }]
|
|
||||||
).call
|
|
||||||
end
|
|
||||||
|
|
||||||
submission
|
submission
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ module Submissions
|
|||||||
bold_italic: FONT_BOLD_NAME
|
bold_italic: FONT_BOLD_NAME
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
SIGN_REASON = 'Signed by %<name>s with DocuSeal.com'
|
SIGN_REASON = 'Signed with DocuSeal.com'
|
||||||
|
|
||||||
RTL_REGEXP = TextUtils::RTL_REGEXP
|
RTL_REGEXP = TextUtils::RTL_REGEXP
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
module Submitters
|
module Submitters
|
||||||
TRUE_VALUES = ['1', 'true', true].freeze
|
TRUE_VALUES = ['1', 'true', true].freeze
|
||||||
PRELOAD_ALL_PAGES_AMOUNT = 200
|
|
||||||
|
|
||||||
FIELD_NAME_WEIGHTS = {
|
FIELD_NAME_WEIGHTS = {
|
||||||
'email' => 'A',
|
'email' => 'A',
|
||||||
|
|||||||
Reference in New Issue
Block a user