mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 06:42:32 -05:00
fix editor undo
This commit is contained in:
1 parent
25504e8cb1
commit
73339d8cd9
7 files changed
+205
-57
No files matched your search
@@ -272,7 +272,7 @@ export default {
|
|||||||
FieldSubmitter,
|
FieldSubmitter,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['t', 'isMobile', 'nativePlatform'],
|
inject: ['t', 'nativePlatform'],
|
||||||
props: {
|
props: {
|
||||||
template: {
|
template: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -1007,6 +1007,9 @@ export default {
|
|||||||
return {
|
return {
|
||||||
template: this.template,
|
template: this.template,
|
||||||
save: this.save,
|
save: this.save,
|
||||||
|
pushUndo: this.pushUndo,
|
||||||
|
undo: this.undo,
|
||||||
|
redo: this.redo,
|
||||||
t: this.t,
|
t: this.t,
|
||||||
assignDropAreaSize: this.assignDropAreaSize,
|
assignDropAreaSize: this.assignDropAreaSize,
|
||||||
currencies: this.currencies,
|
currencies: this.currencies,
|
||||||
@@ -1640,9 +1643,11 @@ export default {
|
|||||||
this.selectedSubmitter = this.template.submitters[0]
|
this.selectedSubmitter = this.template.submitters[0]
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.undoStack = [JSON.stringify(this.template)]
|
this.undoStack = []
|
||||||
this.redoStack = []
|
this.redoStack = []
|
||||||
|
|
||||||
|
this.undoStack.push(this.buildUndoData())
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.resizeObserver = new ResizeObserver(this.onResize)
|
this.resizeObserver = new ResizeObserver(this.onResize)
|
||||||
this.resizeObserver.observe(this.$el.getRootNode().querySelector('div[data-v-app]'))
|
this.resizeObserver.observe(this.$el.getRootNode().querySelector('div[data-v-app]'))
|
||||||
@@ -2287,6 +2292,8 @@ export default {
|
|||||||
|
|
||||||
const dynamicDocumentRefs = this.documentRefs.filter((ref) => ref.isDynamic)
|
const dynamicDocumentRefs = this.documentRefs.filter((ref) => ref.isDynamic)
|
||||||
|
|
||||||
|
this.pushUndo()
|
||||||
|
|
||||||
dynamicDocumentRefs.forEach((ref) => ref.update())
|
dynamicDocumentRefs.forEach((ref) => ref.update())
|
||||||
|
|
||||||
this.rebuildVariablesSchema({ disable: false })
|
this.rebuildVariablesSchema({ disable: false })
|
||||||
@@ -2414,35 +2421,76 @@ export default {
|
|||||||
this.drawOption = null
|
this.drawOption = null
|
||||||
},
|
},
|
||||||
undo () {
|
undo () {
|
||||||
|
this.selectedAreasRef.value = []
|
||||||
|
|
||||||
if (this.undoStack.length > 1) {
|
if (this.undoStack.length > 1) {
|
||||||
this.undoStack.pop()
|
this.undoStack.pop()
|
||||||
const stringData = this.undoStack[this.undoStack.length - 1]
|
const undoData = this.undoStack[this.undoStack.length - 1]
|
||||||
const currentStringData = JSON.stringify(this.template)
|
const currentUndoData = this.buildUndoData()
|
||||||
|
|
||||||
if (stringData && stringData !== currentStringData) {
|
if (!this.isSameUndoData(undoData, currentUndoData)) {
|
||||||
this.redoStack.push(currentStringData)
|
this.redoStack.push(currentUndoData)
|
||||||
|
|
||||||
Object.assign(this.template, JSON.parse(stringData))
|
this.applyUndoData(undoData, currentUndoData)
|
||||||
|
|
||||||
this.save()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
redo () {
|
redo () {
|
||||||
const stringData = this.redoStack.pop()
|
this.selectedAreasRef.value = []
|
||||||
this.lastRedoData = stringData
|
|
||||||
const currentStringData = JSON.stringify(this.template)
|
|
||||||
|
|
||||||
if (stringData && stringData !== currentStringData) {
|
const undoData = this.redoStack.pop()
|
||||||
if (this.undoStack[this.undoStack.length - 1] !== currentStringData) {
|
const currentUndoData = this.buildUndoData()
|
||||||
this.undoStack.push(currentStringData)
|
|
||||||
|
if (undoData && !this.isSameUndoData(undoData, currentUndoData)) {
|
||||||
|
if (!this.isSameUndoData(this.undoStack[this.undoStack.length - 1], currentUndoData)) {
|
||||||
|
this.undoStack.push(currentUndoData)
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this.template, JSON.parse(stringData))
|
this.undoStack.push(undoData)
|
||||||
|
|
||||||
|
this.applyUndoData(undoData, currentUndoData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildUndoData () {
|
||||||
|
const template = JSON.stringify(toRaw(this.template))
|
||||||
|
const lastUndoData = this.undoStack[this.undoStack.length - 1]
|
||||||
|
|
||||||
|
return { template: template === lastUndoData?.template ? lastUndoData.template : template, sectionDocs: this.buildSectionDocs() }
|
||||||
|
},
|
||||||
|
buildSectionDocs () {
|
||||||
|
return new Map(this.documentRefs.flatMap((ref) => ref.isDynamic ? ref.getSectionDocs() : []))
|
||||||
|
},
|
||||||
|
isSameUndoData (undoData, currentUndoData) {
|
||||||
|
return undoData.template === currentUndoData.template && this.isSameSectionDocs(undoData, currentUndoData)
|
||||||
|
},
|
||||||
|
isSameSectionDocs (undoData, currentUndoData) {
|
||||||
|
return [...currentUndoData.sectionDocs].every(([sectionKey, doc]) => undoData.sectionDocs.get(sectionKey)?.eq(doc))
|
||||||
|
},
|
||||||
|
applyUndoData (undoData, currentUndoData) {
|
||||||
|
const isTemplateChanged = undoData.template !== currentUndoData.template
|
||||||
|
|
||||||
|
if (isTemplateChanged) {
|
||||||
|
Object.assign(this.template, JSON.parse(undoData.template))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.documentRefs.forEach((ref) => {
|
||||||
|
if (ref.isDynamic) ref.restoreSectionDocs(undoData.sectionDocs)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.buildSectionDocs().forEach((doc, sectionKey) => undoData.sectionDocs.set(sectionKey, doc))
|
||||||
|
|
||||||
|
if (isTemplateChanged) {
|
||||||
this.save()
|
this.save()
|
||||||
|
|
||||||
|
this.$nextTick(this.syncUndoTemplate)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
syncUndoTemplate () {
|
||||||
|
const lastUndoData = this.undoStack[this.undoStack.length - 1]
|
||||||
|
const template = JSON.stringify(toRaw(this.template))
|
||||||
|
|
||||||
|
if (template !== lastUndoData.template) lastUndoData.template = template
|
||||||
|
},
|
||||||
onResize () {
|
onResize () {
|
||||||
const breakpointLg = 1024
|
const breakpointLg = 1024
|
||||||
const breakpointMd = 768
|
const breakpointMd = 768
|
||||||
@@ -2609,15 +2657,11 @@ export default {
|
|||||||
event.stopImmediatePropagation()
|
event.stopImmediatePropagation()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
this.selectedAreasRef.value = []
|
|
||||||
|
|
||||||
this.redo()
|
this.redo()
|
||||||
} else if ((event.ctrlKey || event.metaKey) && event.key === 'z') {
|
} else if ((event.ctrlKey || event.metaKey) && event.key === 'z') {
|
||||||
event.stopImmediatePropagation()
|
event.stopImmediatePropagation()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
this.selectedAreasRef.value = []
|
|
||||||
|
|
||||||
this.undo()
|
this.undo()
|
||||||
} else if ((event.ctrlKey || event.metaKey) && event.key === 'c' && document.activeElement === document.body) {
|
} else if ((event.ctrlKey || event.metaKey) && event.key === 'c' && document.activeElement === document.body) {
|
||||||
if (this.selectedAreasRef.value.length > 1) {
|
if (this.selectedAreasRef.value.length > 1) {
|
||||||
@@ -2992,16 +3036,29 @@ export default {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pushUndo () {
|
pushUndo ({ amend = false } = {}) {
|
||||||
const stringData = JSON.stringify(this.template)
|
const undoData = this.buildUndoData()
|
||||||
|
const lastUndoData = this.undoStack[this.undoStack.length - 1]
|
||||||
|
const isTemplateChanged = undoData.template !== lastUndoData.template
|
||||||
|
const isSectionDocsChanged = !this.isSameSectionDocs(lastUndoData, undoData)
|
||||||
|
|
||||||
if (this.undoStack[this.undoStack.length - 1] !== stringData) {
|
if (this.isUndoBatch || (amend && (!isSectionDocsChanged || (!this.redoStack.length && !isTemplateChanged)))) {
|
||||||
this.undoStack.push(stringData)
|
this.undoStack[this.undoStack.length - 1] = undoData
|
||||||
|
} else if (isTemplateChanged || isSectionDocsChanged) {
|
||||||
|
this.undoStack.push(undoData)
|
||||||
|
|
||||||
if (this.lastRedoData !== stringData) {
|
|
||||||
this.redoStack = []
|
this.redoStack = []
|
||||||
|
} else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
this.isUndoBatch = true
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.isUndoBatch = false
|
||||||
|
|
||||||
|
this.syncUndoTemplate()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
setDefaultAreaSize (area, type) {
|
setDefaultAreaSize (area, type) {
|
||||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||||
@@ -3971,7 +4028,7 @@ export default {
|
|||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$el.closest('template-builder')) {
|
if (this.$el.closest('template-builder')) {
|
||||||
this.$el.closest('template-builder').dataset.template = JSON.stringify(this.template)
|
this.$el.closest('template-builder').dataset.template = JSON.stringify(toRaw(this.template))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -4004,10 +4061,12 @@ export default {
|
|||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$el.closest('template-builder')) {
|
if (this.$el.closest('template-builder')) {
|
||||||
this.$el.closest('template-builder').dataset.dynamicDocuments = JSON.stringify(this.dynamicDocuments)
|
this.$el.closest('template-builder').dataset.dynamicDocuments = JSON.stringify(toRaw(this.dynamicDocuments))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.pushUndo({ amend: true })
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
rebuildVariablesSchema ({ disable = true } = {}) {
|
rebuildVariablesSchema ({ disable = true } = {}) {
|
||||||
|
|||||||
@@ -191,6 +191,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
this.updatedSectionEditors = new Map()
|
||||||
|
|
||||||
this.loadListsStyle()
|
this.loadListsStyle()
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
@@ -242,6 +244,14 @@ export default {
|
|||||||
|
|
||||||
this.sectionRefs.forEach((ref) => ref.reloadContent())
|
this.sectionRefs.forEach((ref) => ref.reloadContent())
|
||||||
},
|
},
|
||||||
|
getSectionDocs () {
|
||||||
|
return this.sectionRefs.map((ref) => [ref.sectionKey, ref.editor.state.doc])
|
||||||
|
},
|
||||||
|
restoreSectionDocs (sectionDocs) {
|
||||||
|
this.sectionRefs.forEach((ref) => {
|
||||||
|
if (sectionDocs.has(ref.sectionKey)) ref.restoreDoc(sectionDocs.get(ref.sectionKey))
|
||||||
|
})
|
||||||
|
},
|
||||||
onBeforeUnload (event) {
|
onBeforeUnload (event) {
|
||||||
if (this.saveTimer) {
|
if (this.saveTimer) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -267,11 +277,9 @@ export default {
|
|||||||
onSectionUpdate (section, { editor }) {
|
onSectionUpdate (section, { editor }) {
|
||||||
clearTimeout(this.saveTimer)
|
clearTimeout(this.saveTimer)
|
||||||
|
|
||||||
this.saveTimer = setTimeout(async () => {
|
this.updatedSectionEditors.set(section.id, editor)
|
||||||
await this.updateSectionAndSave(section, editor)
|
|
||||||
|
|
||||||
delete this.saveTimer
|
this.saveTimer = setTimeout(() => this.updateSectionsAndSave(), 1000)
|
||||||
}, 1000)
|
|
||||||
},
|
},
|
||||||
loadListsStyle () {
|
loadListsStyle () {
|
||||||
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
|
this.listsStyle = this.bodyDom.querySelector('body > style[data-lists]')?.textContent || null
|
||||||
@@ -291,12 +299,16 @@ export default {
|
|||||||
updateVariablesSchema () {
|
updateVariablesSchema () {
|
||||||
this.document.variables_schema = buildVariablesSchema(this.bodyDom.body)
|
this.document.variables_schema = buildVariablesSchema(this.bodyDom.body)
|
||||||
},
|
},
|
||||||
updateSectionAndSave (section, editor) {
|
updateSectionsAndSave () {
|
||||||
const target = this.bodyDom.getElementById(section.id)
|
this.updatedSectionEditors.forEach((editor, sectionId) => {
|
||||||
|
const target = this.bodyDom.getElementById(sectionId)
|
||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
target.innerHTML = this.getHtmlForSave(editor)
|
target.innerHTML = this.getHtmlForSave(editor)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.updatedSectionEditors.clear()
|
||||||
|
|
||||||
this.document.body = this.bodyDom.body.innerHTML
|
this.document.body = this.bodyDom.body.innerHTML
|
||||||
|
|
||||||
@@ -316,6 +328,8 @@ export default {
|
|||||||
|
|
||||||
delete this.saveTimer
|
delete this.saveTimer
|
||||||
|
|
||||||
|
this.updatedSectionEditors.clear()
|
||||||
|
|
||||||
this.sectionRefs.forEach(({ section, editor }) => {
|
this.sectionRefs.forEach(({ section, editor }) => {
|
||||||
const target = this.bodyDom.getElementById(section.id)
|
const target = this.bodyDom.getElementById(section.id)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Decoration, DecorationSet } from '@tiptap/pm/view'
|
|||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import Text from '@tiptap/extension-text'
|
import Text from '@tiptap/extension-text'
|
||||||
import HardBreak from '@tiptap/extension-hard-break'
|
import HardBreak from '@tiptap/extension-hard-break'
|
||||||
import History from '@tiptap/extension-history'
|
|
||||||
import Gapcursor from '@tiptap/extension-gapcursor'
|
import Gapcursor from '@tiptap/extension-gapcursor'
|
||||||
import Dropcursor from '@tiptap/extension-dropcursor'
|
import Dropcursor from '@tiptap/extension-dropcursor'
|
||||||
import { createApp, reactive } from 'vue'
|
import { createApp, reactive } from 'vue'
|
||||||
@@ -1475,7 +1474,7 @@ const VariableHighlight = Extension.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export function buildEditor ({ dynamicAreaProps, getAttachmentsIndex, renderHtmlForSaveRef, onFieldDrop, onFieldDestroy, editorOptions }) {
|
export function buildEditor ({ dynamicAreaProps, getAttachmentsIndex, renderHtmlForSaveRef, onFieldDrop, onFieldDestroy, onUndo, onRedo, editorOptions }) {
|
||||||
const FieldNode = Node.create({
|
const FieldNode = Node.create({
|
||||||
name: 'fieldNode',
|
name: 'fieldNode',
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -1598,6 +1597,51 @@ export function buildEditor ({ dynamicAreaProps, getAttachmentsIndex, renderHtml
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const undo = () => {
|
||||||
|
onUndo()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const redo = () => {
|
||||||
|
onRedo()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const BuilderHistory = Extension.create({
|
||||||
|
name: 'builderHistory',
|
||||||
|
addKeyboardShortcuts () {
|
||||||
|
return {
|
||||||
|
'Mod-z': undo,
|
||||||
|
'Shift-Mod-z': redo,
|
||||||
|
'Mod-y': redo,
|
||||||
|
'Mod-я': undo,
|
||||||
|
'Shift-Mod-я': redo
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addProseMirrorPlugins () {
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
key: new PluginKey('builderHistory'),
|
||||||
|
props: {
|
||||||
|
handleDOMEvents: {
|
||||||
|
beforeinput (_view, event) {
|
||||||
|
const command = { historyUndo: undo, historyRedo: redo }[event.inputType]
|
||||||
|
|
||||||
|
if (!command) return false
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
return command()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const RESIZE_HANDLE_PX = 4
|
const RESIZE_HANDLE_PX = 4
|
||||||
const MIN_SIZE_PX = 16
|
const MIN_SIZE_PX = 16
|
||||||
|
|
||||||
@@ -1818,7 +1862,7 @@ export function buildEditor ({ dynamicAreaProps, getAttachmentsIndex, renderHtml
|
|||||||
Document,
|
Document,
|
||||||
Text,
|
Text,
|
||||||
HardBreak,
|
HardBreak,
|
||||||
History,
|
BuilderHistory,
|
||||||
Gapcursor,
|
Gapcursor,
|
||||||
Dropcursor,
|
Dropcursor,
|
||||||
CustomParagraph,
|
CustomParagraph,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
:area="selectedArea"
|
:area="selectedArea"
|
||||||
:field="selectedField"
|
:field="selectedField"
|
||||||
:editable="editable"
|
:editable="editable"
|
||||||
|
:is-mobile="isMobile"
|
||||||
:template="template"
|
:template="template"
|
||||||
:selected-areas-ref="selectedAreasRef"
|
:selected-areas-ref="selectedAreasRef"
|
||||||
:get-field-type-index="getFieldTypeIndex"
|
:get-field-type-index="getFieldTypeIndex"
|
||||||
@@ -116,7 +117,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { shallowRef } from 'vue'
|
import { shallowRef } from 'vue'
|
||||||
import { DOMSerializer, Fragment } from '@tiptap/pm/model'
|
import { DOMSerializer, Fragment } from '@tiptap/pm/model'
|
||||||
import { TextSelection } from '@tiptap/pm/state'
|
import { Selection, TextSelection } from '@tiptap/pm/state'
|
||||||
import { findTable, CellSelection } from '@tiptap/pm/tables'
|
import { findTable, CellSelection } from '@tiptap/pm/tables'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { IconPlus, IconX } from '@tabler/icons-vue'
|
import { IconPlus, IconX } from '@tabler/icons-vue'
|
||||||
@@ -138,7 +139,7 @@ export default {
|
|||||||
IconPlus,
|
IconPlus,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 'save', 'baseFetch', 't', 'fieldsDragFieldRef', 'customDragFieldRef', 'selectedAreasRef', 'getFieldTypeIndex', 'fieldTypes', 'withPhone', 'withPayment', 'withVerification', 'withKba', 'backgroundColor'],
|
inject: ['template', 'save', 'pushUndo', 'undo', 'redo', 'baseFetch', 't', 'fieldsDragFieldRef', 'customDragFieldRef', 'selectedAreasRef', 'getFieldTypeIndex', 'fieldTypes', 'withPhone', 'withPayment', 'withVerification', 'withKba', 'backgroundColor', 'isMobile'],
|
||||||
props: {
|
props: {
|
||||||
section: {
|
section: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -260,6 +261,9 @@ export default {
|
|||||||
editor () {
|
editor () {
|
||||||
return this.editorRef.value
|
return this.editorRef.value
|
||||||
},
|
},
|
||||||
|
sectionKey () {
|
||||||
|
return `${this.attachmentUuid}:${this.section.id}`
|
||||||
|
},
|
||||||
sectionWidthPx () {
|
sectionWidthPx () {
|
||||||
const pt = parseFloat(this.section.style.width)
|
const pt = parseFloat(this.section.style.width)
|
||||||
|
|
||||||
@@ -310,6 +314,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
this.lastUpdateTime = 0
|
||||||
|
|
||||||
this.editorRef.value = buildEditor({
|
this.editorRef.value = buildEditor({
|
||||||
dynamicAreaProps: {
|
dynamicAreaProps: {
|
||||||
template: this.template,
|
template: this.template,
|
||||||
@@ -325,6 +331,8 @@ export default {
|
|||||||
getAttachmentsIndex: () => this.attachmentsIndex,
|
getAttachmentsIndex: () => this.attachmentsIndex,
|
||||||
onFieldDrop: this.onFieldDrop,
|
onFieldDrop: this.onFieldDrop,
|
||||||
onFieldDestroy: this.onFieldDestroy,
|
onFieldDestroy: this.onFieldDestroy,
|
||||||
|
onUndo: this.undo,
|
||||||
|
onRedo: this.redo,
|
||||||
renderHtmlForSaveRef: this.renderHtmlForSaveRef,
|
renderHtmlForSaveRef: this.renderHtmlForSaveRef,
|
||||||
editorOptions: {
|
editorOptions: {
|
||||||
element: this.$refs.editorElement,
|
element: this.$refs.editorElement,
|
||||||
@@ -333,6 +341,12 @@ export default {
|
|||||||
onUpdate: (event) => {
|
onUpdate: (event) => {
|
||||||
this.$emit('update', event)
|
this.$emit('update', event)
|
||||||
|
|
||||||
|
if (event.transaction.getMeta('addToHistory') !== false) {
|
||||||
|
this.pushUndo({ amend: event.transaction.time - this.lastUpdateTime < 500 })
|
||||||
|
|
||||||
|
this.lastUpdateTime = event.transaction.time
|
||||||
|
}
|
||||||
|
|
||||||
if (this.blockMenuCoords) {
|
if (this.blockMenuCoords) {
|
||||||
this.$nextTick(() => this.setBlockMenuCoords(this.editor))
|
this.$nextTick(() => this.setBlockMenuCoords(this.editor))
|
||||||
}
|
}
|
||||||
@@ -352,6 +366,8 @@ export default {
|
|||||||
this.editor.view.dom.addEventListener('mousemove', this.onEditorMouseMove)
|
this.editor.view.dom.addEventListener('mousemove', this.onEditorMouseMove)
|
||||||
this.editor.view.dom.addEventListener('mouseleave', this.onEditorMouseLeave)
|
this.editor.view.dom.addEventListener('mouseleave', this.onEditorMouseLeave)
|
||||||
this.editor.view.dom.addEventListener('keydown', this.onEditorKeyDown)
|
this.editor.view.dom.addEventListener('keydown', this.onEditorKeyDown)
|
||||||
|
|
||||||
|
this.$nextTick(() => this.pushUndo({ amend: true }))
|
||||||
},
|
},
|
||||||
beforeUnmount () {
|
beforeUnmount () {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
@@ -367,6 +383,19 @@ export default {
|
|||||||
reloadContent () {
|
reloadContent () {
|
||||||
this.editor.commands.setContent(this.section.innerHTML, { emitUpdate: false, parseOptions: this.editor.options.parseOptions })
|
this.editor.commands.setContent(this.section.innerHTML, { emitUpdate: false, parseOptions: this.editor.options.parseOptions })
|
||||||
},
|
},
|
||||||
|
restoreDoc (doc) {
|
||||||
|
const { state, view } = this.editor
|
||||||
|
const { content } = doc.type.schema === state.schema ? doc : state.schema.nodeFromJSON(doc.toJSON())
|
||||||
|
const diffEnd = content.findDiffEnd(state.doc.content)
|
||||||
|
|
||||||
|
if (diffEnd) {
|
||||||
|
const tr = state.tr.replaceWith(0, state.doc.content.size, content).setMeta('addToHistory', false)
|
||||||
|
|
||||||
|
view.dispatch(tr.setSelection(Selection.near(tr.doc.resolve(diffEnd.a))).scrollIntoView())
|
||||||
|
|
||||||
|
this.lastUpdateTime = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
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}"]`)
|
||||||
|
|
||||||
@@ -711,6 +740,8 @@ export default {
|
|||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
this.editor.chain().setNodeSelection(0).blur().run()
|
this.editor.chain().setNodeSelection(0).blur().run()
|
||||||
this.deselectArea()
|
this.deselectArea()
|
||||||
|
} else if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'z') {
|
||||||
|
event.stopPropagation()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onEditorMouseMove (event) {
|
onEditorMouseMove (event) {
|
||||||
@@ -826,25 +857,23 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newField = {
|
const newField = payload.parentField || {
|
||||||
...field,
|
...field,
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
submitter_uuid: this.selectedSubmitter.uuid,
|
submitter_uuid: this.selectedSubmitter.uuid,
|
||||||
areas: [area]
|
areas: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newField.areas.push(area)
|
||||||
|
|
||||||
if (['radio', 'multiple'].includes(field.type) && field.options?.length) {
|
if (['radio', 'multiple'].includes(field.type) && field.options?.length) {
|
||||||
const oldOptionUuid = area.option_uuid
|
const oldOptionIndex = field.options.findIndex((opt) => opt.uuid === area.option_uuid)
|
||||||
const optionsMap = {}
|
|
||||||
|
|
||||||
newField.options = field.options.map((opt) => {
|
if (!payload.parentField) {
|
||||||
const newUuid = v4()
|
newField.options = field.options.map((opt) => ({ ...opt, uuid: v4() }))
|
||||||
optionsMap[opt.uuid] = newUuid
|
}
|
||||||
|
|
||||||
return { ...opt, uuid: newUuid }
|
area.option_uuid = (newField.options[oldOptionIndex] || newField.options[0]).uuid
|
||||||
})
|
|
||||||
|
|
||||||
area.option_uuid = optionsMap[oldOptionUuid] || newField.options[0]?.uuid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { field: newField, area }
|
return { field: newField, area }
|
||||||
@@ -882,10 +911,13 @@ export default {
|
|||||||
|
|
||||||
let lastArea = null
|
let lastArea = null
|
||||||
|
|
||||||
|
const parentFieldsIndex = {}
|
||||||
|
|
||||||
fieldNodes.forEach((fieldNode) => {
|
fieldNodes.forEach((fieldNode) => {
|
||||||
const fieldValue = fieldNode.dataset.field
|
const fieldValue = fieldNode.dataset.field
|
||||||
const areaValue = fieldNode.dataset.area
|
const areaValue = fieldNode.dataset.area
|
||||||
const templateId = fieldNode.dataset.templateId
|
const templateId = fieldNode.dataset.templateId
|
||||||
|
const sourceFieldUuid = fieldNode.getAttribute('uuid')
|
||||||
|
|
||||||
if (!fieldValue || !areaValue) {
|
if (!fieldValue || !areaValue) {
|
||||||
return
|
return
|
||||||
@@ -894,11 +926,16 @@ export default {
|
|||||||
const { field, area } = this.buildCopiedField({
|
const { field, area } = this.buildCopiedField({
|
||||||
field: JSON.parse(fieldValue),
|
field: JSON.parse(fieldValue),
|
||||||
area: JSON.parse(areaValue),
|
area: JSON.parse(areaValue),
|
||||||
templateId: Number(templateId)
|
templateId: Number(templateId),
|
||||||
|
parentField: parentFieldsIndex[sourceFieldUuid]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!parentFieldsIndex[sourceFieldUuid]) {
|
||||||
|
parentFieldsIndex[sourceFieldUuid] = field
|
||||||
|
|
||||||
this.insertFieldInTemplate(field, insertIndex)
|
this.insertFieldInTemplate(field, insertIndex)
|
||||||
insertIndex += 1
|
insertIndex += 1
|
||||||
|
}
|
||||||
|
|
||||||
fieldNode.setAttribute('uuid', field.uuid)
|
fieldNode.setAttribute('uuid', field.uuid)
|
||||||
fieldNode.setAttribute('area-uuid', area.uuid)
|
fieldNode.setAttribute('area-uuid', area.uuid)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"@tiptap/extension-dropcursor": "^3.19.0",
|
"@tiptap/extension-dropcursor": "^3.19.0",
|
||||||
"@tiptap/extension-gapcursor": "^3.19.0",
|
"@tiptap/extension-gapcursor": "^3.19.0",
|
||||||
"@tiptap/extension-hard-break": "^3.19.0",
|
"@tiptap/extension-hard-break": "^3.19.0",
|
||||||
"@tiptap/extension-history": "^3.19.0",
|
|
||||||
"@tiptap/extension-italic": "^3.19.0",
|
"@tiptap/extension-italic": "^3.19.0",
|
||||||
"@tiptap/extension-link": "^3.19.0",
|
"@tiptap/extension-link": "^3.19.0",
|
||||||
"@tiptap/extension-paragraph": "^3.19.0",
|
"@tiptap/extension-paragraph": "^3.19.0",
|
||||||
|
|||||||
@@ -1796,11 +1796,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz#7120524cec9ed4b957963693cb4c57cbecbaecf8"
|
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz#7120524cec9ed4b957963693cb4c57cbecbaecf8"
|
||||||
integrity sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==
|
integrity sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==
|
||||||
|
|
||||||
"@tiptap/extension-history@^3.19.0":
|
|
||||||
version "3.19.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-3.19.0.tgz#dc3a52f4eb16b7ac077c28284684b558c03adf00"
|
|
||||||
integrity sha512-hhN5nL7Pqcd9iomzeUUMKnmSQieKNlJ2FUgf2dHUqqvn4pWvcHA6P6UwdDNhuKFivSJNNMqtajkOvO4bYq1KPw==
|
|
||||||
|
|
||||||
"@tiptap/extension-italic@^3.19.0":
|
"@tiptap/extension-italic@^3.19.0":
|
||||||
version "3.19.0"
|
version "3.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz#af2a9c095ec846e379041f3e17e1dd101a5a4bf8"
|
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz#af2a9c095ec846e379041f3e17e1dd101a5a4bf8"
|
||||||
|
|||||||
Reference in new issue
Block a user