[FIX] web_editor: do not toggle list with class nav-item

When inserting a list to a mega menu, the closest `LI` is a nav-item within the
navbar. We aim to prevent the toggling of this list. This commit ensures that
lists with class nav-item in the navbar are not toggled.

task-3609500

X-original-commit: a64a37aeeb5c6f6eb838cf5b9c4221e8522721cf
Part-of: odoo/odoo#153211
This commit is contained in:
Deependra Solanki
2024-02-08 12:29:23 +00:00
parent a00aff8b95
commit 511680501f
4 changed files with 212 additions and 9 deletions
@@ -519,7 +519,8 @@ export const editorCommands = {
cli.tagName == 'LI' &&
!li.has(cli) &&
!cli.classList.contains('oe-nested') &&
cli.isContentEditable
cli.isContentEditable &&
!cli.classList.contains('nav-item')
) {
li.add(cli);
}
@@ -546,10 +547,14 @@ export const editorCommands = {
if (node.nodeType === Node.TEXT_NODE && isWhitespace(node) && closestElement(node).isContentEditable) {
node.remove();
} else {
// Ensure nav-item lists are excluded from toggling
const isNavItemList = node => node.nodeName === 'LI' && node.classList.contains('nav-item');
let block = closestBlock(node);
if (!['OL', 'UL'].includes(block.tagName) && block.isContentEditable) {
block = block.closest('li') || block;
const ublock = block.closest('ol, ul');
block = isNavItemList(block) ? node : block;
if (!['OL', 'UL'].includes(block.tagName) && (block.isContentEditable || block.nodeType === Node.TEXT_NODE)) {
const closestLi = closestElement(block, 'li');
block = closestLi && !isNavItemList(closestLi) ? closestLi : block;
const ublock = block.nodeName === 'LI' && block.closest('ol, ul');
ublock && getListMode(ublock) == mode ? li.add(block) : blocks.add(block);
}
}
@@ -31,7 +31,10 @@ HTMLLIElement.prototype.oShiftTab = function () {
}
const restoreCursor = preserveCursor(this.ownerDocument);
if (li.parentNode.parentNode.tagName === 'LI') {
if (
li.parentNode.parentNode.tagName === 'LI' &&
!li.parentNode.parentNode.classList.contains('nav-item')
) {
const ul = li.parentNode;
const shouldRemoveParentLi = !li.previousElementSibling && !ul.previousElementSibling;
const toremove = shouldRemoveParentLi ? ul.parentNode : null;
@@ -8,19 +8,28 @@ import {
toggleClass,
insertListAfter,
getAdjacents,
closestElement,
} from '../utils/utils.js';
Text.prototype.oToggleList = function (offset, mode) {
this.parentElement.oToggleList(childNodeIndex(this), mode);
// Create a new list if textNode is inside a nav-item list
if (closestElement(this, 'li').classList.contains('nav-item')) {
const restoreCursor = preserveCursor(this.ownerDocument);
insertListAfter(this, mode, [this]);
restoreCursor();
} else {
this.parentElement.oToggleList(childNodeIndex(this), mode);
}
};
HTMLElement.prototype.oToggleList = function (offset, mode = 'UL') {
if (!isBlock(this)) {
return this.parentElement.oToggleList(childNodeIndex(this));
}
const inLI = this.closest('li');
if (inLI) {
return inLI.oToggleList(0, mode);
const closestLi = this.closest('li');
// Do not toggle nav-item list as they don't behave like regular list items
if (closestLi && !closestLi.classList.contains('nav-item')) {
return closestLi.oToggleList(0, mode);
}
const restoreCursor = preserveCursor(this.ownerDocument);
if (this.oid === 'root') {
@@ -114,6 +114,20 @@ describe('List', () => {
`),
});
});
it('should create a new unordered list if current node is inside a nav-item list', async () => {
await testEditor(BasicEditor, {
contentBefore: '<ul><li class="nav-item">a[]b</li></ul>',
stepFunction: toggleUnorderedList,
contentAfter: '<ul><li class="nav-item"><ul><li>a[]b</li></ul></li></ul>',
});
});
it('should create a new unordered list if closestBlock is inside a nav-item list', async () => {
await testEditor(BasicEditor, {
contentBefore: '<ul><li class="nav-item"><div><p>a[]b</p></div></li></ul>',
stepFunction: toggleUnorderedList,
contentAfter: '<ul><li class="nav-item"><div><ul><li>a[]b</li></ul></div></li></ul>',
});
});
});
describe('Remove', () => {
it('should turn an empty list into a paragraph', async () => {
@@ -355,6 +369,20 @@ describe('List', () => {
`),
});
});
it('should create a new ordered list if current node is inside a nav-item list', async () => {
await testEditor(BasicEditor, {
contentBefore: '<ul><li class="nav-item">a[]b</li></ul>',
stepFunction: toggleOrderedList,
contentAfter: '<ul><li class="nav-item"><ol><li>a[]b</li></ol></li></ul>',
});
});
it('should create a new ordered list if closestBlock is inside a nav-item list', async () => {
await testEditor(BasicEditor, {
contentBefore: '<ul><li class="nav-item"><div><h1>a[]b</h1></div></li></ul>',
stepFunction: toggleOrderedList,
contentAfter: '<ul><li class="nav-item"><div><ol><li><h1>a[]b</h1></li></ol></div></li></ul>',
});
});
});
describe('Remove', () => {
it('should turn an empty list into a paragraph', async () => {
@@ -674,6 +702,22 @@ describe('List', () => {
`),
});
});
it('should create a new checked list if current node is inside a nav-item list', async () => {
await testEditor(BasicEditor, {
removeCheckIds: true,
contentBefore: '<ul><li class="nav-item">a[]b</li></ul>',
stepFunction: toggleCheckList,
contentAfter: '<ul><li class="nav-item"><ul class="o_checklist"><li>a[]b</li></ul></li></ul>',
});
});
it('should create a new checked list if closestBlock is inside a nav-item list', async () => {
await testEditor(BasicEditor, {
removeCheckIds: true,
contentBefore: '<ul><li class="nav-item"><div><p>a[]b</p></div></li></ul>',
stepFunction: toggleCheckList,
contentAfter: '<ul><li class="nav-item"><div><ul class="o_checklist"><li>a[]b</li></ul></div></li></ul>',
});
});
});
describe('Remove', () => {
it('should turn an empty list into a paragraph', async () => {
@@ -7493,6 +7537,18 @@ describe('List', () => {
`),
});
});
it('should not indent a nav-item list', async () => {
await testEditor(BasicEditor, {
contentBefore: '<ul><li class="nav-item">a[]</li></ul>',
stepFunction: indentList,
contentAfter: '<ul><li class="nav-item">a[]</li></ul>',
});
await testEditor(BasicEditor, {
contentBefore: '<ul><li class="nav-item"><div><p>a[]</p></div></li></ul>',
stepFunction: indentList,
contentAfter: '<ul><li class="nav-item"><div><p>a[]</p></div></li></ul>',
});
});
});
describe('with selection', () => {
it('should indent the first element of a list', async () => {
@@ -8192,6 +8248,136 @@ describe('List', () => {
`),
});
});
it('should outdent a list inside a nav-item list', async () => {
await testEditor(BasicEditor, {
contentBefore: unformat(`
<ul>
<li class="nav-item">
<ul>
<li>a[]b</li>
</ul>
</li>
</ul>
`),
stepFunction: outdentList,
contentAfter: unformat(`
<ul>
<li class="nav-item">
<p>a[]b</p>
</li>
</ul>
`),
});
await testEditor(BasicEditor, {
contentBefore: unformat(`
<ul>
<li class="nav-item">
<ol>
<li>a[]b</li>
</ol>
</li>
</ul>
`),
stepFunction: outdentList,
contentAfter: unformat(`
<ul>
<li class="nav-item">
<p>a[]b</p>
</li>
</ul>
`),
});
await testEditor(BasicEditor, {
contentBefore: unformat(`
<ul>
<li class="nav-item">
<ul class="o_checklist">
<li>a[]b</li>
</ul>
</li>
</ul>
`),
stepFunction: outdentList,
contentAfter: unformat(`
<ul>
<li class="nav-item">
<p>a[]b</p>
</li>
</ul>
`),
});
await testEditor(BasicEditor, {
contentBefore: unformat(`
<ul>
<li class="nav-item">
<div>
<ul>
<li>a[]b</li>
</ul>
</div>
</li>
</ul>
`),
stepFunction: outdentList,
contentAfter: unformat(`
<ul>
<li class="nav-item">
<div>
<p>a[]b</p>
</div>
</li>
</ul>
`),
});
await testEditor(BasicEditor, {
contentBefore: unformat(`
<ul>
<li class="nav-item">
<div>
<ol>
<li>
<h1>a[]b</h1>
</li>
</ol>
</div>
</li>
</ul>
`),
stepFunction: outdentList,
contentAfter: unformat(`
<ul>
<li class="nav-item">
<div>
<h1>a[]b</h1>
</div>
</li>
</ul>
`),
});
await testEditor(BasicEditor, {
contentBefore: unformat(`
<ul>
<li class="nav-item">
<div>
<ul class="o_checklist">
<li>a[]b</li>
</ul>
</div>
</li>
</ul>
`),
stepFunction: outdentList,
contentAfter: unformat(`
<ul>
<li class="nav-item">
<div>
<p>a[]b</p>
</div>
</li>
</ul>
`),
});
});
});
describe('with selection', () => {
it('should outdent the middle element of a list', async () => {