diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js index d400116069e..2f318ef1c2a 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js @@ -589,6 +589,7 @@ export class OdooEditor extends EventTarget { this.addDomListener(this.editable, 'paste', this._onPaste); this.addDomListener(this.editable, 'drop', this._onDrop); + this.addDomListener(this.document, 'copy', this._onClipboardCopy); this.addDomListener(this.document, 'selectionchange', this._onSelectionChange); this.addDomListener(this.document, 'selectionchange', this._handleCommandHint); this.addDomListener(this.document, 'keydown', this._onDocumentKeydown); @@ -3081,6 +3082,26 @@ export class OdooEditor extends EventTarget { } } + _onClipboardCopy(clipboardEvent) { + if (this.isSelectionInEditable()) { + clipboardEvent.preventDefault(); + const selection = this.document.getSelection(); + const range = selection.getRangeAt(0); + const rangeContent = range.cloneContents(); + + const dataHtmlElement = document.createElement('data'); + dataHtmlElement.append(rangeContent); + const odooHtml = dataHtmlElement.innerHTML; + const odooText = dataHtmlElement.innerText; + if (!clipboardEvent.clipboardData.getData('text/plain')) { + clipboardEvent.clipboardData.setData('text/plain', odooText); + } + if (!clipboardEvent.clipboardData.getData('text/html')) { + clipboardEvent.clipboardData.setData('text/html', odooHtml); + } + clipboardEvent.clipboardData.setData('text/odoo-editor', odooHtml); + } + } /** * @private */ @@ -3882,8 +3903,15 @@ export class OdooEditor extends EventTarget { ev.preventDefault(); const sel = this.document.getSelection(); const files = getImageFiles(ev.clipboardData); + const odooEditorHtml = ev.clipboardData.getData('text/odoo-editor'); const clipboardHtml = ev.clipboardData.getData('text/html'); - if (clipboardHtml) { + if (odooEditorHtml) { + const fragment = parseHTML(odooEditorHtml); + DOMPurify.sanitize(fragment, { IN_PLACE: true }); + if (fragment.hasChildNodes()) { + this.execCommand('insert', fragment); + } + } else if (clipboardHtml) { this.execCommand('insert', this._prepareClipboardData(clipboardHtml)); } else if (files.length) { this.addImagesFiles(files); diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/copyPaste.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/copyPaste.test.js index 47a535c9fef..d00c4d0f2da 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/copyPaste.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/copyPaste.test.js @@ -21,6 +21,7 @@ const pasteData = async function (editor, text, type) { const pasteText = async (editor, text) => pasteData(editor, text, 'text/plain'); const pasteHtml = async (editor, html) => pasteData(editor, html, 'text/html'); +const pasteOdooEditorHtml = async (editor, html) => pasteData(editor, html, 'text/odoo-editor'); describe('Copy and paste', () => { describe('Html Paste cleaning', () => { @@ -1479,4 +1480,24 @@ describe('Copy and paste', () => { }); }); }); + describe('Odoo editor own html', () => { + it('should paste html as is', async () => { + await testEditor(BasicEditor, { + contentBefore: '

a[]b

', + stepFunction: async editor => { + await pasteOdooEditorHtml(editor, '
b
'); + }, + contentAfter: '

a

b
[]

b

', + }); + }); + it('should not paste unsafe content', async () => { + await testEditor(BasicEditor, { + contentBefore: '

a[]b

', + stepFunction: async editor => { + await pasteOdooEditorHtml(editor, ``); + }, + contentAfter: '

a[]b

', + }); + }); + }); });