diff --git a/frontend/text-editor/src/editor/TextEditor.test.js b/frontend/text-editor/src/editor/TextEditor.test.js
index cb78558a16..3bfcc22ae8 100644
--- a/frontend/text-editor/src/editor/TextEditor.test.js
+++ b/frontend/text-editor/src/editor/TextEditor.test.js
@@ -1,5 +1,5 @@
import { describe, test, expect } from "vitest";
-import { TextEditor } from "./TextEditor.js";
+import { TextEditor, createRootFromHTML } from "./TextEditor.js";
/* @vitest-environment jsdom */
describe("TextEditor", () => {
@@ -97,4 +97,24 @@ describe("TextEditor", () => {
expect(document.activeElement).toBe(textEditor.element);
expect(selection.containsNode(textEditor.root));
});
+
+ test("createRootFromHTML should keep the HTML structure when HTML paste is allowed", () => {
+ const root = createRootFromHTML(
+ "
Hello, World!
",
+ undefined,
+ true,
+ );
+ expect(root.dataset.itype).toBe("root");
+ expect(root.children).toHaveLength(1);
+ expect(root.firstElementChild.children).toHaveLength(2);
+ expect(root.textContent).toBe("Hello, World!");
+ });
+
+ test("createRootFromHTML should flatten to plain text when HTML paste is not allowed", () => {
+ const root = createRootFromHTML("Hello, World!
");
+ expect(root.dataset.itype).toBe("root");
+ expect(root.children).toHaveLength(1);
+ expect(root.firstElementChild.children).toHaveLength(1);
+ expect(root.textContent).toBe("Hello, World!");
+ });
});
diff --git a/frontend/text-editor/src/editor/clipboard/paste.js b/frontend/text-editor/src/editor/clipboard/paste.js
index ea699349a9..683d40edb0 100644
--- a/frontend/text-editor/src/editor/clipboard/paste.js
+++ b/frontend/text-editor/src/editor/clipboard/paste.js
@@ -15,6 +15,7 @@ import { TextEditor } from "../TextEditor.js";
/**
* Returns a DocumentFragment from text/html.
*
+ * @param {SelectionController} selectionController
* @param {DataTransfer} clipboardData
* @returns {DocumentFragment}
*/
@@ -25,12 +26,14 @@ function getFormattedFragmentFromClipboardData(
return mapContentFragmentFromHTML(
clipboardData.getData("text/html"),
selectionController.currentStyle,
+ true,
);
}
/**
* Returns a DocumentFragment from text/plain.
*
+ * @param {SelectionController} selectionController
* @param {DataTransfer} clipboardData
* @returns {DocumentFragment}
*/
@@ -44,6 +47,7 @@ function getPlainFragmentFromClipboardData(selectionController, clipboardData) {
/**
* Returns a document fragment of html data.
*
+ * @param {SelectionController} selectionController
* @param {DataTransfer} clipboardData
* @returns {DocumentFragment}
*/
@@ -83,6 +87,7 @@ export function paste(event, editor, selectionController) {
let fragment = null;
if (editor?.options?.allowHTMLPaste) {
fragment = getFormattedOrPlainFragmentFromClipboardData(
+ selectionController,
event.clipboardData,
);
} else {
diff --git a/frontend/text-editor/src/editor/clipboard/paste.test.js b/frontend/text-editor/src/editor/clipboard/paste.test.js
index 14dd7a9ac0..53658cac7c 100644
--- a/frontend/text-editor/src/editor/clipboard/paste.test.js
+++ b/frontend/text-editor/src/editor/clipboard/paste.test.js
@@ -5,6 +5,24 @@ import { paste } from "./paste.js";
/* @vitest-environment jsdom */
+/**
+ * Creates a minimal `ClipboardEvent`-like object carrying the given data.
+ *
+ * @param {Object.} data
+ * @returns {object}
+ */
+function createClipboardEvent(data) {
+ return {
+ preventDefault() {},
+ clipboardData: {
+ types: Object.keys(data),
+ getData(type) {
+ return data[type] ?? "";
+ },
+ },
+ };
+}
+
/**
* Creates a minimal `ClipboardEvent`-like object carrying plain text.
*
@@ -12,15 +30,7 @@ import { paste } from "./paste.js";
* @returns {object}
*/
function createPlainTextClipboardEvent(text) {
- return {
- preventDefault() {},
- clipboardData: {
- types: ["text/plain"],
- getData(type) {
- return type === "text/plain" ? text : "";
- },
- },
- };
+ return createClipboardEvent({ "text/plain": text });
}
describe("paste", () => {
@@ -64,4 +74,45 @@ describe("paste", () => {
expect(root.textContent).toBe("Hello, World!");
});
+
+ test("should insert the HTML contents when the editor allows HTML paste", () => {
+ const textEditorMock = TextEditorMock.createTextEditorMockWithText("");
+ textEditorMock.options = { allowHTMLPaste: true };
+ const selection = document.getSelection();
+ const selectionController = new SelectionController(
+ textEditorMock,
+ selection,
+ );
+ textEditorMock.element.focus();
+
+ paste(
+ createClipboardEvent({
+ "text/html": "Hello, World!
",
+ "text/plain": "ignored",
+ }),
+ textEditorMock,
+ selectionController,
+ );
+
+ expect(textEditorMock.root.textContent).toBe("Hello, World!");
+ });
+
+ test("should fall back to plain text when the editor allows HTML paste but there is no HTML", () => {
+ const textEditorMock = TextEditorMock.createTextEditorMockWithText("");
+ textEditorMock.options = { allowHTMLPaste: true };
+ const selection = document.getSelection();
+ const selectionController = new SelectionController(
+ textEditorMock,
+ selection,
+ );
+ textEditorMock.element.focus();
+
+ paste(
+ createPlainTextClipboardEvent("Hello, World!"),
+ textEditorMock,
+ selectionController,
+ );
+
+ expect(textEditorMock.root.textContent).toBe("Hello, World!");
+ });
});
diff --git a/frontend/text-editor/src/editor/content/dom/Content.js b/frontend/text-editor/src/editor/content/dom/Content.js
index 0cb742992d..b04dd7636b 100644
--- a/frontend/text-editor/src/editor/content/dom/Content.js
+++ b/frontend/text-editor/src/editor/content/dom/Content.js
@@ -207,7 +207,11 @@ export function mapContentFragmentFromHTML(
try {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
- return mapContentFragmentFromDocument(document, styleDefaults);
+ return mapContentFragmentFromDocument(
+ document,
+ document.body,
+ styleDefaults,
+ );
} catch (error) {
console.error("Couldn't parse HTML", html, error);
const plainText = htmlToText(html);
diff --git a/frontend/text-editor/src/editor/content/dom/Content.test.js b/frontend/text-editor/src/editor/content/dom/Content.test.js
index 577e41d66b..7480a1642c 100644
--- a/frontend/text-editor/src/editor/content/dom/Content.test.js
+++ b/frontend/text-editor/src/editor/content/dom/Content.test.js
@@ -43,6 +43,19 @@ describe("Content", () => {
expect(contentFragment.textContent).toBe("Hello, World!");
});
+ test("mapContentFragmentFromHTML should map the HTML structure when HTML paste is allowed", () => {
+ const inertElement = document.createElement("div");
+ const contentFragment = mapContentFragmentFromHTML(
+ "Hello, World!
",
+ inertElement.style,
+ true,
+ );
+ expect(contentFragment).toBeInstanceOf(DocumentFragment);
+ expect(contentFragment.children).toHaveLength(1);
+ expect(contentFragment.firstElementChild.children).toHaveLength(2);
+ expect(contentFragment.textContent).toBe("Hello, World!");
+ });
+
/*
test("mapContentFragmentFromHTML should return a valid content for the editor (multiple paragraphs)", () => {
const paragraphs = [