From 8b062d47951825e04f9a65ea3b09d9c70ee8b59d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Jun 2025 19:40:13 +0100 Subject: [PATCH] Lexical: Fixed strange paragraph formatting behaviour Formatting was not persisted on empty paragraphs, and was instead based upon last format encountered in selection. This was due to overly-hasty removal of other formatting code, which this got caught it. Restored required parts from prior codebase. Also updated inline format button active indicator to reflect formats using the above, so correct buttons are shown as active even when just in an empty paragraph. --- resources/js/wysiwyg/index.ts | 2 +- .../js/wysiwyg/lexical/core/LexicalEvents.ts | 1 + .../core/nodes/LexicalParagraphNode.ts | 25 ++++++++++++++++++- resources/js/wysiwyg/utils/selection.ts | 11 ++++++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index ffdc7d7e8..7ecf91d23 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -84,7 +84,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st // @ts-ignore window.debugEditorState = () => { - console.log(editor.getEditorState().toJSON()); + return editor.getEditorState().toJSON(); }; registerCommonNodeMutationListeners(context); diff --git a/resources/js/wysiwyg/lexical/core/LexicalEvents.ts b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts index c70a906a0..26cf25a80 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalEvents.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts @@ -355,6 +355,7 @@ function onSelectionChange( lastNode instanceof ParagraphNode && lastNode.getChildrenSize() === 0 ) { + selection.format = lastNode.getTextFormat(); selection.style = lastNode.getTextStyle(); } else { selection.format = 0; diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts index f6f57c91c..e8d044b21 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts @@ -19,7 +19,7 @@ import type { LexicalNode, NodeKey, } from '../LexicalNode'; -import type {RangeSelection} from 'lexical'; +import {RangeSelection, TEXT_TYPE_TO_FORMAT, TextFormatType} from 'lexical'; import { $applyNodeReplacement, @@ -36,6 +36,7 @@ import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} f export type SerializedParagraphNode = Spread< { + textFormat: number; textStyle: string; }, SerializedCommonBlockNode @@ -45,10 +46,12 @@ export type SerializedParagraphNode = Spread< export class ParagraphNode extends CommonBlockNode { ['constructor']!: KlassConstructor; /** @internal */ + __textFormat: number; __textStyle: string; constructor(key?: NodeKey) { super(key); + this.__textFormat = 0; this.__textStyle = ''; } @@ -56,6 +59,22 @@ export class ParagraphNode extends CommonBlockNode { return 'paragraph'; } + getTextFormat(): number { + const self = this.getLatest(); + return self.__textFormat; + } + + setTextFormat(type: number): this { + const self = this.getWritable(); + self.__textFormat = type; + return self; + } + + hasTextFormat(type: TextFormatType): boolean { + const formatFlag = TEXT_TYPE_TO_FORMAT[type]; + return (this.getTextFormat() & formatFlag) !== 0; + } + getTextStyle(): string { const self = this.getLatest(); return self.__textStyle; @@ -73,6 +92,7 @@ export class ParagraphNode extends CommonBlockNode { afterCloneFrom(prevNode: this) { super.afterCloneFrom(prevNode); + this.__textFormat = prevNode.__textFormat; this.__textStyle = prevNode.__textStyle; copyCommonBlockProperties(prevNode, this); } @@ -125,12 +145,14 @@ export class ParagraphNode extends CommonBlockNode { static importJSON(serializedNode: SerializedParagraphNode): ParagraphNode { const node = $createParagraphNode(); deserializeCommonBlockNode(serializedNode, node); + node.setTextFormat(serializedNode.textFormat); return node; } exportJSON(): SerializedParagraphNode { return { ...super.exportJSON(), + textFormat: this.getTextFormat(), textStyle: this.getTextStyle(), type: 'paragraph', version: 1, @@ -144,6 +166,7 @@ export class ParagraphNode extends CommonBlockNode { restoreSelection: boolean, ): ParagraphNode { const newElement = $createParagraphNode(); + newElement.setTextFormat(rangeSelection.format); newElement.setTextStyle(rangeSelection.style); const direction = this.getDirection(); newElement.setDirection(direction); diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index 167ab32ad..e4b5bf2dc 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -3,7 +3,7 @@ import { $createParagraphNode, $createRangeSelection, $getRoot, $getSelection, $isBlockElementNode, $isDecoratorNode, - $isElementNode, + $isElementNode, $isParagraphNode, $isTextNode, $setSelection, BaseSelection, DecoratorNode, @@ -60,12 +60,19 @@ export function $selectionContainsTextFormat(selection: BaseSelection | null, fo return false; } - for (const node of selection.getNodes()) { + // Check text nodes + const nodes = selection.getNodes(); + for (const node of nodes) { if ($isTextNode(node) && node.hasFormat(format)) { return true; } } + // If we're in an empty paragraph, check the paragraph format + if (nodes.length === 1 && $isParagraphNode(nodes[0]) && nodes[0].hasTextFormat(format)) { + return true; + } + return false; }