diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js index 55121cb58b4..60699680c45 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js @@ -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); } } diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/shiftTab.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/shiftTab.js index be0596bab3a..dc3ae854a0f 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/shiftTab.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/shiftTab.js @@ -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; diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/toggleList.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/toggleList.js index ba064a65d00..9e595953c11 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/toggleList.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/toggleList.js @@ -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') { 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 398dd24c0da..15bf2bbf444 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 @@ -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: '', + stepFunction: toggleUnorderedList, + contentAfter: '', + }); + }); + it('should create a new unordered list if closestBlock is inside a nav-item list', async () => { + await testEditor(BasicEditor, { + contentBefore: '', + stepFunction: toggleUnorderedList, + contentAfter: '', + }); + }); }); 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: '', + stepFunction: toggleOrderedList, + contentAfter: '', + }); + }); + it('should create a new ordered list if closestBlock is inside a nav-item list', async () => { + await testEditor(BasicEditor, { + contentBefore: '', + stepFunction: toggleOrderedList, + contentAfter: '', + }); + }); }); 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: '', + stepFunction: toggleCheckList, + contentAfter: '', + }); + }); + it('should create a new checked list if closestBlock is inside a nav-item list', async () => { + await testEditor(BasicEditor, { + removeCheckIds: true, + contentBefore: '', + stepFunction: toggleCheckList, + contentAfter: '', + }); + }); }); 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: '', + stepFunction: indentList, + contentAfter: '', + }); + await testEditor(BasicEditor, { + contentBefore: '', + stepFunction: indentList, + contentAfter: '', + }); + }); }); 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(` + + `), + stepFunction: outdentList, + contentAfter: unformat(` + + `), + }); + await testEditor(BasicEditor, { + contentBefore: unformat(` + + `), + stepFunction: outdentList, + contentAfter: unformat(` + + `), + }); + await testEditor(BasicEditor, { + contentBefore: unformat(` + + `), + stepFunction: outdentList, + contentAfter: unformat(` + + `), + }); + await testEditor(BasicEditor, { + contentBefore: unformat(` + + `), + stepFunction: outdentList, + contentAfter: unformat(` + + `), + }); + await testEditor(BasicEditor, { + contentBefore: unformat(` + + `), + stepFunction: outdentList, + contentAfter: unformat(` + + `), + }); + await testEditor(BasicEditor, { + contentBefore: unformat(` + + `), + stepFunction: outdentList, + contentAfter: unformat(` + + `), + }); + }); }); describe('with selection', () => { it('should outdent the middle element of a list', async () => {