adjust dynamic editor
This commit is contained in:
@@ -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,60 +2547,80 @@ export default {
|
|||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
|
removeAreasByAttachmentUuid (attachmentUuid) {
|
||||||
|
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 === attachmentUuid) {
|
||||||
|
field.areas.splice(field.areas.indexOf(area), 1)
|
||||||
|
|
||||||
|
removedFieldUuids.push(field.uuid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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) {
|
onDocumentRemove (item) {
|
||||||
if (window.confirm(this.t('are_you_sure_'))) {
|
if (window.confirm(this.t('are_you_sure_'))) {
|
||||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||||
|
|
||||||
const removedFieldUuids = []
|
this.removeAreasByAttachmentUuid(item.attachment_uuid)
|
||||||
|
|
||||||
this.template.fields.forEach((field) => {
|
|
||||||
[...(field.areas || [])].forEach((area) => {
|
|
||||||
if (area.attachment_uuid === item.attachment_uuid) {
|
|
||||||
field.areas.splice(field.areas.indexOf(area), 1)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
const removedFieldUuids = []
|
if (isReplacingDynamicDocument) {
|
||||||
|
this.removeAreasByAttachmentUuid(replaceSchemaItem.attachment_uuid)
|
||||||
|
} else {
|
||||||
|
const removedFieldUuids = []
|
||||||
|
|
||||||
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) {
|
||||||
field.areas.splice(field.areas.indexOf(area), 1)
|
field.areas.splice(field.areas.indexOf(area), 1)
|
||||||
|
|
||||||
removedFieldUuids.push(field.uuid)
|
removedFieldUuids.push(field.uuid)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
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) {
|
||||||
@@ -2576,13 +2631,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.template.fields.forEach((field) => {
|
if (!isReplacingDynamicDocument) {
|
||||||
(field.areas || []).forEach((area) => {
|
this.template.fields.forEach((field) => {
|
||||||
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
|
(field.areas || []).forEach((area) => {
|
||||||
area.attachment_uuid = schema[0].attachment_uuid
|
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
|
||||||
}
|
area.attachment_uuid = schema[0].attachment_uuid
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
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,6 +193,8 @@ 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}"]`)
|
||||||
|
|
||||||
@@ -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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +1,13 @@
|
|||||||
<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"
|
||||||
@@ -45,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>
|
||||||
@@ -54,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'
|
||||||
@@ -73,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,
|
||||||
@@ -101,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: {
|
||||||
@@ -165,6 +206,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]
|
||||||
},
|
},
|
||||||
@@ -193,45 +237,121 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.initEditor()
|
this.editorRef.value = buildEditor({
|
||||||
|
dynamicAreaProps: {
|
||||||
|
template: this.template,
|
||||||
|
t: this.t,
|
||||||
|
selectedAreasRef: this.selectedAreasRef,
|
||||||
|
getFieldTypeIndex: this.getFieldTypeIndex,
|
||||||
|
findFieldArea: (areaUuid) => this.fieldAreaIndex[areaUuid],
|
||||||
|
getZoom: () => this.zoom,
|
||||||
|
onAreaContextMenu: this.onAreaContextMenu,
|
||||||
|
onAreaResize: this.onAreaResize,
|
||||||
|
onAreaDragStart: this.onAreaDragStart
|
||||||
|
},
|
||||||
|
attachmentsIndex: this.attachmentsIndex,
|
||||||
|
onFieldDrop: this.onFieldDrop,
|
||||||
|
onFieldDestroy: this.onFieldDestroy,
|
||||||
|
renderHtmlForSaveRef: this.renderHtmlForSaveRef,
|
||||||
|
editorOptions: {
|
||||||
|
element: this.$refs.editorElement,
|
||||||
|
editable: this.editable,
|
||||||
|
content: this.section.innerHTML,
|
||||||
|
onUpdate: (event) => this.$emit('update', event),
|
||||||
|
onSelectionUpdate: this.onSelectionUpdate,
|
||||||
|
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 () {
|
beforeUnmount () {
|
||||||
if (this.editor) {
|
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()
|
this.editor.destroy()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async initEditor () {
|
|
||||||
this.editorRef.value = buildEditor({
|
|
||||||
dynamicAreaProps: {
|
|
||||||
template: this.template,
|
|
||||||
t: this.t,
|
|
||||||
selectedAreasRef: this.selectedAreasRef,
|
|
||||||
getFieldTypeIndex: this.getFieldTypeIndex,
|
|
||||||
findFieldArea: (areaUuid) => this.fieldAreaIndex[areaUuid],
|
|
||||||
getZoom: () => this.zoom,
|
|
||||||
onAreaContextMenu: this.onAreaContextMenu,
|
|
||||||
onAreaResize: this.onAreaResize,
|
|
||||||
onAreaDragStart: this.onAreaDragStart
|
|
||||||
},
|
|
||||||
attachmentsIndex: this.attachmentsIndex,
|
|
||||||
onFieldDrop: this.onFieldDrop,
|
|
||||||
onFieldDestroy: this.onFieldDestroy,
|
|
||||||
editorOptions: {
|
|
||||||
element: this.$refs.editorElement,
|
|
||||||
editable: this.editable,
|
|
||||||
content: this.section.innerHTML,
|
|
||||||
onUpdate: (event) => this.$emit('update', event),
|
|
||||||
onSelectionUpdate: this.onSelectionUpdate,
|
|
||||||
onBlur: () => { this.dynamicMenuCoords = null }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
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)
|
||||||
@@ -362,6 +482,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]
|
||||||
@@ -399,89 +733,167 @@ 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)
|
|
||||||
|
|
||||||
view.dispatch(tr)
|
|
||||||
} else {
|
|
||||||
const newField = {
|
|
||||||
name: draggedField.name || '',
|
|
||||||
uuid: v4(),
|
|
||||||
required: fieldType !== 'checkbox',
|
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
|
||||||
type: fieldType,
|
|
||||||
areas: [{ uuid: areaUuid, attachment_uuid: this.attachmentUuid }]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['select', 'multiple', 'radio'].includes(fieldType)) {
|
|
||||||
if (draggedField.options?.length) {
|
|
||||||
newField.options = draggedField.options.map((opt) => ({
|
|
||||||
value: typeof opt === 'string' ? opt : opt.value,
|
|
||||||
uuid: v4()
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
newField.options = [{ value: '', uuid: v4() }, { value: '', uuid: v4() }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldType === 'datenow') {
|
|
||||||
newField.type = 'date'
|
|
||||||
newField.readonly = true
|
|
||||||
newField.default_value = '{{date}}'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['stamp', 'heading', 'strikethrough'].includes(fieldType)) {
|
|
||||||
newField.readonly = true
|
|
||||||
|
|
||||||
if (fieldType === 'strikethrough') {
|
|
||||||
newField.default_value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.template.fields.push(newField)
|
|
||||||
|
|
||||||
const nodeType = view.state.schema.nodes.fieldNode
|
|
||||||
const fieldNode = nodeType.create({
|
|
||||||
uuid: newField.uuid,
|
|
||||||
areaUuid,
|
|
||||||
width: dims.width,
|
|
||||||
height: dims.height
|
|
||||||
})
|
|
||||||
|
|
||||||
const tr = view.state.tr.insert(pos.pos, fieldNode)
|
|
||||||
|
|
||||||
view.dispatch(tr)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fieldsDragFieldRef.value = null
|
this.fieldsDragFieldRef.value = null
|
||||||
this.customDragFieldRef.value = null
|
this.customDragFieldRef.value = null
|
||||||
|
|
||||||
this.editor.chain().focus().setNodeSelection(pos.pos).run()
|
return true
|
||||||
|
},
|
||||||
|
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 = {
|
||||||
|
name: sourceField?.name || '',
|
||||||
|
uuid: v4(),
|
||||||
|
required: fieldType !== 'checkbox',
|
||||||
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
|
type: fieldType,
|
||||||
|
areas: [area]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['select', 'multiple', 'radio'].includes(fieldType)) {
|
||||||
|
if (sourceField?.options?.length) {
|
||||||
|
newField.options = sourceField.options.map((opt) => ({
|
||||||
|
value: typeof opt === 'string' ? opt : opt.value,
|
||||||
|
uuid: v4()
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
newField.options = [{ value: '', uuid: v4() }, { value: '', uuid: v4() }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldType === 'datenow') {
|
||||||
|
newField.type = 'date'
|
||||||
|
newField.readonly = true
|
||||||
|
newField.default_value = '{{date}}'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['stamp', 'heading', 'strikethrough'].includes(fieldType)) {
|
||||||
|
newField.readonly = true
|
||||||
|
|
||||||
|
if (fieldType === 'strikethrough') {
|
||||||
|
newField.default_value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 fieldNode = nodeType.create({
|
||||||
|
uuid: field.uuid,
|
||||||
|
areaUuid: area.uuid,
|
||||||
|
width: dims.width,
|
||||||
|
height: dims.height
|
||||||
|
})
|
||||||
|
|
||||||
|
const tr = from !== to
|
||||||
|
? view.state.tr.replaceWith(from, to, fieldNode)
|
||||||
|
: view.state.tr.insert(from, fieldNode)
|
||||||
|
|
||||||
|
view.dispatch(tr)
|
||||||
|
|
||||||
|
this.editor.chain().focus().setNodeSelection(from).run()
|
||||||
this.save()
|
this.save()
|
||||||
|
|
||||||
return true
|
return { area, field, pos: from }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user