From 755d720b34b2ac041ff23306fc523c1930ed83e6 Mon Sep 17 00:00:00 2001 From: Aitor Moreno Date: Wed, 11 Feb 2026 12:06:24 +0100 Subject: [PATCH 1/3] :bug: Fix text editor fills not being updated --- .../src/app/main/data/workspace/texts.cljs | 24 +++++++++---------- frontend/text-editor/src/editor/TextEditor.js | 24 +++++++------------ .../editor/controllers/SelectionController.js | 12 ++++------ 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 518d7f0b60..62fb30e2ec 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -508,12 +508,12 @@ ptk/EffectEvent (effect [_ state _] (when (features/active-feature? state "text-editor/v2") - (let [instance (:workspace-editor state) - styles (some-> (editor.v2/getCurrentStyle instance) - (styles/get-styles-from-style-declaration :removed-mixed true) - ((comp update-node-fn migrate-node)) - (styles/attrs->styles))] - (editor.v2/applyStylesToSelection instance styles))))))) + (when-let [instance (:workspace-editor state)] + (let [styles (some-> (editor.v2/getCurrentStyle instance) + (styles/get-styles-from-style-declaration :removed-mixed true) + ((comp update-node-fn migrate-node)) + (styles/attrs->styles))] + (editor.v2/applyStylesToSelection instance styles)))))))) ;; --- RESIZE UTILS @@ -782,12 +782,12 @@ ptk/EffectEvent (effect [_ state _] (when (features/active-feature? state "text-editor/v2") - (let [instance (:workspace-editor state) - attrs-to-override (some-> (editor.v2/getCurrentStyle instance) - (styles/get-styles-from-style-declaration)) - overriden-attrs (merge attrs-to-override attrs) - styles (styles/attrs->styles overriden-attrs)] - (editor.v2/applyStylesToSelection instance styles)))))) + (when-let [instance (:workspace-editor state)] + (let [attrs-to-override (some-> (editor.v2/getCurrentStyle instance) + (styles/get-styles-from-style-declaration)) + overriden-attrs (merge attrs-to-override attrs) + styles (styles/attrs->styles overriden-attrs)] + (editor.v2/applyStylesToSelection instance styles))))))) (defn update-all-attrs [ids attrs] diff --git a/frontend/text-editor/src/editor/TextEditor.js b/frontend/text-editor/src/editor/TextEditor.js index ed43501b89..8408dafa9b 100644 --- a/frontend/text-editor/src/editor/TextEditor.js +++ b/frontend/text-editor/src/editor/TextEditor.js @@ -326,9 +326,7 @@ export class TextEditor extends EventTarget { * @param {FocusEvent} e */ #onBlur = (e) => { - if (!this.isEmpty) { - this.#changeController.notifyImmediately(); - } + this.#changeController.notifyImmediately(); this.#selectionController.saveSelection(); this.dispatchEvent(new FocusEvent(e.type, e)); }; @@ -685,7 +683,7 @@ export function createRootFromString(string) { * Returns true if the passed object is a TextEditor * instance. * - * @param {TextEditor} instance + * @param {*} instance * @returns {boolean} */ export function isTextEditor(instance) { @@ -702,8 +700,7 @@ export function isEmpty(instance) { if (isTextEditor(instance)) { return instance.isEmpty; } - return null; - // throw new TypeError('Instance is not a TextEditor'); + throw new TypeError('Instance is not a TextEditor'); } /** @@ -718,7 +715,6 @@ export function getRoot(instance) { return instance.root; } return null; - // throw new TypeError("Instance is not a TextEditor"); } /** @@ -731,10 +727,9 @@ export function getRoot(instance) { export function setRoot(instance, root) { if (isTextEditor(instance)) { instance.root = root; - // return instance; + return instance; } - return instance; - // throw new TypeError("Instance is not a TextEditor"); + throw new TypeError("Instance is not a TextEditor"); } /** @@ -759,8 +754,7 @@ export function getCurrentStyle(instance) { if (isTextEditor(instance)) { return instance.currentStyle; } - // throw new TypeError("Instance is not a TextEditor"); - return null; + throw new TypeError('Instance is not a TextEditor'); } /** @@ -775,8 +769,7 @@ export function applyStylesToSelection(instance, styles) { if (isTextEditor(instance)) { return instance.applyStylesToSelection(styles); } - // throw new TypeError("Instance is not a TextEditor"); - return null; + throw new TypeError('Instance is not a TextEditor'); } /** @@ -790,8 +783,7 @@ export function dispose(instance) { if (isTextEditor(instance)) { return instance.dispose(); } - // throw new TypeError("Instance is not a TextEditor"); - return null; + throw new TypeError('Instance is not a TextEditor'); } export default TextEditor; diff --git a/frontend/text-editor/src/editor/controllers/SelectionController.js b/frontend/text-editor/src/editor/controllers/SelectionController.js index 6d4c11c136..410c7daa13 100644 --- a/frontend/text-editor/src/editor/controllers/SelectionController.js +++ b/frontend/text-editor/src/editor/controllers/SelectionController.js @@ -1969,11 +1969,9 @@ export class SelectionController extends EventTarget { setTextSpanStyles(textSpan, newStyles); } } - return this.#notifyStyleChange(); - - // If the startContainer and endContainer are different - // then we need to iterate through those nodes to apply - // the styles. + // If the startContainer and endContainer are different + // then we need to iterate through those nodes to apply + // the styles. } else if (startNode !== endNode) { const safeGuard = new SafeGuard("applyStylesTo"); safeGuard.start(); @@ -2022,12 +2020,12 @@ export class SelectionController extends EventTarget { } // We've reached the final node so we can return safely. - if (this.#textNodeIterator.currentNode === expectedEndNode) return; + if (this.#textNodeIterator.currentNode === expectedEndNode) + break; this.#textNodeIterator.nextNode(); } while (this.#textNodeIterator.currentNode); } - return this.#notifyStyleChange(); } From e722e17b10aa11e1e8710e405bd67011bae9ab25 Mon Sep 17 00:00:00 2001 From: Aitor Moreno Date: Wed, 11 Feb 2026 12:49:20 +0100 Subject: [PATCH 2/3] :bug: Fix paragraph styles not being applied --- .../src/editor/content/dom/Style.js | 28 ++++++++++--------- .../editor/controllers/SelectionController.js | 3 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/frontend/text-editor/src/editor/content/dom/Style.js b/frontend/text-editor/src/editor/content/dom/Style.js index f8866550ed..aaf9e19227 100644 --- a/frontend/text-editor/src/editor/content/dom/Style.js +++ b/frontend/text-editor/src/editor/content/dom/Style.js @@ -336,20 +336,22 @@ export function getStyle(element, styleName, styleUnit) { * @returns {HTMLElement} */ export function setStylesFromObject(element, allowedStyles, styleObject) { - if (element.tagName === "SPAN") - for (const [styleName, styleUnit] of allowedStyles) { - if (!(styleName in styleObject)) { - continue; - } - let styleValue = styleObject[styleName]; - if (!styleValue) continue; - - if (styleName === "font-family") { - styleValue = sanitizeFontFamily(styleValue); - } - - setStyle(element, styleName, styleValue, styleUnit); + for (const [styleName, styleUnit] of allowedStyles) { + if (!(styleName in styleObject)) { + continue; } + + let styleValue = styleObject[styleName]; + if (!styleValue) { + continue; + } + + if (styleName === "font-family") { + styleValue = sanitizeFontFamily(styleValue); + } + + setStyle(element, styleName, styleValue, styleUnit); + } return element; } diff --git a/frontend/text-editor/src/editor/controllers/SelectionController.js b/frontend/text-editor/src/editor/controllers/SelectionController.js index 410c7daa13..88583a1116 100644 --- a/frontend/text-editor/src/editor/controllers/SelectionController.js +++ b/frontend/text-editor/src/editor/controllers/SelectionController.js @@ -1961,7 +1961,8 @@ export class SelectionController extends EventTarget { this.setSelection(newTextSpan.firstChild, 0, newTextSpan.firstChild, 0); } // The styles are applied to the paragraph - else { + else + { const paragraph = this.startParagraph; setParagraphStyles(paragraph, newStyles); // Apply styles to child text spans. From b2231e520c2bb029b30d70d469255454c1aa9cc7 Mon Sep 17 00:00:00 2001 From: Aitor Moreno Date: Wed, 11 Feb 2026 13:09:56 +0100 Subject: [PATCH 3/3] :books: Add best practices to text editor README.md --- frontend/text-editor/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frontend/text-editor/README.md b/frontend/text-editor/README.md index 705c2277bd..35281f8647 100644 --- a/frontend/text-editor/README.md +++ b/frontend/text-editor/README.md @@ -82,6 +82,26 @@ The `TextEditor` contains a series of references to DOM elements, one of them is `ChangeController` is called by the `TextEditor` instance everytime a change is performed on the content of the `contenteditable` element. +### Best practices + +#### Use `isType` functions + +Instead of handling elements by their properties like this: + +```javascript +if (element.tagName === "SPAN") { + ... +} +``` + +Use functions like `isParagraph`, `isTextSpan` or `isLineBreak`: + +```javascript +if (isTextSpan(element)) { + ... +} +``` + ### Events - `change`: This event is dispatched every time a change is made in the editor. All changes are debounced to prevent dispatching too many change events. This event is also dispatched when there are pending change events and the user blurs the textarea element.