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 99cae8935c2..e8ff0343536 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 @@ -174,7 +174,6 @@ export const CLIPBOARD_WHITELISTS = { ], attributes: ['class', 'href', 'src', 'target'], styledTags: ['SPAN', 'B', 'STRONG', 'I', 'S', 'U', 'FONT', 'TD'], - styles: ['text-decoration', 'font-weight', 'background-color', 'color', 'font-style', 'text-decoration-line', 'font-size'] }; // Commands that don't require a DOM selection but take an argument instead. @@ -3275,7 +3274,12 @@ export class OdooEditor extends EventTarget { * @param {Node} node */ _cleanForPaste(node) { - if (!this._isWhitelisted(node) || this._isBlacklisted(node)) { + if ( + !this._isWhitelisted(node) || + this._isBlacklisted(node) || + // Google Docs have their html inside a B tag with custom id. + node.id && node.id.startsWith('docs-internal-guid') + ) { if (!node.matches || node.matches(CLIPBOARD_BLACKLISTS.remove.join(','))) { node.remove(); } else { @@ -3312,14 +3316,8 @@ export class OdooEditor extends EventTarget { for (const attribute of [...node.attributes]) { // Keep allowed styles on nodes with allowed tags. if (CLIPBOARD_WHITELISTS.styledTags.includes(node.nodeName) && attribute.name === 'style') { - const spanInlineStyles = attribute.value.split(';').map(x => x.trim()); - const allowedSpanInlineStyles = spanInlineStyles.filter(rawStyle => { - return CLIPBOARD_WHITELISTS.styles.includes(rawStyle.split(':')[0].trim()); - }); node.removeAttribute(attribute.name); - if (allowedSpanInlineStyles.length > 0) { - node.setAttribute(attribute.name, allowedSpanInlineStyles.join(';')); - } else if (['SPAN', 'FONT'].includes(node.tagName)) { + if (['SPAN', 'FONT'].includes(node.tagName)) { for (const unwrappedNode of unwrapContents(node)) { this._cleanForPaste(unwrappedNode); } @@ -3355,12 +3353,11 @@ export class OdooEditor extends EventTarget { okClass instanceof RegExp ? okClass.test(item) : okClass === item, ); } else { - const allowedSpanStyles = CLIPBOARD_WHITELISTS.styles.map(s => `span[style*="${s}"]`); return ( item.nodeType === Node.TEXT_NODE || ( item.matches && - item.matches([...CLIPBOARD_WHITELISTS.nodes, ...allowedSpanStyles].join(',')) + item.matches(CLIPBOARD_WHITELISTS.nodes) ) ); } diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/sanitize.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/sanitize.js index 6b930465d42..2cd8074a036 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/sanitize.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/sanitize.js @@ -21,6 +21,7 @@ import { EMAIL_REGEX, PHONE_REGEX, URL_REGEX, + unwrapContents, } from './utils.js'; const NOT_A_NUMBER = /[^\d]/g; @@ -149,13 +150,16 @@ function sanitizeNode(node, root) { node = parent; // The node has been removed, update the reference. } else if ( node.nodeName === 'P' && // Note: not sure we should limit to

. - node.parentElement.nodeName === 'LI' && - isEmptyBlock(node) + node.parentElement.nodeName === 'LI' ) { // Remove empty paragraphs in

  • . const parent = node.parentElement; const restoreCursor = node.isConnected && preserveCursor(root.ownerDocument); - node.remove(); + if (isEmptyBlock(node)) { + node.remove(); + } else { + unwrapContents(node); + } fillEmpty(parent); restoreCursor?.(new Map([[node, parent]])); node = parent; // The node has been removed, update the reference. 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 e739147acc8..121eb082ad4 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 @@ -239,7 +239,34 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, 'abcd'); }, - contentAfter: '

    123abcd[]

    ', + contentAfter: '

    123abcd[]

    ', + }); + }); + it('should remove unwanted styles and b tag when pasting from paragraph from gdocs', async () => { + await testEditor(BasicEditor, { + contentBefore: '

    []

    ', + stepFunction: async editor => { + await pasteHtml(editor, `

    test1

    test2

    `); + }, + contentAfter: '

    test1

    test2[]

    ', + }); + }); + it('should remove unwanted b tag and p tag with unwanted styles when pasting list from gdocs', async () => { + await testEditor(BasicEditor, { + contentBefore: '

    []

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

    []

    ', + }); + }); + it('should remove unwanted styles and keep tags when pasting list from gdoc', async () => { + await testEditor(BasicEditor, { + contentBefore: '

    []

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

    []

    ', }); }); }); @@ -668,7 +695,7 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '

    123 4[]abcd

    ', + contentAfter: '

    123 4[]abcd

    ', }); }); it('should paste a text in a p', async () => { @@ -677,7 +704,7 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '

    ab123 4[]cd

    ', + contentAfter: '

    ab123 4[]cd

    ', }); }); it('should paste a text in a span', async () => { @@ -686,7 +713,7 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '

    ab123 4[]cd

    ', + contentAfter: '

    ab123 4[]cd

    ', }); }); }); @@ -697,7 +724,7 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '

    a123 4[]d

    ', + contentAfter: '

    a123 4[]d

    ', }); }); it('should paste a text in a span', async () => { @@ -706,7 +733,7 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '

    ab123 4[]ef

    ', + contentAfter: '

    ab123 4[]ef

    ', }); }); it('should paste a text when selection across two span', async () => { @@ -715,14 +742,14 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '

    ab123 4[]ef

    ', + contentAfter: '

    ab123 4[]ef

    ', }); await testEditor(BasicEditor, { contentBefore: '

    ab[c- -d]ef

    ', stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '

    ab123 4[]ef

    ', + contentAfter: '

    ab123 4[]ef

    ', }); }); it('should paste a text when selection across two p', async () => { @@ -731,14 +758,14 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '
    a

    b123 4[]e

    f
    ', + contentAfter: '
    a

    b123 4[]e

    f
    ', }); await testEditor(BasicEditor, { contentBefore: '
    a

    b[c

    - -

    d]e

    f
    ', stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '
    a

    b123 4[]e

    f
    ', + contentAfter: '
    a

    b123 4[]e

    f
    ', }); }); it('should paste a text when selection leave a span', async () => { @@ -747,14 +774,14 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '
    abc123 4[]f
    ', + contentAfter: '
    abc123 4[]f
    ', }); await testEditor(BasicEditor, { contentBefore: '
    a[bc]def
    ', stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '
    a123 4[]def
    ', + contentAfter: '
    a123 4[]def
    ', }); }); it('should paste a text when selection across two element', async () => { @@ -763,21 +790,21 @@ describe('Paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '
    1a

    b123 4[]ef

    ', + contentAfter: '
    1a

    b123 4[]ef

    ', }); await testEditor(BasicEditor, { contentBefore: '
    2ab[c

    d]e

    f
    ', stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '
    2ab123 4[]e
    f
    ', + contentAfter: '
    2ab123 4[]e
    f
    ', }); await testEditor(BasicEditor, { contentBefore: '
    3a

    b[c

    d]e

    f
    ', stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '
    3a

    b123 4[]e

    f
    ', + contentAfter: '
    3a

    b123 4[]e

    f
    ', }); }); }); diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/htmlTables.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/htmlTables.test.js index 1fc925cff4c..28b7f2e0d02 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/htmlTables.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/htmlTables.test.js @@ -223,24 +223,24 @@ describe('Paste HTML tables', () => { - Italic - then also BOLD - Italic strike + Italic + then also BOLD + Italic strike - Just bold Just Italic - Bold underline + Just bold Just Italic + Bold underline - Color text - Color strike and underline + Color text + Color strike and underline - Color background - Color text on color background + Color background + Color text on color background - 14pt MONO TEXT + 14pt MONO TEXT

    @@ -330,33 +330,33 @@ describe('Paste HTML tables', () => { - - Italic then also - BOLD + + Italic then also + BOLD - Italic strike + Italic strike - - Just - Bold Just - Italic + + Just + Bold Just + Italic - Bold underline + Bold underline - Color text - Color + Color text + Color strike and underline - Color background + Color background - Color + Color text on color background - 14pt MONO TEXT + 14pt MONO TEXT

    @@ -473,21 +473,21 @@ describe('Paste HTML tables', () => { - Color text + Color text - Color strike and underline + Color strike and underline - Color background - - Color text on color background + Color background + + Color text on color background - 14pt MONO TEXT + 14pt MONO TEXT

    diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/list.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/list.test.js index 941e77eb647..933dfca7dce 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/list.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/list.test.js @@ -2673,14 +2673,6 @@ describe('List', () => { }); describe('Complex merges with some containers parsed in list item', () => { it('should treat two blocks in a list item and keep the blocks', async () => { - await testEditor(BasicEditor, { - contentBefore: - '

    ', - stepFunction: deleteForward, - // Paragraphs in list items are treated as nonsense. - contentAfter: - '', - }); await testEditor(BasicEditor, { contentBefore: '', @@ -2691,27 +2683,7 @@ describe('List', () => { '', }); }); - it('should merge a bold list item (checked/unchecked) into a non-formatted list item', async () => { - await testEditor(BasicEditor, { - removeCheckIds: true, - contentBefore: - '', - stepFunction: deleteForward, - // Two paragraphs in a checklist item = Two list items. - // Paragraphs in list items are treated as nonsense. - contentAfter: - '', - }); - }); it('should treat two blocks in a checklist item and keep the blocks', async () => { - await testEditor(BasicEditor, { - removeCheckIds: true, - contentBefore: - '', - stepFunction: deleteForward, - contentAfter: - '', - }); await testEditor(BasicEditor, { removeCheckIds: true, contentBefore: @@ -2721,16 +2693,6 @@ describe('List', () => { '', }); }); - it('should merge a bold list item into a non-formatted list item', async () => { - await testEditor(BasicEditor, { - removeCheckIds: true, - contentBefore: - '', - stepFunction: deleteForward, - contentAfter: - '', - }); - }); }); }); describe('Selection not collapsed', () => { @@ -4842,7 +4804,7 @@ describe('List', () => { await deleteBackward(editor); await deleteBackward(editor); }, - contentAfter: '', + contentAfter: '', }); await testEditor(BasicEditor, { contentBefore: @@ -4851,7 +4813,7 @@ describe('List', () => { await deleteBackward(editor); await deleteBackward(editor); }, - contentAfter: '', + contentAfter: '', }); }); it('should merge an ordered list item that is in an unordered list item into a non-indented list item', async () => { @@ -4948,7 +4910,7 @@ describe('List', () => { await deleteBackward(editor); await deleteBackward(editor); }, - contentAfter: '
    1. a[]b

    ', + contentAfter: '
    1. a[]b
    ', }); await testEditor(BasicEditor, { contentBefore: @@ -4957,7 +4919,7 @@ describe('List', () => { await deleteBackward(editor); await deleteBackward(editor); }, - contentAfter: '
    1. a[]b

    ', + contentAfter: '
    1. a[]b
    ', }); }); it('should merge an unordered list item that is in an ordered list item into a non-indented list item', async () => { @@ -5075,7 +5037,7 @@ describe('List', () => { await deleteBackward(editor); await deleteBackward(editor); }, - contentAfter: '', + contentAfter: '', }); await testEditor(BasicEditor, { contentBefore: @@ -5084,7 +5046,7 @@ describe('List', () => { await deleteBackward(editor); await deleteBackward(editor); }, - contentAfter: '', + contentAfter: '', }); }); it('should merge an checklist list item that is in an unordered list item into a non-indented list item', async () => { @@ -5198,7 +5160,7 @@ describe('List', () => { }, // Paragraphs in list items are kept unless empty contentAfter: - '', + '', }); await testEditor(BasicEditor, { removeCheckIds: true, @@ -5210,7 +5172,7 @@ describe('List', () => { }, // Paragraphs in list items are kept unless empty contentAfter: - '', + '', }); }); it('should merge an unordered list item that is in an checklist list item into a non-indented list item', async () => { @@ -5335,14 +5297,6 @@ describe('List', () => { }); describe('Complex merges with some containers parsed in list item', () => { it('should treat two blocks in a list item and keep blocks', async () => { - await testEditor(BasicEditor, { - contentBefore: - '
    1. abc

    2. def

      []ghi

    3. klm

    ', - stepFunction: deleteBackward, - // Paragraphs in list items are treated as nonsense. - contentAfter: - '
    1. abc

    2. def[]ghi

    3. klm

    ', - }); await testEditor(BasicEditor, { contentBefore: '
    1. abc

    2. def

      []ghi

    3. klm

    ', @@ -5353,26 +5307,7 @@ describe('List', () => { '
    1. abc

    2. def[]ghi

    3. klm

    ', }); }); - it('should merge a bold list item into a non-formatted list item', async () => { - await testEditor(BasicEditor, { - contentBefore: - '
    1. abc

    2. defg

      []hijklm

    3. nop

    ', - stepFunction: deleteBackward, - // Two paragraphs in a list item = Two list items. - // Paragraphs in list items are treated as nonsense. - contentAfter: - '
    1. abc

    2. defg[]hijklm

    3. nop

    ', - }); - }); it('should treat two blocks in a list item and keep blocks', async () => { - await testEditor(BasicEditor, { - contentBefore: - '', - stepFunction: deleteBackward, - // Paragraphs in list items are treated as nonsense. - contentAfter: - '', - }); await testEditor(BasicEditor, { contentBefore: '', @@ -5383,27 +5318,7 @@ describe('List', () => { '', }); }); - it('should merge a bold list item into a non-formatted list item', async () => { - await testEditor(BasicEditor, { - contentBefore: - '', - stepFunction: deleteBackward, - // Two paragraphs in a list item = Two list items. - // Paragraphs in list items are treated as nonsense. - contentAfter: - '', - }); - }); it('should treat two blocks in a list item and keep blocks', async () => { - await testEditor(BasicEditor, { - removeCheckIds: true, - contentBefore: - '', - stepFunction: deleteBackward, - // Paragraphs in list items are treated as nonsense. - contentAfter: - '', - }); await testEditor(BasicEditor, { removeCheckIds: true, contentBefore: @@ -5415,18 +5330,6 @@ describe('List', () => { '', }); }); - it('should merge a bold list item into a non-formatted list item', async () => { - await testEditor(BasicEditor, { - removeCheckIds: true, - contentBefore: - '', - stepFunction: deleteBackward, - // Two paragraphs in a list item = Two list items. - // Paragraphs in list items are treated as nonsense. - contentAfter: - '', - }); - }); }); }); describe('Selection not collapsed', () => {