From e851bbdb29fe9b932ee1d54bb53ad1be4456ef70 Mon Sep 17 00:00:00 2001 From: Nicolas Bayet Date: Mon, 12 Sep 2022 13:12:31 +0000 Subject: [PATCH] [FIX] web_editor: fix paste style Allow to paste font tag as it is used to style colors. Allow style attribute on tag B, STRONG, I, S, U, FONT. task-2980754 closes odoo/odoo#100262 X-original-commit: 0b907fe5cfb9a1f83da4079e30e46e6711ade797 Signed-off-by: David Monjoie (dmo) --- .../js/editor/odoo-editor/src/OdooEditor.js | 17 ++++++----- .../odoo-editor/test/spec/copyPaste.test.js | 30 +++++++++---------- 2 files changed, 25 insertions(+), 22 deletions(-) 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 b71c1d2a406..3a879d5f27b 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 @@ -119,7 +119,9 @@ export const CLIPBOARD_WHITELISTS = { 'I', 'B', 'U', + 'S', 'EM', + 'FONT', 'STRONG', // Table 'TABLE', @@ -158,7 +160,8 @@ export const CLIPBOARD_WHITELISTS = { /^fa/, ], attributes: ['class', 'href', 'src'], - spanStyle: { + styledTags: ['SPAN', 'B', 'STRONG', 'I', 'S', 'U', 'FONT'], + styles: { 'text-decoration': { defaultValues: ['', 'none'] }, 'font-weight': { defaultValues: ['', '400'] }, 'background-color': { defaultValues: ['', '#fff', '#ffffff', 'rgb(255, 255, 255)', 'rgba(255, 255, 255, 1)'] }, @@ -2795,18 +2798,18 @@ export class OdooEditor extends EventTarget { // Remove all illegal attributes and classes from the node, then // clean its children. for (const attribute of [...node.attributes]) { - // we kee some style on span to be able to paste text styled in the editor - if (node.nodeName === 'SPAN' && attribute.name === 'style') { - const spanInlineStyles = attribute.value.split(';'); + // 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 => { const [styleName, styleValue] = rawStyle.split(':'); - const style = CLIPBOARD_WHITELISTS.spanStyle[styleName.trim()]; + const style = CLIPBOARD_WHITELISTS.styles[styleName.trim()]; return style && !style.defaultValues.includes(styleValue.trim()); }); node.removeAttribute(attribute.name); if (allowedSpanInlineStyles.length > 0) { node.setAttribute(attribute.name, allowedSpanInlineStyles.join(';')); - } else { + } else if (['SPAN', 'FONT'].includes(node.tagName)) { for (const unwrappedNode of unwrapContents(node)) { this._cleanForPaste(unwrappedNode); } @@ -2842,7 +2845,7 @@ export class OdooEditor extends EventTarget { okClass instanceof RegExp ? okClass.test(item) : okClass === item, ); } else { - const allowedSpanStyles = Object.keys(CLIPBOARD_WHITELISTS.spanStyle).map(s => `span[style*="${s}"]`); + const allowedSpanStyles = Object.keys(CLIPBOARD_WHITELISTS.styles).map(s => `span[style*="${s}"]`); return ( item.nodeType === Node.TEXT_NODE || ( 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 dcd4ce2eadc..47a535c9fef 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 @@ -28,7 +28,7 @@ describe('Copy and paste', () => { it('should keep whitelisted Tags tag', async () => { for (const node of CLIPBOARD_WHITELISTS.nodes) { if (!['TABLE', 'THEAD', 'TH', 'TBODY', 'TR', 'TD', 'IMG', 'BR', 'LI', '.fa'].includes(node)) { - const isInline = ['I', 'B', 'U', 'EM', 'STRONG', 'IMG', 'BR', 'A'].includes(node) + const isInline = ['I', 'B', 'U', 'S', 'EM', 'STRONG', 'IMG', 'BR', 'A', 'FONT'].includes(node); const html = isInline ? `a<${node.toLowerCase()}>bc` : `a

<${node.toLowerCase()}>b

c`; await testEditor(BasicEditor, { @@ -532,7 +532,7 @@ describe('Copy and 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 () => { @@ -541,7 +541,7 @@ describe('Copy and 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 () => { @@ -550,7 +550,7 @@ describe('Copy and paste', () => { stepFunction: async editor => { await pasteHtml(editor, complexHtmlData); }, - contentAfter: '

ab123 4[]cd

', + contentAfter: '

ab123 4[]cd

', }); }); }); @@ -561,7 +561,7 @@ describe('Copy and 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 () => { @@ -570,7 +570,7 @@ describe('Copy and 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 () => { @@ -579,14 +579,14 @@ describe('Copy and 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 () => { @@ -595,14 +595,14 @@ describe('Copy and 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 () => { @@ -611,14 +611,14 @@ describe('Copy and 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 () => { @@ -627,21 +627,21 @@ describe('Copy and 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
', }); }); });