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()}>b${node.toLowerCase()}>c` : `a
<${node.toLowerCase()}>b${node.toLowerCase()}>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: '',
+ contentAfter: '',
});
await testEditor(BasicEditor, {
contentBefore: '',
stepFunction: async editor => {
await pasteHtml(editor, complexHtmlData);
},
- contentAfter: '',
+ contentAfter: '',
});
});
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: '',
+ contentAfter: '',
});
await testEditor(BasicEditor, {
contentBefore: '',
stepFunction: async editor => {
await pasteHtml(editor, complexHtmlData);
},
- contentAfter: '2ab123 4[]e
f
',
+ contentAfter: '2ab123 4[]e
f
',
});
await testEditor(BasicEditor, {
contentBefore: '',
stepFunction: async editor => {
await pasteHtml(editor, complexHtmlData);
},
- contentAfter: '',
+ contentAfter: '',
});
});
});