diff --git a/addons/html_editor/static/src/editor/list/list_plugin.js b/addons/html_editor/static/src/editor/list/list_plugin.js index cf36206a6653e..52c9a5166ff05 100644 --- a/addons/html_editor/static/src/editor/list/list_plugin.js +++ b/addons/html_editor/static/src/editor/list/list_plugin.js @@ -6,7 +6,14 @@ import { copyAttributes, removeClass, setTagName, toggleClass } from "../utils/d import { isVisible } from "../utils/dom_info"; import { closestElement, descendants, getAdjacents } from "../utils/dom_traversal"; import { getTraversedBlocks } from "../utils/selection"; -import { applyToTree, createList, getListMode, insertListAfter, mergeSimilarLists } from "./utils"; +import { + applyToTree, + createList, + getListMode, + insertListAfter, + mergeSimilarLists, + unwrapParagraphInLI, +} from "./utils"; // @todo @phoenix: isFormatApplied for toolbar buttons should probably // get a selection as parameter instead of the editable. @@ -107,7 +114,7 @@ export class ListPlugin extends Plugin { this.toggleList(payload.mode); break; case "NORMALIZE": { - this.mergeLists(payload.node); + this.normalize(payload.node); break; } } @@ -183,12 +190,16 @@ export class ListPlugin extends Plugin { this.dispatch("ADD_STEP"); } - mergeLists(root = this.editable) { + normalize(root = this.editable) { const closestNestedLI = closestElement(root, "li.oe-nested"); if (closestNestedLI) { root = closestNestedLI; } - applyToTree(root, mergeSimilarLists); + applyToTree(root, (element) => { + element = mergeSimilarLists(element); + element = unwrapParagraphInLI(element); + return element; + }); } // -------------------------------------------------------------------------- diff --git a/addons/html_editor/static/src/editor/list/utils.js b/addons/html_editor/static/src/editor/list/utils.js index 2e9b8b1617f3b..0e4a604282803 100644 --- a/addons/html_editor/static/src/editor/list/utils.js +++ b/addons/html_editor/static/src/editor/list/utils.js @@ -1,3 +1,4 @@ +import { unwrapContents } from "../utils/dom"; import { getAdjacents } from "../utils/dom_traversal"; import { preserveCursor } from "../utils/selection"; @@ -70,6 +71,7 @@ function compareListTypes(a, b) { return true; } +// @todo @phoenix: use the selection plugin to preserve the cursor export function mergeSimilarLists(element) { if (!element.matches("ul, ol, li.oe-nested")) { return element; @@ -80,6 +82,20 @@ export function mergeSimilarLists(element) { return mergedList; } +// @todo @phoenix: use the selection plugin to preserve the cursor +// @todo @phoenix: wrap P in a span if P has classes (mind the oe-hint class) +export function unwrapParagraphInLI(element) { + if (!element.matches("li > p")) { + return element; + } + const parentLI = element.parentElement; + const restoreCursor = preserveCursor(element.ownerDocument); + const contents = unwrapContents(element); + restoreCursor(new Map([element, parentLI])); + // This assumes an empty P has at least one child (BR). + return contents[0]; +} + export function applyToTree(root, func) { const modifiedRoot = func(root); let next = modifiedRoot.firstElementChild; diff --git a/addons/html_editor/static/tests/editor/list/delete_backward.test.js b/addons/html_editor/static/tests/editor/list/delete_backward.test.js index 5f33f376aed5f..db57f9ee5042c 100644 --- a/addons/html_editor/static/tests/editor/list/delete_backward.test.js +++ b/addons/html_editor/static/tests/editor/list/delete_backward.test.js @@ -45,7 +45,7 @@ describe("Selection collapsed", () => { }); }); - test.todo("should remove the only character in a list", async () => { + test("should remove the only character in a list", async () => { await testEditor({ contentBefore: "
  1. a[]
", stepFunction: deleteBackward, @@ -405,7 +405,7 @@ describe("Selection collapsed", () => { }); }); - test.todo("should remove the only character in a list", async () => { + test("should remove the only character in a list", async () => { await testEditor({ contentBefore: "", stepFunction: deleteBackward, @@ -796,7 +796,7 @@ describe("Selection collapsed", () => { }); }); - test.todo("should remove the only character in a list", async () => { + test("should remove the only character in a list", async () => { await testEditor({ removeCheckIds: true, contentBefore: '', @@ -1357,7 +1357,7 @@ describe("Selection collapsed", () => { }); describe("Mixed", () => { describe("Ordered to unordered", () => { - test.todo("should merge an ordered list into an unordered list", async () => { + test("should merge an ordered list into an unordered list", async () => { await testEditor({ contentBefore: "
  1. []b
", stepFunction: async (editor) => { @@ -1467,7 +1467,7 @@ describe("Selection collapsed", () => { }); }); describe("Unordered to ordered", () => { - test.todo("should merge an unordered list into an ordered list", async () => { + test("should merge an unordered list into an ordered list", async () => { await testEditor({ contentBefore: "
  1. a
", stepFunction: async (editor) => { @@ -1595,7 +1595,7 @@ describe("Selection collapsed", () => { }); }); describe("Checklist to unordered", () => { - test.todo("should merge an checklist list into an unordered list", async () => { + test("should merge an checklist list into an unordered list", async () => { await testEditor({ contentBefore: '', stepFunction: async (editor) => { @@ -1716,7 +1716,7 @@ describe("Selection collapsed", () => { }); }); describe("Unordered to checklist", () => { - test.todo("should merge an unordered list into an checklist list", async () => { + test("should merge an unordered list into an checklist list", async () => { await testEditor({ removeCheckIds: true, contentBefore: diff --git a/addons/html_editor/static/tests/editor/list/delete_forward.test.js b/addons/html_editor/static/tests/editor/list/delete_forward.test.js index dad7d69add128..8b570b589b9fe 100644 --- a/addons/html_editor/static/tests/editor/list/delete_forward.test.js +++ b/addons/html_editor/static/tests/editor/list/delete_forward.test.js @@ -54,7 +54,7 @@ describe("Selection collapsed", () => { }); }); - test.todo("should remove the only character in a list", async () => { + test("should remove the only character in a list", async () => { await testEditor({ contentBefore: "", stepFunction: deleteForward, @@ -255,7 +255,7 @@ describe("Selection collapsed", () => { }); }); - test.todo("should remove the only character in a checklist", async () => { + test("should remove the only character in a checklist", async () => { await testEditor({ removeCheckIds: true, contentBefore: '',