]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Fixed strange paragraph formatting behaviour
authorDan Brown <redacted>
Fri, 13 Jun 2025 18:40:13 +0000 (19:40 +0100)
committerDan Brown <redacted>
Fri, 13 Jun 2025 18:40:13 +0000 (19:40 +0100)
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
resources/js/wysiwyg/lexical/core/LexicalEvents.ts
resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts
resources/js/wysiwyg/utils/selection.ts

index ffdc7d7e82cffbab217f0800341278950a740033..7ecf91d230e4fa1671108306a611ee0f0e4f12c7 100644 (file)
@@ -84,7 +84,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
 
     // @ts-ignore
     window.debugEditorState = () => {
 
     // @ts-ignore
     window.debugEditorState = () => {
-        console.log(editor.getEditorState().toJSON());
+        return editor.getEditorState().toJSON();
     };
 
     registerCommonNodeMutationListeners(context);
     };
 
     registerCommonNodeMutationListeners(context);
index c70a906a08e897d369442625446278732eccc5b2..26cf25a800d583a6411c685e167e5b704b265948 100644 (file)
@@ -355,6 +355,7 @@ function onSelectionChange(
               lastNode instanceof ParagraphNode &&
               lastNode.getChildrenSize() === 0
             ) {
               lastNode instanceof ParagraphNode &&
               lastNode.getChildrenSize() === 0
             ) {
+              selection.format = lastNode.getTextFormat();
               selection.style = lastNode.getTextStyle();
             } else {
               selection.format = 0;
               selection.style = lastNode.getTextStyle();
             } else {
               selection.format = 0;
index f6f57c91c7573db70ae87fa75d5d198693ec9f81..e8d044b218bc37866f445b6c7facfd83392978f9 100644 (file)
@@ -19,7 +19,7 @@ import type {
   LexicalNode,
   NodeKey,
 } from '../LexicalNode';
   LexicalNode,
   NodeKey,
 } from '../LexicalNode';
-import type {RangeSelection} from 'lexical';
+import {RangeSelection, TEXT_TYPE_TO_FORMAT, TextFormatType} from 'lexical';
 
 import {
   $applyNodeReplacement,
 
 import {
   $applyNodeReplacement,
@@ -36,6 +36,7 @@ import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} f
 
 export type SerializedParagraphNode = Spread<
   {
 
 export type SerializedParagraphNode = Spread<
   {
+    textFormat: number;
     textStyle: string;
   },
   SerializedCommonBlockNode
     textStyle: string;
   },
   SerializedCommonBlockNode
@@ -45,10 +46,12 @@ export type SerializedParagraphNode = Spread<
 export class ParagraphNode extends CommonBlockNode {
   ['constructor']!: KlassConstructor<typeof ParagraphNode>;
   /** @internal */
 export class ParagraphNode extends CommonBlockNode {
   ['constructor']!: KlassConstructor<typeof ParagraphNode>;
   /** @internal */
+  __textFormat: number;
   __textStyle: string;
 
   constructor(key?: NodeKey) {
     super(key);
   __textStyle: string;
 
   constructor(key?: NodeKey) {
     super(key);
+    this.__textFormat = 0;
     this.__textStyle = '';
   }
 
     this.__textStyle = '';
   }
 
@@ -56,6 +59,22 @@ export class ParagraphNode extends CommonBlockNode {
     return 'paragraph';
   }
 
     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;
   getTextStyle(): string {
     const self = this.getLatest();
     return self.__textStyle;
@@ -73,6 +92,7 @@ export class ParagraphNode extends CommonBlockNode {
 
   afterCloneFrom(prevNode: this) {
     super.afterCloneFrom(prevNode);
 
   afterCloneFrom(prevNode: this) {
     super.afterCloneFrom(prevNode);
+    this.__textFormat = prevNode.__textFormat;
     this.__textStyle = prevNode.__textStyle;
     copyCommonBlockProperties(prevNode, this);
   }
     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);
   static importJSON(serializedNode: SerializedParagraphNode): ParagraphNode {
     const node = $createParagraphNode();
     deserializeCommonBlockNode(serializedNode, node);
+    node.setTextFormat(serializedNode.textFormat);
     return node;
   }
 
   exportJSON(): SerializedParagraphNode {
     return {
       ...super.exportJSON(),
     return node;
   }
 
   exportJSON(): SerializedParagraphNode {
     return {
       ...super.exportJSON(),
+      textFormat: this.getTextFormat(),
       textStyle: this.getTextStyle(),
       type: 'paragraph',
       version: 1,
       textStyle: this.getTextStyle(),
       type: 'paragraph',
       version: 1,
@@ -144,6 +166,7 @@ export class ParagraphNode extends CommonBlockNode {
     restoreSelection: boolean,
   ): ParagraphNode {
     const newElement = $createParagraphNode();
     restoreSelection: boolean,
   ): ParagraphNode {
     const newElement = $createParagraphNode();
+    newElement.setTextFormat(rangeSelection.format);
     newElement.setTextStyle(rangeSelection.style);
     const direction = this.getDirection();
     newElement.setDirection(direction);
     newElement.setTextStyle(rangeSelection.style);
     const direction = this.getDirection();
     newElement.setDirection(direction);
index 167ab32adcae58f6e7c562c58471b0b6b758b1e4..e4b5bf2dce6fb589160b750d5cd885125c1fed11 100644 (file)
@@ -3,7 +3,7 @@ import {
     $createParagraphNode, $createRangeSelection,
     $getRoot,
     $getSelection, $isBlockElementNode, $isDecoratorNode,
     $createParagraphNode, $createRangeSelection,
     $getRoot,
     $getSelection, $isBlockElementNode, $isDecoratorNode,
-    $isElementNode,
+    $isElementNode, $isParagraphNode,
     $isTextNode,
     $setSelection,
     BaseSelection, DecoratorNode,
     $isTextNode,
     $setSelection,
     BaseSelection, DecoratorNode,
@@ -60,12 +60,19 @@ export function $selectionContainsTextFormat(selection: BaseSelection | null, fo
         return false;
     }
 
         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 ($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;
 }
 
     return false;
 }