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: '',
+ 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 () => {
@@ -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: '',
+ 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: '',
});
});
});
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: 'a[]b
',
+ contentAfter: '- a[]b
',
});
await testEditor(BasicEditor, {
contentBefore:
@@ -4957,7 +4919,7 @@ describe('List', () => {
await deleteBackward(editor);
await deleteBackward(editor);
},
- contentAfter: 'a[]b
',
+ contentAfter: '- 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:
- 'abc
def
[]ghi
klm
',
- stepFunction: deleteBackward,
- // Paragraphs in list items are treated as nonsense.
- contentAfter:
- 'abc
def[]ghi
klm
',
- });
await testEditor(BasicEditor, {
contentBefore:
'abc
def
[]ghi
klm
',
@@ -5353,26 +5307,7 @@ describe('List', () => {
'abc
def[]ghi
klm
',
});
});
- it('should merge a bold list item into a non-formatted list item', async () => {
- await testEditor(BasicEditor, {
- contentBefore:
- 'abc
defg
[]hijklm
nop
',
- stepFunction: deleteBackward,
- // Two paragraphs in a list item = Two list items.
- // Paragraphs in list items are treated as nonsense.
- contentAfter:
- 'abc
defg[]hijklm
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', () => {