diff --git a/frontend/src/app/render_wasm/api/texts.cljs b/frontend/src/app/render_wasm/api/texts.cljs index f3f0d29386..9713fa0633 100644 --- a/frontend/src/app/render_wasm/api/texts.cljs +++ b/frontend/src/app/render_wasm/api/texts.cljs @@ -16,7 +16,7 @@ [app.render-wasm.wasm :as wasm])) (def ^:const PARAGRAPH-ATTR-U8-SIZE 44) -(def ^:const LEAF-ATTR-U8-SIZE 60) +(def ^:const SPAN-ATTR-U8-SIZE 60) (def ^:const MAX-TEXT-FILLS types.fills.impl/MAX-FILLS) (defn- encode-text @@ -25,7 +25,7 @@ (let [encoder (js/TextEncoder.)] (.encode encoder text))) -(defn- write-leaf-fills +(defn- write-span-fills [offset dview fills] (let [new-ofset (reduce (fn [offset fill] (let [opacity (get fill :fill-opacity 1.0) @@ -76,20 +76,20 @@ (defn- write-leaves [offset dview leaves paragraph] - (reduce (fn [offset leaf] - (let [font-style (sr/translate-font-style (get leaf :font-style)) - font-size (get leaf :font-size) - letter-spacing (get leaf :letter-spacing) - font-weight (get leaf :font-weight) - font-id (f/normalize-font-id (get leaf :font-id)) - font-family (hash (get leaf :font-family)) + (reduce (fn [offset span] + (let [font-style (sr/translate-font-style (get span :font-style)) + font-size (get span :font-size) + letter-spacing (get span :letter-spacing) + font-weight (get span :font-weight) + font-id (f/normalize-font-id (get span :font-id)) + font-family (hash (get span :font-family)) - text-buffer (encode-text (get leaf :text)) + text-buffer (encode-text (get span :text)) text-length (mem/size text-buffer) - fills (take MAX-TEXT-FILLS (get leaf :fills)) + fills (take MAX-TEXT-FILLS (get span :fills)) font-variant-id - (get leaf :font-variant-id) + (get span :font-variant-id) font-variant-id (if (uuid? font-variant-id) @@ -97,17 +97,17 @@ uuid/zero) text-decoration - (or (sr/translate-text-decoration (:text-decoration leaf)) + (or (sr/translate-text-decoration (:text-decoration span)) (sr/translate-text-decoration (:text-decoration paragraph)) (sr/translate-text-decoration "none")) text-transform - (or (sr/translate-text-transform (:text-transform leaf)) + (or (sr/translate-text-transform (:text-transform span)) (sr/translate-text-transform (:text-transform paragraph)) (sr/translate-text-transform "none")) text-direction - (or (sr/translate-text-direction (:text-direction leaf)) + (or (sr/translate-text-direction (:text-direction span)) (sr/translate-text-direction (:text-direction paragraph)) (sr/translate-text-direction "ltr"))] @@ -127,20 +127,20 @@ (mem/write-i32 dview text-length) (mem/write-i32 dview (count fills)) - (mem/assert-written offset LEAF-ATTR-U8-SIZE) + (mem/assert-written offset SPAN-ATTR-U8-SIZE) - (write-leaf-fills dview fills)))) + (write-span-fills dview fills)))) offset leaves)) (defn write-shape-text ;; buffer has the following format: ;; [ ] - [leaves paragraph text] - (let [num-leaves (count leaves) + [spans paragraph text] + (let [num-spans (count spans) fills-size (* types.fills.impl/FILL-U8-SIZE MAX-TEXT-FILLS) metadata-size (+ PARAGRAPH-ATTR-U8-SIZE - (* num-leaves (+ LEAF-ATTR-U8-SIZE fills-size))) + (* num-spans (+ SPAN-ATTR-U8-SIZE fills-size))) text-buffer (encode-text text) text-size (mem/size text-buffer) @@ -151,9 +151,9 @@ offset (mem/alloc total-size)] (-> offset - (mem/write-u32 dview num-leaves) + (mem/write-u32 dview num-spans) (write-paragraph dview paragraph) - (write-leaves dview leaves paragraph) + (write-leaves dview spans paragraph) (mem/write-buffer heapu8 text-buffer)) (h/call wasm/internal-module "_set_shape_text_content"))) diff --git a/frontend/text-editor/src/editor/TextEditor.js b/frontend/text-editor/src/editor/TextEditor.js index 11c327642f..268f405581 100644 --- a/frontend/text-editor/src/editor/TextEditor.js +++ b/frontend/text-editor/src/editor/TextEditor.js @@ -18,7 +18,7 @@ import { import { resetInertElement } from "./content/dom/Style.js"; import { createRoot, createEmptyRoot } from "./content/dom/Root.js"; import { createParagraph } from "./content/dom/Paragraph.js"; -import { createEmptyInline, createInline } from "./content/dom/Inline.js"; +import { createEmptyTextSpan, createTextSpan } from "./content/dom/TextSpan.js"; import { isLineBreak } from "./content/dom/LineBreak.js"; import LayoutType from "./layout/LayoutType.js"; @@ -428,8 +428,8 @@ export class TextEditor extends EventTarget { } /** - * CSS Style declaration for the current inline. From here we - * can infer root, paragraph and inline declarations. + * CSS Style declaration for the current text span. From here we + * can infer root, paragraph and text span declarations. * * @type {CSSStyleDeclaration} */ @@ -472,27 +472,27 @@ export class TextEditor extends EventTarget { } /** - * Creates a new inline from a string. + * Creates a new text span from a string. * * @param {string} text * @param {Object.|CSSStyleDeclaration} styles * @returns {HTMLSpanElement} */ - createInlineFromString(text, styles) { + createTextSpanFromString(text, styles) { if (text === "") { - return createEmptyInline(styles); + return createEmptyTextSpan(styles); } - return createInline(new Text(text), styles); + return createTextSpan(new Text(text), styles); } /** - * Creates a new inline. + * Creates a new text span. * * @param {...any} args * @returns {HTMLSpanElement} */ - createInline(...args) { - return createInline(...args); + createTextSpan(...args) { + return createTextSpan(...args); } /** diff --git a/frontend/text-editor/src/editor/TextEditor.test.js b/frontend/text-editor/src/editor/TextEditor.test.js index d8dc8040bc..cb78558a16 100644 --- a/frontend/text-editor/src/editor/TextEditor.test.js +++ b/frontend/text-editor/src/editor/TextEditor.test.js @@ -29,14 +29,14 @@ describe("TextEditor", () => { const textEditor = new TextEditor(document.createElement("div")); textEditor.root = textEditor.createRoot([ textEditor.createParagraph([ - textEditor.createInlineFromString("Hello, World!"), + textEditor.createTextSpanFromString("Hello, World!"), ]), - textEditor.createParagraph([textEditor.createInlineFromString("")]), + textEditor.createParagraph([textEditor.createTextSpanFromString("")]), textEditor.createParagraph([ - textEditor.createInlineFromString("¡Hola, Mundo!"), + textEditor.createTextSpanFromString("¡Hola, Mundo!"), ]), textEditor.createParagraph([ - textEditor.createInlineFromString("Hallo, Welt!"), + textEditor.createTextSpanFromString("Hallo, Welt!"), ]), ]); expect(textEditor).toBeInstanceOf(TextEditor); @@ -76,7 +76,7 @@ describe("TextEditor", () => { const textEditor = new TextEditor(textEditorElement); textEditor.root = textEditor.createRoot([ textEditor.createParagraph([ - textEditor.createInlineFromString("Hello, World!"), + textEditor.createTextSpanFromString("Hello, World!"), ]), ]); expect(textEditor).toBeInstanceOf(TextEditor); diff --git a/frontend/text-editor/src/editor/commands/deleteContentBackward.js b/frontend/text-editor/src/editor/commands/deleteContentBackward.js index 142e2236bd..cbc112aa68 100644 --- a/frontend/text-editor/src/editor/commands/deleteContentBackward.js +++ b/frontend/text-editor/src/editor/commands/deleteContentBackward.js @@ -27,7 +27,7 @@ export function deleteContentBackward(event, editor, selectionController) { } // If we're in a text node and the offset is - // greater than 0 (not at the start of the inline) + // greater than 0 (not at the start of the text span) // we simple remove a character from the text. if (selectionController.isTextFocus && selectionController.focusOffset > 0) { return selectionController.removeBackwardText(); @@ -41,11 +41,11 @@ export function deleteContentBackward(event, editor, selectionController) { ) { return selectionController.mergeBackwardParagraph(); - // If we're at an inline or a line break paragraph + // If we're at an text span or a line break paragraph // and there's more than one paragraph, then we should // remove the next paragraph. } else if ( - selectionController.isInlineFocus || + selectionController.isTextSpanFocus || selectionController.isLineBreakFocus ) { return selectionController.removeBackwardParagraph(); diff --git a/frontend/text-editor/src/editor/commands/deleteContentForward.js b/frontend/text-editor/src/editor/commands/deleteContentForward.js index c8b5c4d709..53951bd7f1 100644 --- a/frontend/text-editor/src/editor/commands/deleteContentForward.js +++ b/frontend/text-editor/src/editor/commands/deleteContentForward.js @@ -26,7 +26,7 @@ export function deleteContentForward(event, editor, selectionController) { } // If we're in a text node and the offset is - // greater than 0 (not at the start of the inline) + // greater than 0 (not at the start of the text span) // we simple remove a character from the text. if (selectionController.isTextFocus && selectionController.focusAtEnd) { @@ -41,11 +41,11 @@ export function deleteContentForward(event, editor, selectionController) { ) { return selectionController.removeForwardText(); - // If we're at an inline or a line break paragraph + // If we're at a text span or a line break paragraph // and there's more than one paragraph, then we should // remove the next paragraph. } else if ( - (selectionController.isInlineFocus || + (selectionController.isTextSpanFocus || selectionController.isLineBreakFocus) && editor.numParagraphs > 1 ) { diff --git a/frontend/text-editor/src/editor/commands/insertText.js b/frontend/text-editor/src/editor/commands/insertText.js index c8df8d8fdb..780f3e9f7f 100644 --- a/frontend/text-editor/src/editor/commands/insertText.js +++ b/frontend/text-editor/src/editor/commands/insertText.js @@ -25,8 +25,8 @@ export function insertText(event, editor, selectionController) { } else { if (selectionController.isMultiParagraph) { return selectionController.replaceParagraphs(event.data); - } else if (selectionController.isMultiInline) { - return selectionController.replaceInlines(event.data); + } else if (selectionController.isMultiTextSpan) { + return selectionController.replaceTextSpans(event.data); } else if (selectionController.isTextSame) { return selectionController.replaceText(event.data); } diff --git a/frontend/text-editor/src/editor/content/dom/Content.js b/frontend/text-editor/src/editor/content/dom/Content.js index aa550e9724..55aa7ad7f0 100644 --- a/frontend/text-editor/src/editor/content/dom/Content.js +++ b/frontend/text-editor/src/editor/content/dom/Content.js @@ -6,7 +6,7 @@ * Copyright (c) KALEIDOS INC */ -import { createInline, isLikeInline } from "./Inline.js"; +import { createTextSpan, isLikeTextSpan } from "./TextSpan.js"; import { createEmptyParagraph, createParagraph, @@ -21,11 +21,11 @@ const DEFAULT_FILLS = '[["^ ","~:fill-color", "#000000","~:fill-opacity", 1]]'; /** * Returns if the content fragment should be treated as - * inline content and not a paragraphed one. + * text span content and not a paragraphed one. * * @returns {boolean} */ -function isContentFragmentFromDocumentInline(document) { +function isContentFragmentFromDocumentTextSpan(document) { const nodeIterator = document.createNodeIterator( document.documentElement, NodeFilter.SHOW_ELEMENT, @@ -37,7 +37,7 @@ function isContentFragmentFromDocumentInline(document) { continue; } - if (!isLikeInline(currentNode)) return false; + if (!isLikeTextSpan(currentNode)) return false; currentNode = nodeIterator.nextNode(); } @@ -75,29 +75,29 @@ export function mapContentFragmentFromDocument(document, root, styleDefaults) { currentParagraph = createParagraph(undefined, currentStyle); } } - const inline = createInline(new Text(currentNode.nodeValue), currentStyle); - const fontSize = inline.style.getPropertyValue("font-size"); + const textSpan = createTextSpan(new Text(currentNode.nodeValue), currentStyle); + const fontSize = textSpan.style.getPropertyValue("font-size"); if (!fontSize) { console.warn("font-size", fontSize); - inline.style.setProperty("font-size", styleDefaults?.getPropertyValue("font-size") ?? DEFAULT_FONT_SIZE); + textSpan.style.setProperty("font-size", styleDefaults?.getPropertyValue("font-size") ?? DEFAULT_FONT_SIZE); } - const fontFamily = inline.style.getPropertyValue("font-family"); + const fontFamily = textSpan.style.getPropertyValue("font-family"); if (!fontFamily) { console.warn("font-family", fontFamily); - inline.style.setProperty("font-family", styleDefaults?.getPropertyValue("font-family") ?? DEFAULT_FONT_FAMILY); + textSpan.style.setProperty("font-family", styleDefaults?.getPropertyValue("font-family") ?? DEFAULT_FONT_FAMILY); } - const fontWeight = inline.style.getPropertyValue("font-weight"); + const fontWeight = textSpan.style.getPropertyValue("font-weight"); if (!fontWeight) { console.warn("font-weight", fontWeight); - inline.style.setProperty("font-weight", styleDefaults?.getPropertyValue("font-weight") ?? DEFAULT_FONT_WEIGHT) + textSpan.style.setProperty("font-weight", styleDefaults?.getPropertyValue("font-weight") ?? DEFAULT_FONT_WEIGHT) } - const fills = inline.style.getPropertyValue('--fills'); + const fills = textSpan.style.getPropertyValue('--fills'); if (!fills) { console.warn("fills", fills); - inline.style.setProperty("--fills", styleDefaults?.getPropertyValue("--fills") ?? DEFAULT_FILLS); + textSpan.style.setProperty("--fills", styleDefaults?.getPropertyValue("--fills") ?? DEFAULT_FILLS); } - currentParagraph.appendChild(inline); + currentParagraph.appendChild(textSpan); currentNode = nodeIterator.nextNode(); } @@ -107,9 +107,9 @@ export function mapContentFragmentFromDocument(document, root, styleDefaults) { } if (fragment.children.length === 1) { - const isContentInline = isContentFragmentFromDocumentInline(document); - if (isContentInline) { - currentParagraph.dataset.inline = "force"; + const isContentTextSpan = isContentFragmentFromDocumentTextSpan(document); + if (isContentTextSpan) { + currentParagraph.dataset.textSpan = "force"; } } @@ -149,7 +149,7 @@ export function mapContentFragmentFromString(string, styleDefaults) { } else { fragment.appendChild( createParagraph( - [createInline(new Text(line), styleDefaults)], + [createTextSpan(new Text(line), styleDefaults)], styleDefaults, ), ); 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 693e54b0da..03b74e27b6 100644 --- a/frontend/text-editor/src/editor/content/dom/Content.test.js +++ b/frontend/text-editor/src/editor/content/dom/Content.test.js @@ -24,7 +24,7 @@ describe("Content", () => { expect(contentFragment.textContent).toBe("Hello, World!"); }); - test("mapContentFragmentFromHTML should return a valid content for the editor (multiple inlines)", () => { + test("mapContentFragmentFromHTML should return a valid content for the editor (multiple text spans)", () => { const inertElement = document.createElement("div"); const contentFragment = mapContentFragmentFromHTML( "
Hello,
World!
", diff --git a/frontend/text-editor/src/editor/content/dom/Inline.test.js b/frontend/text-editor/src/editor/content/dom/Inline.test.js index 4b1cff17a6..2d1cbf8c65 100644 --- a/frontend/text-editor/src/editor/content/dom/Inline.test.js +++ b/frontend/text-editor/src/editor/content/dom/Inline.test.js @@ -1,123 +1,123 @@ import { describe, test, expect } from "vitest"; import { - createEmptyInline, - createInline, - getInline, - getInlineLength, - isInline, - isInlineEnd, - isInlineStart, - isLikeInline, - splitInline, + createEmptyTextSpan, + createTextSpan, + getTextSpan, + getTextSpanLength, + isTextSpan, + isTextSpanEnd, + isTextSpanStart, + isLikeTextSpan, + splitTextSpan, TAG, TYPE, -} from "./Inline.js"; +} from "./TextSpan.js"; import { createLineBreak } from "./LineBreak.js"; /* @vitest-environment jsdom */ -describe("Inline", () => { - test("createInline should throw when passed an invalid child", () => { - expect(() => createInline("Hello, World!")).toThrowError( - "Invalid inline child", +describe("TextSpan", () => { + test("createTextSpan should throw when passed an invalid child", () => { + expect(() => createTextSpan("Hello, World!")).toThrowError( + "Invalid textSpan child", ); }); - test("createInline creates a new inline element with a
inside", () => { - const inline = createInline(createLineBreak()); - expect(inline).toBeInstanceOf(HTMLSpanElement); - expect(inline.dataset.itype).toBe(TYPE); - expect(inline.nodeName).toBe(TAG); - expect(inline.textContent).toBe(""); - expect(inline.firstChild).toBeInstanceOf(HTMLBRElement); + test("createTextSpan creates a new textSpan element with a
inside", () => { + const textSpan = createTextSpan(createLineBreak()); + expect(textSpan).toBeInstanceOf(HTMLSpanElement); + expect(textSpan.dataset.itype).toBe(TYPE); + expect(textSpan.nodeName).toBe(TAG); + expect(textSpan.textContent).toBe(""); + expect(textSpan.firstChild).toBeInstanceOf(HTMLBRElement); }); - test("createInline creates a new inline element with a text inside", () => { - const inline = createInline(new Text("Hello, World!")); - expect(inline).toBeInstanceOf(HTMLSpanElement); - expect(inline.dataset.itype).toBe(TYPE); - expect(inline.nodeName).toBe(TAG); - expect(inline.textContent).toBe("Hello, World!"); - expect(inline.firstChild).toBeInstanceOf(Text); + test("createTextSpan creates a new textSpan element with a text inside", () => { + const textSpan = createTextSpan(new Text("Hello, World!")); + expect(textSpan).toBeInstanceOf(HTMLSpanElement); + expect(textSpan.dataset.itype).toBe(TYPE); + expect(textSpan.nodeName).toBe(TAG); + expect(textSpan.textContent).toBe("Hello, World!"); + expect(textSpan.firstChild).toBeInstanceOf(Text); }); - test("createEmptyInline creates a new empty inline element with a
inside", () => { - const emptyInline = createEmptyInline(); - expect(emptyInline).toBeInstanceOf(HTMLSpanElement); - expect(emptyInline.dataset.itype).toBe(TYPE); - expect(emptyInline.nodeName).toBe(TAG); - expect(emptyInline.textContent).toBe(""); - expect(emptyInline.firstChild).toBeInstanceOf(HTMLBRElement); + test("createEmptyTextSpan creates a new empty textSpan element with a
inside", () => { + const emptyTextSpan = createEmptyTextSpan(); + expect(emptyTextSpan).toBeInstanceOf(HTMLSpanElement); + expect(emptyTextSpan.dataset.itype).toBe(TYPE); + expect(emptyTextSpan.nodeName).toBe(TAG); + expect(emptyTextSpan.textContent).toBe(""); + expect(emptyTextSpan.firstChild).toBeInstanceOf(HTMLBRElement); }); - test("isInline should return true on elements that are inlines", () => { - const inline = createInline(new Text("Hello, World!")); - expect(isInline(inline)).toBe(true); + test("isTextSpan should return true on elements that are text spans", () => { + const textSpan = createTextSpan(new Text("Hello, World!")); + expect(isTextSpan(textSpan)).toBe(true); const a = document.createElement("a"); - expect(isInline(a)).toBe(false); + expect(isTextSpan(a)).toBe(false); const b = null; - expect(isInline(b)).toBe(false); + expect(isTextSpan(b)).toBe(false); const c = document.createElement("span"); - expect(isInline(c)).toBe(false); + expect(isTextSpan(c)).toBe(false); }); - test("isLikeInline should return true on elements that have inline behavior by default", () => { - expect(isLikeInline(Infinity)).toBe(false); - expect(isLikeInline(null)).toBe(false); - expect(isLikeInline(document.createElement("A"))).toBe(true); + test("isLikeTextSpan should return true on elements that have textSpan behavior by default", () => { + expect(isLikeTextSpan(Infinity)).toBe(false); + expect(isLikeTextSpan(null)).toBe(false); + expect(isLikeTextSpan(document.createElement("A"))).toBe(true); }); // FIXME: Should throw? - test("isInlineStart returns false when passed node is not an inline", () => { - const inline = document.createElement("div"); - expect(isInlineStart(inline, 0)).toBe(false); - expect(isInlineStart(inline, "Hello, World!".length)).toBe(false); + test("isTextSpanStart returns false when passed node is not an textSpan", () => { + const textSpan = document.createElement("div"); + expect(isTextSpanStart(textSpan, 0)).toBe(false); + expect(isTextSpanStart(textSpan, "Hello, World!".length)).toBe(false); }); - test("isInlineStart returns if we're at the start of an inline", () => { - const inline = createInline(new Text("Hello, World!")); - expect(isInlineStart(inline, 0)).toBe(true); - expect(isInlineStart(inline, "Hello, World!".length)).toBe(false); + test("isTextSpanStart returns if we're at the start of an textSpan", () => { + const textSpan = createTextSpan(new Text("Hello, World!")); + expect(isTextSpanStart(textSpan, 0)).toBe(true); + expect(isTextSpanStart(textSpan, "Hello, World!".length)).toBe(false); }); // FIXME: Should throw? - test("isInlineEnd returns false when passed node is not an inline", () => { - const inline = document.createElement("div"); - expect(isInlineEnd(inline, 0)).toBe(false); - expect(isInlineEnd(inline, "Hello, World!".length)).toBe(false); + test("isTextSpanEnd returns false when passed node is not an textSpan", () => { + const textSpan = document.createElement("div"); + expect(isTextSpanEnd(textSpan, 0)).toBe(false); + expect(isTextSpanEnd(textSpan, "Hello, World!".length)).toBe(false); }); - test("isInlineEnd returns if we're in the end of an inline", () => { - const inline = createInline(new Text("Hello, World!")); - expect(isInlineEnd(inline, 0)).toBe(false); - expect(isInlineEnd(inline, "Hello, World!".length)).toBe(true); + test("isTextSpanEnd returns if we're in the end of an textSpan", () => { + const textSpan = createTextSpan(new Text("Hello, World!")); + expect(isTextSpanEnd(textSpan, 0)).toBe(false); + expect(isTextSpanEnd(textSpan, "Hello, World!".length)).toBe(true); }); - test("getInline ", () => { - expect(getInline(null)).toBe(null); + test("getTextSpan ", () => { + expect(getTextSpan(null)).toBe(null); }); - test("getInlineLength throws when the passed node is not an inline", () => { - const inline = document.createElement("div"); - expect(() => getInlineLength(inline)).toThrowError("Invalid inline"); + test("getTextSpanLength throws when the passed node is not an textSpan", () => { + const textSpan = document.createElement("div"); + expect(() => getTextSpanLength(textSpan)).toThrowError("Invalid textSpan"); }); - test("getInlineLength returns the length of the inline content", () => { - const inline = createInline(new Text("Hello, World!")); - expect(getInlineLength(inline)).toBe(13); + test("getTextSpanLength returns the length of the textSpan content", () => { + const textSpan = createTextSpan(new Text("Hello, World!")); + expect(getTextSpanLength(textSpan)).toBe(13); }); - test("getInlineLength should return 0 when the inline content is a
", () => { - const emptyInline = createEmptyInline(); - expect(getInlineLength(emptyInline)).toBe(0); + test("getTextSpanLength should return 0 when the textSpan content is a
", () => { + const emptyTextSpan = createEmptyTextSpan(); + expect(getTextSpanLength(emptyTextSpan)).toBe(0); }); - test("splitInline returns a new inline from the splitted inline", () => { - const inline = createInline(new Text("Hello, World!")); - const newInline = splitInline(inline, 5); - expect(newInline).toBeInstanceOf(HTMLSpanElement); - expect(newInline.firstChild).toBeInstanceOf(Text); - expect(newInline.textContent).toBe(", World!"); - expect(newInline.dataset.itype).toBe(TYPE); - expect(newInline.nodeName).toBe(TAG); + test("splitTextSpan returns a new textSpan from the splitted textSpan", () => { + const textSpan = createTextSpan(new Text("Hello, World!")); + const newTextSpan = splitTextSpan(textSpan, 5); + expect(newTextSpan).toBeInstanceOf(HTMLSpanElement); + expect(newTextSpan.firstChild).toBeInstanceOf(Text); + expect(newTextSpan.textContent).toBe(", World!"); + expect(newTextSpan.dataset.itype).toBe(TYPE); + expect(newTextSpan.nodeName).toBe(TAG); }); }); diff --git a/frontend/text-editor/src/editor/content/dom/Paragraph.js b/frontend/text-editor/src/editor/content/dom/Paragraph.js index 267650b7ef..38c30b91c9 100644 --- a/frontend/text-editor/src/editor/content/dom/Paragraph.js +++ b/frontend/text-editor/src/editor/content/dom/Paragraph.js @@ -14,15 +14,15 @@ import { isOffsetAtEnd, } from "./Element.js"; import { - isInline, - isLikeInline, - getInline, - getInlinesFrom, - createInline, - createEmptyInline, - isInlineEnd, - splitInline, -} from "./Inline.js"; + isTextSpan, + isLikeTextSpan, + getTextSpan, + getTextSpansFrom, + createTextSpan, + createEmptyTextSpan, + isTextSpanEnd, + splitTextSpan, +} from "./TextSpan.js"; import { createLineBreak, isLineBreak } from "./LineBreak.js"; import { setStyles } from "./Style.js"; @@ -50,7 +50,7 @@ export const STYLES = [ /** * FIXME: This is a fix for Chrome that removes the - * current inline when the last character is deleted + * current text span when the last character is deleted * in `insertCompositionText`. * * @param {*} node @@ -60,7 +60,7 @@ export function fixParagraph(node) { return; } const br = createLineBreak(); - node.replaceChildren(createInline(br)); + node.replaceChildren(createTextSpan(br)); return br; } @@ -74,7 +74,7 @@ export function fixParagraph(node) { * @returns {boolean} */ export function isLikeParagraph(element) { - return !isLikeInline(element); + return !isLikeTextSpan(element); } /** @@ -85,9 +85,9 @@ export function isLikeParagraph(element) { */ export function isEmptyParagraph(element) { if (!isParagraph(element)) throw new TypeError("Invalid paragraph"); - const inline = element.firstChild; - if (!isInline(inline)) throw new TypeError("Invalid inline"); - return isLineBreak(inline.firstChild); + const textSpan = element.firstChild; + if (!isTextSpan(textSpan)) throw new TypeError("Invalid text span"); + return isLineBreak(textSpan.firstChild); } /** @@ -106,20 +106,20 @@ export function isParagraph(node) { /** * Creates a new paragraph. * - * @param {Array} inlines + * @param {Array} textSpans * @param {Object.|CSSStyleDeclaration} styles * @param {Object.} [attrs] * @returns {HTMLDivElement} */ -export function createParagraph(inlines, styles, attrs) { - if (inlines && (!Array.isArray(inlines) || !inlines.every(isInline))) +export function createParagraph(textSpans, styles, attrs) { + if (textSpans && (!Array.isArray(textSpans) || !textSpans.every(isTextSpan))) throw new TypeError("Invalid paragraph children"); return createElement(TAG, { attributes: { id: createRandomId(), ...attrs }, data: { itype: TYPE }, styles: styles, allowedStyles: STYLES, - children: inlines, + children: textSpans, }); } @@ -130,7 +130,7 @@ export function createParagraph(inlines, styles, attrs) { * @returns {HTMLDivElement} */ export function createEmptyParagraph(styles) { - return createParagraph([createEmptyInline(styles)], styles); + return createParagraph([createEmptyTextSpan(styles)], styles); } /** @@ -177,11 +177,11 @@ export function getParagraph(node) { export function isParagraphStart(node, offset) { const paragraph = getParagraph(node); if (!paragraph) throw new Error("Can't find the paragraph"); - const inline = getInline(node); - if (!inline) throw new Error("Can't find the inline"); + const textSpan = getTextSpan(node); + if (!textSpan) throw new Error("Can't find the text span"); return ( - paragraph.firstElementChild === inline && - isOffsetAtStart(inline.firstChild, offset) + paragraph.firstElementChild === textSpan && + isOffsetAtStart(textSpan.firstChild, offset) ); } @@ -196,11 +196,11 @@ export function isParagraphStart(node, offset) { export function isParagraphEnd(node, offset) { const paragraph = getParagraph(node); if (!paragraph) throw new Error("Cannot find the paragraph"); - const inline = getInline(node); - if (!inline) throw new Error("Cannot find the inline"); + const textSpan = getTextSpan(node); + if (!textSpan) throw new Error("Cannot find the text span"); return ( - paragraph.lastElementChild === inline && - isOffsetAtEnd(inline.firstChild, offset) + paragraph.lastElementChild === textSpan && + isOffsetAtEnd(textSpan.firstChild, offset) ); } @@ -208,17 +208,17 @@ export function isParagraphEnd(node, offset) { * Splits a paragraph. * * @param {HTMLDivElement} paragraph - * @param {HTMLSpanElement} inline + * @param {HTMLSpanElement} textSpan * @param {number} offset */ -export function splitParagraph(paragraph, inline, offset) { +export function splitParagraph(paragraph, textSpan, offset) { const style = paragraph.style; - if (isInlineEnd(inline, offset)) { - const newParagraph = createParagraph(getInlinesFrom(inline), style); + if (isTextSpanEnd(textSpan, offset)) { + const newParagraph = createParagraph(getTextSpansFrom(textSpan), style); return newParagraph; } - const newInline = splitInline(inline, offset); - const newParagraph = createParagraph([newInline], style); + const newTextSpan = splitTextSpan(textSpan, offset); + const newParagraph = createParagraph([newTextSpan], style); return newParagraph; } @@ -231,11 +231,11 @@ export function splitParagraph(paragraph, inline, offset) { export function splitParagraphAtNode(paragraph, startIndex) { const style = paragraph.style; const newParagraph = createParagraph(null, style); - const newInlines = []; + const newTextSpans = []; for (let index = startIndex; index < paragraph.children.length; index++) { - newInlines.push(paragraph.children.item(index)); + newTextSpans.push(paragraph.children.item(index)); } - newParagraph.append(...newInlines); + newParagraph.append(...newTextSpans); return newParagraph; } diff --git a/frontend/text-editor/src/editor/content/dom/Paragraph.test.js b/frontend/text-editor/src/editor/content/dom/Paragraph.test.js index 3874c5eba0..28c79612af 100644 --- a/frontend/text-editor/src/editor/content/dom/Paragraph.test.js +++ b/frontend/text-editor/src/editor/content/dom/Paragraph.test.js @@ -13,7 +13,7 @@ import { splitParagraphAtNode, isEmptyParagraph, } from "./Paragraph.js"; -import { createInline, isInline } from "./Inline.js"; +import { createTextSpan, isTextSpan } from "./TextSpan.js"; /* @vitest-environment jsdom */ describe("Paragraph", () => { @@ -28,7 +28,7 @@ describe("Paragraph", () => { expect(emptyParagraph).toBeInstanceOf(HTMLDivElement); expect(emptyParagraph.nodeName).toBe(TAG); expect(emptyParagraph.dataset.itype).toBe(TYPE); - expect(isInline(emptyParagraph.firstChild)).toBe(true); + expect(isTextSpan(emptyParagraph.firstChild)).toBe(true); }); test("isParagraph should return true when the passed node is a paragraph", () => { @@ -37,7 +37,7 @@ describe("Paragraph", () => { expect(isParagraph(document.createElement("h1"))).toBe(false); expect(isParagraph(createEmptyParagraph())).toBe(true); expect( - isParagraph(createParagraph([createInline(new Text("Hello, World!"))])), + isParagraph(createParagraph([createTextSpan(new Text("Hello, World!"))])), ).toBe(true); }); @@ -62,8 +62,8 @@ describe("Paragraph", () => { test("getParagraph should return the closest paragraph of the passed node", () => { const text = new Text("Hello, World!"); - const inline = createInline(text); - const paragraph = createParagraph([inline]); + const textSpan = createTextSpan(text); + const paragraph = createParagraph([textSpan]); expect(getParagraph(text)).toBe(paragraph); }); @@ -81,7 +81,7 @@ describe("Paragraph", () => { test("isParagraphStart should return true on a paragraph", () => { const paragraph = createParagraph([ - createInline(new Text("Hello, World!")), + createTextSpan(new Text("Hello, World!")), ]); expect(isParagraphStart(paragraph.firstChild.firstChild, 0)).toBe(true); }); @@ -93,15 +93,15 @@ describe("Paragraph", () => { test("isParagraphEnd should return true on a paragraph", () => { const paragraph = createParagraph([ - createInline(new Text("Hello, World!")), + createTextSpan(new Text("Hello, World!")), ]); expect(isParagraphEnd(paragraph.firstChild.firstChild, 13)).toBe(true); }); test("splitParagraph should split a paragraph", () => { - const inline = createInline(new Text("Hello, World!")); - const paragraph = createParagraph([inline]); - const newParagraph = splitParagraph(paragraph, inline, 6); + const textSpan = createTextSpan(new Text("Hello, World!")); + const paragraph = createParagraph([textSpan]); + const newParagraph = splitParagraph(paragraph, textSpan, 6); expect(newParagraph).toBeInstanceOf(HTMLDivElement); expect(newParagraph.nodeName).toBe(TAG); expect(newParagraph.dataset.itype).toBe(TYPE); @@ -109,10 +109,10 @@ describe("Paragraph", () => { }); test("splitParagraphAtNode should split a paragraph at a specified node", () => { - const helloInline = createInline(new Text("Hello, ")); - const worldInline = createInline(new Text("World")); - const exclInline = createInline(new Text("!")); - const paragraph = createParagraph([helloInline, worldInline, exclInline]); + const helloTextSpan = createTextSpan(new Text("Hello, ")); + const worldTextSpan = createTextSpan(new Text("World")); + const exclTextSpan = createTextSpan(new Text("!")); + const paragraph = createParagraph([helloTextSpan, worldTextSpan, exclTextSpan]); const newParagraph = splitParagraphAtNode(paragraph, 1); expect(newParagraph).toBeInstanceOf(HTMLDivElement); expect(newParagraph.nodeName).toBe(TAG); @@ -121,7 +121,7 @@ describe("Paragraph", () => { expect(newParagraph.textContent).toBe("World!"); }); - test("isLikeParagraph should return true if the element it's not an inline element", () => { + test("isLikeParagraph should return true if the element it's not an text span element", () => { const span = document.createElement("span"); const a = document.createElement("a"); const br = document.createElement("br"); @@ -149,23 +149,23 @@ describe("Paragraph", () => { paragraph.dataset.itype = "paragraph"; paragraph.appendChild(document.createElement("svg")); isEmptyParagraph(paragraph); - }).toThrowError("Invalid inline"); + }).toThrowError("Invalid text span"); const lineBreak = document.createElement("br"); - const emptyInline = document.createElement("span"); - emptyInline.dataset.itype = "inline"; - emptyInline.appendChild(lineBreak); + const emptyTextSpan = document.createElement("span"); + emptyTextSpan.dataset.itype = "span"; + emptyTextSpan.appendChild(lineBreak); const emptyParagraph = document.createElement("div"); emptyParagraph.dataset.itype = "paragraph"; - emptyParagraph.appendChild(emptyInline); + emptyParagraph.appendChild(emptyTextSpan); expect(isEmptyParagraph(emptyParagraph)).toBe(true); - const nonEmptyInline = document.createElement("span"); - nonEmptyInline.dataset.itype = "inline"; - nonEmptyInline.appendChild(new Text("Not empty!")); + const nonEmptyTextSpan = document.createElement("span"); + nonEmptyTextSpan.dataset.itype = "span"; + nonEmptyTextSpan.appendChild(new Text("Not empty!")); const nonEmptyParagraph = document.createElement("div"); nonEmptyParagraph.dataset.itype = "paragraph"; - nonEmptyParagraph.appendChild(nonEmptyInline); + nonEmptyParagraph.appendChild(nonEmptyTextSpan); expect(isEmptyParagraph(nonEmptyParagraph)).toBe(false); }); }); diff --git a/frontend/text-editor/src/editor/content/dom/TextNode.js b/frontend/text-editor/src/editor/content/dom/TextNode.js index 82d918f89b..5aaa8afd6c 100644 --- a/frontend/text-editor/src/editor/content/dom/TextNode.js +++ b/frontend/text-editor/src/editor/content/dom/TextNode.js @@ -6,7 +6,7 @@ * Copyright (c) KALEIDOS INC */ -import { isInline } from "./Inline.js"; +import { isTextSpan } from "./TextSpan.js"; import { isLineBreak } from "./LineBreak.js"; import { isParagraph } from "./Paragraph.js"; import { isEditor } from "./Editor.js"; @@ -58,7 +58,7 @@ export function getTextNodeLength(node) { */ export function getClosestTextNode(node) { if (isTextNode(node)) return node; - if (isInline(node)) return node.firstChild; + if (isTextSpan(node)) return node.firstChild; if (isParagraph(node)) return node.firstChild.firstChild; if (isRoot(node)) return node.firstChild.firstChild.firstChild; if (isEditor(node)) return node.firstChild.firstChild.firstChild.firstChild; diff --git a/frontend/text-editor/src/editor/content/dom/TextNodeIterator.test.js b/frontend/text-editor/src/editor/content/dom/TextNodeIterator.test.js index c19311a22c..66bbb27d30 100644 --- a/frontend/text-editor/src/editor/content/dom/TextNodeIterator.test.js +++ b/frontend/text-editor/src/editor/content/dom/TextNodeIterator.test.js @@ -1,6 +1,6 @@ import { describe, test, expect } from "vitest"; import TextNodeIterator from "./TextNodeIterator.js"; -import { createInline } from "./Inline.js"; +import { createTextSpan } from "./TextSpan.js"; import { createParagraph } from "./Paragraph.js"; import { createRoot } from "./Root.js"; import { createLineBreak } from "./LineBreak.js"; @@ -21,16 +21,16 @@ describe("TextNodeIterator", () => { test("Create a new TextNodeIterator and iterate only over text nodes", () => { const rootNode = createRoot([ createParagraph([ - createInline(new Text("Hello, ")), - createInline(new Text("World!")), - createInline(new Text("Whatever")), + createTextSpan(new Text("Hello, ")), + createTextSpan(new Text("World!")), + createTextSpan(new Text("Whatever")), ]), - createParagraph([createInline(createLineBreak())]), + createParagraph([createTextSpan(createLineBreak())]), createParagraph([ - createInline(new Text("This is a ")), - createInline(new Text("test")), + createTextSpan(new Text("This is a ")), + createTextSpan(new Text("test")), ]), - createParagraph([createInline(new Text("Hi!"))]), + createParagraph([createTextSpan(new Text("Hi!"))]), ]); const textNodeIterator = new TextNodeIterator(rootNode); diff --git a/frontend/text-editor/src/editor/content/dom/Inline.js b/frontend/text-editor/src/editor/content/dom/TextSpan.js similarity index 57% rename from frontend/text-editor/src/editor/content/dom/Inline.js rename to frontend/text-editor/src/editor/content/dom/TextSpan.js index 4478e1b466..8b2d596e1b 100644 --- a/frontend/text-editor/src/editor/content/dom/Inline.js +++ b/frontend/text-editor/src/editor/content/dom/TextSpan.js @@ -17,7 +17,7 @@ import { setStyles, mergeStyles } from "./Style.js"; import { createRandomId } from "./Element.js"; export const TAG = "SPAN"; -export const TYPE = "inline"; +export const TYPE = "span"; export const QUERY = `[data-itype="${TYPE}"]`; export const STYLES = [ ["--typography-ref-id"], @@ -37,12 +37,12 @@ export const STYLES = [ ]; /** - * Returns true if passed node is an inline. + * Returns true if passed node is a text span. * * @param {Node} node * @returns {boolean} */ -export function isInline(node) { +export function isTextSpan(node) { if (!node) return false; if (!isElement(node, TAG)) return false; if (node.dataset.itype !== TYPE) return false; @@ -50,13 +50,13 @@ export function isInline(node) { } /** - * Returns true if the passed node "behaves" like an - * inline. + * Returns true if the passed node "behaves" like a + * text span. * * @param {Node} element * @returns {boolean} */ -export function isLikeInline(element) { +export function isLikeTextSpan(element) { return element ? [ "A", @@ -97,26 +97,26 @@ export function isLikeInline(element) { } /** - * Creates a new Inline + * Creates a new TextSpan * * @param {Text|HTMLBRElement} text * @param {Object.|CSSStyleDeclaration} styles * @param {Object.} [attrs] * @returns {HTMLSpanElement} */ -export function createInline(textOrLineBreak, styles, attrs) { +export function createTextSpan(textOrLineBreak, styles, attrs) { if ( !(textOrLineBreak instanceof HTMLBRElement) && !(textOrLineBreak instanceof Text) ) { - throw new TypeError("Invalid inline child"); + throw new TypeError("Invalid text span child"); } if ( textOrLineBreak instanceof Text && textOrLineBreak.nodeValue.length === 0 ) { console.trace("nodeValue", textOrLineBreak.nodeValue); - throw new TypeError("Invalid inline child, cannot be an empty text"); + throw new TypeError("Invalid text span child, cannot be an empty text"); } return createElement(TAG, { attributes: { id: createRandomId(), ...attrs }, @@ -128,34 +128,42 @@ export function createInline(textOrLineBreak, styles, attrs) { } /** - * Creates a new inline from an older inline. This only - * merges styles from the older inline to the new inline. + * Creates a new text span from an older text span. This only + * merges styles from the older text span to the new text span. * - * @param {HTMLSpanElement} inline + * @param {HTMLSpanElement} textSpan * @param {Object.} textOrLineBreak * @param {Object.|CSSStyleDeclaration} styles * @param {Object.} [attrs] * @returns {HTMLSpanElement} */ -export function createInlineFrom(inline, textOrLineBreak, styles, attrs) { - return createInline( +export function createTextSpanFrom(textSpan, textOrLineBreak, styles, attrs) { + return createTextSpan( textOrLineBreak, - mergeStyles(STYLES, inline.style, styles), + mergeStyles(STYLES, textSpan.style, styles), attrs, ); } /** - * Creates a new empty inline. + * Creates a new empty text span. * * @param {Object.|CSSStyleDeclaration} styles * @returns {HTMLSpanElement} */ -export function createEmptyInline(styles) { - return createInline(createLineBreak(), styles); +export function createEmptyTextSpan(styles) { + return createTextSpan(createLineBreak(), styles); } -export function createVoidInline(styles) { +/** + * Creates a new text span with an empty text. The difference between + * this and the createEmptyTextSpan is that createEmptyTextSpan creates + * a text span with a
inside. + * + * @param {Object.|CSSStyleDeclaration} styles + * @returns {HTMLSpanElement} + */ +export function createVoidTextSpan(styles) { return createElement(TAG, { attributes: { id: createRandomId() }, data: { itype: TYPE }, @@ -166,116 +174,116 @@ export function createVoidInline(styles) { } /** - * Sets the inline styles. + * Sets the text span styles. * * @param {HTMLSpanElement} element * @param {Object.|CSSStyleDeclaration} styles * @returns {HTMLSpanElement} */ -export function setInlineStyles(element, styles) { +export function setTextSpanStyles(element, styles) { return setStyles(element, STYLES, styles); } /** - * Gets the closest inline from a node. + * Gets the closest text span from a node. * * @param {Node} node * @returns {HTMLElement|null} */ -export function getInline(node) { +export function getTextSpan(node) { if (!node) return null; // FIXME: Should throw? - if (isInline(node)) return node; + if (isTextSpan(node)) return node; if (node.nodeType === Node.TEXT_NODE) { - const inline = node?.parentElement; - if (!inline) return null; - if (!isInline(inline)) return null; - return inline; + const textSpan = node?.parentElement; + if (!textSpan) return null; + if (!isTextSpan(textSpan)) return null; + return textSpan; } return node.closest(QUERY); } /** * Returns true if we are at the start offset - * of an inline. + * of a text span. * - * NOTE: Only the first inline returns this as true + * NOTE: Only the first text span returns this as true * * @param {TextNode|HTMLBRElement} node * @param {number} offset * @returns {boolean} */ -export function isInlineStart(node, offset) { - const inline = getInline(node); - if (!inline) return false; - return isOffsetAtStart(inline, offset); +export function isTextSpanStart(node, offset) { + const textSpan = getTextSpan(node); + if (!textSpan) return false; + return isOffsetAtStart(textSpan, offset); } /** * Returns true if we are at the end offset - * of an inline. + * of a text span. * * @param {TextNode|HTMLBRElement} node * @param {number} offset * @returns {boolean} */ -export function isInlineEnd(node, offset) { - const inline = getInline(node); - if (!inline) return false; - return isOffsetAtEnd(inline.firstChild, offset); +export function isTextSpanEnd(node, offset) { + const textSpan = getTextSpan(node); + if (!textSpan) return false; + return isOffsetAtEnd(textSpan.firstChild, offset); } /** - * Splits an inline. + * Splits a text span. * - * @param {HTMLSpanElement} inline + * @param {HTMLSpanElement} textSpan * @param {number} offset */ -export function splitInline(inline, offset) { - const textNode = inline.firstChild; - const style = inline.style; +export function splitTextSpan(textSpan, offset) { + const textNode = textSpan.firstChild; + const style = textSpan.style; const newTextNode = textNode.splitText(offset); - return createInline(newTextNode, style); + return createTextSpan(newTextNode, style); } /** - * Returns all the inlines of a paragraph starting at - * the specified inline. + * Returns all the text spans of a paragraph starting at + * the specified text span. * - * @param {HTMLSpanElement} startInline + * @param {HTMLSpanElement} startTextSpan * @returns {Array} */ -export function getInlinesFrom(startInline) { - const inlines = []; - let currentInline = startInline; +export function getTextSpansFrom(startTextSpan) { + const textSpans = []; + let currentTextSpan = startTextSpan; let index = 0; - while (currentInline) { - if (index > 0) inlines.push(currentInline); - currentInline = currentInline.nextElementSibling; + while (currentTextSpan) { + if (index > 0) textSpans.push(currentTextSpan); + currentTextSpan = currentTextSpan.nextElementSibling; index++; } - return inlines; + return textSpans; } /** - * Returns the length of an inline. + * Returns the length of a text span. * - * @param {HTMLElement} inline + * @param {HTMLElement} textSpan * @returns {number} */ -export function getInlineLength(inline) { - if (!isInline(inline)) throw new Error("Invalid inline"); - if (isLineBreak(inline.firstChild)) return 0; - return inline.firstChild.nodeValue.length; +export function getTextSpanLength(textSpan) { + if (!isTextSpan(textSpan)) throw new Error("Invalid text span"); + if (isLineBreak(textSpan.firstChild)) return 0; + return textSpan.firstChild.nodeValue.length; } /** - * Merges two inlines. + * Merges two text spans. * * @param {HTMLSpanElement} a * @param {HTMLSpanElement} b * @returns {HTMLSpanElement} */ -export function mergeInlines(a, b) { +export function mergeTextSpans(a, b) { a.append(...b.childNodes); b.remove(); // We need to normalize Text nodes. diff --git a/frontend/text-editor/src/editor/controllers/SelectionController.js b/frontend/text-editor/src/editor/controllers/SelectionController.js index 699b99d1a7..7864990808 100644 --- a/frontend/text-editor/src/editor/controllers/SelectionController.js +++ b/frontend/text-editor/src/editor/controllers/SelectionController.js @@ -8,18 +8,18 @@ import { createLineBreak, isLineBreak } from "../content/dom/LineBreak.js"; import { - createInline, - createInlineFrom, - getInline, - getInlineLength, - isInline, - isInlineStart, - isInlineEnd, - setInlineStyles, - splitInline, - createEmptyInline, - createVoidInline, -} from "../content/dom/Inline.js"; + createTextSpan, + createTextSpanFrom, + getTextSpan, + getTextSpanLength, + isTextSpan, + isTextSpanStart, + isTextSpanEnd, + setTextSpanStyles, + splitTextSpan, + createEmptyTextSpan, + createVoidTextSpan, +} from "../content/dom/TextSpan.js"; import { createEmptyParagraph, isEmptyParagraph, @@ -62,9 +62,9 @@ import SafeGuard from "./SafeGuard.js"; /** * SelectionController uses the same concepts used by the Selection API but extending it to support * our own internal model based on paragraphs (in drafconst textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, "))]), + createParagraph([createTextSpan(new Text("Hello, "))]), createEmptyParagraph(), - createParagraph([createInline(new Text("World!"))]), + createParagraph([createTextSpan(new Text("World!"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -88,12 +88,12 @@ import SafeGuard from "./SafeGuard.js"; HTMLSpanElement ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline" + "span" ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.textContent).toBe("Hello, "); expect(textEditorMock.root.lastChild.textContent).toBe("World!"); - t.js they were called blocks) and inlines. + t.js they were called blocks) and text spans. */ export class SelectionController extends EventTarget { /** @@ -228,7 +228,7 @@ export class SelectionController extends EventTarget { } /** - * Styles of the current inline. + * Styles of the current text span. * * @type {CSSStyleDeclaration} */ @@ -266,18 +266,18 @@ export class SelectionController extends EventTarget { } /** - * Updates current styles based on the currently selected inline. + * Updates current styles based on the currently selected text span. * - * @param {HTMLSpanElement} inline + * @param {HTMLSpanElement} textSpan * @returns {SelectionController} */ - #updateCurrentStyle(inline) { + #updateCurrentStyle(textSpan) { this.#applyDefaultStylesToCurrentStyle(); - const root = inline.parentElement.parentElement; + const root = textSpan.parentElement.parentElement; this.#applyStylesToCurrentStyle(root); - const paragraph = inline.parentElement; + const paragraph = textSpan.parentElement; this.#applyStylesToCurrentStyle(paragraph); - this.#applyStylesToCurrentStyle(inline); + this.#applyStylesToCurrentStyle(textSpan); return this; } @@ -334,7 +334,7 @@ export class SelectionController extends EventTarget { } // If focus node changed, we need to retrieve all the - // styles of the current inline and dispatch an event + // styles of the current text span and dispatch an event // to notify that the styles have changed. if (focusNodeChanges) { this.#notifyStyleChange(); @@ -355,19 +355,19 @@ export class SelectionController extends EventTarget { * Notifies that the styles have changed. */ #notifyStyleChange() { - const inline = this.focusInline; - if (inline) { - this.#updateCurrentStyle(inline); + const textSpan = this.focusTextSpan; + if (textSpan) { + this.#updateCurrentStyle(textSpan); this.dispatchEvent( new CustomEvent("stylechange", { detail: this.#currentStyle, }), ); } else { - const firstInline = + const firstTextSpan = this.#textEditor.root?.firstElementChild?.firstElementChild; - if (firstInline) { - this.#updateCurrentStyle(firstInline); + if (firstTextSpan) { + this.#updateCurrentStyle(firstTextSpan); this.dispatchEvent( new CustomEvent("stylechange", { detail: this.#currentStyle, @@ -766,13 +766,13 @@ export class SelectionController extends EventTarget { } /** - * Returns the inline in the focus node + * Returns the text span in the focus node * of the current selection. * * @type {HTMLElement|null} */ - get focusInline() { - return getInline(this.focusNode); + get focusTextSpan() { + return getTextSpan(this.focusNode); } /** @@ -786,13 +786,13 @@ export class SelectionController extends EventTarget { } /** - * Returns the current inline in the anchor + * Returns the current text span in the anchor * node of the current selection. * * @type {HTMLElement|null} */ - get anchorInline() { - return getInline(this.anchorNode); + get anchorTextSpan() { + return getTextSpan(this.anchorNode); } /** @@ -829,14 +829,14 @@ export class SelectionController extends EventTarget { } /** - * Start inline of the current page. + * Start text span of the current page. * * @type {HTMLElement|null} */ - get startInline() { + get startTextSpan() { const startContainer = this.startContainer; if (!startContainer) return null; - return getInline(startContainer); + return getTextSpan(startContainer); } /** @@ -876,15 +876,15 @@ export class SelectionController extends EventTarget { } /** - * Inline element of the `endContainer` of + * TextSpan element of the `endContainer` of * the current range. * * @type {HTMLElement|null} */ - get endInline() { + get endTextSpan() { const endContainer = this.endContainer; if (!endContainer) return null; - return getInline(endContainer); + return getTextSpan(endContainer); } /** @@ -919,21 +919,21 @@ export class SelectionController extends EventTarget { } /** - * Is true if the current focus node is a inline. + * Is true if the current focus node is a text span. * * @type {boolean} */ - get isInlineFocus() { - return isInline(this.focusNode); + get isTextSpanFocus() { + return isTextSpan(this.focusNode); } /** - * Is true if the current anchor node is a inline. + * Is true if the current anchor node is a text span. * * @type {boolean} */ - get isInlineAnchor() { - return isInline(this.anchorNode); + get isTextSpanAnchor() { + return isTextSpan(this.anchorNode); } /** @@ -962,7 +962,7 @@ export class SelectionController extends EventTarget { get isLineBreakFocus() { return ( isLineBreak(this.focusNode) || - (isInline(this.focusNode) && isLineBreak(this.focusNode.firstChild)) + (isTextSpan(this.focusNode) && isLineBreak(this.focusNode.firstChild)) ); } @@ -996,35 +996,35 @@ export class SelectionController extends EventTarget { /** * Indicates that we have selected multiple - * inline elements. + * text span elements. * * @type {boolean} */ - get isMultiInline() { - return this.isMulti && this.focusInline !== this.anchorInline; + get isMultiTextSpan() { + return this.isMulti && this.focusTextSpan !== this.anchorTextSpan; } /** * Indicates that the caret (only the caret) - * is at the start of an inline. + * is at the start of an text span. * * @type {boolean} */ - get isInlineStart() { + get isTextSpanStart() { if (!this.isCollapsed) return false; - return isInlineStart(this.focusNode, this.focusOffset); + return isTextSpanStart(this.focusNode, this.focusOffset); } /** * Indicates that the caret (only the caret) - * is at the end of an inline. This value doesn't + * is at the end of an text span. This value doesn't * matter when dealing with selections. * * @type {boolean} */ - get isInlineEnd() { + get isTextSpanEnd() { if (!this.isCollapsed) return false; - return isInlineEnd(this.focusNode, this.focusOffset); + return isTextSpanEnd(this.focusNode, this.focusOffset); } /** @@ -1055,18 +1055,18 @@ export class SelectionController extends EventTarget { insertPaste(fragment) { if ( fragment.children.length === 1 && - fragment.firstElementChild?.dataset?.inline === "force" + fragment.firstElementChild?.dataset?.textSpan === "force" ) { const collapseNode = fragment.firstElementChild.firstChild; - if (this.isInlineStart) { - this.focusInline.before(...fragment.firstElementChild.children); - } else if (this.isInlineEnd) { - this.focusInline.after(...fragment.firstElementChild.children); + if (this.isTextSpanStart) { + this.focusTextSpan.before(...fragment.firstElementChild.children); + } else if (this.isTextSpanEnd) { + this.focusTextSpan.after(...fragment.firstElementChild.children); } else { - const newInline = splitInline(this.focusInline, this.focusOffset); - this.focusInline.after( + const newTextSpan = splitTextSpan(this.focusTextSpan, this.focusOffset); + this.focusTextSpan.after( ...fragment.firstElementChild.children, - newInline, + newTextSpan, ); } return this.collapse(collapseNode, collapseNode.nodeValue.length); @@ -1085,7 +1085,7 @@ export class SelectionController extends EventTarget { } else { const newParagraph = splitParagraph( this.focusParagraph, - this.focusInline, + this.focusTextSpan, this.focusOffset, ); this.focusParagraph.after(fragment, newParagraph); @@ -1110,7 +1110,7 @@ export class SelectionController extends EventTarget { */ replaceLineBreak(text) { const newText = new Text(text); - this.focusInline.replaceChildren(newText); + this.focusTextSpan.replaceChildren(newText); this.collapse(newText, text.length); } @@ -1131,23 +1131,23 @@ export class SelectionController extends EventTarget { const paragraph = this.focusParagraph; if (!paragraph) throw new Error("Cannot find paragraph"); - const inline = this.focusInline; - if (!inline) throw new Error("Cannot find inline"); + const textSpan = this.focusTextSpan; + if (!textSpan) throw new Error("Cannot find text span"); const nextTextNode = this.#textNodeIterator.nextNode(); if (this.focusNode.nodeValue === "") { this.focusNode.remove(); } - if (paragraph.childNodes.length === 1 && inline.childNodes.length === 0) { + if (paragraph.childNodes.length === 1 && textSpan.childNodes.length === 0) { const lineBreak = createLineBreak(); - inline.appendChild(lineBreak); + textSpan.appendChild(lineBreak); return this.collapse(lineBreak, 0); } else if ( paragraph.childNodes.length > 1 && - inline.childNodes.length === 0 + textSpan.childNodes.length === 0 ) { - inline.remove(); + textSpan.remove(); return this.collapse(nextTextNode, 0); } return this.collapse(this.focusNode, this.focusOffset); @@ -1177,23 +1177,23 @@ export class SelectionController extends EventTarget { const paragraph = this.focusParagraph; if (!paragraph) throw new Error("Cannot find paragraph"); - const inline = this.focusInline; - if (!inline) throw new Error("Cannot find inline"); + const textSpan = this.focusTextSpan; + if (!textSpan) throw new Error("Cannot find text span"); const previousTextNode = this.#textNodeIterator.previousNode(); if (this.focusNode.nodeValue === "") { this.focusNode.remove(); } - if (paragraph.children.length === 1 && inline.childNodes.length === 0) { + if (paragraph.children.length === 1 && textSpan.childNodes.length === 0) { const lineBreak = createLineBreak(); - inline.appendChild(lineBreak); + textSpan.appendChild(lineBreak); return this.collapse(lineBreak, 0); } else if ( paragraph.children.length > 1 && - inline.childNodes.length === 0 + textSpan.childNodes.length === 0 ) { - inline.remove(); + textSpan.remove(); return this.collapse( previousTextNode, getTextNodeLength(previousTextNode), @@ -1214,7 +1214,7 @@ export class SelectionController extends EventTarget { this.focusOffset, newText, ); - this.#mutations.update(this.focusInline); + this.#mutations.update(this.focusTextSpan); return this.collapse(this.focusNode, this.focusOffset + newText.length); } @@ -1259,36 +1259,36 @@ export class SelectionController extends EventTarget { this.focusNode.replaceWith(new Text(newText)); } else if (this.isRootFocus) { const newTextNode = new Text(newText); - const newInline = createInline(newTextNode, this.#currentStyle); - const newParagraph = createParagraph([newInline], this.#currentStyle); + const newTextSpan = createTextSpan(newTextNode, this.#currentStyle); + const newParagraph = createParagraph([newTextSpan], this.#currentStyle); this.focusNode.replaceChildren(newParagraph); return this.collapse(newTextNode, newText.length + 1); } else { throw new Error("Unknown node type"); } - this.#mutations.update(this.focusInline); + this.#mutations.update(this.focusTextSpan); return this.collapse(this.focusNode, startOffset + newText.length); } /** - * Replaces the selected inlines with new text. + * Replaces the selected text spans with new text. * * @param {string} newText */ - replaceInlines(newText) { + replaceTextSpans(newText) { const currentParagraph = this.focusParagraph; // This is the special (and fast) case where we're // removing everything inside a paragraph. if ( - this.startInline === currentParagraph.firstChild && + this.startTextSpan === currentParagraph.firstChild && this.startOffset === 0 && - this.endInline === currentParagraph.lastChild && + this.endTextSpan === currentParagraph.lastChild && this.endOffset === currentParagraph.lastChild.textContent.length ) { const newTextNode = new Text(newText); currentParagraph.replaceChildren( - createInline(newTextNode, this.anchorInline.style), + createTextSpan(newTextNode, this.anchorTextSpan.style), ); return this.collapse(newTextNode, newTextNode.nodeValue.length); } @@ -1304,10 +1304,10 @@ export class SelectionController extends EventTarget { ); */ - // FIXME: I'm not sure if we should merge inlines when they share the same styles. - // For example: if we have > 2 inlines and the start inline and the end inline + // FIXME: I'm not sure if we should merge text spans when they share the same styles. + // For example: if we have > 2 text spans and the start text span and the end text span // share the same styles, maybe we should merge them? - // mergeInlines(startInline, endInline); + // mergeTextSpans(startTextSpan, endTextSpan); return this.collapse(this.focusNode, this.focusOffset + newText.length); } @@ -1368,7 +1368,7 @@ export class SelectionController extends EventTarget { const currentParagraph = this.focusParagraph; const newParagraph = splitParagraph( this.focusParagraph, - this.focusInline, + this.focusTextSpan, this.#focusOffset, ); this.focusParagraph.after(newParagraph); @@ -1395,13 +1395,13 @@ export class SelectionController extends EventTarget { */ replaceWithParagraph() { const currentParagraph = this.focusParagraph; - const currentInline = this.focusInline; + const currentTextSpan = this.focusTextSpan; this.removeSelected(); const newParagraph = splitParagraph( currentParagraph, - currentInline, + currentTextSpan, this.focusOffset, ); currentParagraph.after(newParagraph); @@ -1422,15 +1422,15 @@ export class SelectionController extends EventTarget { } const paragraphToBeRemoved = this.focusParagraph; paragraphToBeRemoved.remove(); - const previousInline = + const previousTextSpan = previousParagraph.children.length > 1 ? previousParagraph.lastElementChild : previousParagraph.firstChild; - const previousOffset = isLineBreak(previousInline.firstChild) + const previousOffset = isLineBreak(previousTextSpan.firstChild) ? 0 - : previousInline.firstChild.nodeValue.length; + : previousTextSpan.firstChild.nodeValue.length; this.#mutations.remove(paragraphToBeRemoved); - return this.collapse(previousInline.firstChild, previousOffset); + return this.collapse(previousTextSpan.firstChild, previousOffset); } /** @@ -1442,18 +1442,18 @@ export class SelectionController extends EventTarget { if (!previousParagraph) { return; } - let previousInline = previousParagraph.lastChild; - const previousOffset = getInlineLength(previousInline); + let previousTextSpan = previousParagraph.lastChild; + const previousOffset = getTextSpanLength(previousTextSpan); if (isEmptyParagraph(previousParagraph)) { previousParagraph.replaceChildren(...currentParagraph.children); - previousInline = previousParagraph.firstChild; + previousTextSpan = previousParagraph.firstChild; currentParagraph.remove(); } else { mergeParagraphs(previousParagraph, currentParagraph); } this.#mutations.remove(currentParagraph); this.#mutations.update(previousParagraph); - return this.collapse(previousInline.firstChild, previousOffset); + return this.collapse(previousTextSpan.firstChild, previousOffset); } /** @@ -1482,24 +1482,24 @@ export class SelectionController extends EventTarget { } const paragraphToBeRemoved = this.focusParagraph; paragraphToBeRemoved.remove(); - const nextInline = nextParagraph.firstChild; + const nextTextSpan = nextParagraph.firstChild; const nextOffset = this.focusOffset; this.#mutations.remove(paragraphToBeRemoved); - return this.collapse(nextInline.firstChild, nextOffset); + return this.collapse(nextTextSpan.firstChild, nextOffset); } /** * Cleans up all the affected paragraphs. * * @param {Set} affectedParagraphs - * @param {Set} affectedInlines + * @param {Set} affectedTextSpans */ - cleanUp(affectedParagraphs, affectedInlines) { - // Remove empty inlines - for (const inline of affectedInlines) { - if (inline.textContent === "") { - inline.remove(); - this.#mutations.remove(inline); + cleanUp(affectedParagraphs, affectedTextSpans) { + // Remove empty text spans + for (const textSpan of affectedTextSpans) { + if (textSpan.textContent === "") { + textSpan.remove(); + this.#mutations.remove(textSpan); } } @@ -1520,7 +1520,7 @@ export class SelectionController extends EventTarget { removeSelected(options) { if (this.isCollapsed) return; - const affectedInlines = new Set(); + const affectedTextSpans = new Set(); const affectedParagraphs = new Set(); const startNode = getClosestTextNode(this.#range.startContainer); @@ -1541,9 +1541,9 @@ export class SelectionController extends EventTarget { this.#textNodeIterator.currentNode = startNode; nextNode = this.#textNodeIterator.nextNode(); - const inline = getInline(startNode); + const textSpan = getTextSpan(startNode); const paragraph = getParagraph(startNode); - affectedInlines.add(inline); + affectedTextSpans.add(textSpan); affectedParagraphs.add(paragraph); const newNodeValue = removeSlice( @@ -1553,7 +1553,7 @@ export class SelectionController extends EventTarget { ); if (newNodeValue === "") { const lineBreak = createLineBreak(); - inline.replaceChildren(lineBreak); + textSpan.replaceChildren(lineBreak); return this.collapse(lineBreak, 0); } startNode.nodeValue = newNodeValue; @@ -1567,9 +1567,9 @@ export class SelectionController extends EventTarget { // Select initial node. this.#textNodeIterator.currentNode = startNode; - const startInline = getInline(startNode); + const startTextSpan = getTextSpan(startNode); const startParagraph = getParagraph(startNode); - const endInline = getInline(endNode); + const endTextSpan = getTextSpan(endNode); const endParagraph = getParagraph(endNode); SafeGuard.start(); @@ -1578,11 +1578,11 @@ export class SelectionController extends EventTarget { const { currentNode } = this.#textNodeIterator; - // We retrieve the inline and paragraph of the + // We retrieve the textSpan and paragraph of the // current node. - const inline = getInline(currentNode); + const textSpan = getTextSpan(currentNode); const paragraph = getParagraph(currentNode); - affectedInlines.add(inline); + affectedTextSpans.add(textSpan); affectedParagraphs.add(paragraph); let shouldRemoveNodeCompletely = false; @@ -1619,8 +1619,8 @@ export class SelectionController extends EventTarget { continue; } - if (inline.childNodes.length === 0) { - inline.remove(); + if (textSpan.childNodes.length === 0) { + textSpan.remove(); } if (paragraph !== startParagraph && paragraph.children.length === 0) { @@ -1636,44 +1636,44 @@ export class SelectionController extends EventTarget { if (startParagraph !== endParagraph) { const mergedParagraph = mergeParagraphs(startParagraph, endParagraph); if (mergedParagraph.children.length === 0) { - const newEmptyInline = createEmptyInline(this.#currentStyle); - mergedParagraph.appendChild(newEmptyInline); - return this.collapse(newEmptyInline.firstChild, 0); + const newEmptyTextSpan = createEmptyTextSpan(this.#currentStyle); + mergedParagraph.appendChild(newEmptyTextSpan); + return this.collapse(newEmptyTextSpan.firstChild, 0); } } if ( - startInline.childNodes.length === 0 && - endInline.childNodes.length > 0 + startTextSpan.childNodes.length === 0 && + endTextSpan.childNodes.length > 0 ) { - startInline.remove(); + startTextSpan.remove(); return this.collapse(endNode, 0); } else if ( - startInline.childNodes.length > 0 && - endInline.childNodes.length === 0 + startTextSpan.childNodes.length > 0 && + endTextSpan.childNodes.length === 0 ) { - endInline.remove(); + endTextSpan.remove(); return this.collapse(startNode, startOffset); } else if ( - startInline.childNodes.length === 0 && - endInline.childNodes.length === 0 + startTextSpan.childNodes.length === 0 && + endTextSpan.childNodes.length === 0 ) { - const previousInline = startInline.previousElementSibling; - const nextInline = endInline.nextElementSibling; - startInline.remove(); - endInline.remove(); - if (previousInline) { + const previousTextSpan = startTextSpan.previousElementSibling; + const nextTextSpan = endTextSpan.nextElementSibling; + startTextSpan.remove(); + endTextSpan.remove(); + if (previousTextSpan) { return this.collapse( - previousInline.firstChild, - previousInline.firstChild.nodeValue.length, + previousTextSpan.firstChild, + previousTextSpan.firstChild.nodeValue.length, ); } - if (nextInline) { - return this.collapse(nextInline.firstChild, 0); + if (nextTextSpan) { + return this.collapse(nextTextSpan.firstChild, 0); } - const newEmptyInline = createEmptyInline(this.#currentStyle); - startParagraph.appendChild(newEmptyInline); - return this.collapse(newEmptyInline.firstChild, 0); + const newEmptyTextSpan = createEmptyTextSpan(this.#currentStyle); + startParagraph.appendChild(newEmptyTextSpan); + return this.collapse(newEmptyTextSpan.firstChild, 0); } return this.collapse(startNode, startOffset); @@ -1701,30 +1701,30 @@ export class SelectionController extends EventTarget { // The styles are applied to the node completely. if (startOffset === 0 && endOffset === endNode.nodeValue.length) { const paragraph = this.startParagraph; - const inline = this.startInline; + const textSpan = this.startTextSpan; setParagraphStyles(paragraph, newStyles); - setInlineStyles(inline, newStyles); + setTextSpanStyles(textSpan, newStyles); // The styles are applied to a part of the node. } else if (startOffset !== endOffset) { const paragraph = this.startParagraph; setParagraphStyles(paragraph, newStyles); - const inline = this.startInline; + const textSpan = this.startTextSpan; const midText = startNode.splitText(startOffset); const endText = midText.splitText(endOffset - startOffset); - const midInline = createInlineFrom(inline, midText, newStyles); - inline.after(midInline); + const midTextSpan = createTextSpanFrom(textSpan, midText, newStyles); + textSpan.after(midTextSpan); if (endText.length > 0) { - const endInline = createInline(endText, inline.style); - midInline.after(endInline); + const endTextSpan = createTextSpan(endText, textSpan.style); + midTextSpan.after(endTextSpan); } // NOTE: This is necessary because sometimes - // inlines are splitted from the beginning + // text spans are splitted from the beginning // to a mid offset and then the starting node // remains empty. - if (inline.firstChild.nodeValue === "") { - inline.remove(); + if (textSpan.firstChild.nodeValue === "") { + textSpan.remove(); } // FIXME: This can change focus <-> anchor order. @@ -1735,9 +1735,9 @@ export class SelectionController extends EventTarget { this.startOffset === this.endOffset && this.endOffset === endNode.nodeValue.length ) { - const newInline = createVoidInline(newStyles); - this.endInline.after(newInline); - this.setSelection(newInline.firstChild, 0, newInline.firstChild, 0); + const newTextSpan = createVoidTextSpan(newStyles); + this.endTextSpan.after(newTextSpan); + this.setSelection(newTextSpan.firstChild, 0, newTextSpan.firstChild, 0); } // The styles are applied to the paragraph else { @@ -1758,17 +1758,17 @@ export class SelectionController extends EventTarget { const paragraph = getParagraph(this.#textNodeIterator.currentNode); setParagraphStyles(paragraph, newStyles); - const inline = getInline(this.#textNodeIterator.currentNode); + const textSpan = getTextSpan(this.#textNodeIterator.currentNode); // If we're at the start node and offset is greater than 0 - // then we should split the inline and apply styles to that - // new inline. + // then we should split the text span and apply styles to that + // new text span. if ( this.#textNodeIterator.currentNode === startNode && startOffset > 0 ) { - const newInline = splitInline(inline, startOffset); - setInlineStyles(newInline, newStyles); - inline.after(newInline); + const newTextSpan = splitTextSpan(textSpan, startOffset); + setTextSpanStyles(newTextSpan, newStyles); + textSpan.after(newTextSpan); // If we're at the start node and offset is equal to 0 // or current node is different to start node and // different to end node or we're at the end node @@ -1781,16 +1781,16 @@ export class SelectionController extends EventTarget { (this.#textNodeIterator.currentNode === endNode && endOffset === endNode.nodeValue.length) ) { - setInlineStyles(inline, newStyles); + setTextSpanStyles(textSpan, newStyles); // If we're at end node } else if ( this.#textNodeIterator.currentNode === endNode && endOffset < endNode.nodeValue.length ) { - const newInline = splitInline(inline, endOffset); - setInlineStyles(inline, newStyles); - inline.after(newInline); + const newTextSpan = splitTextSpan(textSpan, endOffset); + setTextSpanStyles(textSpan, newStyles); + textSpan.after(newTextSpan); } // We've reached the final node so we can return safely. diff --git a/frontend/text-editor/src/editor/controllers/SelectionController.test.js b/frontend/text-editor/src/editor/controllers/SelectionController.test.js index c44989f692..726acf2e4c 100644 --- a/frontend/text-editor/src/editor/controllers/SelectionController.test.js +++ b/frontend/text-editor/src/editor/controllers/SelectionController.test.js @@ -3,7 +3,7 @@ import { createEmptyParagraph, createParagraph, } from "../content/dom/Paragraph.js"; -import { createInline } from "../content/dom/Inline.js"; +import { createTextSpan } from "../content/dom/TextSpan.js"; import { createLineBreak } from "../content/dom/LineBreak.js"; import { TextEditorMock } from "../../test/TextEditorMock.js"; import { SelectionController } from "./SelectionController.js"; @@ -206,7 +206,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -235,7 +235,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -256,7 +256,7 @@ describe("SelectionController", () => { selection, ); focus(selection, textEditorMock, root.firstChild.firstChild.firstChild, 0); - const paragraph = createParagraph([createInline(new Text("Hello"))]); + const paragraph = createParagraph([createTextSpan(new Text("Hello"))]); const fragment = document.createDocumentFragment(); fragment.append(paragraph); @@ -269,7 +269,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -293,7 +293,7 @@ describe("SelectionController", () => { selection, ); focus(selection, textEditorMock, root.firstChild.firstChild.firstChild, "Lorem ".length); - const paragraph = createParagraph([createInline(new Text("ipsum "))]); + const paragraph = createParagraph([createTextSpan(new Text("ipsum "))]); const fragment = document.createDocumentFragment(); fragment.append(paragraph); @@ -306,7 +306,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Lorem ipsum dolor"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -337,7 +337,7 @@ describe("SelectionController", () => { root.firstChild.firstChild.firstChild, "Hello".length, ); - const paragraph = createParagraph([createInline(new Text(", World!"))]); + const paragraph = createParagraph([createTextSpan(new Text(", World!"))]); const fragment = document.createDocumentFragment(); fragment.append(paragraph); @@ -350,7 +350,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -364,7 +364,7 @@ describe("SelectionController", () => { ).toBe(", World!"); }); - test("`insertPaste` should insert an inline from a pasted fragment (at start)", () => { + test("`insertPaste` should insert a text span from a pasted fragment (at start)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithText(", World!"); const root = textEditorMock.root; const selection = document.getSelection(); @@ -378,8 +378,8 @@ describe("SelectionController", () => { root.firstChild.firstChild.firstChild, 0, ); - const paragraph = createParagraph([createInline(new Text("Hello"))]); - paragraph.dataset.inline = "force"; + const paragraph = createParagraph([createTextSpan(new Text("Hello"))]); + paragraph.dataset.textSpan = "force"; const fragment = document.createDocumentFragment(); fragment.append(paragraph); @@ -392,7 +392,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -406,7 +406,7 @@ describe("SelectionController", () => { ).toBe(", World!"); }); - test("`insertPaste` should insert an inline from a pasted fragment (at middle)", () => { + test("`insertPaste` should insert an text span from a pasted fragment (at middle)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithText("Lorem dolor"); const root = textEditorMock.root; @@ -416,8 +416,8 @@ describe("SelectionController", () => { selection, ); focus(selection, textEditorMock, root.firstChild.firstChild.firstChild, "Lorem ".length); - const paragraph = createParagraph([createInline(new Text("ipsum "))]); - paragraph.dataset.inline = "force"; + const paragraph = createParagraph([createTextSpan(new Text("ipsum "))]); + paragraph.dataset.textSpan = "force"; const fragment = document.createDocumentFragment(); fragment.append(paragraph); @@ -430,7 +430,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Lorem ipsum dolor"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -447,7 +447,7 @@ describe("SelectionController", () => { ).toBe("dolor"); }); - test("`insertPaste` should insert an inline from a pasted fragment (at end)", () => { + test("`insertPaste` should insert an text span from a pasted fragment (at end)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithText("Hello"); const root = textEditorMock.root; const selection = document.getSelection(); @@ -462,9 +462,9 @@ describe("SelectionController", () => { "Hello".length, ); const paragraph = createParagraph([ - createInline(new Text(", World!")) + createTextSpan(new Text(", World!")) ]); - paragraph.dataset.inline = "force"; + paragraph.dataset.textSpan = "force"; const fragment = document.createDocumentFragment(); fragment.append(paragraph); @@ -477,7 +477,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -515,7 +515,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -549,15 +549,15 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe(""); }); test("`mergeBackwardParagraph` should merge two paragraphs in backward direction (backspace)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, "))]), - createParagraph([createInline(new Text("World!"))]), + createParagraph([createTextSpan(new Text("Hello, "))]), + createParagraph([createTextSpan(new Text("World!"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -581,16 +581,16 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); }); test("`mergeBackwardParagraph` should merge two paragraphs in backward direction (backspace)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, "))]), + createParagraph([createTextSpan(new Text("Hello, "))]), createEmptyParagraph(), - createParagraph([createInline(new Text("World!"))]), + createParagraph([createTextSpan(new Text("World!"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -614,7 +614,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.textContent).toBe("Hello, "); @@ -623,8 +623,8 @@ describe("SelectionController", () => { test("`mergeForwardParagraph` should merge two paragraphs in forward direction (backspace)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, "))]), - createParagraph([createInline(new Text("World!"))]), + createParagraph([createTextSpan(new Text("Hello, "))]), + createParagraph([createTextSpan(new Text("World!"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -648,16 +648,16 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); }); test("`mergeForwardParagraph` should merge two paragraphs in forward direction (backspace)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, "))]), + createParagraph([createTextSpan(new Text("Hello, "))]), createEmptyParagraph(), - createParagraph([createInline(new Text("World!"))]), + createParagraph([createTextSpan(new Text("World!"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -681,7 +681,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.textContent).toBe("Hello, "); @@ -707,7 +707,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("ello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -744,7 +744,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, Mundo!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -755,10 +755,10 @@ describe("SelectionController", () => { ); }); - test("`replaceInlines` should replace the selected text in multiple inlines (2 completelly selected)", () => { + test("`replaceTextSpans` should replace the selected text in multiple text spans (2 completelly selected)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraph([ - createInline(new Text("Hello, ")), - createInline(new Text("World!")), + createTextSpan(new Text("Hello, ")), + createTextSpan(new Text("World!")), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -774,7 +774,7 @@ describe("SelectionController", () => { root.firstChild.lastChild.firstChild, "World!".length, ); - selectionController.replaceInlines("Mundo"); + selectionController.replaceTextSpans("Mundo"); expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement); expect(textEditorMock.root.dataset.itype).toBe("root"); @@ -785,7 +785,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Mundo"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -796,10 +796,10 @@ describe("SelectionController", () => { ); }); - test("`replaceInlines` should replace the selected text in multiple inlines (2 partially selected)", () => { + test("`replaceTextSpans` should replace the selected text in multiple text spans (2 partially selected)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraph([ - createInline(new Text("Hello, ")), - createInline(new Text("World!")), + createTextSpan(new Text("Hello, ")), + createTextSpan(new Text("World!")), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -815,7 +815,7 @@ describe("SelectionController", () => { root.firstChild.lastChild.firstChild, "World!".length - 3, ); - selectionController.replaceInlines("Mundo"); + selectionController.replaceTextSpans("Mundo"); expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement); expect(textEditorMock.root.dataset.itype).toBe("root"); expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement); @@ -825,7 +825,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("HeMundold!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -842,10 +842,10 @@ describe("SelectionController", () => { ); }); - test("`replaceInlines` should replace the selected text in multiple inlines (1 partially selected, 1 completelly selected)", () => { + test("`replaceTextSpans` should replace the selected text in multiple text spans (1 partially selected, 1 completelly selected)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraph([ - createInline(new Text("Hello, ")), - createInline(new Text("World!")), + createTextSpan(new Text("Hello, ")), + createTextSpan(new Text("World!")), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -861,7 +861,7 @@ describe("SelectionController", () => { root.firstChild.lastChild.firstChild, "World!".length, ); - selectionController.replaceInlines("Mundo"); + selectionController.replaceTextSpans("Mundo"); expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement); expect(textEditorMock.root.dataset.itype).toBe("root"); expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement); @@ -871,7 +871,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("HeMundo"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -882,10 +882,10 @@ describe("SelectionController", () => { ); }); - test("`replaceInlines` should replace the selected text in multiple inlines (1 completelly selected, 1 partially selected)", () => { + test("`replaceTextSpans` should replace the selected text in multiple text spans (1 completelly selected, 1 partially selected)", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraph([ - createInline(new Text("Hello, ")), - createInline(new Text("World!")), + createTextSpan(new Text("Hello, ")), + createTextSpan(new Text("World!")), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -901,7 +901,7 @@ describe("SelectionController", () => { root.firstChild.lastChild.firstChild, "World!".length - 3, ); - selectionController.replaceInlines("Mundo"); + selectionController.replaceTextSpans("Mundo"); expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement); expect(textEditorMock.root.dataset.itype).toBe("root"); expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement); @@ -911,7 +911,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Mundold!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -949,7 +949,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, !"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -960,10 +960,10 @@ describe("SelectionController", () => { ); }); - test("`removeSelected` multiple inlines", () => { + test("`removeSelected` multiple text spans", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraph([ - createInline(new Text("Hello, ")), - createInline(new Text("World!")), + createTextSpan(new Text("Hello, ")), + createTextSpan(new Text("World!")), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -989,7 +989,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe(""); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -999,9 +999,9 @@ describe("SelectionController", () => { test("`removeSelected` multiple paragraphs", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, "))]), - createParagraph([createInline(createLineBreak())]), - createParagraph([createInline(new Text("World!"))]), + createParagraph([createTextSpan(new Text("Hello, "))]), + createParagraph([createTextSpan(createLineBreak())]), + createParagraph([createTextSpan(new Text("World!"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -1028,7 +1028,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -1047,9 +1047,9 @@ describe("SelectionController", () => { test("`removeSelected` and `removeBackwardParagraph`", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, World!"))]), - createParagraph([createInline(createLineBreak())]), - createParagraph([createInline(new Text("This is a test"))]), + createParagraph([createTextSpan(new Text("Hello, World!"))]), + createParagraph([createTextSpan(createLineBreak())]), + createParagraph([createTextSpan(new Text("This is a test"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -1077,7 +1077,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -1090,9 +1090,9 @@ describe("SelectionController", () => { test("`removeSelected` and `removeForwardParagraph`", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, World!"))]), - createParagraph([createInline(createLineBreak())]), - createParagraph([createInline(new Text("This is a test"))]), + createParagraph([createTextSpan(new Text("Hello, World!"))]), + createParagraph([createTextSpan(createLineBreak())]), + createParagraph([createTextSpan(new Text("This is a test"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -1120,7 +1120,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("This is a test"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -1133,9 +1133,9 @@ describe("SelectionController", () => { test("performing a `removeSelected` after a `removeSelected` should do nothing", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, World!"))]), - createParagraph([createInline(createLineBreak())]), - createParagraph([createInline(new Text("This is a test"))]), + createParagraph([createTextSpan(new Text("Hello, World!"))]), + createParagraph([createTextSpan(createLineBreak())]), + createParagraph([createTextSpan(new Text("This is a test"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -1166,7 +1166,7 @@ describe("SelectionController", () => { HTMLSpanElement, ); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("This is a test"); expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf( @@ -1179,9 +1179,9 @@ describe("SelectionController", () => { test("`removeSelected` removes everything", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, World!"))]), - createParagraph([createInline(createLineBreak())]), - createParagraph([createInline(new Text("This is a test"))]), + createParagraph([createTextSpan(new Text("Hello, World!"))]), + createParagraph([createTextSpan(createLineBreak())]), + createParagraph([createTextSpan(new Text("This is a test"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -1212,9 +1212,9 @@ describe("SelectionController", () => { test("`removeSelected` removes everything and insert text", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ - createParagraph([createInline(new Text("Hello, World!"))]), - createParagraph([createInline(createLineBreak())]), - createParagraph([createInline(new Text("This is a test"))]), + createParagraph([createTextSpan(new Text("Hello, World!"))]), + createParagraph([createTextSpan(createLineBreak())]), + createParagraph([createTextSpan(new Text("This is a test"))]), ]); const root = textEditorMock.root; const selection = document.getSelection(); @@ -1277,7 +1277,7 @@ describe("SelectionController", () => { ); expect(textEditorMock.root.firstChild.children.length).toBe(3); expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.textContent).toBe("Hello, World!"); expect(textEditorMock.root.firstChild.children.item(0).textContent).toBe( @@ -1291,12 +1291,12 @@ describe("SelectionController", () => { ); }); - test("`applyStyles` to inlines", () => { + test("`applyStyles` to text spans", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraph([ - createInline(new Text("Hello, "), { + createTextSpan(new Text("Hello, "), { "font-style": "italic", }), - createInline(new Text("World!"), { + createTextSpan(new Text("World!"), { "font-style": "oblique", }), ]); @@ -1328,25 +1328,25 @@ describe("SelectionController", () => { ); expect(textEditorMock.root.firstChild.children.length).toBe(4); expect(textEditorMock.root.firstChild.children.item(0).dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.firstChild.children.item(0).textContent).toBe( "He", ); expect(textEditorMock.root.firstChild.children.item(1).dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.firstChild.children.item(1).textContent).toBe( "llo, ", ); expect(textEditorMock.root.firstChild.children.item(2).dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.firstChild.children.item(2).textContent).toBe( "Wor", ); expect(textEditorMock.root.firstChild.children.item(3).dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.firstChild.children.item(3).textContent).toBe( "ld!", @@ -1356,12 +1356,12 @@ describe("SelectionController", () => { test("`applyStyles` to paragraphs", () => { const textEditorMock = TextEditorMock.createTextEditorMockWithParagraphs([ createParagraph([ - createInline(new Text("Hello, "), { + createTextSpan(new Text("Hello, "), { "font-style": "italic", }), ]), createParagraph([ - createInline(new Text("World!"), { + createTextSpan(new Text("World!"), { "font-style": "oblique", }), ]), @@ -1394,26 +1394,26 @@ describe("SelectionController", () => { ); expect(textEditorMock.root.firstChild.children.length).toBe(2); expect(textEditorMock.root.firstChild.children.item(0).dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.firstChild.children.item(0).textContent).toBe( "He", ); expect(textEditorMock.root.firstChild.children.item(1).dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.firstChild.children.item(1).textContent).toBe( "llo, ", ); expect(textEditorMock.root.lastChild.children.length).toBe(2); expect(textEditorMock.root.lastChild.children.item(0).dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.lastChild.children.item(0).textContent).toBe( "Wor", ); expect(textEditorMock.root.lastChild.children.item(1).dataset.itype).toBe( - "inline", + "span", ); expect(textEditorMock.root.lastChild.children.item(1).textContent).toBe( "ld!", diff --git a/frontend/text-editor/src/editor/debug/SelectionControllerDebug.js b/frontend/text-editor/src/editor/debug/SelectionControllerDebug.js index d79ec09e28..e588497a4d 100644 --- a/frontend/text-editor/src/editor/debug/SelectionControllerDebug.js +++ b/frontend/text-editor/src/editor/debug/SelectionControllerDebug.js @@ -36,15 +36,15 @@ export class SelectionControllerDebug { update(selectionController) { this.#elements.direction.value = selectionController.direction; this.#elements.multiElement.checked = selectionController.isMulti; - this.#elements.multiInlineElement.checked = - selectionController.isMultiInline; + this.#elements.multiTextSpanElement.checked = + selectionController.isMultiTextSpan; this.#elements.multiParagraphElement.checked = selectionController.isMultiParagraph; this.#elements.isParagraphStart.checked = selectionController.isParagraphStart; this.#elements.isParagraphEnd.checked = selectionController.isParagraphEnd; - this.#elements.isInlineStart.checked = selectionController.isInlineStart; - this.#elements.isInlineEnd.checked = selectionController.isInlineEnd; + this.#elements.isTextSpanStart.checked = selectionController.isTextSpanStart; + this.#elements.isTextSpanEnd.checked = selectionController.isTextSpanEnd; this.#elements.isTextAnchor.checked = selectionController.isTextAnchor; this.#elements.isTextFocus.checked = selectionController.isTextFocus; this.#elements.focusNode.value = this.getNodeDescription( @@ -57,11 +57,11 @@ export class SelectionControllerDebug { selectionController.anchorOffset ); this.#elements.anchorOffset.value = selectionController.anchorOffset; - this.#elements.focusInline.value = this.getNodeDescription( - selectionController.focusInline + this.#elements.focusTextSpan.value = this.getNodeDescription( + selectionController.focusTextSpan ); - this.#elements.anchorInline.value = this.getNodeDescription( - selectionController.anchorInline + this.#elements.anchorTextSpan.value = this.getNodeDescription( + selectionController.anchorTextSpan ); this.#elements.focusParagraph.value = this.getNodeDescription( selectionController.focusParagraph diff --git a/frontend/text-editor/src/index.html b/frontend/text-editor/src/index.html index 581231d20f..1f1e9e5ae7 100644 --- a/frontend/text-editor/src/index.html +++ b/frontend/text-editor/src/index.html @@ -129,8 +129,8 @@
- - + +
@@ -145,8 +145,8 @@
- - + +
@@ -173,8 +173,8 @@
- - + +
@@ -197,12 +197,12 @@
- - + +
- - + +
diff --git a/frontend/text-editor/src/playground.js b/frontend/text-editor/src/playground.js index 30e64fe0e7..8db4a3ac09 100644 --- a/frontend/text-editor/src/playground.js +++ b/frontend/text-editor/src/playground.js @@ -6,7 +6,7 @@ import { UUID } from "./playground/uuid.js"; import { Rect, Point } from "./playground/geom.js"; import { WASMModuleWrapper } from "./playground/wasm.js"; import { FontManager } from "./playground/font.js"; -import { TextContent, TextParagraph, TextLeaf } from "./playground/text.js"; +import { TextContent, TextParagraph, TextSpan } from "./playground/text.js"; import { Viewport } from "./playground/viewport.js"; import { Fill } from "./playground/fill.js"; import { Shape } from "./playground/shape.js"; @@ -80,21 +80,21 @@ class TextEditorPlayground { debug: new SelectionControllerDebug({ direction: document.getElementById("direction"), multiElement: document.getElementById("multi"), - multiInlineElement: document.getElementById("multi-inline"), + multiTextSpanElement: document.getElementById("multi-textspan"), multiParagraphElement: document.getElementById("multi-paragraph"), isParagraphStart: document.getElementById("is-paragraph-start"), isParagraphEnd: document.getElementById("is-paragraph-end"), - isInlineStart: document.getElementById("is-inline-start"), - isInlineEnd: document.getElementById("is-inline-end"), + isTextSpanStart: document.getElementById("is-textspan-start"), + isTextSpanEnd: document.getElementById("is-textspan-end"), isTextAnchor: document.getElementById("is-text-anchor"), isTextFocus: document.getElementById("is-text-focus"), focusNode: document.getElementById("focus-node"), focusOffset: document.getElementById("focus-offset"), - focusInline: document.getElementById("focus-inline"), + focusTextSpan: document.getElementById("focus-textspan"), focusParagraph: document.getElementById("focus-paragraph"), anchorNode: document.getElementById("anchor-node"), anchorOffset: document.getElementById("anchor-offset"), - anchorInline: document.getElementById("anchor-inline"), + anchorTextSpan: document.getElementById("anchor-textspan"), anchorParagraph: document.getElementById("anchor-paragraph"), startContainer: document.getElementById("start-container"), startOffset: document.getElementById("start-offset"), @@ -496,7 +496,7 @@ class TextEditorPlayground { leaf.fills.forEach((fill, index) => { const fillOffset = - offset + TextLeaf.BYTE_LENGTH + index * Fill.BYTE_LENGTH; + offset + TextSpan.BYTE_LENGTH + index * Fill.BYTE_LENGTH; if (fill.type === Fill.Type.SOLID) { view.setUint8(fillOffset + 0, fill.type, true); view.setUint32(fillOffset + 4, fill.solid.color.argb32, true); diff --git a/frontend/text-editor/src/playground/text.js b/frontend/text-editor/src/playground/text.js index c0f6969962..a5cf56d964 100644 --- a/frontend/text-editor/src/playground/text.js +++ b/frontend/text-editor/src/playground/text.js @@ -37,11 +37,11 @@ export const TextTransform = { fromStyle, }; -export class TextLeaf { +export class TextSpan { static BYTE_LENGTH = 60; - static fromDOM(leafElement, fontManager) { - const elementStyle = leafElement.style; //window.getComputedStyle(leafElement); + static fromDOM(spanElement, fontManager) { + const elementStyle = spanElement.style; //window.getComputedStyle(leafElement); const fontSize = parseFloat( elementStyle.getPropertyValue("font-size"), ); @@ -77,7 +77,7 @@ export class TextLeaf { if (!font) { throw new Error(`Invalid font "${fontFamily}"`) } - return new TextLeaf({ + return new TextSpan({ fontId: font.id, // leafElement.style.getPropertyValue("--font-id"), fontFamilyHash: 0, fontVariantId: UUID.ZERO, // leafElement.style.getPropertyValue("--font-variant-id"), @@ -88,7 +88,7 @@ export class TextLeaf { textDecoration, textTransform, textDirection, - text: leafElement.textContent, + text: spanElement.textContent, }); } @@ -134,7 +134,7 @@ export class TextLeaf { } get leafByteLength() { - return this.fills.length * Fill.BYTE_LENGTH + TextLeaf.BYTE_LENGTH; + return this.fills.length * Fill.BYTE_LENGTH + TextSpan.BYTE_LENGTH; } } @@ -162,7 +162,7 @@ export class TextParagraph { paragraphElement.style.getPropertyValue("letter-spacing"), ), leaves: Array.from(paragraphElement.children, (leafElement) => - TextLeaf.fromDOM(leafElement, fontManager), + TextSpan.fromDOM(leafElement, fontManager), ), }); } @@ -189,7 +189,7 @@ export class TextParagraph { this.#leaves = init?.leaves ?? []; if ( !Array.isArray(this.#leaves) || - !this.#leaves.every((leaf) => leaf instanceof TextLeaf) + !this.#leaves.every((leaf) => leaf instanceof TextSpan) ) { throw new TypeError("Invalid text leaves"); } diff --git a/frontend/text-editor/src/test/TextEditorMock.js b/frontend/text-editor/src/test/TextEditorMock.js index ef66f5d6f1..2fe620d3a3 100644 --- a/frontend/text-editor/src/test/TextEditorMock.js +++ b/frontend/text-editor/src/test/TextEditorMock.js @@ -1,6 +1,6 @@ import { createRoot } from "../editor/content/dom/Root.js"; import { createParagraph } from "../editor/content/dom/Paragraph.js"; -import { createEmptyInline, createInline } from "../editor/content/dom/Inline.js"; +import { createEmptyTextSpan, createTextSpan } from "../editor/content/dom/TextSpan.js"; import { createLineBreak } from "../editor/content/dom/LineBreak.js"; export class TextEditorMock extends EventTarget { @@ -68,7 +68,7 @@ export class TextEditorMock extends EventTarget { */ static createTextEditorMockEmpty() { const root = createRoot([ - createParagraph([createInline(createLineBreak())]), + createParagraph([createTextSpan(createLineBreak())]), ]); return this.createTextEditorMockWithRoot(root); } @@ -76,7 +76,7 @@ export class TextEditorMock extends EventTarget { /** * Creates a TextEditor mock with some text. * - * NOTE: If the text is empty an empty inline will be + * NOTE: If the text is empty an empty text span will be * created. * * @param {string} text @@ -86,21 +86,21 @@ export class TextEditorMock extends EventTarget { return this.createTextEditorMockWithParagraphs([ createParagraph([ text.length === 0 - ? createEmptyInline() - : createInline(new Text(text)) + ? createEmptyTextSpan() + : createTextSpan(new Text(text)) ]), ]); } /** - * Creates a TextEditor mock with some inlines and + * Creates a TextEditor mock with some textSpans and * only one paragraph. * - * @param {Array} inlines + * @param {Array} textSpans * @returns */ - static createTextEditorMockWithParagraph(inlines) { - return this.createTextEditorMockWithParagraphs([createParagraph(inlines)]); + static createTextEditorMockWithParagraph(textSpans) { + return this.createTextEditorMockWithParagraphs([createParagraph(textSpans)]); } #element = null; diff --git a/render-wasm/src/render/text.rs b/render-wasm/src/render/text.rs index 3b8322964c..52c4e25de6 100644 --- a/render-wasm/src/render/text.rs +++ b/render-wasm/src/render/text.rs @@ -33,8 +33,8 @@ pub fn stroke_paragraph_builder_group_from_text( let mut stroke_paragraphs_map: std::collections::HashMap = std::collections::HashMap::new(); - for leaf in paragraph.children().iter() { - let text_paint: skia_safe::Handle<_> = merge_fills(leaf.fills(), *bounds); + for span in paragraph.children().iter() { + let text_paint: skia_safe::Handle<_> = merge_fills(span.fills(), *bounds); let stroke_paints = get_text_stroke_paints( stroke, bounds, @@ -43,7 +43,7 @@ pub fn stroke_paragraph_builder_group_from_text( remove_stroke_alpha, ); - let text: String = leaf.apply_text_transform(); + let text: String = span.apply_text_transform(); for (paint_idx, stroke_paint) in stroke_paints.iter().enumerate() { let builder = stroke_paragraphs_map.entry(paint_idx).or_insert_with(|| { @@ -51,9 +51,9 @@ pub fn stroke_paragraph_builder_group_from_text( ParagraphBuilder::new(¶graph_style, fonts) }); let stroke_paint = stroke_paint.clone(); - let remove_alpha = use_shadow.unwrap_or(false) && !leaf.is_transparent(); + let remove_alpha = use_shadow.unwrap_or(false) && !span.is_transparent(); let stroke_style = - leaf.to_stroke_style(&stroke_paint, fallback_fonts, remove_alpha); + span.to_stroke_style(&stroke_paint, fallback_fonts, remove_alpha); builder.push_style(&stroke_style); builder.add_text(&text); } @@ -342,7 +342,7 @@ fn render_text_decoration( let (max_underline_thickness, underline_y, max_strike_thickness, strike_y) = calculate_decoration_metrics(&style_metrics, line_baseline); - // Draw decorations per segment (text leaf) + // Draw decorations per segment (text span) for (i, (style_start, style_metric)) in style_metrics.iter().enumerate() { let text_style = &style_metric.text_style; let style_end = style_metrics diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index b083742cd4..257c95790d 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -98,7 +98,7 @@ impl TextContentSize { pub struct TextPositionWithAffinity { pub position_with_affinity: PositionWithAffinity, pub paragraph: i32, - pub leaf: i32, + pub span: i32, pub offset: i32, } @@ -106,13 +106,13 @@ impl TextPositionWithAffinity { pub fn new( position_with_affinity: PositionWithAffinity, paragraph: i32, - leaf: i32, + span: i32, offset: i32, ) -> Self { Self { position_with_affinity, paragraph, - leaf, + span, offset, } } @@ -289,7 +289,7 @@ impl TextContent { let layout_paragraphs = self.layout.paragraphs.iter().flatten(); let mut paragraph_index: i32 = -1; - let mut leaf_index: i32 = -1; + let mut span_index: i32 = -1; for layout_paragraph in layout_paragraphs { paragraph_index += 1; let start_y = offset_y; @@ -303,17 +303,17 @@ impl TextContent { if let Some(paragraph) = self.paragraphs().get(paragraph_index as usize) { // Computed position keeps the current position in terms // of number of characters of text. This is used to know - // in which leaf we are. + // in which span we are. let mut computed_position = 0; - let mut leaf_offset = 0; - for leaf in paragraph.children() { - leaf_index += 1; - let length = leaf.text.len(); + let mut span_offset = 0; + for span in paragraph.children() { + span_index += 1; + let length = span.text.len(); let start_position = computed_position; let end_position = computed_position + length; let current_position = position_with_affinity.position as usize; if start_position <= current_position && end_position >= current_position { - leaf_offset = position_with_affinity.position - start_position as i32; + span_offset = position_with_affinity.position - start_position as i32; break; } computed_position += length; @@ -321,8 +321,8 @@ impl TextContent { return Some(TextPositionWithAffinity::new( position_with_affinity, paragraph_index, - leaf_index, - leaf_offset, + span_index, + span_offset, )); } } @@ -344,10 +344,10 @@ impl TextContent { for paragraph in self.paragraphs() { let paragraph_style = paragraph.paragraph_to_style(); let mut builder = ParagraphBuilder::new(¶graph_style, fonts); - for leaf in paragraph.children() { - let remove_alpha = use_shadow.unwrap_or(false) && !leaf.is_transparent(); - let text_style = leaf.to_style(&self.bounds(), fallback_fonts, remove_alpha); - let text = leaf.apply_text_transform(); + for span in paragraph.children() { + let remove_alpha = use_shadow.unwrap_or(false) && !span.is_transparent(); + let text_style = span.to_style(&self.bounds(), fallback_fonts, remove_alpha); + let text = span.apply_text_transform(); builder.push_style(&text_style); builder.add_text(&text); } @@ -519,7 +519,7 @@ pub struct Paragraph { letter_spacing: f32, typography_ref_file: Uuid, typography_ref_id: Uuid, - children: Vec, + children: Vec, } impl Default for Paragraph { @@ -549,7 +549,7 @@ impl Paragraph { letter_spacing: f32, typography_ref_file: Uuid, typography_ref_id: Uuid, - children: Vec, + children: Vec, ) -> Self { Self { text_align, @@ -565,17 +565,17 @@ impl Paragraph { } #[allow(dead_code)] - fn set_children(&mut self, children: Vec) { + fn set_children(&mut self, children: Vec) { self.children = children; } - pub fn children(&self) -> &[TextLeaf] { + pub fn children(&self) -> &[TextSpan] { &self.children } #[allow(dead_code)] - fn add_leaf(&mut self, leaf: TextLeaf) { - self.children.push(leaf); + fn add_span(&mut self, span: TextSpan) { + self.children.push(span); } // FIXME: move serialization to wasm module @@ -622,7 +622,7 @@ impl Paragraph { } #[derive(Debug, PartialEq, Clone)] -pub struct TextLeaf { +pub struct TextSpan { text: String, font_family: FontFamily, font_size: f32, @@ -635,7 +635,7 @@ pub struct TextLeaf { fills: Vec, } -impl TextLeaf { +impl TextSpan { #[allow(clippy::too_many_arguments)] pub fn new( text: String, diff --git a/render-wasm/src/shapes/text_paths.rs b/render-wasm/src/shapes/text_paths.rs index 4bc1d1d628..e5b155dbd2 100644 --- a/render-wasm/src/shapes/text_paths.rs +++ b/render-wasm/src/shapes/text_paths.rs @@ -37,7 +37,7 @@ impl TextPaths { let start = line_metrics.start_index; let end = line_metrics.end_index; - // 3. Get styles present in line for each text leaf + // 3. Get styles present in line for each text span let style_metrics = line_metrics.get_style_metrics(start..end); let mut offset_x = 0.0; @@ -56,23 +56,23 @@ impl TextPaths { .map(|(i, _)| i) .unwrap_or(text.len()); - let leaf_text = &text[start_byte..end_byte]; + let span_text = &text[start_byte..end_byte]; let font = skia_paragraph.get_font_at(*start_index); let blob_offset_x = self.bounds.x() + line_metrics.left as f32 + offset_x; let blob_offset_y = line_offset_y; - // 4. Get the path for each text leaf + // 4. Get the path for each text span if let Some((text_path, paint)) = self.generate_text_path( - leaf_text, + span_text, &font, blob_offset_x, blob_offset_y, style_metric, antialias, ) { - let text_width = font.measure_text(leaf_text, None).0; + let text_width = font.measure_text(span_text, None).0; offset_x += text_width; paths.push((text_path, paint)); } @@ -87,7 +87,7 @@ impl TextPaths { fn generate_text_path( &self, - leaf_text: &str, + span_text: &str, font: &skia::Font, blob_offset_x: f32, blob_offset_y: f32, @@ -99,10 +99,10 @@ impl TextPaths { // This is used to avoid rendering empty paths, but we can // revisit this logic later if let Some((text_blob_path, text_blob_bounds)) = - Self::get_text_blob_path(leaf_text, font, blob_offset_x, blob_offset_y) + Self::get_text_blob_path(span_text, font, blob_offset_x, blob_offset_y) { let mut text_path = text_blob_path.clone(); - let text_width = font.measure_text(leaf_text, None).0; + let text_width = font.measure_text(span_text, None).0; let decoration = style_metric.text_style.decoration(); let font_metrics = style_metric.font_metrics; @@ -165,13 +165,13 @@ impl TextPaths { } fn get_text_blob_path( - leaf_text: &str, + span_text: &str, font: &skia::Font, blob_offset_x: f32, blob_offset_y: f32, ) -> Option<(skia::Path, skia::Rect)> { with_state_mut!(state, { - let utf16_text = leaf_text.encode_utf16().collect::>(); + let utf16_text = span_text.encode_utf16().collect::>(); let text = unsafe { skia_safe::as_utf16_unchecked(&utf16_text) }; let emoji_font = state.render_state.fonts().get_emoji_font(font.size()); let use_font = emoji_font.as_ref().unwrap_or(font); diff --git a/render-wasm/src/state/text_editor.rs b/render-wasm/src/state/text_editor.rs index c32c648260..1664b3bb2d 100644 --- a/render-wasm/src/state/text_editor.rs +++ b/render-wasm/src/state/text_editor.rs @@ -3,21 +3,21 @@ use crate::shapes::TextPositionWithAffinity; /// TODO: Now this is just a tuple with 2 i32 working -/// as indices (paragraph and leaf). +/// as indices (paragraph and span). #[derive(Debug, PartialEq, Clone, Copy)] pub struct TextNodePosition { pub paragraph: i32, - pub leaf: i32, + pub span: i32, } impl TextNodePosition { - pub fn new(paragraph: i32, leaf: i32) -> Self { - Self { paragraph, leaf } + pub fn new(paragraph: i32, span: i32) -> Self { + Self { paragraph, span } } #[allow(dead_code)] pub fn is_invalid(&self) -> bool { - self.paragraph < 0 || self.leaf < 0 + self.paragraph < 0 || self.span < 0 } } @@ -95,7 +95,7 @@ impl TextEditorState { self.selection.set( Some(TextNodePosition::new( text_position_with_affinity.paragraph, - text_position_with_affinity.leaf, + text_position_with_affinity.span, )), text_position_with_affinity.offset, ); diff --git a/render-wasm/src/wasm/text.rs b/render-wasm/src/wasm/text.rs index e7e56108f2..910135097b 100644 --- a/render-wasm/src/wasm/text.rs +++ b/render-wasm/src/wasm/text.rs @@ -9,7 +9,7 @@ use crate::shapes::{ use crate::utils::{uuid_from_u32, uuid_from_u32_quartet}; use crate::{with_current_shape_mut, with_state_mut, with_state_mut_current_shape, STATE}; -const RAW_LEAF_DATA_SIZE: usize = std::mem::size_of::(); +const RAW_SPAN_DATA_SIZE: usize = std::mem::size_of::(); const RAW_PARAGRAPH_DATA_SIZE: usize = std::mem::size_of::(); const MAX_TEXT_FILLS: usize = 8; @@ -94,7 +94,7 @@ impl From for Option { #[repr(align(4))] #[derive(Debug, Clone, Copy)] pub struct RawParagraphData { - leaf_count: u32, + span_count: u32, text_align: RawTextAlign, text_direction: RawTextDirection, text_decoration: RawTextDecoration, @@ -124,7 +124,7 @@ impl TryFrom<&[u8]> for RawParagraphData { #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq)] -pub struct RawTextLeaf { +pub struct RawTextSpan { font_style: RawFontStyle, text_decoration: RawTextDecoration, text_transform: RawTextTransform, @@ -140,25 +140,25 @@ pub struct RawTextLeaf { fills: [RawFillData; MAX_TEXT_FILLS], } -impl From<[u8; RAW_LEAF_DATA_SIZE]> for RawTextLeaf { - fn from(bytes: [u8; RAW_LEAF_DATA_SIZE]) -> Self { +impl From<[u8; RAW_SPAN_DATA_SIZE]> for RawTextSpan { + fn from(bytes: [u8; RAW_SPAN_DATA_SIZE]) -> Self { unsafe { std::mem::transmute(bytes) } } } -impl TryFrom<&[u8]> for RawTextLeaf { +impl TryFrom<&[u8]> for RawTextSpan { type Error = String; fn try_from(bytes: &[u8]) -> Result { - let data: [u8; RAW_LEAF_DATA_SIZE] = bytes - .get(0..RAW_LEAF_DATA_SIZE) + let data: [u8; RAW_SPAN_DATA_SIZE] = bytes + .get(0..RAW_SPAN_DATA_SIZE) .and_then(|slice| slice.try_into().ok()) - .ok_or("Invalid text leaf data".to_string())?; - Ok(RawTextLeaf::from(data)) + .ok_or("Invalid text span data".to_string())?; + Ok(RawTextSpan::from(data)) } } -impl From for shapes::TextLeaf { - fn from(value: RawTextLeaf) -> Self { +impl From for shapes::TextSpan { + fn from(value: RawTextSpan) -> Self { let text = String::default(); let font_family = shapes::FontFamily::new( @@ -193,7 +193,7 @@ impl From for shapes::TextLeaf { #[derive(Debug, Clone)] pub struct RawParagraph { attrs: RawParagraphData, - leaves: Vec, + leaves: Vec, text_buffer: Vec, } @@ -204,12 +204,12 @@ impl TryFrom<&Vec> for RawParagraph { fn try_from(bytes: &Vec) -> Result { let attrs = RawParagraphData::try_from(&bytes[..RAW_PARAGRAPH_DATA_SIZE])?; let mut offset = RAW_PARAGRAPH_DATA_SIZE; - let mut raw_text_leaves: Vec = Vec::new(); + let mut raw_text_leaves: Vec = Vec::new(); - for _ in 0..attrs.leaf_count { - let text_leaf = RawTextLeaf::try_from(&bytes[offset..(offset + RAW_LEAF_DATA_SIZE)])?; - offset += RAW_LEAF_DATA_SIZE; - raw_text_leaves.push(text_leaf); + for _ in 0..attrs.span_count { + let text_span = RawTextSpan::try_from(&bytes[offset..(offset + RAW_SPAN_DATA_SIZE)])?; + offset += RAW_SPAN_DATA_SIZE; + raw_text_leaves.push(text_span); } let text_buffer = &bytes[offset..]; @@ -230,16 +230,16 @@ impl From for shapes::Paragraph { let mut leaves = vec![]; let mut offset = 0; - for raw_leaf in value.leaves.into_iter() { - let delta = raw_leaf.text_length as usize; + for raw_span in value.leaves.into_iter() { + let delta = raw_span.text_length as usize; let text_buffer = &value.text_buffer[offset..offset + delta]; - let mut leaf = shapes::TextLeaf::from(raw_leaf); + let mut span = shapes::TextSpan::from(raw_span); if !text_buffer.is_empty() { - leaf.set_text(String::from_utf8_lossy(text_buffer).to_string()); + span.set_text(String::from_utf8_lossy(text_buffer).to_string()); } - leaves.push(leaf); + leaves.push(span); offset += delta; }