[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) <dmo@odoo.com>
This commit is contained in:
@@ -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 ||
|
||||
(
|
||||
|
||||
@@ -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</p><${node.toLowerCase()}>b</${node.toLowerCase()}><p>c`;
|
||||
|
||||
await testEditor(BasicEditor, {
|
||||
@@ -532,7 +532,7 @@ describe('Copy and paste', () => {
|
||||
stepFunction: async editor => {
|
||||
await pasteHtml(editor, complexHtmlData);
|
||||
},
|
||||
contentAfter: '<p>1<b>23</b> 4[]abcd</p>',
|
||||
contentAfter: '<p>1<b style="font-weight: bolder">23</b> 4[]abcd</p>',
|
||||
});
|
||||
});
|
||||
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: '<p>ab1<b>23</b> 4[]cd</p>',
|
||||
contentAfter: '<p>ab1<b style="font-weight: bolder">23</b> 4[]cd</p>',
|
||||
});
|
||||
});
|
||||
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: '<p>a<span>b1<b>23</b> 4[]c</span>d</p>',
|
||||
contentAfter: '<p>a<span>b1<b style="font-weight: bolder">23</b> 4[]c</span>d</p>',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -561,7 +561,7 @@ describe('Copy and paste', () => {
|
||||
stepFunction: async editor => {
|
||||
await pasteHtml(editor, complexHtmlData);
|
||||
},
|
||||
contentAfter: '<p>a1<b>23</b> 4[]d</p>',
|
||||
contentAfter: '<p>a1<b style="font-weight: bolder">23</b> 4[]d</p>',
|
||||
});
|
||||
});
|
||||
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: '<p>a<span>b1<b>23</b> 4[]e</span>f</p>',
|
||||
contentAfter: '<p>a<span>b1<b style="font-weight: bolder">23</b> 4[]e</span>f</p>',
|
||||
});
|
||||
});
|
||||
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: '<p>a<span>b1<b>23</b> 4[]e</span>f</p>',
|
||||
contentAfter: '<p>a<span>b1<b style="font-weight: bolder">23</b> 4[]e</span>f</p>',
|
||||
});
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<p>a<span>b[c</span>- -<span>d]e</span>f</p>',
|
||||
stepFunction: async editor => {
|
||||
await pasteHtml(editor, complexHtmlData);
|
||||
},
|
||||
contentAfter: '<p>a<span>b1<b>23</b> 4[]e</span>f</p>',
|
||||
contentAfter: '<p>a<span>b1<b style="font-weight: bolder">23</b> 4[]e</span>f</p>',
|
||||
});
|
||||
});
|
||||
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: '<div>a<p>b1<b>23</b> 4[]e</p>f</div>',
|
||||
contentAfter: '<div>a<p>b1<b style="font-weight: bolder">23</b> 4[]e</p>f</div>',
|
||||
});
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<div>a<p>b[c</p>- -<p>d]e</p>f</div>',
|
||||
stepFunction: async editor => {
|
||||
await pasteHtml(editor, complexHtmlData);
|
||||
},
|
||||
contentAfter: '<div>a<p>b1<b>23</b> 4[]e</p>f</div>',
|
||||
contentAfter: '<div>a<p>b1<b style="font-weight: bolder">23</b> 4[]e</p>f</div>',
|
||||
});
|
||||
});
|
||||
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: '<div>ab<span>c1<b>23</b> 4[]</span>f</div>',
|
||||
contentAfter: '<div>ab<span>c1<b style="font-weight: bolder">23</b> 4[]</span>f</div>',
|
||||
});
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<div>a[b<span>c]d</span>ef</div>',
|
||||
stepFunction: async editor => {
|
||||
await pasteHtml(editor, complexHtmlData);
|
||||
},
|
||||
contentAfter: '<div>a1<b>23</b> 4[]<span>d</span>ef</div>',
|
||||
contentAfter: '<div>a1<b style="font-weight: bolder">23</b> 4[]<span>d</span>ef</div>',
|
||||
});
|
||||
});
|
||||
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: '<div>1a<p>b1<b>23</b> 4[]<span>e</span>f</p></div>',
|
||||
contentAfter: '<div>1a<p>b1<b style="font-weight: bolder">23</b> 4[]<span>e</span>f</p></div>',
|
||||
});
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<div>2a<span>b[c</span><p>d]e</p>f</div>',
|
||||
stepFunction: async editor => {
|
||||
await pasteHtml(editor, complexHtmlData);
|
||||
},
|
||||
contentAfter: '<div>2a<span>b1<b>23</b> 4[]</span>e<br>f</div>',
|
||||
contentAfter: '<div>2a<span>b1<b style="font-weight: bolder">23</b> 4[]</span>e<br>f</div>',
|
||||
});
|
||||
await testEditor(BasicEditor, {
|
||||
contentBefore: '<div>3a<p>b[c</p><p>d]e</p>f</div>',
|
||||
stepFunction: async editor => {
|
||||
await pasteHtml(editor, complexHtmlData);
|
||||
},
|
||||
contentAfter: '<div>3a<p>b1<b>23</b> 4[]e</p>f</div>',
|
||||
contentAfter: '<div>3a<p>b1<b style="font-weight: bolder">23</b> 4[]e</p>f</div>',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user