From f3fa63a5ae5d671d10c9313965723683608ddc4e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 3 Dec 2024 16:24:49 +0000 Subject: [PATCH 1/6] Lexical: Merged custom paragraph node, removed old format/indent refs Start of work to merge custom nodes into lexical, removing old unused format/indent core logic while extending common block elements where possible. --- .../js/wysiwyg/lexical/core/LexicalEvents.ts | 3 - .../wysiwyg/lexical/core/LexicalMutations.ts | 9 -- .../wysiwyg/lexical/core/LexicalReconciler.ts | 67 ---------- .../lexical/core/__tests__/utils/index.ts | 8 -- .../lexical/core/nodes/CommonBlockNode.ts | 54 ++++++++ .../lexical/core/nodes/LexicalElementNode.ts | 41 ------ .../core/nodes/LexicalParagraphNode.ts | 69 +++------- .../lexical/core/nodes/LexicalRootNode.ts | 4 - resources/js/wysiwyg/lexical/html/index.ts | 4 - resources/js/wysiwyg/lexical/link/index.ts | 4 - .../lexical/list/LexicalListItemNode.ts | 38 ------ .../js/wysiwyg/lexical/list/formatList.ts | 6 - .../js/wysiwyg/lexical/rich-text/index.ts | 56 +------- .../lexical/selection/range-selection.ts | 8 -- .../js/wysiwyg/nodes/custom-paragraph.ts | 123 ------------------ resources/js/wysiwyg/nodes/index.ts | 9 +- .../wysiwyg/services/drop-paste-handling.ts | 4 +- .../js/wysiwyg/services/keyboard-handling.ts | 4 +- resources/js/wysiwyg/todo.md | 6 +- resources/js/wysiwyg/utils/formats.ts | 13 +- resources/js/wysiwyg/utils/nodes.ts | 4 +- resources/js/wysiwyg/utils/selection.ts | 6 +- 22 files changed, 95 insertions(+), 445 deletions(-) create mode 100644 resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts delete mode 100644 resources/js/wysiwyg/nodes/custom-paragraph.ts diff --git a/resources/js/wysiwyg/lexical/core/LexicalEvents.ts b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts index 5fd671a76c5..c70a906a08e 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalEvents.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts @@ -355,7 +355,6 @@ function onSelectionChange( lastNode instanceof ParagraphNode && lastNode.getChildrenSize() === 0 ) { - selection.format = lastNode.getTextFormat(); selection.style = lastNode.getTextStyle(); } else { selection.format = 0; @@ -578,7 +577,6 @@ function onBeforeInput(event: InputEvent, editor: LexicalEditor): void { if ($isRangeSelection(selection)) { const anchorNode = selection.anchor.getNode(); anchorNode.markDirty(); - selection.format = anchorNode.getFormat(); invariant( $isTextNode(anchorNode), 'Anchor node must be a TextNode', @@ -912,7 +910,6 @@ function onCompositionStart( // need to invoke the empty space heuristic below. anchor.type === 'element' || !selection.isCollapsed() || - node.getFormat() !== selection.format || ($isTextNode(node) && node.getStyle() !== selection.style) ) { // We insert a zero width character, ready for the composition diff --git a/resources/js/wysiwyg/lexical/core/LexicalMutations.ts b/resources/js/wysiwyg/lexical/core/LexicalMutations.ts index 56f364501ee..c24dc9ebb3c 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalMutations.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalMutations.ts @@ -96,15 +96,6 @@ function shouldUpdateTextNodeFromMutation( targetDOM: Node, targetNode: TextNode, ): boolean { - if ($isRangeSelection(selection)) { - const anchorNode = selection.anchor.getNode(); - if ( - anchorNode.is(targetNode) && - selection.format !== anchorNode.getFormat() - ) { - return false; - } - } return targetDOM.nodeType === DOM_TEXT_TYPE && targetNode.isAttached(); } diff --git a/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts b/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts index 09d01bffd43..7843027d713 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts @@ -17,7 +17,6 @@ import type {NodeKey, NodeMap} from './LexicalNode'; import type {ElementNode} from './nodes/LexicalElementNode'; import invariant from 'lexical/shared/invariant'; -import normalizeClassNames from 'lexical/shared/normalizeClassNames'; import { $isDecoratorNode, @@ -117,51 +116,6 @@ function setTextAlign(domStyle: CSSStyleDeclaration, value: string): void { domStyle.setProperty('text-align', value); } -const DEFAULT_INDENT_VALUE = '40px'; - -function setElementIndent(dom: HTMLElement, indent: number): void { - const indentClassName = activeEditorConfig.theme.indent; - - if (typeof indentClassName === 'string') { - const elementHasClassName = dom.classList.contains(indentClassName); - - if (indent > 0 && !elementHasClassName) { - dom.classList.add(indentClassName); - } else if (indent < 1 && elementHasClassName) { - dom.classList.remove(indentClassName); - } - } - - const indentationBaseValue = - getComputedStyle(dom).getPropertyValue('--lexical-indent-base-value') || - DEFAULT_INDENT_VALUE; - - dom.style.setProperty( - 'padding-inline-start', - indent === 0 ? '' : `calc(${indent} * ${indentationBaseValue})`, - ); -} - -function setElementFormat(dom: HTMLElement, format: number): void { - const domStyle = dom.style; - - if (format === 0) { - setTextAlign(domStyle, ''); - } else if (format === IS_ALIGN_LEFT) { - setTextAlign(domStyle, 'left'); - } else if (format === IS_ALIGN_CENTER) { - setTextAlign(domStyle, 'center'); - } else if (format === IS_ALIGN_RIGHT) { - setTextAlign(domStyle, 'right'); - } else if (format === IS_ALIGN_JUSTIFY) { - setTextAlign(domStyle, 'justify'); - } else if (format === IS_ALIGN_START) { - setTextAlign(domStyle, 'start'); - } else if (format === IS_ALIGN_END) { - setTextAlign(domStyle, 'end'); - } -} - function $createNode( key: NodeKey, parentDOM: null | HTMLElement, @@ -185,22 +139,14 @@ function $createNode( } if ($isElementNode(node)) { - const indent = node.__indent; const childrenSize = node.__size; - if (indent !== 0) { - setElementIndent(dom, indent); - } if (childrenSize !== 0) { const endIndex = childrenSize - 1; const children = createChildrenArray(node, activeNextNodeMap); $createChildren(children, node, 0, endIndex, dom, null); } - const format = node.__format; - if (format !== 0) { - setElementFormat(dom, format); - } if (!node.isInline()) { reconcileElementTerminatingLineBreak(null, node, dom); } @@ -349,10 +295,8 @@ function reconcileParagraphFormat(element: ElementNode): void { if ( $isParagraphNode(element) && subTreeTextFormat != null && - subTreeTextFormat !== element.__textFormat && !activeEditorStateReadOnly ) { - element.setTextFormat(subTreeTextFormat); element.setTextStyle(subTreeTextStyle); } } @@ -563,17 +507,6 @@ function $reconcileNode( if ($isElementNode(prevNode) && $isElementNode(nextNode)) { // Reconcile element children - const nextIndent = nextNode.__indent; - - if (nextIndent !== prevNode.__indent) { - setElementIndent(dom, nextIndent); - } - - const nextFormat = nextNode.__format; - - if (nextFormat !== prevNode.__format) { - setElementFormat(dom, nextFormat); - } if (isDirty) { $reconcileChildrenWithDirection(prevNode, nextNode, dom); if (!$isRootNode(nextNode) && !nextNode.isInline()) { diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index f7230595a43..a4d74210e41 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -129,8 +129,6 @@ export class TestElementNode extends ElementNode { serializedNode: SerializedTestElementNode, ): TestInlineElementNode { const node = $createTestInlineElementNode(); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); return node; } @@ -195,8 +193,6 @@ export class TestInlineElementNode extends ElementNode { serializedNode: SerializedTestInlineElementNode, ): TestInlineElementNode { const node = $createTestInlineElementNode(); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); return node; } @@ -241,8 +237,6 @@ export class TestShadowRootNode extends ElementNode { serializedNode: SerializedTestShadowRootNode, ): TestShadowRootNode { const node = $createTestShadowRootNode(); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); return node; } @@ -322,8 +316,6 @@ export class TestExcludeFromCopyElementNode extends ElementNode { serializedNode: SerializedTestExcludeFromCopyElementNode, ): TestExcludeFromCopyElementNode { const node = $createTestExcludeFromCopyElementNode(); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); return node; } diff --git a/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts b/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts new file mode 100644 index 00000000000..37ca1cdef7f --- /dev/null +++ b/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts @@ -0,0 +1,54 @@ +import {ElementNode} from "./LexicalElementNode"; +import {CommonBlockAlignment, SerializedCommonBlockNode} from "../../../nodes/_common"; + + +export class CommonBlockNode extends ElementNode { + __id: string = ''; + __alignment: CommonBlockAlignment = ''; + __inset: number = 0; + + setId(id: string) { + const self = this.getWritable(); + self.__id = id; + } + + getId(): string { + const self = this.getLatest(); + return self.__id; + } + + setAlignment(alignment: CommonBlockAlignment) { + const self = this.getWritable(); + self.__alignment = alignment; + } + + getAlignment(): CommonBlockAlignment { + const self = this.getLatest(); + return self.__alignment; + } + + setInset(size: number) { + const self = this.getWritable(); + self.__inset = size; + } + + getInset(): number { + const self = this.getLatest(); + return self.__inset; + } + + exportJSON(): SerializedCommonBlockNode { + return { + ...super.exportJSON(), + id: this.__id, + alignment: this.__alignment, + inset: this.__inset, + }; + } +} + +export function copyCommonBlockProperties(from: CommonBlockNode, to: CommonBlockNode): void { + to.__id = from.__id; + to.__alignment = from.__alignment; + to.__inset = from.__inset; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts index 88c6d56780f..002d825d6ea 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts @@ -42,8 +42,6 @@ export type SerializedElementNode< { children: Array; direction: 'ltr' | 'rtl' | null; - format: ElementFormatType; - indent: number; }, SerializedLexicalNode >; @@ -74,12 +72,8 @@ export class ElementNode extends LexicalNode { /** @internal */ __size: number; /** @internal */ - __format: number; - /** @internal */ __style: string; /** @internal */ - __indent: number; - /** @internal */ __dir: 'ltr' | 'rtl' | null; constructor(key?: NodeKey) { @@ -87,9 +81,7 @@ export class ElementNode extends LexicalNode { this.__first = null; this.__last = null; this.__size = 0; - this.__format = 0; this.__style = ''; - this.__indent = 0; this.__dir = null; } @@ -98,28 +90,14 @@ export class ElementNode extends LexicalNode { this.__first = prevNode.__first; this.__last = prevNode.__last; this.__size = prevNode.__size; - this.__indent = prevNode.__indent; - this.__format = prevNode.__format; this.__style = prevNode.__style; this.__dir = prevNode.__dir; } - getFormat(): number { - const self = this.getLatest(); - return self.__format; - } - getFormatType(): ElementFormatType { - const format = this.getFormat(); - return ELEMENT_FORMAT_TO_TYPE[format] || ''; - } getStyle(): string { const self = this.getLatest(); return self.__style; } - getIndent(): number { - const self = this.getLatest(); - return self.__indent; - } getChildren(): Array { const children: Array = []; let child: T | null = this.getFirstChild(); @@ -301,13 +279,6 @@ export class ElementNode extends LexicalNode { const self = this.getLatest(); return self.__dir; } - hasFormat(type: ElementFormatType): boolean { - if (type !== '') { - const formatFlag = ELEMENT_TYPE_TO_FORMAT[type]; - return (this.getFormat() & formatFlag) !== 0; - } - return false; - } // Mutators @@ -378,21 +349,11 @@ export class ElementNode extends LexicalNode { self.__dir = direction; return self; } - setFormat(type: ElementFormatType): this { - const self = this.getWritable(); - self.__format = type !== '' ? ELEMENT_TYPE_TO_FORMAT[type] : 0; - return this; - } setStyle(style: string): this { const self = this.getWritable(); self.__style = style || ''; return this; } - setIndent(indentLevel: number): this { - const self = this.getWritable(); - self.__indent = indentLevel; - return this; - } splice( start: number, deleteCount: number, @@ -528,8 +489,6 @@ export class ElementNode extends LexicalNode { return { children: [], direction: this.getDirection(), - format: this.getFormatType(), - indent: this.getIndent(), type: 'element', version: 1, }; diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts index 4e69dc21c3c..6517d939eda 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts @@ -19,39 +19,36 @@ import type { LexicalNode, NodeKey, } from '../LexicalNode'; -import type { - ElementFormatType, - SerializedElementNode, -} from './LexicalElementNode'; import type {RangeSelection} from 'lexical'; -import {TEXT_TYPE_TO_FORMAT} from '../LexicalConstants'; import { $applyNodeReplacement, getCachedClassNameArray, isHTMLElement, } from '../LexicalUtils'; -import {ElementNode} from './LexicalElementNode'; -import {$isTextNode, TextFormatType} from './LexicalTextNode'; +import {$isTextNode} from './LexicalTextNode'; +import { + commonPropertiesDifferent, deserializeCommonBlockNode, + SerializedCommonBlockNode, setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "../../../nodes/_common"; +import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; export type SerializedParagraphNode = Spread< { - textFormat: number; textStyle: string; }, - SerializedElementNode + SerializedCommonBlockNode >; /** @noInheritDoc */ -export class ParagraphNode extends ElementNode { +export class ParagraphNode extends CommonBlockNode { ['constructor']!: KlassConstructor; /** @internal */ - __textFormat: number; __textStyle: string; constructor(key?: NodeKey) { super(key); - this.__textFormat = 0; this.__textStyle = ''; } @@ -59,22 +56,6 @@ export class ParagraphNode extends ElementNode { 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; @@ -92,8 +73,8 @@ export class ParagraphNode extends ElementNode { afterCloneFrom(prevNode: this) { super.afterCloneFrom(prevNode); - this.__textFormat = prevNode.__textFormat; this.__textStyle = prevNode.__textStyle; + copyCommonBlockProperties(prevNode, this); } // View @@ -105,6 +86,9 @@ export class ParagraphNode extends ElementNode { const domClassList = dom.classList; domClassList.add(...classNames); } + + updateElementWithCommonBlockProps(dom, this); + return dom; } updateDOM( @@ -112,7 +96,7 @@ export class ParagraphNode extends ElementNode { dom: HTMLElement, config: EditorConfig, ): boolean { - return false; + return commonPropertiesDifferent(prevNode, this); } static importDOM(): DOMConversionMap | null { @@ -131,16 +115,6 @@ export class ParagraphNode extends ElementNode { if (this.isEmpty()) { element.append(document.createElement('br')); } - - const formatType = this.getFormatType(); - element.style.textAlign = formatType; - - const indent = this.getIndent(); - if (indent > 0) { - // padding-inline-start is not widely supported in email HTML, but - // Lexical Reconciler uses padding-inline-start. Using text-indent instead. - element.style.textIndent = `${indent * 20}px`; - } } return { @@ -150,16 +124,13 @@ export class ParagraphNode extends ElementNode { static importJSON(serializedNode: SerializedParagraphNode): ParagraphNode { const node = $createParagraphNode(); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); - node.setTextFormat(serializedNode.textFormat); + deserializeCommonBlockNode(serializedNode, node); return node; } exportJSON(): SerializedParagraphNode { return { ...super.exportJSON(), - textFormat: this.getTextFormat(), textStyle: this.getTextStyle(), type: 'paragraph', version: 1, @@ -173,11 +144,9 @@ export class ParagraphNode extends ElementNode { restoreSelection: boolean, ): ParagraphNode { const newElement = $createParagraphNode(); - newElement.setTextFormat(rangeSelection.format); newElement.setTextStyle(rangeSelection.style); const direction = this.getDirection(); newElement.setDirection(direction); - newElement.setFormat(this.getFormatType()); newElement.setStyle(this.getTextStyle()); this.insertAfter(newElement, restoreSelection); return newElement; @@ -210,13 +179,7 @@ export class ParagraphNode extends ElementNode { function $convertParagraphElement(element: HTMLElement): DOMConversionOutput { const node = $createParagraphNode(); - if (element.style) { - node.setFormat(element.style.textAlign as ElementFormatType); - const indent = parseInt(element.style.textIndent, 10) / 20; - if (indent > 0) { - node.setIndent(indent); - } - } + setCommonBlockPropsFromElement(element, node); return {node}; } diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalRootNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalRootNode.ts index 74c8d5a7f9b..a1c8813c390 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalRootNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalRootNode.ts @@ -99,8 +99,6 @@ export class RootNode extends ElementNode { static importJSON(serializedNode: SerializedRootNode): RootNode { // We don't create a root, and instead use the existing root. const node = $getRoot(); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); return node; } @@ -109,8 +107,6 @@ export class RootNode extends ElementNode { return { children: [], direction: this.getDirection(), - format: this.getFormatType(), - indent: this.getIndent(), type: 'root', version: 1, }; diff --git a/resources/js/wysiwyg/lexical/html/index.ts b/resources/js/wysiwyg/lexical/html/index.ts index 2975315cc35..3e962ec72f7 100644 --- a/resources/js/wysiwyg/lexical/html/index.ts +++ b/resources/js/wysiwyg/lexical/html/index.ts @@ -327,9 +327,6 @@ function wrapContinuousInlines( for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if ($isBlockElementNode(node)) { - if (textAlign && !node.getFormat()) { - node.setFormat(textAlign); - } out.push(node); } else { continuousInlines.push(node); @@ -338,7 +335,6 @@ function wrapContinuousInlines( (i < nodes.length - 1 && $isBlockElementNode(nodes[i + 1])) ) { const wrapper = createWrapperFn(); - wrapper.setFormat(textAlign); wrapper.append(...continuousInlines); out.push(wrapper); continuousInlines = []; diff --git a/resources/js/wysiwyg/lexical/link/index.ts b/resources/js/wysiwyg/lexical/link/index.ts index fe2b9757048..884fe9153a0 100644 --- a/resources/js/wysiwyg/lexical/link/index.ts +++ b/resources/js/wysiwyg/lexical/link/index.ts @@ -162,8 +162,6 @@ export class LinkNode extends ElementNode { target: serializedNode.target, title: serializedNode.title, }); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); return node; } @@ -402,8 +400,6 @@ export class AutoLinkNode extends LinkNode { target: serializedNode.target, title: serializedNode.title, }); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); return node; } diff --git a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts index 5026a01293e..c20329e4be9 100644 --- a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts +++ b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts @@ -126,14 +126,12 @@ export class ListItemNode extends ElementNode { const node = $createListItemNode(); node.setChecked(serializedNode.checked); node.setValue(serializedNode.value); - node.setFormat(serializedNode.format); node.setDirection(serializedNode.direction); return node; } exportDOM(editor: LexicalEditor): DOMExportOutput { const element = this.createDOM(editor._config); - element.style.textAlign = this.getFormatType(); return { element, }; @@ -172,7 +170,6 @@ export class ListItemNode extends ElementNode { if ($isListItemNode(replaceWithNode)) { return super.replace(replaceWithNode); } - this.setIndent(0); const list = this.getParentOrThrow(); if (!$isListNode(list)) { return replaceWithNode; @@ -351,41 +348,6 @@ export class ListItemNode extends ElementNode { this.setChecked(!this.__checked); } - getIndent(): number { - // If we don't have a parent, we are likely serializing - const parent = this.getParent(); - if (parent === null) { - return this.getLatest().__indent; - } - // ListItemNode should always have a ListNode for a parent. - let listNodeParent = parent.getParentOrThrow(); - let indentLevel = 0; - while ($isListItemNode(listNodeParent)) { - listNodeParent = listNodeParent.getParentOrThrow().getParentOrThrow(); - indentLevel++; - } - - return indentLevel; - } - - setIndent(indent: number): this { - invariant(typeof indent === 'number', 'Invalid indent value.'); - indent = Math.floor(indent); - invariant(indent >= 0, 'Indent value must be non-negative.'); - let currentIndent = this.getIndent(); - while (currentIndent !== indent) { - if (currentIndent < indent) { - $handleIndent(this); - currentIndent++; - } else { - $handleOutdent(this); - currentIndent--; - } - } - - return this; - } - /** @deprecated @internal */ canInsertAfter(node: LexicalNode): boolean { return $isListItemNode(node); diff --git a/resources/js/wysiwyg/lexical/list/formatList.ts b/resources/js/wysiwyg/lexical/list/formatList.ts index b9ca011696a..aa0d5d61129 100644 --- a/resources/js/wysiwyg/lexical/list/formatList.ts +++ b/resources/js/wysiwyg/lexical/list/formatList.ts @@ -84,10 +84,6 @@ export function insertList(editor: LexicalEditor, listType: ListType): void { if ($isRootOrShadowRoot(anchorNodeParent)) { anchorNode.replace(list); const listItem = $createListItemNode(); - if ($isElementNode(anchorNode)) { - listItem.setFormat(anchorNode.getFormatType()); - listItem.setIndent(anchorNode.getIndent()); - } list.append(listItem); } else if ($isListItemNode(anchorNode)) { const parent = anchorNode.getParentOrThrow(); @@ -157,8 +153,6 @@ function $createListOrMerge(node: ElementNode, listType: ListType): ListNode { const previousSibling = node.getPreviousSibling(); const nextSibling = node.getNextSibling(); const listItem = $createListItemNode(); - listItem.setFormat(node.getFormatType()); - listItem.setIndent(node.getIndent()); append(listItem, node.getChildren()); if ( diff --git a/resources/js/wysiwyg/lexical/rich-text/index.ts b/resources/js/wysiwyg/lexical/rich-text/index.ts index d937060c658..bc5c3f1d207 100644 --- a/resources/js/wysiwyg/lexical/rich-text/index.ts +++ b/resources/js/wysiwyg/lexical/rich-text/index.ts @@ -155,9 +155,6 @@ export class QuoteNode extends ElementNode { if (this.isEmpty()) { element.append(document.createElement('br')); } - - const formatType = this.getFormatType(); - element.style.textAlign = formatType; } return { @@ -167,8 +164,6 @@ export class QuoteNode extends ElementNode { static importJSON(serializedNode: SerializedQuoteNode): QuoteNode { const node = $createQuoteNode(); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); return node; } @@ -315,9 +310,6 @@ export class HeadingNode extends ElementNode { if (this.isEmpty()) { element.append(document.createElement('br')); } - - const formatType = this.getFormatType(); - element.style.textAlign = formatType; } return { @@ -326,10 +318,7 @@ export class HeadingNode extends ElementNode { } static importJSON(serializedNode: SerializedHeadingNode): HeadingNode { - const node = $createHeadingNode(serializedNode.tag); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); - return node; + return $createHeadingNode(serializedNode.tag); } exportJSON(): SerializedHeadingNode { @@ -402,18 +391,12 @@ function $convertHeadingElement(element: HTMLElement): DOMConversionOutput { nodeName === 'h6' ) { node = $createHeadingNode(nodeName); - if (element.style !== null) { - node.setFormat(element.style.textAlign as ElementFormatType); - } } return {node}; } function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput { const node = $createQuoteNode(); - if (element.style !== null) { - node.setFormat(element.style.textAlign as ElementFormatType); - } return {node}; } @@ -651,9 +634,6 @@ export function registerRichText(editor: LexicalEditor): () => void { (parentNode): parentNode is ElementNode => $isElementNode(parentNode) && !parentNode.isInline(), ); - if (element !== null) { - element.setFormat(format); - } } return true; }, @@ -691,28 +671,6 @@ export function registerRichText(editor: LexicalEditor): () => void { }, COMMAND_PRIORITY_EDITOR, ), - editor.registerCommand( - INDENT_CONTENT_COMMAND, - () => { - return $handleIndentAndOutdent((block) => { - const indent = block.getIndent(); - block.setIndent(indent + 1); - }); - }, - COMMAND_PRIORITY_EDITOR, - ), - editor.registerCommand( - OUTDENT_CONTENT_COMMAND, - () => { - return $handleIndentAndOutdent((block) => { - const indent = block.getIndent(); - if (indent > 0) { - block.setIndent(indent - 1); - } - }); - }, - COMMAND_PRIORITY_EDITOR, - ), editor.registerCommand( KEY_ARROW_UP_COMMAND, (event) => { @@ -846,19 +804,7 @@ export function registerRichText(editor: LexicalEditor): () => void { return false; } event.preventDefault(); - const {anchor} = selection; - const anchorNode = anchor.getNode(); - if ( - selection.isCollapsed() && - anchor.offset === 0 && - !$isRootNode(anchorNode) - ) { - const element = $getNearestBlockElementAncestorOrThrow(anchorNode); - if (element.getIndent() > 0) { - return editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined); - } - } return editor.dispatchCommand(DELETE_CHARACTER_COMMAND, true); }, COMMAND_PRIORITY_EDITOR, diff --git a/resources/js/wysiwyg/lexical/selection/range-selection.ts b/resources/js/wysiwyg/lexical/selection/range-selection.ts index dbadaf346b6..542eae4db9f 100644 --- a/resources/js/wysiwyg/lexical/selection/range-selection.ts +++ b/resources/js/wysiwyg/lexical/selection/range-selection.ts @@ -81,8 +81,6 @@ export function $setBlocksType( invariant($isElementNode(node), 'Expected block node to be an ElementNode'); const targetElement = createElement(); - targetElement.setFormat(node.getFormatType()); - targetElement.setIndent(node.getIndent()); node.replace(targetElement, true); } } @@ -136,8 +134,6 @@ export function $wrapNodes( : anchor.getNode(); const children = target.getChildren(); let element = createElement(); - element.setFormat(target.getFormatType()); - element.setIndent(target.getIndent()); children.forEach((child) => element.append(child)); if (wrappingElement) { @@ -277,8 +273,6 @@ export function $wrapNodesImpl( if (elementMapping.get(parentKey) === undefined) { const targetElement = createElement(); - targetElement.setFormat(parent.getFormatType()); - targetElement.setIndent(parent.getIndent()); elements.push(targetElement); elementMapping.set(parentKey, targetElement); // Move node and its siblings to the new @@ -299,8 +293,6 @@ export function $wrapNodesImpl( 'Expected node in emptyElements to be an ElementNode', ); const targetElement = createElement(); - targetElement.setFormat(node.getFormatType()); - targetElement.setIndent(node.getIndent()); elements.push(targetElement); node.remove(true); } diff --git a/resources/js/wysiwyg/nodes/custom-paragraph.ts b/resources/js/wysiwyg/nodes/custom-paragraph.ts deleted file mode 100644 index 3adc10d0e9c..00000000000 --- a/resources/js/wysiwyg/nodes/custom-paragraph.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { - DOMConversion, - DOMConversionMap, - DOMConversionOutput, - LexicalNode, - ParagraphNode, SerializedParagraphNode, Spread, -} from "lexical"; -import {EditorConfig} from "lexical/LexicalEditor"; -import { - CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode, - SerializedCommonBlockNode, - setCommonBlockPropsFromElement, - updateElementWithCommonBlockProps -} from "./_common"; - -export type SerializedCustomParagraphNode = Spread - -export class CustomParagraphNode extends ParagraphNode { - __id: string = ''; - __alignment: CommonBlockAlignment = ''; - __inset: number = 0; - - static getType() { - return 'custom-paragraph'; - } - - setId(id: string) { - const self = this.getWritable(); - self.__id = id; - } - - getId(): string { - const self = this.getLatest(); - return self.__id; - } - - setAlignment(alignment: CommonBlockAlignment) { - const self = this.getWritable(); - self.__alignment = alignment; - } - - getAlignment(): CommonBlockAlignment { - const self = this.getLatest(); - return self.__alignment; - } - - setInset(size: number) { - const self = this.getWritable(); - self.__inset = size; - } - - getInset(): number { - const self = this.getLatest(); - return self.__inset; - } - - static clone(node: CustomParagraphNode): CustomParagraphNode { - const newNode = new CustomParagraphNode(node.__key); - newNode.__id = node.__id; - newNode.__alignment = node.__alignment; - newNode.__inset = node.__inset; - return newNode; - } - - createDOM(config: EditorConfig): HTMLElement { - const dom = super.createDOM(config); - updateElementWithCommonBlockProps(dom, this); - return dom; - } - - updateDOM(prevNode: CustomParagraphNode, dom: HTMLElement, config: EditorConfig): boolean { - return super.updateDOM(prevNode, dom, config) - || commonPropertiesDifferent(prevNode, this); - } - - exportJSON(): SerializedCustomParagraphNode { - return { - ...super.exportJSON(), - type: 'custom-paragraph', - version: 1, - id: this.__id, - alignment: this.__alignment, - inset: this.__inset, - }; - } - - static importJSON(serializedNode: SerializedCustomParagraphNode): CustomParagraphNode { - const node = $createCustomParagraphNode(); - deserializeCommonBlockNode(serializedNode, node); - return node; - } - - static importDOM(): DOMConversionMap|null { - return { - p(node: HTMLElement): DOMConversion|null { - return { - conversion: (element: HTMLElement): DOMConversionOutput|null => { - const node = $createCustomParagraphNode(); - if (element.style.textIndent) { - const indent = parseInt(element.style.textIndent, 10) / 20; - if (indent > 0) { - node.setIndent(indent); - } - } - - setCommonBlockPropsFromElement(element, node); - - return {node}; - }, - priority: 1, - }; - }, - }; - } -} - -export function $createCustomParagraphNode(): CustomParagraphNode { - return new CustomParagraphNode(); -} - -export function $isCustomParagraphNode(node: LexicalNode | null | undefined): node is CustomParagraphNode { - return node instanceof CustomParagraphNode; -} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index b5483c5009c..062394a9887 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -7,7 +7,6 @@ import { LexicalNodeReplacement, NodeMutation, ParagraphNode } from "lexical"; -import {CustomParagraphNode} from "./custom-paragraph"; import {LinkNode} from "@lexical/link"; import {ImageNode} from "./image"; import {DetailsNode, SummaryNode} from "./details"; @@ -45,14 +44,8 @@ export function getNodesForPageEditor(): (KlassConstructor | CodeBlockNode, DiagramNode, MediaNode, // TODO - Alignment - CustomParagraphNode, + ParagraphNode, LinkNode, - { - replace: ParagraphNode, - with: (node: ParagraphNode) => { - return new CustomParagraphNode(); - } - }, { replace: HeadingNode, with: (node: HeadingNode) => { diff --git a/resources/js/wysiwyg/services/drop-paste-handling.ts b/resources/js/wysiwyg/services/drop-paste-handling.ts index 07e35d4438e..e049d5e7c9e 100644 --- a/resources/js/wysiwyg/services/drop-paste-handling.ts +++ b/resources/js/wysiwyg/services/drop-paste-handling.ts @@ -1,4 +1,5 @@ import { + $createParagraphNode, $insertNodes, $isDecoratorNode, COMMAND_PRIORITY_HIGH, DROP_COMMAND, LexicalEditor, @@ -8,7 +9,6 @@ import {$insertNewBlockNodesAtSelection, $selectSingleNode} from "../utils/selec import {$getNearestBlockNodeForCoords, $htmlToBlockNodes} from "../utils/nodes"; import {Clipboard} from "../../services/clipboard"; import {$createImageNode} from "../nodes/image"; -import {$createCustomParagraphNode} from "../nodes/custom-paragraph"; import {$createLinkNode} from "@lexical/link"; import {EditorImageData, uploadImageFile} from "../utils/images"; import {EditorUiContext} from "../ui/framework/core"; @@ -67,7 +67,7 @@ function handleMediaInsert(data: DataTransfer, context: EditorUiContext): boolea for (const imageFile of images) { const loadingImage = window.baseUrl('/loading.gif'); const loadingNode = $createImageNode(loadingImage); - const imageWrap = $createCustomParagraphNode(); + const imageWrap = $createParagraphNode(); imageWrap.append(loadingNode); $insertNodes([imageWrap]); diff --git a/resources/js/wysiwyg/services/keyboard-handling.ts b/resources/js/wysiwyg/services/keyboard-handling.ts index 2c7bfdbbae7..3f0b0c495e0 100644 --- a/resources/js/wysiwyg/services/keyboard-handling.ts +++ b/resources/js/wysiwyg/services/keyboard-handling.ts @@ -1,5 +1,6 @@ import {EditorUiContext} from "../ui/framework/core"; import { + $createParagraphNode, $getSelection, $isDecoratorNode, COMMAND_PRIORITY_LOW, @@ -13,7 +14,6 @@ import {$isImageNode} from "../nodes/image"; import {$isMediaNode} from "../nodes/media"; import {getLastSelection} from "../utils/selection"; import {$getNearestNodeBlockParent} from "../utils/nodes"; -import {$createCustomParagraphNode} from "../nodes/custom-paragraph"; import {$isCustomListItemNode} from "../nodes/custom-list-item"; import {$setInsetForSelection} from "../utils/lists"; @@ -45,7 +45,7 @@ function insertAfterSingleSelectedNode(editor: LexicalEditor, event: KeyboardEve if (nearestBlock) { requestAnimationFrame(() => { editor.update(() => { - const newParagraph = $createCustomParagraphNode(); + const newParagraph = $createParagraphNode(); nearestBlock.insertAfter(newParagraph); newParagraph.select(); }); diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index a49cccd26dc..817a235a712 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -2,7 +2,11 @@ ## In progress -// +Reorg + - Merge custom nodes into original nodes + - Reduce down to use CommonBlockNode where possible + - Remove existing formatType/ElementFormatType references (replaced with alignment). + - Remove existing indent references (replaced with inset). ## Main Todo diff --git a/resources/js/wysiwyg/utils/formats.ts b/resources/js/wysiwyg/utils/formats.ts index 0ec9220dd2e..3cfc964423f 100644 --- a/resources/js/wysiwyg/utils/formats.ts +++ b/resources/js/wysiwyg/utils/formats.ts @@ -1,5 +1,13 @@ import {$isQuoteNode, HeadingNode, HeadingTagType} from "@lexical/rich-text"; -import {$createTextNode, $getSelection, $insertNodes, LexicalEditor, LexicalNode} from "lexical"; +import { + $createParagraphNode, + $createTextNode, + $getSelection, + $insertNodes, + $isParagraphNode, + LexicalEditor, + LexicalNode +} from "lexical"; import { $getBlockElementNodesInSelection, $getNodeFromSelection, @@ -8,7 +16,6 @@ import { getLastSelection } from "./selection"; import {$createCustomHeadingNode, $isCustomHeadingNode} from "../nodes/custom-heading"; -import {$createCustomParagraphNode, $isCustomParagraphNode} from "../nodes/custom-paragraph"; import {$createCustomQuoteNode} from "../nodes/custom-quote"; import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../nodes/code-block"; import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "../nodes/callout"; @@ -31,7 +38,7 @@ export function toggleSelectionAsHeading(editor: LexicalEditor, tag: HeadingTagT export function toggleSelectionAsParagraph(editor: LexicalEditor) { editor.update(() => { - $toggleSelectionBlockNodeType($isCustomParagraphNode, $createCustomParagraphNode); + $toggleSelectionBlockNodeType($isParagraphNode, $createParagraphNode); }); } diff --git a/resources/js/wysiwyg/utils/nodes.ts b/resources/js/wysiwyg/utils/nodes.ts index 2dd99d369c7..97634f96b4e 100644 --- a/resources/js/wysiwyg/utils/nodes.ts +++ b/resources/js/wysiwyg/utils/nodes.ts @@ -1,4 +1,5 @@ import { + $createParagraphNode, $getRoot, $isDecoratorNode, $isElementNode, $isRootNode, @@ -8,7 +9,6 @@ import { LexicalNode } from "lexical"; import {LexicalNodeMatcher} from "../nodes"; -import {$createCustomParagraphNode} from "../nodes/custom-paragraph"; import {$generateNodesFromDOM} from "@lexical/html"; import {htmlToDom} from "./dom"; import {NodeHasAlignment, NodeHasInset} from "../nodes/_common"; @@ -17,7 +17,7 @@ import {$findMatchingParent} from "@lexical/utils"; function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] { return nodes.map(node => { if ($isTextNode(node)) { - const paragraph = $createCustomParagraphNode(); + const paragraph = $createParagraphNode(); paragraph.append(node); return paragraph; } diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index 67c2d91b26c..02838eba034 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -7,17 +7,15 @@ import { $isTextNode, $setSelection, BaseSelection, DecoratorNode, - ElementFormatType, ElementNode, LexicalEditor, LexicalNode, TextFormatType, TextNode } from "lexical"; -import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; +import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes"; import {$setBlocksType} from "@lexical/selection"; import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes"; -import {$createCustomParagraphNode} from "../nodes/custom-paragraph"; import {CommonBlockAlignment} from "../nodes/_common"; const lastSelectionByEditor = new WeakMap; @@ -71,7 +69,7 @@ export function $toggleSelectionBlockNodeType(matcher: LexicalNodeMatcher, creat const selection = $getSelection(); const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null; if (selection && matcher(blockElement)) { - $setBlocksType(selection, $createCustomParagraphNode); + $setBlocksType(selection, $createParagraphNode); } else { $setBlocksType(selection, creator); } From 36a4d791205f824ce6d7d487ab4578ae736c78c0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 3 Dec 2024 17:04:50 +0000 Subject: [PATCH 2/6] Lexical: Extracted & merged heading & quote nodes --- .../unit/LexicalSerialization.test.ts | 3 +- .../lexical/core/__tests__/utils/index.ts | 3 +- .../lexical/core/nodes/CommonBlockNode.ts | 2 +- .../__tests__/unit/LexicalTabNode.test.ts | 3 +- .../html/__tests__/unit/LexicalHtml.test.ts | 3 +- .../lexical/rich-text/LexicalHeadingNode.ts | 202 ++++++++++ .../lexical/rich-text/LexicalQuoteNode.ts | 129 +++++++ .../__tests__/unit/LexicalHeadingNode.test.ts | 6 +- .../__tests__/unit/LexicalQuoteNode.test.ts | 2 +- .../js/wysiwyg/lexical/rich-text/index.ts | 345 +----------------- .../__tests__/unit/LexicalSelection.test.ts | 3 +- .../unit/LexicalSelectionHelpers.test.ts | 2 +- .../unit/LexicalEventHelpers.test.ts | 4 +- resources/js/wysiwyg/nodes/custom-heading.ts | 146 -------- resources/js/wysiwyg/nodes/custom-quote.ts | 115 ------ resources/js/wysiwyg/nodes/index.ts | 21 +- resources/js/wysiwyg/services/shortcuts.ts | 2 +- .../ui/defaults/buttons/block-formats.ts | 8 +- .../wysiwyg/ui/framework/blocks/link-field.ts | 11 +- resources/js/wysiwyg/utils/formats.ts | 11 +- 20 files changed, 370 insertions(+), 651 deletions(-) create mode 100644 resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts create mode 100644 resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts delete mode 100644 resources/js/wysiwyg/nodes/custom-heading.ts delete mode 100644 resources/js/wysiwyg/nodes/custom-quote.ts diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts index 5599604c059..81eff674a99 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts @@ -8,11 +8,12 @@ import {$createLinkNode} from '@lexical/link'; import {$createListItemNode, $createListNode} from '@lexical/list'; -import {$createHeadingNode, $createQuoteNode} from '@lexical/rich-text'; import {$createTableNodeWithDimensions} from '@lexical/table'; import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical'; import {initializeUnitTest} from '../utils'; +import {$createHeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; +import {$createQuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; function $createEditorContent() { const root = $getRoot(); diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index a4d74210e41..e9d14ef1139 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -10,7 +10,6 @@ import {createHeadlessEditor} from '@lexical/headless'; import {AutoLinkNode, LinkNode} from '@lexical/link'; import {ListItemNode, ListNode} from '@lexical/list'; -import {HeadingNode, QuoteNode} from '@lexical/rich-text'; import {TableCellNode, TableNode, TableRowNode} from '@lexical/table'; import { @@ -36,6 +35,8 @@ import { LexicalNodeReplacement, } from '../../LexicalEditor'; import {resetRandomKey} from '../../LexicalUtils'; +import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; +import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; type TestEnv = { diff --git a/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts b/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts index 37ca1cdef7f..bf4fc08ca60 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts @@ -48,7 +48,7 @@ export class CommonBlockNode extends ElementNode { } export function copyCommonBlockProperties(from: CommonBlockNode, to: CommonBlockNode): void { - to.__id = from.__id; + // to.__id = from.__id; to.__alignment = from.__alignment; to.__inset = from.__inset; } \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts index d8525fb369f..9831114340d 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts @@ -11,7 +11,7 @@ import { $insertDataTransferForRichText, } from '@lexical/clipboard'; import {$createListItemNode, $createListNode} from '@lexical/list'; -import {$createHeadingNode, registerRichText} from '@lexical/rich-text'; +import {registerRichText} from '@lexical/rich-text'; import { $createParagraphNode, $createRangeSelection, @@ -32,6 +32,7 @@ import { initializeUnitTest, invariant, } from '../../../__tests__/utils'; +import {$createHeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; describe('LexicalTabNode tests', () => { initializeUnitTest((testEnv) => { diff --git a/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts b/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts index 947e591b4ff..a4e2d231389 100644 --- a/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts +++ b/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts @@ -13,13 +13,14 @@ import {createHeadlessEditor} from '@lexical/headless'; import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html'; import {LinkNode} from '@lexical/link'; import {ListItemNode, ListNode} from '@lexical/list'; -import {HeadingNode, QuoteNode} from '@lexical/rich-text'; import { $createParagraphNode, $createRangeSelection, $createTextNode, $getRoot, } from 'lexical'; +import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; +import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; describe('HTML', () => { type Input = Array<{ diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts new file mode 100644 index 00000000000..0f30263ba0e --- /dev/null +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts @@ -0,0 +1,202 @@ +import { + $applyNodeReplacement, + $createParagraphNode, + type DOMConversionMap, + DOMConversionOutput, + type DOMExportOutput, + type EditorConfig, + isHTMLElement, + type LexicalEditor, + type LexicalNode, + type NodeKey, + type ParagraphNode, + type RangeSelection, + type SerializedElementNode, + type Spread +} from "lexical"; +import {addClassNamesToElement} from "@lexical/utils"; +import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; +import { + commonPropertiesDifferent, deserializeCommonBlockNode, + SerializedCommonBlockNode, setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "../../nodes/_common"; + +export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + +export type SerializedHeadingNode = Spread< + { + tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + }, + SerializedCommonBlockNode +>; + +/** @noInheritDoc */ +export class HeadingNode extends CommonBlockNode { + /** @internal */ + __tag: HeadingTagType; + + static getType(): string { + return 'heading'; + } + + static clone(node: HeadingNode): HeadingNode { + const clone = new HeadingNode(node.__tag, node.__key); + copyCommonBlockProperties(node, clone); + return clone; + } + + constructor(tag: HeadingTagType, key?: NodeKey) { + super(key); + this.__tag = tag; + } + + getTag(): HeadingTagType { + return this.__tag; + } + + // View + + createDOM(config: EditorConfig): HTMLElement { + const tag = this.__tag; + const element = document.createElement(tag); + const theme = config.theme; + const classNames = theme.heading; + if (classNames !== undefined) { + const className = classNames[tag]; + addClassNamesToElement(element, className); + } + updateElementWithCommonBlockProps(element, this); + return element; + } + + updateDOM(prevNode: HeadingNode, dom: HTMLElement): boolean { + return commonPropertiesDifferent(prevNode, this); + } + + static importDOM(): DOMConversionMap | null { + return { + h1: (node: Node) => ({ + conversion: $convertHeadingElement, + priority: 0, + }), + h2: (node: Node) => ({ + conversion: $convertHeadingElement, + priority: 0, + }), + h3: (node: Node) => ({ + conversion: $convertHeadingElement, + priority: 0, + }), + h4: (node: Node) => ({ + conversion: $convertHeadingElement, + priority: 0, + }), + h5: (node: Node) => ({ + conversion: $convertHeadingElement, + priority: 0, + }), + h6: (node: Node) => ({ + conversion: $convertHeadingElement, + priority: 0, + }), + }; + } + + exportDOM(editor: LexicalEditor): DOMExportOutput { + const {element} = super.exportDOM(editor); + + if (element && isHTMLElement(element)) { + if (this.isEmpty()) { + element.append(document.createElement('br')); + } + } + + return { + element, + }; + } + + static importJSON(serializedNode: SerializedHeadingNode): HeadingNode { + const node = $createHeadingNode(serializedNode.tag); + deserializeCommonBlockNode(serializedNode, node); + return node; + } + + exportJSON(): SerializedHeadingNode { + return { + ...super.exportJSON(), + tag: this.getTag(), + type: 'heading', + version: 1, + }; + } + + // Mutation + insertNewAfter( + selection?: RangeSelection, + restoreSelection = true, + ): ParagraphNode | HeadingNode { + const anchorOffet = selection ? selection.anchor.offset : 0; + const lastDesc = this.getLastDescendant(); + const isAtEnd = + !lastDesc || + (selection && + selection.anchor.key === lastDesc.getKey() && + anchorOffet === lastDesc.getTextContentSize()); + const newElement = + isAtEnd || !selection + ? $createParagraphNode() + : $createHeadingNode(this.getTag()); + const direction = this.getDirection(); + newElement.setDirection(direction); + this.insertAfter(newElement, restoreSelection); + if (anchorOffet === 0 && !this.isEmpty() && selection) { + const paragraph = $createParagraphNode(); + paragraph.select(); + this.replace(paragraph, true); + } + return newElement; + } + + collapseAtStart(): true { + const newElement = !this.isEmpty() + ? $createHeadingNode(this.getTag()) + : $createParagraphNode(); + const children = this.getChildren(); + children.forEach((child) => newElement.append(child)); + this.replace(newElement); + return true; + } + + extractWithChild(): boolean { + return true; + } +} + +function $convertHeadingElement(element: HTMLElement): DOMConversionOutput { + const nodeName = element.nodeName.toLowerCase(); + let node = null; + if ( + nodeName === 'h1' || + nodeName === 'h2' || + nodeName === 'h3' || + nodeName === 'h4' || + nodeName === 'h5' || + nodeName === 'h6' + ) { + node = $createHeadingNode(nodeName); + setCommonBlockPropsFromElement(element, node); + } + return {node}; +} + +export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode { + return $applyNodeReplacement(new HeadingNode(headingTag)); +} + +export function $isHeadingNode( + node: LexicalNode | null | undefined, +): node is HeadingNode { + return node instanceof HeadingNode; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts new file mode 100644 index 00000000000..53caca80115 --- /dev/null +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts @@ -0,0 +1,129 @@ +import { + $applyNodeReplacement, + $createParagraphNode, + type DOMConversionMap, + type DOMConversionOutput, + type DOMExportOutput, + type EditorConfig, + ElementNode, + isHTMLElement, + type LexicalEditor, + LexicalNode, + type NodeKey, + type ParagraphNode, + type RangeSelection, + SerializedElementNode +} from "lexical"; +import {addClassNamesToElement} from "@lexical/utils"; +import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; +import { + commonPropertiesDifferent, deserializeCommonBlockNode, + SerializedCommonBlockNode, setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "../../nodes/_common"; + +export type SerializedQuoteNode = SerializedCommonBlockNode; + +/** @noInheritDoc */ +export class QuoteNode extends CommonBlockNode { + static getType(): string { + return 'quote'; + } + + static clone(node: QuoteNode): QuoteNode { + const clone = new QuoteNode(node.__key); + copyCommonBlockProperties(node, clone); + return clone; + } + + constructor(key?: NodeKey) { + super(key); + } + + // View + + createDOM(config: EditorConfig): HTMLElement { + const element = document.createElement('blockquote'); + addClassNamesToElement(element, config.theme.quote); + updateElementWithCommonBlockProps(element, this); + return element; + } + + updateDOM(prevNode: QuoteNode, dom: HTMLElement): boolean { + return commonPropertiesDifferent(prevNode, this); + } + + static importDOM(): DOMConversionMap | null { + return { + blockquote: (node: Node) => ({ + conversion: $convertBlockquoteElement, + priority: 0, + }), + }; + } + + exportDOM(editor: LexicalEditor): DOMExportOutput { + const {element} = super.exportDOM(editor); + + if (element && isHTMLElement(element)) { + if (this.isEmpty()) { + element.append(document.createElement('br')); + } + } + + return { + element, + }; + } + + static importJSON(serializedNode: SerializedQuoteNode): QuoteNode { + const node = $createQuoteNode(); + deserializeCommonBlockNode(serializedNode, node); + return node; + } + + exportJSON(): SerializedQuoteNode { + return { + ...super.exportJSON(), + type: 'quote', + }; + } + + // Mutation + + insertNewAfter(_: RangeSelection, restoreSelection?: boolean): ParagraphNode { + const newBlock = $createParagraphNode(); + const direction = this.getDirection(); + newBlock.setDirection(direction); + this.insertAfter(newBlock, restoreSelection); + return newBlock; + } + + collapseAtStart(): true { + const paragraph = $createParagraphNode(); + const children = this.getChildren(); + children.forEach((child) => paragraph.append(child)); + this.replace(paragraph); + return true; + } + + canMergeWhenEmpty(): true { + return true; + } +} + +export function $createQuoteNode(): QuoteNode { + return $applyNodeReplacement(new QuoteNode()); +} + +export function $isQuoteNode( + node: LexicalNode | null | undefined, +): node is QuoteNode { + return node instanceof QuoteNode; +} + +function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput { + const node = $createQuoteNode(); + setCommonBlockPropsFromElement(element, node); + return {node}; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalHeadingNode.test.ts b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalHeadingNode.test.ts index a94f9ee0bb8..be4b97ba3f3 100644 --- a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalHeadingNode.test.ts +++ b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalHeadingNode.test.ts @@ -6,11 +6,6 @@ * */ -import { - $createHeadingNode, - $isHeadingNode, - HeadingNode, -} from '@lexical/rich-text'; import { $createTextNode, $getRoot, @@ -19,6 +14,7 @@ import { RangeSelection, } from 'lexical'; import {initializeUnitTest} from 'lexical/__tests__/utils'; +import {$createHeadingNode, $isHeadingNode, HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; const editorConfig = Object.freeze({ namespace: '', diff --git a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalQuoteNode.test.ts b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalQuoteNode.test.ts index 66374bf5ff0..cf85045cda1 100644 --- a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalQuoteNode.test.ts +++ b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalQuoteNode.test.ts @@ -6,9 +6,9 @@ * */ -import {$createQuoteNode, QuoteNode} from '@lexical/rich-text'; import {$createRangeSelection, $getRoot, ParagraphNode} from 'lexical'; import {initializeUnitTest} from 'lexical/__tests__/utils'; +import {$createQuoteNode, QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; const editorConfig = Object.freeze({ namespace: '', diff --git a/resources/js/wysiwyg/lexical/rich-text/index.ts b/resources/js/wysiwyg/lexical/rich-text/index.ts index bc5c3f1d207..c585c028a5a 100644 --- a/resources/js/wysiwyg/lexical/rich-text/index.ts +++ b/resources/js/wysiwyg/lexical/rich-text/index.ts @@ -8,42 +8,14 @@ import type { CommandPayloadType, - DOMConversionMap, - DOMConversionOutput, - DOMExportOutput, - EditorConfig, ElementFormatType, LexicalCommand, LexicalEditor, - LexicalNode, - NodeKey, - ParagraphNode, PasteCommandType, RangeSelection, - SerializedElementNode, - Spread, TextFormatType, } from 'lexical'; - -import { - $insertDataTransferForRichText, - copyToClipboard, -} from '@lexical/clipboard'; -import { - $moveCharacter, - $shouldOverrideDefaultCharacterSelection, -} from '@lexical/selection'; import { - $findMatchingParent, - $getNearestBlockElementAncestorOrThrow, - addClassNamesToElement, - isHTMLElement, - mergeRegister, - objectKlassEquals, -} from '@lexical/utils'; -import { - $applyNodeReplacement, - $createParagraphNode, $createRangeSelection, $createTabNode, $getAdjacentNode, @@ -55,7 +27,6 @@ import { $isElementNode, $isNodeSelection, $isRangeSelection, - $isRootNode, $isTextNode, $normalizeSelection__EXPERIMENTAL, $selectAll, @@ -75,7 +46,6 @@ import { ElementNode, FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, - INDENT_CONTENT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, @@ -88,327 +58,22 @@ import { KEY_DELETE_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, - OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, REMOVE_TEXT_COMMAND, SELECT_ALL_COMMAND, } from 'lexical'; -import caretFromPoint from 'lexical/shared/caretFromPoint'; -import { - CAN_USE_BEFORE_INPUT, - IS_APPLE_WEBKIT, - IS_IOS, - IS_SAFARI, -} from 'lexical/shared/environment'; -export type SerializedHeadingNode = Spread< - { - tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; - }, - SerializedElementNode ->; +import {$insertDataTransferForRichText, copyToClipboard,} from '@lexical/clipboard'; +import {$moveCharacter, $shouldOverrideDefaultCharacterSelection,} from '@lexical/selection'; +import {$findMatchingParent, mergeRegister, objectKlassEquals,} from '@lexical/utils'; +import caretFromPoint from 'lexical/shared/caretFromPoint'; +import {CAN_USE_BEFORE_INPUT, IS_APPLE_WEBKIT, IS_IOS, IS_SAFARI,} from 'lexical/shared/environment'; export const DRAG_DROP_PASTE: LexicalCommand> = createCommand( 'DRAG_DROP_PASTE_FILE', ); -export type SerializedQuoteNode = SerializedElementNode; - -/** @noInheritDoc */ -export class QuoteNode extends ElementNode { - static getType(): string { - return 'quote'; - } - - static clone(node: QuoteNode): QuoteNode { - return new QuoteNode(node.__key); - } - - constructor(key?: NodeKey) { - super(key); - } - - // View - - createDOM(config: EditorConfig): HTMLElement { - const element = document.createElement('blockquote'); - addClassNamesToElement(element, config.theme.quote); - return element; - } - updateDOM(prevNode: QuoteNode, dom: HTMLElement): boolean { - return false; - } - - static importDOM(): DOMConversionMap | null { - return { - blockquote: (node: Node) => ({ - conversion: $convertBlockquoteElement, - priority: 0, - }), - }; - } - - exportDOM(editor: LexicalEditor): DOMExportOutput { - const {element} = super.exportDOM(editor); - - if (element && isHTMLElement(element)) { - if (this.isEmpty()) { - element.append(document.createElement('br')); - } - } - - return { - element, - }; - } - - static importJSON(serializedNode: SerializedQuoteNode): QuoteNode { - const node = $createQuoteNode(); - return node; - } - - exportJSON(): SerializedElementNode { - return { - ...super.exportJSON(), - type: 'quote', - }; - } - - // Mutation - - insertNewAfter(_: RangeSelection, restoreSelection?: boolean): ParagraphNode { - const newBlock = $createParagraphNode(); - const direction = this.getDirection(); - newBlock.setDirection(direction); - this.insertAfter(newBlock, restoreSelection); - return newBlock; - } - - collapseAtStart(): true { - const paragraph = $createParagraphNode(); - const children = this.getChildren(); - children.forEach((child) => paragraph.append(child)); - this.replace(paragraph); - return true; - } - - canMergeWhenEmpty(): true { - return true; - } -} - -export function $createQuoteNode(): QuoteNode { - return $applyNodeReplacement(new QuoteNode()); -} - -export function $isQuoteNode( - node: LexicalNode | null | undefined, -): node is QuoteNode { - return node instanceof QuoteNode; -} - -export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; - -/** @noInheritDoc */ -export class HeadingNode extends ElementNode { - /** @internal */ - __tag: HeadingTagType; - - static getType(): string { - return 'heading'; - } - - static clone(node: HeadingNode): HeadingNode { - return new HeadingNode(node.__tag, node.__key); - } - - constructor(tag: HeadingTagType, key?: NodeKey) { - super(key); - this.__tag = tag; - } - - getTag(): HeadingTagType { - return this.__tag; - } - - // View - - createDOM(config: EditorConfig): HTMLElement { - const tag = this.__tag; - const element = document.createElement(tag); - const theme = config.theme; - const classNames = theme.heading; - if (classNames !== undefined) { - const className = classNames[tag]; - addClassNamesToElement(element, className); - } - return element; - } - - updateDOM(prevNode: HeadingNode, dom: HTMLElement): boolean { - return false; - } - - static importDOM(): DOMConversionMap | null { - return { - h1: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h2: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h3: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h4: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h5: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h6: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - p: (node: Node) => { - // domNode is a

since we matched it by nodeName - const paragraph = node as HTMLParagraphElement; - const firstChild = paragraph.firstChild; - if (firstChild !== null && isGoogleDocsTitle(firstChild)) { - return { - conversion: () => ({node: null}), - priority: 3, - }; - } - return null; - }, - span: (node: Node) => { - if (isGoogleDocsTitle(node)) { - return { - conversion: (domNode: Node) => { - return { - node: $createHeadingNode('h1'), - }; - }, - priority: 3, - }; - } - return null; - }, - }; - } - - exportDOM(editor: LexicalEditor): DOMExportOutput { - const {element} = super.exportDOM(editor); - - if (element && isHTMLElement(element)) { - if (this.isEmpty()) { - element.append(document.createElement('br')); - } - } - - return { - element, - }; - } - - static importJSON(serializedNode: SerializedHeadingNode): HeadingNode { - return $createHeadingNode(serializedNode.tag); - } - - exportJSON(): SerializedHeadingNode { - return { - ...super.exportJSON(), - tag: this.getTag(), - type: 'heading', - version: 1, - }; - } - - // Mutation - insertNewAfter( - selection?: RangeSelection, - restoreSelection = true, - ): ParagraphNode | HeadingNode { - const anchorOffet = selection ? selection.anchor.offset : 0; - const lastDesc = this.getLastDescendant(); - const isAtEnd = - !lastDesc || - (selection && - selection.anchor.key === lastDesc.getKey() && - anchorOffet === lastDesc.getTextContentSize()); - const newElement = - isAtEnd || !selection - ? $createParagraphNode() - : $createHeadingNode(this.getTag()); - const direction = this.getDirection(); - newElement.setDirection(direction); - this.insertAfter(newElement, restoreSelection); - if (anchorOffet === 0 && !this.isEmpty() && selection) { - const paragraph = $createParagraphNode(); - paragraph.select(); - this.replace(paragraph, true); - } - return newElement; - } - - collapseAtStart(): true { - const newElement = !this.isEmpty() - ? $createHeadingNode(this.getTag()) - : $createParagraphNode(); - const children = this.getChildren(); - children.forEach((child) => newElement.append(child)); - this.replace(newElement); - return true; - } - - extractWithChild(): boolean { - return true; - } -} -function isGoogleDocsTitle(domNode: Node): boolean { - if (domNode.nodeName.toLowerCase() === 'span') { - return (domNode as HTMLSpanElement).style.fontSize === '26pt'; - } - return false; -} - -function $convertHeadingElement(element: HTMLElement): DOMConversionOutput { - const nodeName = element.nodeName.toLowerCase(); - let node = null; - if ( - nodeName === 'h1' || - nodeName === 'h2' || - nodeName === 'h3' || - nodeName === 'h4' || - nodeName === 'h5' || - nodeName === 'h6' - ) { - node = $createHeadingNode(nodeName); - } - return {node}; -} - -function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput { - const node = $createQuoteNode(); - return {node}; -} - -export function $createHeadingNode(headingTag: HeadingTagType): HeadingNode { - return $applyNodeReplacement(new HeadingNode(headingTag)); -} - -export function $isHeadingNode( - node: LexicalNode | null | undefined, -): node is HeadingNode { - return node instanceof HeadingNode; -} function onPasteForRichText( event: CommandPayloadType, diff --git a/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts index 5f2d9dcc093..466be7498de 100644 --- a/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts +++ b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts @@ -8,7 +8,7 @@ import {$createLinkNode} from '@lexical/link'; import {$createListItemNode, $createListNode} from '@lexical/list'; -import {$createHeadingNode, registerRichText} from '@lexical/rich-text'; +import {registerRichText} from '@lexical/rich-text'; import { $addNodeStyle, $getSelectionStyleValueForProperty, @@ -74,6 +74,7 @@ import { } from '../utils'; import {createEmptyHistoryState, registerHistory} from "@lexical/history"; import {mergeRegister} from "@lexical/utils"; +import {$createHeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; interface ExpectedSelection { anchorPath: number[]; diff --git a/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelectionHelpers.test.ts b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelectionHelpers.test.ts index 4d88bde0e4c..0523b7f7164 100644 --- a/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelectionHelpers.test.ts +++ b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelectionHelpers.test.ts @@ -7,7 +7,6 @@ */ import {$createLinkNode} from '@lexical/link'; -import {$createHeadingNode, $isHeadingNode} from '@lexical/rich-text'; import { $getSelectionStyleValueForProperty, $patchStyleText, @@ -44,6 +43,7 @@ import { } from 'lexical/__tests__/utils'; import {$setAnchorPoint, $setFocusPoint} from '../utils'; +import {$createHeadingNode, $isHeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; Range.prototype.getBoundingClientRect = function (): DOMRect { const rect = { diff --git a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalEventHelpers.test.ts b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalEventHelpers.test.ts index fd7731f9061..d76937ed606 100644 --- a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalEventHelpers.test.ts +++ b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalEventHelpers.test.ts @@ -7,7 +7,7 @@ */ import {AutoLinkNode, LinkNode} from '@lexical/link'; import {ListItemNode, ListNode} from '@lexical/list'; -import {HeadingNode, QuoteNode, registerRichText} from '@lexical/rich-text'; +import {registerRichText} from '@lexical/rich-text'; import { applySelectionInputs, pasteHTML, @@ -15,6 +15,8 @@ import { import {TableCellNode, TableNode, TableRowNode} from '@lexical/table'; import {$createParagraphNode, $insertNodes, LexicalEditor} from 'lexical'; import {createTestEditor, initializeClipboard} from 'lexical/__tests__/utils'; +import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; +import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; jest.mock('lexical/shared/environment', () => { const originalModule = jest.requireActual('lexical/shared/environment'); diff --git a/resources/js/wysiwyg/nodes/custom-heading.ts b/resources/js/wysiwyg/nodes/custom-heading.ts deleted file mode 100644 index 5df6245f5a5..00000000000 --- a/resources/js/wysiwyg/nodes/custom-heading.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { - DOMConversionMap, - DOMConversionOutput, - LexicalNode, - Spread -} from "lexical"; -import {EditorConfig} from "lexical/LexicalEditor"; -import {HeadingNode, HeadingTagType, SerializedHeadingNode} from "@lexical/rich-text"; -import { - CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode, - SerializedCommonBlockNode, - setCommonBlockPropsFromElement, - updateElementWithCommonBlockProps -} from "./_common"; - - -export type SerializedCustomHeadingNode = Spread - -export class CustomHeadingNode extends HeadingNode { - __id: string = ''; - __alignment: CommonBlockAlignment = ''; - __inset: number = 0; - - static getType() { - return 'custom-heading'; - } - - setId(id: string) { - const self = this.getWritable(); - self.__id = id; - } - - getId(): string { - const self = this.getLatest(); - return self.__id; - } - - setAlignment(alignment: CommonBlockAlignment) { - const self = this.getWritable(); - self.__alignment = alignment; - } - - getAlignment(): CommonBlockAlignment { - const self = this.getLatest(); - return self.__alignment; - } - - setInset(size: number) { - const self = this.getWritable(); - self.__inset = size; - } - - getInset(): number { - const self = this.getLatest(); - return self.__inset; - } - - static clone(node: CustomHeadingNode) { - const newNode = new CustomHeadingNode(node.__tag, node.__key); - newNode.__alignment = node.__alignment; - newNode.__inset = node.__inset; - return newNode; - } - - createDOM(config: EditorConfig): HTMLElement { - const dom = super.createDOM(config); - updateElementWithCommonBlockProps(dom, this); - return dom; - } - - updateDOM(prevNode: CustomHeadingNode, dom: HTMLElement): boolean { - return super.updateDOM(prevNode, dom) - || commonPropertiesDifferent(prevNode, this); - } - - exportJSON(): SerializedCustomHeadingNode { - return { - ...super.exportJSON(), - type: 'custom-heading', - version: 1, - id: this.__id, - alignment: this.__alignment, - inset: this.__inset, - }; - } - - static importJSON(serializedNode: SerializedCustomHeadingNode): CustomHeadingNode { - const node = $createCustomHeadingNode(serializedNode.tag); - deserializeCommonBlockNode(serializedNode, node); - return node; - } - - static importDOM(): DOMConversionMap | null { - return { - h1: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h2: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h3: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h4: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h5: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - h6: (node: Node) => ({ - conversion: $convertHeadingElement, - priority: 0, - }), - }; - } -} - -function $convertHeadingElement(element: HTMLElement): DOMConversionOutput { - const nodeName = element.nodeName.toLowerCase(); - let node = null; - if ( - nodeName === 'h1' || - nodeName === 'h2' || - nodeName === 'h3' || - nodeName === 'h4' || - nodeName === 'h5' || - nodeName === 'h6' - ) { - node = $createCustomHeadingNode(nodeName); - setCommonBlockPropsFromElement(element, node); - } - return {node}; -} - -export function $createCustomHeadingNode(tag: HeadingTagType) { - return new CustomHeadingNode(tag); -} - -export function $isCustomHeadingNode(node: LexicalNode | null | undefined): node is CustomHeadingNode { - return node instanceof CustomHeadingNode; -} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/custom-quote.ts b/resources/js/wysiwyg/nodes/custom-quote.ts deleted file mode 100644 index 39ae7bf8af3..00000000000 --- a/resources/js/wysiwyg/nodes/custom-quote.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { - DOMConversionMap, - DOMConversionOutput, - LexicalNode, - Spread -} from "lexical"; -import {EditorConfig} from "lexical/LexicalEditor"; -import {QuoteNode, SerializedQuoteNode} from "@lexical/rich-text"; -import { - CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode, - SerializedCommonBlockNode, - setCommonBlockPropsFromElement, - updateElementWithCommonBlockProps -} from "./_common"; - - -export type SerializedCustomQuoteNode = Spread - -export class CustomQuoteNode extends QuoteNode { - __id: string = ''; - __alignment: CommonBlockAlignment = ''; - __inset: number = 0; - - static getType() { - return 'custom-quote'; - } - - setId(id: string) { - const self = this.getWritable(); - self.__id = id; - } - - getId(): string { - const self = this.getLatest(); - return self.__id; - } - - setAlignment(alignment: CommonBlockAlignment) { - const self = this.getWritable(); - self.__alignment = alignment; - } - - getAlignment(): CommonBlockAlignment { - const self = this.getLatest(); - return self.__alignment; - } - - setInset(size: number) { - const self = this.getWritable(); - self.__inset = size; - } - - getInset(): number { - const self = this.getLatest(); - return self.__inset; - } - - static clone(node: CustomQuoteNode) { - const newNode = new CustomQuoteNode(node.__key); - newNode.__id = node.__id; - newNode.__alignment = node.__alignment; - newNode.__inset = node.__inset; - return newNode; - } - - createDOM(config: EditorConfig): HTMLElement { - const dom = super.createDOM(config); - updateElementWithCommonBlockProps(dom, this); - return dom; - } - - updateDOM(prevNode: CustomQuoteNode): boolean { - return commonPropertiesDifferent(prevNode, this); - } - - exportJSON(): SerializedCustomQuoteNode { - return { - ...super.exportJSON(), - type: 'custom-quote', - version: 1, - id: this.__id, - alignment: this.__alignment, - inset: this.__inset, - }; - } - - static importJSON(serializedNode: SerializedCustomQuoteNode): CustomQuoteNode { - const node = $createCustomQuoteNode(); - deserializeCommonBlockNode(serializedNode, node); - return node; - } - - static importDOM(): DOMConversionMap | null { - return { - blockquote: (node: Node) => ({ - conversion: $convertBlockquoteElement, - priority: 0, - }), - }; - } -} - -function $convertBlockquoteElement(element: HTMLElement): DOMConversionOutput { - const node = $createCustomQuoteNode(); - setCommonBlockPropsFromElement(element, node); - return {node}; -} - -export function $createCustomQuoteNode() { - return new CustomQuoteNode(); -} - -export function $isCustomQuoteNode(node: LexicalNode | null | undefined): node is CustomQuoteNode { - return node instanceof CustomQuoteNode; -} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index 062394a9887..7b274eba13c 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -1,4 +1,3 @@ -import {HeadingNode, QuoteNode} from '@lexical/rich-text'; import {CalloutNode} from './callout'; import { ElementNode, @@ -21,9 +20,9 @@ import {MediaNode} from "./media"; import {CustomListItemNode} from "./custom-list-item"; import {CustomTableCellNode} from "./custom-table-cell"; import {CustomTableRowNode} from "./custom-table-row"; -import {CustomHeadingNode} from "./custom-heading"; -import {CustomQuoteNode} from "./custom-quote"; import {CustomListNode} from "./custom-list"; +import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; +import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; /** * Load the nodes for lexical. @@ -31,8 +30,8 @@ import {CustomListNode} from "./custom-list"; export function getNodesForPageEditor(): (KlassConstructor | LexicalNodeReplacement)[] { return [ CalloutNode, - CustomHeadingNode, - CustomQuoteNode, + HeadingNode, + QuoteNode, CustomListNode, CustomListItemNode, // TODO - Alignment? CustomTableNode, @@ -46,18 +45,6 @@ export function getNodesForPageEditor(): (KlassConstructor | MediaNode, // TODO - Alignment ParagraphNode, LinkNode, - { - replace: HeadingNode, - with: (node: HeadingNode) => { - return new CustomHeadingNode(node.__tag); - } - }, - { - replace: QuoteNode, - with: (node: QuoteNode) => { - return new CustomQuoteNode(); - } - }, { replace: ListNode, with: (node: ListNode) => { diff --git a/resources/js/wysiwyg/services/shortcuts.ts b/resources/js/wysiwyg/services/shortcuts.ts index 05bdb5dccd3..0384a3bf133 100644 --- a/resources/js/wysiwyg/services/shortcuts.ts +++ b/resources/js/wysiwyg/services/shortcuts.ts @@ -6,12 +6,12 @@ import { toggleSelectionAsHeading, toggleSelectionAsList, toggleSelectionAsParagraph } from "../utils/formats"; -import {HeadingTagType} from "@lexical/rich-text"; import {EditorUiContext} from "../ui/framework/core"; import {$getNodeFromSelection} from "../utils/selection"; import {$isLinkNode, LinkNode} from "@lexical/link"; import {$showLinkForm} from "../ui/defaults/forms/objects"; import {showLinkSelector} from "../utils/links"; +import {HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode"; function headerHandler(editor: LexicalEditor, tag: HeadingTagType): boolean { toggleSelectionAsHeading(editor, tag); diff --git a/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts b/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts index f86e33c311e..e0d1e7077fa 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts @@ -2,18 +2,14 @@ import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../ import {EditorButtonDefinition} from "../../framework/buttons"; import {EditorUiContext} from "../../framework/core"; import {$isParagraphNode, BaseSelection, LexicalNode} from "lexical"; -import { - $isHeadingNode, - $isQuoteNode, - HeadingNode, - HeadingTagType -} from "@lexical/rich-text"; import {$selectionContainsNodeType, $toggleSelectionBlockNodeType} from "../../../utils/selection"; import { toggleSelectionAsBlockquote, toggleSelectionAsHeading, toggleSelectionAsParagraph } from "../../../utils/formats"; +import {$isHeadingNode, HeadingNode, HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode"; +import {$isQuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition { return { diff --git a/resources/js/wysiwyg/ui/framework/blocks/link-field.ts b/resources/js/wysiwyg/ui/framework/blocks/link-field.ts index 5a64cdc30fc..f88b22c3f05 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/link-field.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/link-field.ts @@ -1,14 +1,13 @@ import {EditorContainerUiElement} from "../core"; import {el} from "../../../utils/dom"; import {EditorFormField} from "../forms"; -import {CustomHeadingNode} from "../../../nodes/custom-heading"; import {$getAllNodesOfType} from "../../../utils/nodes"; -import {$isHeadingNode} from "@lexical/rich-text"; import {uniqueIdSmall} from "../../../../services/util"; +import {$isHeadingNode, HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; export class LinkField extends EditorContainerUiElement { protected input: EditorFormField; - protected headerMap = new Map(); + protected headerMap = new Map(); constructor(input: EditorFormField) { super([input]); @@ -43,7 +42,7 @@ export class LinkField extends EditorContainerUiElement { return container; } - updateFormFromHeader(header: CustomHeadingNode) { + updateFormFromHeader(header: HeadingNode) { this.getHeaderIdAndText(header).then(({id, text}) => { console.log('updating form', id, text); const modal = this.getContext().manager.getActiveModal('link'); @@ -57,7 +56,7 @@ export class LinkField extends EditorContainerUiElement { }); } - getHeaderIdAndText(header: CustomHeadingNode): Promise<{id: string, text: string}> { + getHeaderIdAndText(header: HeadingNode): Promise<{id: string, text: string}> { return new Promise((res) => { this.getContext().editor.update(() => { let id = header.getId(); @@ -75,7 +74,7 @@ export class LinkField extends EditorContainerUiElement { updateDataList(listEl: HTMLElement) { this.getContext().editor.getEditorState().read(() => { - const headers = $getAllNodesOfType($isHeadingNode) as CustomHeadingNode[]; + const headers = $getAllNodesOfType($isHeadingNode) as HeadingNode[]; this.headerMap.clear(); const listEls: HTMLElement[] = []; diff --git a/resources/js/wysiwyg/utils/formats.ts b/resources/js/wysiwyg/utils/formats.ts index 3cfc964423f..d724730e3a2 100644 --- a/resources/js/wysiwyg/utils/formats.ts +++ b/resources/js/wysiwyg/utils/formats.ts @@ -1,4 +1,3 @@ -import {$isQuoteNode, HeadingNode, HeadingTagType} from "@lexical/rich-text"; import { $createParagraphNode, $createTextNode, @@ -15,23 +14,23 @@ import { $toggleSelectionBlockNodeType, getLastSelection } from "./selection"; -import {$createCustomHeadingNode, $isCustomHeadingNode} from "../nodes/custom-heading"; -import {$createCustomQuoteNode} from "../nodes/custom-quote"; import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../nodes/code-block"; import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "../nodes/callout"; import {insertList, ListNode, ListType, removeList} from "@lexical/list"; import {$isCustomListNode} from "../nodes/custom-list"; import {$createLinkNode, $isLinkNode} from "@lexical/link"; +import {$createHeadingNode, $isHeadingNode, HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode"; +import {$createQuoteNode, $isQuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; const $isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => { - return $isCustomHeadingNode(node) && (node as HeadingNode).getTag() === tag; + return $isHeadingNode(node) && node.getTag() === tag; }; export function toggleSelectionAsHeading(editor: LexicalEditor, tag: HeadingTagType) { editor.update(() => { $toggleSelectionBlockNodeType( (node) => $isHeaderNodeOfTag(node, tag), - () => $createCustomHeadingNode(tag), + () => $createHeadingNode(tag), ) }); } @@ -44,7 +43,7 @@ export function toggleSelectionAsParagraph(editor: LexicalEditor) { export function toggleSelectionAsBlockquote(editor: LexicalEditor) { editor.update(() => { - $toggleSelectionBlockNodeType($isQuoteNode, $createCustomQuoteNode); + $toggleSelectionBlockNodeType($isQuoteNode, $createQuoteNode); }); } From ebd4604f21060cd5414fda5d2d79ed41e79e9b62 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 3 Dec 2024 19:03:52 +0000 Subject: [PATCH 3/6] Lexical: Merged list nodes --- .../lexical/list/LexicalListItemNode.ts | 126 ++++++---------- .../wysiwyg/lexical/list/LexicalListNode.ts | 66 ++++++--- .../unit/LexicalListItemNode.test.ts | 94 ------------ .../js/wysiwyg/nodes/custom-list-item.ts | 120 --------------- resources/js/wysiwyg/nodes/custom-list.ts | 139 ------------------ resources/js/wysiwyg/nodes/index.ts | 18 +-- .../js/wysiwyg/services/keyboard-handling.ts | 4 +- .../ui/framework/helpers/task-list-handler.ts | 4 +- resources/js/wysiwyg/utils/formats.ts | 5 +- resources/js/wysiwyg/utils/lists.ts | 27 ++-- 10 files changed, 111 insertions(+), 492 deletions(-) delete mode 100644 resources/js/wysiwyg/nodes/custom-list-item.ts delete mode 100644 resources/js/wysiwyg/nodes/custom-list.ts diff --git a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts index c20329e4be9..33b021298a6 100644 --- a/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts +++ b/resources/js/wysiwyg/lexical/list/LexicalListItemNode.ts @@ -13,7 +13,6 @@ import type { DOMConversionOutput, DOMExportOutput, EditorConfig, - EditorThemeClasses, LexicalNode, NodeKey, ParagraphNode, @@ -22,10 +21,6 @@ import type { Spread, } from 'lexical'; -import { - addClassNamesToElement, - removeClassNamesFromElement, -} from '@lexical/utils'; import { $applyNodeReplacement, $createParagraphNode, @@ -36,11 +31,11 @@ import { LexicalEditor, } from 'lexical'; import invariant from 'lexical/shared/invariant'; -import normalizeClassNames from 'lexical/shared/normalizeClassNames'; import {$createListNode, $isListNode} from './'; -import {$handleIndent, $handleOutdent, mergeLists} from './formatList'; +import {mergeLists} from './formatList'; import {isNestedListNode} from './utils'; +import {el} from "../../utils/dom"; export type SerializedListItemNode = Spread< { @@ -74,11 +69,17 @@ export class ListItemNode extends ElementNode { createDOM(config: EditorConfig): HTMLElement { const element = document.createElement('li'); const parent = this.getParent(); + if ($isListNode(parent) && parent.getListType() === 'check') { - updateListItemChecked(element, this, null, parent); + updateListItemChecked(element, this); } + element.value = this.__value; - $setListItemThemeClassNames(element, config.theme, this); + + if ($hasNestedListWithoutLabel(this)) { + element.style.listStyle = 'none'; + } + return element; } @@ -89,11 +90,12 @@ export class ListItemNode extends ElementNode { ): boolean { const parent = this.getParent(); if ($isListNode(parent) && parent.getListType() === 'check') { - updateListItemChecked(dom, this, prevNode, parent); + updateListItemChecked(dom, this); } + + dom.style.listStyle = $hasNestedListWithoutLabel(this) ? 'none' : ''; // @ts-expect-error - this is always HTMLListItemElement dom.value = this.__value; - $setListItemThemeClassNames(dom, config.theme, this); return false; } @@ -132,6 +134,20 @@ export class ListItemNode extends ElementNode { exportDOM(editor: LexicalEditor): DOMExportOutput { const element = this.createDOM(editor._config); + + if (element.classList.contains('task-list-item')) { + const input = el('input', { + type: 'checkbox', + disabled: 'disabled', + }); + if (element.hasAttribute('checked')) { + input.setAttribute('checked', 'checked'); + element.removeAttribute('checked'); + } + + element.prepend(input); + } + return { element, }; @@ -390,89 +406,33 @@ export class ListItemNode extends ElementNode { } } -function $setListItemThemeClassNames( - dom: HTMLElement, - editorThemeClasses: EditorThemeClasses, - node: ListItemNode, -): void { - const classesToAdd = []; - const classesToRemove = []; - const listTheme = editorThemeClasses.list; - const listItemClassName = listTheme ? listTheme.listitem : undefined; - let nestedListItemClassName; - - if (listTheme && listTheme.nested) { - nestedListItemClassName = listTheme.nested.listitem; - } - - if (listItemClassName !== undefined) { - classesToAdd.push(...normalizeClassNames(listItemClassName)); - } - - if (listTheme) { - const parentNode = node.getParent(); - const isCheckList = - $isListNode(parentNode) && parentNode.getListType() === 'check'; - const checked = node.getChecked(); - - if (!isCheckList || checked) { - classesToRemove.push(listTheme.listitemUnchecked); - } - - if (!isCheckList || !checked) { - classesToRemove.push(listTheme.listitemChecked); - } +function $hasNestedListWithoutLabel(node: ListItemNode): boolean { + const children = node.getChildren(); + let hasLabel = false; + let hasNestedList = false; - if (isCheckList) { - classesToAdd.push( - checked ? listTheme.listitemChecked : listTheme.listitemUnchecked, - ); + for (const child of children) { + if ($isListNode(child)) { + hasNestedList = true; + } else if (child.getTextContent().trim().length > 0) { + hasLabel = true; } } - if (nestedListItemClassName !== undefined) { - const nestedListItemClasses = normalizeClassNames(nestedListItemClassName); - - if (node.getChildren().some((child) => $isListNode(child))) { - classesToAdd.push(...nestedListItemClasses); - } else { - classesToRemove.push(...nestedListItemClasses); - } - } - - if (classesToRemove.length > 0) { - removeClassNamesFromElement(dom, ...classesToRemove); - } - - if (classesToAdd.length > 0) { - addClassNamesToElement(dom, ...classesToAdd); - } + return hasNestedList && !hasLabel; } function updateListItemChecked( dom: HTMLElement, listItemNode: ListItemNode, - prevListItemNode: ListItemNode | null, - listNode: ListNode, ): void { - // Only add attributes for leaf list items - if ($isListNode(listItemNode.getFirstChild())) { - dom.removeAttribute('role'); - dom.removeAttribute('tabIndex'); - dom.removeAttribute('aria-checked'); + // Only set task list attrs for leaf list items + const shouldBeTaskItem = !$isListNode(listItemNode.getFirstChild()); + dom.classList.toggle('task-list-item', shouldBeTaskItem); + if (listItemNode.__checked) { + dom.setAttribute('checked', 'checked'); } else { - dom.setAttribute('role', 'checkbox'); - dom.setAttribute('tabIndex', '-1'); - - if ( - !prevListItemNode || - listItemNode.__checked !== prevListItemNode.__checked - ) { - dom.setAttribute( - 'aria-checked', - listItemNode.getChecked() ? 'true' : 'false', - ); - } + dom.removeAttribute('checked'); } } diff --git a/resources/js/wysiwyg/lexical/list/LexicalListNode.ts b/resources/js/wysiwyg/lexical/list/LexicalListNode.ts index e22fbf7717f..138c895e6b8 100644 --- a/resources/js/wysiwyg/lexical/list/LexicalListNode.ts +++ b/resources/js/wysiwyg/lexical/list/LexicalListNode.ts @@ -36,9 +36,11 @@ import { updateChildrenListItemValue, } from './formatList'; import {$getListDepth, $wrapInListItem} from './utils'; +import {extractDirectionFromElement} from "../../nodes/_common"; export type SerializedListNode = Spread< { + id: string; listType: ListType; start: number; tag: ListNodeTagType; @@ -58,15 +60,18 @@ export class ListNode extends ElementNode { __start: number; /** @internal */ __listType: ListType; + /** @internal */ + __id: string = ''; static getType(): string { return 'list'; } static clone(node: ListNode): ListNode { - const listType = node.__listType || TAG_TO_LIST_TYPE[node.__tag]; - - return new ListNode(listType, node.__start, node.__key); + const newNode = new ListNode(node.__listType, node.__start, node.__key); + newNode.__id = node.__id; + newNode.__dir = node.__dir; + return newNode; } constructor(listType: ListType, start: number, key?: NodeKey) { @@ -81,6 +86,16 @@ export class ListNode extends ElementNode { return this.__tag; } + setId(id: string) { + const self = this.getWritable(); + self.__id = id; + } + + getId(): string { + const self = this.getLatest(); + return self.__id; + } + setListType(type: ListType): void { const writable = this.getWritable(); writable.__listType = type; @@ -108,6 +123,14 @@ export class ListNode extends ElementNode { dom.__lexicalListType = this.__listType; $setListThemeClassNames(dom, config.theme, this); + if (this.__id) { + dom.setAttribute('id', this.__id); + } + + if (this.__dir) { + dom.setAttribute('dir', this.__dir); + } + return dom; } @@ -116,7 +139,11 @@ export class ListNode extends ElementNode { dom: HTMLElement, config: EditorConfig, ): boolean { - if (prevNode.__tag !== this.__tag) { + if ( + prevNode.__tag !== this.__tag + || prevNode.__dir !== this.__dir + || prevNode.__id !== this.__id + ) { return true; } @@ -148,8 +175,7 @@ export class ListNode extends ElementNode { static importJSON(serializedNode: SerializedListNode): ListNode { const node = $createListNode(serializedNode.listType, serializedNode.start); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); + node.setId(serializedNode.id); node.setDirection(serializedNode.direction); return node; } @@ -177,6 +203,7 @@ export class ListNode extends ElementNode { tag: this.getTag(), type: 'list', version: 1, + id: this.__id, }; } @@ -277,28 +304,21 @@ function $setListThemeClassNames( } /* - * This function normalizes the children of a ListNode after the conversion from HTML, - * ensuring that they are all ListItemNodes and contain either a single nested ListNode - * or some other inline content. + * This function is a custom normalization function to allow nested lists within list item elements. + * Original taken from https://github.com/facebook/lexical/blob/6e10210fd1e113ccfafdc999b1d896733c5c5bea/packages/lexical-list/src/LexicalListNode.ts#L284-L303 + * With modifications made. */ function $normalizeChildren(nodes: Array): Array { const normalizedListItems: Array = []; - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; + + for (const node of nodes) { if ($isListItemNode(node)) { normalizedListItems.push(node); - const children = node.getChildren(); - if (children.length > 1) { - children.forEach((child) => { - if ($isListNode(child)) { - normalizedListItems.push($wrapInListItem(child)); - } - }); - } } else { normalizedListItems.push($wrapInListItem(node)); } } + return normalizedListItems; } @@ -334,6 +354,14 @@ function $convertListNode(domNode: HTMLElement): DOMConversionOutput { } } + if (domNode.id && node) { + node.setId(domNode.id); + } + + if (domNode.dir && node) { + node.setDirection(extractDirectionFromElement(domNode)); + } + return { after: $normalizeChildren, node, diff --git a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts index 581db0294f5..523c7eb126e 100644 --- a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts +++ b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts @@ -1265,99 +1265,5 @@ describe('LexicalListItemNode tests', () => { expect($isListItemNode(listItemNode)).toBe(true); }); }); - - describe('ListItemNode.setIndent()', () => { - let listNode: ListNode; - let listItemNode1: ListItemNode; - let listItemNode2: ListItemNode; - - beforeEach(async () => { - const {editor} = testEnv; - - await editor.update(() => { - const root = $getRoot(); - listNode = new ListNode('bullet', 1); - listItemNode1 = new ListItemNode(); - - listItemNode2 = new ListItemNode(); - - root.append(listNode); - listNode.append(listItemNode1, listItemNode2); - listItemNode1.append(new TextNode('one')); - listItemNode2.append(new TextNode('two')); - }); - }); - it('indents and outdents list item', async () => { - const {editor} = testEnv; - - await editor.update(() => { - listItemNode1.setIndent(3); - }); - - await editor.update(() => { - expect(listItemNode1.getIndent()).toBe(3); - }); - - expectHtmlToBeEqual( - editor.getRootElement()!.innerHTML, - html` -

    -
  • -
      -
    • -
        -
      • -
          -
        • - one -
        • -
        -
      • -
      -
    • -
    -
  • -
  • - two -
  • -
- `, - ); - - await editor.update(() => { - listItemNode1.setIndent(0); - }); - - await editor.update(() => { - expect(listItemNode1.getIndent()).toBe(0); - }); - - expectHtmlToBeEqual( - editor.getRootElement()!.innerHTML, - html` -
    -
  • - one -
  • -
  • - two -
  • -
- `, - ); - }); - - it('handles fractional indent values', async () => { - const {editor} = testEnv; - - await editor.update(() => { - listItemNode1.setIndent(0.5); - }); - - await editor.update(() => { - expect(listItemNode1.getIndent()).toBe(0); - }); - }); - }); }); }); diff --git a/resources/js/wysiwyg/nodes/custom-list-item.ts b/resources/js/wysiwyg/nodes/custom-list-item.ts deleted file mode 100644 index 11887b4364d..00000000000 --- a/resources/js/wysiwyg/nodes/custom-list-item.ts +++ /dev/null @@ -1,120 +0,0 @@ -import {$isListNode, ListItemNode, SerializedListItemNode} from "@lexical/list"; -import {EditorConfig} from "lexical/LexicalEditor"; -import {DOMExportOutput, LexicalEditor, LexicalNode} from "lexical"; - -import {el} from "../utils/dom"; -import {$isCustomListNode} from "./custom-list"; - -function updateListItemChecked( - dom: HTMLElement, - listItemNode: ListItemNode, -): void { - // Only set task list attrs for leaf list items - const shouldBeTaskItem = !$isListNode(listItemNode.getFirstChild()); - dom.classList.toggle('task-list-item', shouldBeTaskItem); - if (listItemNode.__checked) { - dom.setAttribute('checked', 'checked'); - } else { - dom.removeAttribute('checked'); - } -} - - -export class CustomListItemNode extends ListItemNode { - static getType(): string { - return 'custom-list-item'; - } - - static clone(node: CustomListItemNode): CustomListItemNode { - return new CustomListItemNode(node.__value, node.__checked, node.__key); - } - - createDOM(config: EditorConfig): HTMLElement { - const element = document.createElement('li'); - const parent = this.getParent(); - - if ($isListNode(parent) && parent.getListType() === 'check') { - updateListItemChecked(element, this); - } - - element.value = this.__value; - - if ($hasNestedListWithoutLabel(this)) { - element.style.listStyle = 'none'; - } - - return element; - } - - updateDOM( - prevNode: ListItemNode, - dom: HTMLElement, - config: EditorConfig, - ): boolean { - const parent = this.getParent(); - if ($isListNode(parent) && parent.getListType() === 'check') { - updateListItemChecked(dom, this); - } - - dom.style.listStyle = $hasNestedListWithoutLabel(this) ? 'none' : ''; - // @ts-expect-error - this is always HTMLListItemElement - dom.value = this.__value; - - return false; - } - - exportDOM(editor: LexicalEditor): DOMExportOutput { - const element = this.createDOM(editor._config); - element.style.textAlign = this.getFormatType(); - - if (element.classList.contains('task-list-item')) { - const input = el('input', { - type: 'checkbox', - disabled: 'disabled', - }); - if (element.hasAttribute('checked')) { - input.setAttribute('checked', 'checked'); - element.removeAttribute('checked'); - } - - element.prepend(input); - } - - return { - element, - }; - } - - exportJSON(): SerializedListItemNode { - return { - ...super.exportJSON(), - type: 'custom-list-item', - }; - } -} - -function $hasNestedListWithoutLabel(node: CustomListItemNode): boolean { - const children = node.getChildren(); - let hasLabel = false; - let hasNestedList = false; - - for (const child of children) { - if ($isCustomListNode(child)) { - hasNestedList = true; - } else if (child.getTextContent().trim().length > 0) { - hasLabel = true; - } - } - - return hasNestedList && !hasLabel; -} - -export function $isCustomListItemNode( - node: LexicalNode | null | undefined, -): node is CustomListItemNode { - return node instanceof CustomListItemNode; -} - -export function $createCustomListItemNode(): CustomListItemNode { - return new CustomListItemNode(); -} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/custom-list.ts b/resources/js/wysiwyg/nodes/custom-list.ts deleted file mode 100644 index 4b05fa62e25..00000000000 --- a/resources/js/wysiwyg/nodes/custom-list.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { - DOMConversionFn, - DOMConversionMap, EditorConfig, - LexicalNode, - Spread -} from "lexical"; -import {$isListItemNode, ListItemNode, ListNode, ListType, SerializedListNode} from "@lexical/list"; -import {$createCustomListItemNode} from "./custom-list-item"; -import {extractDirectionFromElement} from "./_common"; - - -export type SerializedCustomListNode = Spread<{ - id: string; -}, SerializedListNode> - -export class CustomListNode extends ListNode { - __id: string = ''; - - static getType() { - return 'custom-list'; - } - - setId(id: string) { - const self = this.getWritable(); - self.__id = id; - } - - getId(): string { - const self = this.getLatest(); - return self.__id; - } - - static clone(node: CustomListNode) { - const newNode = new CustomListNode(node.__listType, node.__start, node.__key); - newNode.__id = node.__id; - newNode.__dir = node.__dir; - return newNode; - } - - createDOM(config: EditorConfig): HTMLElement { - const dom = super.createDOM(config); - if (this.__id) { - dom.setAttribute('id', this.__id); - } - - if (this.__dir) { - dom.setAttribute('dir', this.__dir); - } - - return dom; - } - - updateDOM(prevNode: ListNode, dom: HTMLElement, config: EditorConfig): boolean { - return super.updateDOM(prevNode, dom, config) || - prevNode.__dir !== this.__dir; - } - - exportJSON(): SerializedCustomListNode { - return { - ...super.exportJSON(), - type: 'custom-list', - version: 1, - id: this.__id, - }; - } - - static importJSON(serializedNode: SerializedCustomListNode): CustomListNode { - const node = $createCustomListNode(serializedNode.listType); - node.setId(serializedNode.id); - node.setDirection(serializedNode.direction); - return node; - } - - static importDOM(): DOMConversionMap | null { - // @ts-ignore - const converter = super.importDOM().ol().conversion as DOMConversionFn; - const customConvertFunction = (element: HTMLElement) => { - const baseResult = converter(element); - if (element.id && baseResult?.node) { - (baseResult.node as CustomListNode).setId(element.id); - } - - if (element.dir && baseResult?.node) { - (baseResult.node as CustomListNode).setDirection(extractDirectionFromElement(element)); - } - - if (baseResult) { - baseResult.after = $normalizeChildren; - } - - return baseResult; - }; - - return { - ol: () => ({ - conversion: customConvertFunction, - priority: 0, - }), - ul: () => ({ - conversion: customConvertFunction, - priority: 0, - }), - }; - } -} - -/* - * This function is a custom normalization function to allow nested lists within list item elements. - * Original taken from https://github.com/facebook/lexical/blob/6e10210fd1e113ccfafdc999b1d896733c5c5bea/packages/lexical-list/src/LexicalListNode.ts#L284-L303 - * With modifications made. - * Copyright (c) Meta Platforms, Inc. and affiliates. - * MIT license - */ -function $normalizeChildren(nodes: Array): Array { - const normalizedListItems: Array = []; - - for (const node of nodes) { - if ($isListItemNode(node)) { - normalizedListItems.push(node); - } else { - normalizedListItems.push($wrapInListItem(node)); - } - } - - return normalizedListItems; -} - -function $wrapInListItem(node: LexicalNode): ListItemNode { - const listItemWrapper = $createCustomListItemNode(); - return listItemWrapper.append(node); -} - -export function $createCustomListNode(type: ListType): CustomListNode { - return new CustomListNode(type, 1); -} - -export function $isCustomListNode(node: LexicalNode | null | undefined): node is CustomListNode { - return node instanceof CustomListNode; -} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index 7b274eba13c..7e0ce9daf2b 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -17,10 +17,8 @@ import {CodeBlockNode} from "./code-block"; import {DiagramNode} from "./diagram"; import {EditorUiContext} from "../ui/framework/core"; import {MediaNode} from "./media"; -import {CustomListItemNode} from "./custom-list-item"; import {CustomTableCellNode} from "./custom-table-cell"; import {CustomTableRowNode} from "./custom-table-row"; -import {CustomListNode} from "./custom-list"; import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; @@ -32,8 +30,8 @@ export function getNodesForPageEditor(): (KlassConstructor | CalloutNode, HeadingNode, QuoteNode, - CustomListNode, - CustomListItemNode, // TODO - Alignment? + ListNode, + ListItemNode, CustomTableNode, CustomTableRowNode, CustomTableCellNode, @@ -45,18 +43,6 @@ export function getNodesForPageEditor(): (KlassConstructor | MediaNode, // TODO - Alignment ParagraphNode, LinkNode, - { - replace: ListNode, - with: (node: ListNode) => { - return new CustomListNode(node.getListType(), node.getStart()); - } - }, - { - replace: ListItemNode, - with: (node: ListItemNode) => { - return new CustomListItemNode(node.__value, node.__checked); - } - }, { replace: TableNode, with(node: TableNode) { diff --git a/resources/js/wysiwyg/services/keyboard-handling.ts b/resources/js/wysiwyg/services/keyboard-handling.ts index 3f0b0c495e0..5f7f41ef02c 100644 --- a/resources/js/wysiwyg/services/keyboard-handling.ts +++ b/resources/js/wysiwyg/services/keyboard-handling.ts @@ -14,8 +14,8 @@ import {$isImageNode} from "../nodes/image"; import {$isMediaNode} from "../nodes/media"; import {getLastSelection} from "../utils/selection"; import {$getNearestNodeBlockParent} from "../utils/nodes"; -import {$isCustomListItemNode} from "../nodes/custom-list-item"; import {$setInsetForSelection} from "../utils/lists"; +import {$isListItemNode} from "@lexical/list"; function isSingleSelectedNode(nodes: LexicalNode[]): boolean { if (nodes.length === 1) { @@ -62,7 +62,7 @@ function handleInsetOnTab(editor: LexicalEditor, event: KeyboardEvent|null): boo const change = event?.shiftKey ? -40 : 40; const selection = $getSelection(); const nodes = selection?.getNodes() || []; - if (nodes.length > 1 || (nodes.length === 1 && $isCustomListItemNode(nodes[0].getParent()))) { + if (nodes.length > 1 || (nodes.length === 1 && $isListItemNode(nodes[0].getParent()))) { editor.update(() => { $setInsetForSelection(editor, change); }); diff --git a/resources/js/wysiwyg/ui/framework/helpers/task-list-handler.ts b/resources/js/wysiwyg/ui/framework/helpers/task-list-handler.ts index da8c0eae3dd..62a784d83ed 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/task-list-handler.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/task-list-handler.ts @@ -1,5 +1,5 @@ import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical"; -import {$isCustomListItemNode} from "../../../nodes/custom-list-item"; +import {$isListItemNode} from "@lexical/list"; class TaskListHandler { protected editorContainer: HTMLElement; @@ -38,7 +38,7 @@ class TaskListHandler { this.editor.update(() => { const node = $getNearestNodeFromDOMNode(listItem); - if ($isCustomListItemNode(node)) { + if ($isListItemNode(node)) { node.setChecked(!node.getChecked()); } }); diff --git a/resources/js/wysiwyg/utils/formats.ts b/resources/js/wysiwyg/utils/formats.ts index d724730e3a2..1be802ebf1c 100644 --- a/resources/js/wysiwyg/utils/formats.ts +++ b/resources/js/wysiwyg/utils/formats.ts @@ -16,8 +16,7 @@ import { } from "./selection"; import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../nodes/code-block"; import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "../nodes/callout"; -import {insertList, ListNode, ListType, removeList} from "@lexical/list"; -import {$isCustomListNode} from "../nodes/custom-list"; +import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list"; import {$createLinkNode, $isLinkNode} from "@lexical/link"; import {$createHeadingNode, $isHeadingNode, HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode"; import {$createQuoteNode, $isQuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; @@ -51,7 +50,7 @@ export function toggleSelectionAsList(editor: LexicalEditor, type: ListType) { editor.getEditorState().read(() => { const selection = $getSelection(); const listSelected = $selectionContainsNodeType(selection, (node: LexicalNode | null | undefined): boolean => { - return $isCustomListNode(node) && (node as ListNode).getListType() === type; + return $isListNode(node) && (node as ListNode).getListType() === type; }); if (listSelected) { diff --git a/resources/js/wysiwyg/utils/lists.ts b/resources/js/wysiwyg/utils/lists.ts index 30a97cbc1f9..646f341c2ba 100644 --- a/resources/js/wysiwyg/utils/lists.ts +++ b/resources/js/wysiwyg/utils/lists.ts @@ -1,22 +1,21 @@ -import {$createCustomListItemNode, $isCustomListItemNode, CustomListItemNode} from "../nodes/custom-list-item"; -import {$createCustomListNode, $isCustomListNode} from "../nodes/custom-list"; import {$getSelection, BaseSelection, LexicalEditor} from "lexical"; import {$getBlockElementNodesInSelection, $selectNodes, $toggleSelection} from "./selection"; import {nodeHasInset} from "./nodes"; +import {$createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode} from "@lexical/list"; -export function $nestListItem(node: CustomListItemNode): CustomListItemNode { +export function $nestListItem(node: ListItemNode): ListItemNode { const list = node.getParent(); - if (!$isCustomListNode(list)) { + if (!$isListNode(list)) { return node; } - const listItems = list.getChildren() as CustomListItemNode[]; + const listItems = list.getChildren() as ListItemNode[]; const nodeIndex = listItems.findIndex((n) => n.getKey() === node.getKey()); const isFirst = nodeIndex === 0; - const newListItem = $createCustomListItemNode(); - const newList = $createCustomListNode(list.getListType()); + const newListItem = $createListItemNode(); + const newList = $createListNode(list.getListType()); newList.append(newListItem); newListItem.append(...node.getChildren()); @@ -31,11 +30,11 @@ export function $nestListItem(node: CustomListItemNode): CustomListItemNode { return newListItem; } -export function $unnestListItem(node: CustomListItemNode): CustomListItemNode { +export function $unnestListItem(node: ListItemNode): ListItemNode { const list = node.getParent(); const parentListItem = list?.getParent(); const outerList = parentListItem?.getParent(); - if (!$isCustomListNode(list) || !$isCustomListNode(outerList) || !$isCustomListItemNode(parentListItem)) { + if (!$isListNode(list) || !$isListNode(outerList) || !$isListItemNode(parentListItem)) { return node; } @@ -51,19 +50,19 @@ export function $unnestListItem(node: CustomListItemNode): CustomListItemNode { return node; } -function getListItemsForSelection(selection: BaseSelection|null): (CustomListItemNode|null)[] { +function getListItemsForSelection(selection: BaseSelection|null): (ListItemNode|null)[] { const nodes = selection?.getNodes() || []; const listItemNodes = []; outer: for (const node of nodes) { - if ($isCustomListItemNode(node)) { + if ($isListItemNode(node)) { listItemNodes.push(node); continue; } const parents = node.getParents(); for (const parent of parents) { - if ($isCustomListItemNode(parent)) { + if ($isListItemNode(parent)) { listItemNodes.push(parent); continue outer; } @@ -75,8 +74,8 @@ function getListItemsForSelection(selection: BaseSelection|null): (CustomListIte return listItemNodes; } -function $reduceDedupeListItems(listItems: (CustomListItemNode|null)[]): CustomListItemNode[] { - const listItemMap: Record = {}; +function $reduceDedupeListItems(listItems: (ListItemNode|null)[]): ListItemNode[] { + const listItemMap: Record = {}; for (const item of listItems) { if (item === null) { From 57d8449660e2cf5acd9bfaa6c951b3dfd040bf42 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 3 Dec 2024 20:08:33 +0000 Subject: [PATCH 4/6] Lexical: Merged custom table node code --- .../lexical/table/LexicalTableCellNode.ts | 134 ++++++---- .../wysiwyg/lexical/table/LexicalTableNode.ts | 98 ++++++- .../lexical/table/LexicalTableRowNode.ts | 57 ++-- .../table/LexicalTableSelectionHelpers.ts | 53 ---- .../unit/LexicalTableRowNode.test.ts | 5 +- .../js/wysiwyg/nodes/custom-table-cell.ts | 247 ------------------ .../js/wysiwyg/nodes/custom-table-row.ts | 106 -------- resources/js/wysiwyg/nodes/custom-table.ts | 166 ------------ resources/js/wysiwyg/nodes/index.ts | 33 +-- .../js/wysiwyg/ui/defaults/buttons/tables.ts | 35 ++- .../js/wysiwyg/ui/defaults/forms/tables.ts | 21 +- .../ui/framework/blocks/table-creator.ts | 3 +- .../ui/framework/helpers/table-resizer.ts | 5 +- .../helpers/table-selection-handler.ts | 8 +- .../js/wysiwyg/utils/table-copy-paste.ts | 42 +-- resources/js/wysiwyg/utils/table-map.ts | 28 +- resources/js/wysiwyg/utils/tables.ts | 70 ++--- 17 files changed, 323 insertions(+), 788 deletions(-) delete mode 100644 resources/js/wysiwyg/nodes/custom-table-cell.ts delete mode 100644 resources/js/wysiwyg/nodes/custom-table-row.ts delete mode 100644 resources/js/wysiwyg/nodes/custom-table.ts diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts index 455d39bf6c9..72676b9bacb 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts @@ -28,7 +28,8 @@ import { ElementNode, } from 'lexical'; -import {COLUMN_WIDTH, PIXEL_VALUE_REG_EXP} from './constants'; +import {extractStyleMapFromElement, StyleMap} from "../../utils/dom"; +import {CommonBlockAlignment, extractAlignmentFromElement} from "../../nodes/_common"; export const TableCellHeaderStates = { BOTH: 3, @@ -47,6 +48,8 @@ export type SerializedTableCellNode = Spread< headerState: TableCellHeaderState; width?: number; backgroundColor?: null | string; + styles: Record; + alignment: CommonBlockAlignment; }, SerializedElementNode >; @@ -63,6 +66,10 @@ export class TableCellNode extends ElementNode { __width?: number; /** @internal */ __backgroundColor: null | string; + /** @internal */ + __styles: StyleMap = new Map; + /** @internal */ + __alignment: CommonBlockAlignment = ''; static getType(): string { return 'tablecell'; @@ -77,6 +84,8 @@ export class TableCellNode extends ElementNode { ); cellNode.__rowSpan = node.__rowSpan; cellNode.__backgroundColor = node.__backgroundColor; + cellNode.__styles = new Map(node.__styles); + cellNode.__alignment = node.__alignment; return cellNode; } @@ -94,16 +103,20 @@ export class TableCellNode extends ElementNode { } static importJSON(serializedNode: SerializedTableCellNode): TableCellNode { - const colSpan = serializedNode.colSpan || 1; - const rowSpan = serializedNode.rowSpan || 1; - const cellNode = $createTableCellNode( - serializedNode.headerState, - colSpan, - serializedNode.width || undefined, + const node = $createTableCellNode( + serializedNode.headerState, + serializedNode.colSpan, + serializedNode.width, ); - cellNode.__rowSpan = rowSpan; - cellNode.__backgroundColor = serializedNode.backgroundColor || null; - return cellNode; + + if (serializedNode.rowSpan) { + node.setRowSpan(serializedNode.rowSpan); + } + + node.setStyles(new Map(Object.entries(serializedNode.styles))); + node.setAlignment(serializedNode.alignment); + + return node; } constructor( @@ -144,34 +157,19 @@ export class TableCellNode extends ElementNode { this.hasHeader() && config.theme.tableCellHeader, ); + for (const [name, value] of this.__styles.entries()) { + element.style.setProperty(name, value); + } + + if (this.__alignment) { + element.classList.add('align-' + this.__alignment); + } + return element; } exportDOM(editor: LexicalEditor): DOMExportOutput { const {element} = super.exportDOM(editor); - - if (element) { - const element_ = element as HTMLTableCellElement; - element_.style.border = '1px solid black'; - if (this.__colSpan > 1) { - element_.colSpan = this.__colSpan; - } - if (this.__rowSpan > 1) { - element_.rowSpan = this.__rowSpan; - } - element_.style.width = `${this.getWidth() || COLUMN_WIDTH}px`; - - element_.style.verticalAlign = 'top'; - element_.style.textAlign = 'start'; - - const backgroundColor = this.getBackgroundColor(); - if (backgroundColor !== null) { - element_.style.backgroundColor = backgroundColor; - } else if (this.hasHeader()) { - element_.style.backgroundColor = '#f2f3f5'; - } - } - return { element, }; @@ -186,6 +184,8 @@ export class TableCellNode extends ElementNode { rowSpan: this.__rowSpan, type: 'tablecell', width: this.getWidth(), + styles: Object.fromEntries(this.__styles), + alignment: this.__alignment, }; } @@ -231,6 +231,38 @@ export class TableCellNode extends ElementNode { return this.getLatest().__width; } + clearWidth(): void { + const self = this.getWritable(); + self.__width = undefined; + } + + getStyles(): StyleMap { + const self = this.getLatest(); + return new Map(self.__styles); + } + + setStyles(styles: StyleMap): void { + const self = this.getWritable(); + self.__styles = new Map(styles); + } + + setAlignment(alignment: CommonBlockAlignment) { + const self = this.getWritable(); + self.__alignment = alignment; + } + + getAlignment(): CommonBlockAlignment { + const self = this.getLatest(); + return self.__alignment; + } + + updateTag(tag: string): void { + const isHeader = tag.toLowerCase() === 'th'; + const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS; + const self = this.getWritable(); + self.__headerState = state; + } + getBackgroundColor(): null | string { return this.getLatest().__backgroundColor; } @@ -265,7 +297,9 @@ export class TableCellNode extends ElementNode { prevNode.__width !== this.__width || prevNode.__colSpan !== this.__colSpan || prevNode.__rowSpan !== this.__rowSpan || - prevNode.__backgroundColor !== this.__backgroundColor + prevNode.__backgroundColor !== this.__backgroundColor || + prevNode.__styles !== this.__styles || + prevNode.__alignment !== this.__alignment ); } @@ -287,38 +321,42 @@ export class TableCellNode extends ElementNode { } export function $convertTableCellNodeElement( - domNode: Node, + domNode: Node, ): DOMConversionOutput { const domNode_ = domNode as HTMLTableCellElement; const nodeName = domNode.nodeName.toLowerCase(); let width: number | undefined = undefined; + + const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/; if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) { width = parseFloat(domNode_.style.width); } const tableCellNode = $createTableCellNode( - nodeName === 'th' - ? TableCellHeaderStates.ROW - : TableCellHeaderStates.NO_STATUS, - domNode_.colSpan, - width, + nodeName === 'th' + ? TableCellHeaderStates.ROW + : TableCellHeaderStates.NO_STATUS, + domNode_.colSpan, + width, ); tableCellNode.__rowSpan = domNode_.rowSpan; - const backgroundColor = domNode_.style.backgroundColor; - if (backgroundColor !== '') { - tableCellNode.__backgroundColor = backgroundColor; - } const style = domNode_.style; const textDecoration = style.textDecoration.split(' '); const hasBoldFontWeight = - style.fontWeight === '700' || style.fontWeight === 'bold'; + style.fontWeight === '700' || style.fontWeight === 'bold'; const hasLinethroughTextDecoration = textDecoration.includes('line-through'); const hasItalicFontStyle = style.fontStyle === 'italic'; const hasUnderlineTextDecoration = textDecoration.includes('underline'); + + if (domNode instanceof HTMLElement) { + tableCellNode.setStyles(extractStyleMapFromElement(domNode)); + tableCellNode.setAlignment(extractAlignmentFromElement(domNode)); + } + return { after: (childLexicalNodes) => { if (childLexicalNodes.length === 0) { @@ -330,8 +368,8 @@ export function $convertTableCellNodeElement( if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) { const paragraphNode = $createParagraphNode(); if ( - $isLineBreakNode(lexicalNode) && - lexicalNode.getTextContent() === '\n' + $isLineBreakNode(lexicalNode) && + lexicalNode.getTextContent() === '\n' ) { return null; } @@ -360,7 +398,7 @@ export function $convertTableCellNodeElement( } export function $createTableCellNode( - headerState: TableCellHeaderState, + headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width?: number, ): TableCellNode { diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts index 357ba3e738b..ab163005370 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts @@ -7,7 +7,7 @@ */ import type {TableCellNode} from './LexicalTableCellNode'; -import type { +import { DOMConversionMap, DOMConversionOutput, DOMExportOutput, @@ -15,7 +15,7 @@ import type { LexicalEditor, LexicalNode, NodeKey, - SerializedElementNode, + SerializedElementNode, Spread, } from 'lexical'; import {addClassNamesToElement, isHTMLElement} from '@lexical/utils'; @@ -27,19 +27,36 @@ import { import {$isTableCellNode} from './LexicalTableCellNode'; import {TableDOMCell, TableDOMTable} from './LexicalTableObserver'; -import {$isTableRowNode, TableRowNode} from './LexicalTableRowNode'; import {getTable} from './LexicalTableSelectionHelpers'; - -export type SerializedTableNode = SerializedElementNode; +import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; +import { + commonPropertiesDifferent, deserializeCommonBlockNode, + SerializedCommonBlockNode, setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "../../nodes/_common"; +import {el, extractStyleMapFromElement, StyleMap} from "../../utils/dom"; +import {getTableColumnWidths} from "../../utils/tables"; + +export type SerializedTableNode = Spread<{ + colWidths: string[]; + styles: Record, +}, SerializedCommonBlockNode> /** @noInheritDoc */ -export class TableNode extends ElementNode { +export class TableNode extends CommonBlockNode { + __colWidths: string[] = []; + __styles: StyleMap = new Map; + static getType(): string { return 'table'; } static clone(node: TableNode): TableNode { - return new TableNode(node.__key); + const newNode = new TableNode(node.__key); + copyCommonBlockProperties(node, newNode); + newNode.__colWidths = node.__colWidths; + newNode.__styles = new Map(node.__styles); + return newNode; } static importDOM(): DOMConversionMap | null { @@ -52,18 +69,24 @@ export class TableNode extends ElementNode { } static importJSON(_serializedNode: SerializedTableNode): TableNode { - return $createTableNode(); + const node = $createTableNode(); + deserializeCommonBlockNode(_serializedNode, node); + node.setColWidths(_serializedNode.colWidths); + node.setStyles(new Map(Object.entries(_serializedNode.styles))); + return node; } constructor(key?: NodeKey) { super(key); } - exportJSON(): SerializedElementNode { + exportJSON(): SerializedTableNode { return { ...super.exportJSON(), type: 'table', version: 1, + colWidths: this.__colWidths, + styles: Object.fromEntries(this.__styles), }; } @@ -72,11 +95,33 @@ export class TableNode extends ElementNode { addClassNamesToElement(tableElement, config.theme.table); + updateElementWithCommonBlockProps(tableElement, this); + + const colWidths = this.getColWidths(); + if (colWidths.length > 0) { + const colgroup = el('colgroup'); + for (const width of colWidths) { + const col = el('col'); + if (width) { + col.style.width = width; + } + colgroup.append(col); + } + tableElement.append(colgroup); + } + + for (const [name, value] of this.__styles.entries()) { + tableElement.style.setProperty(name, value); + } + return tableElement; } - updateDOM(): boolean { - return false; + updateDOM(_prevNode: TableNode): boolean { + return commonPropertiesDifferent(_prevNode, this) + || this.__colWidths.join(':') !== _prevNode.__colWidths.join(':') + || this.__styles.size !== _prevNode.__styles.size + || (Array.from(this.__styles.values()).join(':') !== (Array.from(_prevNode.__styles.values()).join(':'))); } exportDOM(editor: LexicalEditor): DOMExportOutput { @@ -115,6 +160,26 @@ export class TableNode extends ElementNode { return true; } + setColWidths(widths: string[]) { + const self = this.getWritable(); + self.__colWidths = widths; + } + + getColWidths(): string[] { + const self = this.getLatest(); + return self.__colWidths; + } + + getStyles(): StyleMap { + const self = this.getLatest(); + return new Map(self.__styles); + } + + setStyles(styles: StyleMap): void { + const self = this.getWritable(); + self.__styles = new Map(styles); + } + getCordsFromCellNode( tableCellNode: TableCellNode, table: TableDOMTable, @@ -239,8 +304,15 @@ export function $getElementForTableNode( return getTable(tableElement); } -export function $convertTableElement(_domNode: Node): DOMConversionOutput { - return {node: $createTableNode()}; +export function $convertTableElement(element: HTMLElement): DOMConversionOutput { + const node = $createTableNode(); + setCommonBlockPropsFromElement(element, node); + + const colWidths = getTableColumnWidths(element as HTMLTableElement); + node.setColWidths(colWidths); + node.setStyles(extractStyleMapFromElement(element)); + + return {node}; } export function $createTableNode(): TableNode { diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableRowNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableRowNode.ts index eddea69a27e..07db2b65dc0 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableRowNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableRowNode.ts @@ -20,11 +20,12 @@ import { SerializedElementNode, } from 'lexical'; -import {PIXEL_VALUE_REG_EXP} from './constants'; +import {extractStyleMapFromElement, sizeToPixels, StyleMap} from "../../utils/dom"; export type SerializedTableRowNode = Spread< { - height?: number; + styles: Record, + height?: number, }, SerializedElementNode >; @@ -33,13 +34,17 @@ export type SerializedTableRowNode = Spread< export class TableRowNode extends ElementNode { /** @internal */ __height?: number; + /** @internal */ + __styles: StyleMap = new Map(); static getType(): string { return 'tablerow'; } static clone(node: TableRowNode): TableRowNode { - return new TableRowNode(node.__height, node.__key); + const newNode = new TableRowNode(node.__key); + newNode.__styles = new Map(node.__styles); + return newNode; } static importDOM(): DOMConversionMap | null { @@ -52,20 +57,24 @@ export class TableRowNode extends ElementNode { } static importJSON(serializedNode: SerializedTableRowNode): TableRowNode { - return $createTableRowNode(serializedNode.height); + const node = $createTableRowNode(); + + node.setStyles(new Map(Object.entries(serializedNode.styles))); + + return node; } - constructor(height?: number, key?: NodeKey) { + constructor(key?: NodeKey) { super(key); - this.__height = height; } exportJSON(): SerializedTableRowNode { return { ...super.exportJSON(), - ...(this.getHeight() && {height: this.getHeight()}), type: 'tablerow', version: 1, + styles: Object.fromEntries(this.__styles), + height: this.__height || 0, }; } @@ -76,6 +85,10 @@ export class TableRowNode extends ElementNode { element.style.height = `${this.__height}px`; } + for (const [name, value] of this.__styles.entries()) { + element.style.setProperty(name, value); + } + addClassNamesToElement(element, config.theme.tableRow); return element; @@ -85,6 +98,16 @@ export class TableRowNode extends ElementNode { return true; } + getStyles(): StyleMap { + const self = this.getLatest(); + return new Map(self.__styles); + } + + setStyles(styles: StyleMap): void { + const self = this.getWritable(); + self.__styles = new Map(styles); + } + setHeight(height: number): number | null | undefined { const self = this.getWritable(); self.__height = height; @@ -96,7 +119,8 @@ export class TableRowNode extends ElementNode { } updateDOM(prevNode: TableRowNode): boolean { - return prevNode.__height !== this.__height; + return prevNode.__height !== this.__height + || prevNode.__styles !== this.__styles; } canBeEmpty(): false { @@ -109,18 +133,21 @@ export class TableRowNode extends ElementNode { } export function $convertTableRowElement(domNode: Node): DOMConversionOutput { - const domNode_ = domNode as HTMLTableCellElement; - let height: number | undefined = undefined; + const rowNode = $createTableRowNode(); + const domNode_ = domNode as HTMLElement; + + const height = sizeToPixels(domNode_.style.height); + rowNode.setHeight(height); - if (PIXEL_VALUE_REG_EXP.test(domNode_.style.height)) { - height = parseFloat(domNode_.style.height); + if (domNode instanceof HTMLElement) { + rowNode.setStyles(extractStyleMapFromElement(domNode)); } - return {node: $createTableRowNode(height)}; + return {node: rowNode}; } -export function $createTableRowNode(height?: number): TableRowNode { - return $applyNodeReplacement(new TableRowNode(height)); +export function $createTableRowNode(): TableRowNode { + return $applyNodeReplacement(new TableRowNode()); } export function $isTableRowNode( diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts b/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts index 812cccc0d25..6c3317c5dfa 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts @@ -438,59 +438,6 @@ export function applyTableHandlers( ), ); - tableObserver.listenersToRemove.add( - editor.registerCommand( - FORMAT_ELEMENT_COMMAND, - (formatType) => { - const selection = $getSelection(); - if ( - !$isTableSelection(selection) || - !$isSelectionInTable(selection, tableNode) - ) { - return false; - } - - const anchorNode = selection.anchor.getNode(); - const focusNode = selection.focus.getNode(); - if (!$isTableCellNode(anchorNode) || !$isTableCellNode(focusNode)) { - return false; - } - - const [tableMap, anchorCell, focusCell] = $computeTableMap( - tableNode, - anchorNode, - focusNode, - ); - const maxRow = Math.max(anchorCell.startRow, focusCell.startRow); - const maxColumn = Math.max( - anchorCell.startColumn, - focusCell.startColumn, - ); - const minRow = Math.min(anchorCell.startRow, focusCell.startRow); - const minColumn = Math.min( - anchorCell.startColumn, - focusCell.startColumn, - ); - for (let i = minRow; i <= maxRow; i++) { - for (let j = minColumn; j <= maxColumn; j++) { - const cell = tableMap[i][j].cell; - cell.setFormat(formatType); - - const cellChildren = cell.getChildren(); - for (let k = 0; k < cellChildren.length; k++) { - const child = cellChildren[k]; - if ($isElementNode(child) && !child.isInline()) { - child.setFormat(formatType); - } - } - } - } - return true; - }, - COMMAND_PRIORITY_CRITICAL, - ), - ); - tableObserver.listenersToRemove.add( editor.registerCommand( CONTROLLED_TEXT_INSERTION_COMMAND, diff --git a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableRowNode.test.ts b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableRowNode.test.ts index 285d587bf5f..5dbf03d9e9c 100644 --- a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableRowNode.test.ts +++ b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableRowNode.test.ts @@ -39,10 +39,9 @@ describe('LexicalTableRowNode tests', () => { ``, ); - const rowHeight = 36; - const rowWithCustomHeightNode = $createTableRowNode(36); + const rowWithCustomHeightNode = $createTableRowNode(); expect(rowWithCustomHeightNode.createDOM(editorConfig).outerHTML).toBe( - ``, + ``, ); }); }); diff --git a/resources/js/wysiwyg/nodes/custom-table-cell.ts b/resources/js/wysiwyg/nodes/custom-table-cell.ts deleted file mode 100644 index 793302cfec4..00000000000 --- a/resources/js/wysiwyg/nodes/custom-table-cell.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { - $createParagraphNode, - $isElementNode, - $isLineBreakNode, - $isTextNode, - DOMConversionMap, - DOMConversionOutput, - DOMExportOutput, - EditorConfig, - LexicalEditor, - LexicalNode, - Spread -} from "lexical"; - -import { - $createTableCellNode, - $isTableCellNode, - SerializedTableCellNode, - TableCellHeaderStates, - TableCellNode -} from "@lexical/table"; -import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode"; -import {extractStyleMapFromElement, StyleMap} from "../utils/dom"; -import {CommonBlockAlignment, extractAlignmentFromElement} from "./_common"; - -export type SerializedCustomTableCellNode = Spread<{ - styles: Record; - alignment: CommonBlockAlignment; -}, SerializedTableCellNode> - -export class CustomTableCellNode extends TableCellNode { - __styles: StyleMap = new Map; - __alignment: CommonBlockAlignment = ''; - - static getType(): string { - return 'custom-table-cell'; - } - - static clone(node: CustomTableCellNode): CustomTableCellNode { - const cellNode = new CustomTableCellNode( - node.__headerState, - node.__colSpan, - node.__width, - node.__key, - ); - cellNode.__rowSpan = node.__rowSpan; - cellNode.__styles = new Map(node.__styles); - cellNode.__alignment = node.__alignment; - return cellNode; - } - - clearWidth(): void { - const self = this.getWritable(); - self.__width = undefined; - } - - getStyles(): StyleMap { - const self = this.getLatest(); - return new Map(self.__styles); - } - - setStyles(styles: StyleMap): void { - const self = this.getWritable(); - self.__styles = new Map(styles); - } - - setAlignment(alignment: CommonBlockAlignment) { - const self = this.getWritable(); - self.__alignment = alignment; - } - - getAlignment(): CommonBlockAlignment { - const self = this.getLatest(); - return self.__alignment; - } - - updateTag(tag: string): void { - const isHeader = tag.toLowerCase() === 'th'; - const state = isHeader ? TableCellHeaderStates.ROW : TableCellHeaderStates.NO_STATUS; - const self = this.getWritable(); - self.__headerState = state; - } - - createDOM(config: EditorConfig): HTMLElement { - const element = super.createDOM(config); - - for (const [name, value] of this.__styles.entries()) { - element.style.setProperty(name, value); - } - - if (this.__alignment) { - element.classList.add('align-' + this.__alignment); - } - - return element; - } - - updateDOM(prevNode: CustomTableCellNode): boolean { - return super.updateDOM(prevNode) - || this.__styles !== prevNode.__styles - || this.__alignment !== prevNode.__alignment; - } - - static importDOM(): DOMConversionMap | null { - return { - td: (node: Node) => ({ - conversion: $convertCustomTableCellNodeElement, - priority: 0, - }), - th: (node: Node) => ({ - conversion: $convertCustomTableCellNodeElement, - priority: 0, - }), - }; - } - - exportDOM(editor: LexicalEditor): DOMExportOutput { - const element = this.createDOM(editor._config); - return { - element - }; - } - - static importJSON(serializedNode: SerializedCustomTableCellNode): CustomTableCellNode { - const node = $createCustomTableCellNode( - serializedNode.headerState, - serializedNode.colSpan, - serializedNode.width, - ); - - node.setStyles(new Map(Object.entries(serializedNode.styles))); - node.setAlignment(serializedNode.alignment); - - return node; - } - - exportJSON(): SerializedCustomTableCellNode { - return { - ...super.exportJSON(), - type: 'custom-table-cell', - styles: Object.fromEntries(this.__styles), - alignment: this.__alignment, - }; - } -} - -function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput { - const output = $convertTableCellNodeElement(domNode); - - if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) { - output.node.setStyles(extractStyleMapFromElement(domNode)); - output.node.setAlignment(extractAlignmentFromElement(domNode)); - } - - return output; -} - -/** - * Function taken from: - * https://github.com/facebook/lexical/blob/e1881a6e409e1541c10dd0b5378f3a38c9dc8c9e/packages/lexical-table/src/LexicalTableCellNode.ts#L289 - * Copyright (c) Meta Platforms, Inc. and affiliates. - * MIT LICENSE - * Modified since copy. - */ -export function $convertTableCellNodeElement( - domNode: Node, -): DOMConversionOutput { - const domNode_ = domNode as HTMLTableCellElement; - const nodeName = domNode.nodeName.toLowerCase(); - - let width: number | undefined = undefined; - - - const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/; - if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) { - width = parseFloat(domNode_.style.width); - } - - const tableCellNode = $createTableCellNode( - nodeName === 'th' - ? TableCellHeaderStates.ROW - : TableCellHeaderStates.NO_STATUS, - domNode_.colSpan, - width, - ); - - tableCellNode.__rowSpan = domNode_.rowSpan; - - const style = domNode_.style; - const textDecoration = style.textDecoration.split(' '); - const hasBoldFontWeight = - style.fontWeight === '700' || style.fontWeight === 'bold'; - const hasLinethroughTextDecoration = textDecoration.includes('line-through'); - const hasItalicFontStyle = style.fontStyle === 'italic'; - const hasUnderlineTextDecoration = textDecoration.includes('underline'); - return { - after: (childLexicalNodes) => { - if (childLexicalNodes.length === 0) { - childLexicalNodes.push($createParagraphNode()); - } - return childLexicalNodes; - }, - forChild: (lexicalNode, parentLexicalNode) => { - if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) { - const paragraphNode = $createParagraphNode(); - if ( - $isLineBreakNode(lexicalNode) && - lexicalNode.getTextContent() === '\n' - ) { - return null; - } - if ($isTextNode(lexicalNode)) { - if (hasBoldFontWeight) { - lexicalNode.toggleFormat('bold'); - } - if (hasLinethroughTextDecoration) { - lexicalNode.toggleFormat('strikethrough'); - } - if (hasItalicFontStyle) { - lexicalNode.toggleFormat('italic'); - } - if (hasUnderlineTextDecoration) { - lexicalNode.toggleFormat('underline'); - } - } - paragraphNode.append(lexicalNode); - return paragraphNode; - } - - return lexicalNode; - }, - node: tableCellNode, - }; -} - - -export function $createCustomTableCellNode( - headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS, - colSpan = 1, - width?: number, -): CustomTableCellNode { - return new CustomTableCellNode(headerState, colSpan, width); -} - -export function $isCustomTableCellNode(node: LexicalNode | null | undefined): node is CustomTableCellNode { - return node instanceof CustomTableCellNode; -} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/custom-table-row.ts b/resources/js/wysiwyg/nodes/custom-table-row.ts deleted file mode 100644 index f4702f36dd5..00000000000 --- a/resources/js/wysiwyg/nodes/custom-table-row.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { - DOMConversionMap, - DOMConversionOutput, - EditorConfig, - LexicalNode, - Spread -} from "lexical"; - -import { - SerializedTableRowNode, - TableRowNode -} from "@lexical/table"; -import {NodeKey} from "lexical/LexicalNode"; -import {extractStyleMapFromElement, StyleMap} from "../utils/dom"; - -export type SerializedCustomTableRowNode = Spread<{ - styles: Record, -}, SerializedTableRowNode> - -export class CustomTableRowNode extends TableRowNode { - __styles: StyleMap = new Map(); - - constructor(key?: NodeKey) { - super(0, key); - } - - static getType(): string { - return 'custom-table-row'; - } - - static clone(node: CustomTableRowNode): CustomTableRowNode { - const cellNode = new CustomTableRowNode(node.__key); - - cellNode.__styles = new Map(node.__styles); - return cellNode; - } - - getStyles(): StyleMap { - const self = this.getLatest(); - return new Map(self.__styles); - } - - setStyles(styles: StyleMap): void { - const self = this.getWritable(); - self.__styles = new Map(styles); - } - - createDOM(config: EditorConfig): HTMLElement { - const element = super.createDOM(config); - - for (const [name, value] of this.__styles.entries()) { - element.style.setProperty(name, value); - } - - return element; - } - - updateDOM(prevNode: CustomTableRowNode): boolean { - return super.updateDOM(prevNode) - || this.__styles !== prevNode.__styles; - } - - static importDOM(): DOMConversionMap | null { - return { - tr: (node: Node) => ({ - conversion: $convertTableRowElement, - priority: 0, - }), - }; - } - - static importJSON(serializedNode: SerializedCustomTableRowNode): CustomTableRowNode { - const node = $createCustomTableRowNode(); - - node.setStyles(new Map(Object.entries(serializedNode.styles))); - - return node; - } - - exportJSON(): SerializedCustomTableRowNode { - return { - ...super.exportJSON(), - height: 0, - type: 'custom-table-row', - styles: Object.fromEntries(this.__styles), - }; - } -} - -export function $convertTableRowElement(domNode: Node): DOMConversionOutput { - const rowNode = $createCustomTableRowNode(); - - if (domNode instanceof HTMLElement) { - rowNode.setStyles(extractStyleMapFromElement(domNode)); - } - - return {node: rowNode}; -} - -export function $createCustomTableRowNode(): CustomTableRowNode { - return new CustomTableRowNode(); -} - -export function $isCustomTableRowNode(node: LexicalNode | null | undefined): node is CustomTableRowNode { - return node instanceof CustomTableRowNode; -} \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/custom-table.ts b/resources/js/wysiwyg/nodes/custom-table.ts deleted file mode 100644 index c25c06c6515..00000000000 --- a/resources/js/wysiwyg/nodes/custom-table.ts +++ /dev/null @@ -1,166 +0,0 @@ -import {SerializedTableNode, TableNode} from "@lexical/table"; -import {DOMConversion, DOMConversionMap, DOMConversionOutput, LexicalNode, Spread} from "lexical"; -import {EditorConfig} from "lexical/LexicalEditor"; - -import {el, extractStyleMapFromElement, StyleMap} from "../utils/dom"; -import {getTableColumnWidths} from "../utils/tables"; -import { - CommonBlockAlignment, deserializeCommonBlockNode, - SerializedCommonBlockNode, - setCommonBlockPropsFromElement, - updateElementWithCommonBlockProps -} from "./_common"; - -export type SerializedCustomTableNode = Spread, -}, SerializedTableNode>, SerializedCommonBlockNode> - -export class CustomTableNode extends TableNode { - __id: string = ''; - __colWidths: string[] = []; - __styles: StyleMap = new Map; - __alignment: CommonBlockAlignment = ''; - __inset: number = 0; - - static getType() { - return 'custom-table'; - } - - setId(id: string) { - const self = this.getWritable(); - self.__id = id; - } - - getId(): string { - const self = this.getLatest(); - return self.__id; - } - - setAlignment(alignment: CommonBlockAlignment) { - const self = this.getWritable(); - self.__alignment = alignment; - } - - getAlignment(): CommonBlockAlignment { - const self = this.getLatest(); - return self.__alignment; - } - - setInset(size: number) { - const self = this.getWritable(); - self.__inset = size; - } - - getInset(): number { - const self = this.getLatest(); - return self.__inset; - } - - setColWidths(widths: string[]) { - const self = this.getWritable(); - self.__colWidths = widths; - } - - getColWidths(): string[] { - const self = this.getLatest(); - return self.__colWidths; - } - - getStyles(): StyleMap { - const self = this.getLatest(); - return new Map(self.__styles); - } - - setStyles(styles: StyleMap): void { - const self = this.getWritable(); - self.__styles = new Map(styles); - } - - static clone(node: CustomTableNode) { - const newNode = new CustomTableNode(node.__key); - newNode.__id = node.__id; - newNode.__colWidths = node.__colWidths; - newNode.__styles = new Map(node.__styles); - newNode.__alignment = node.__alignment; - newNode.__inset = node.__inset; - return newNode; - } - - createDOM(config: EditorConfig): HTMLElement { - const dom = super.createDOM(config); - updateElementWithCommonBlockProps(dom, this); - - const colWidths = this.getColWidths(); - if (colWidths.length > 0) { - const colgroup = el('colgroup'); - for (const width of colWidths) { - const col = el('col'); - if (width) { - col.style.width = width; - } - colgroup.append(col); - } - dom.append(colgroup); - } - - for (const [name, value] of this.__styles.entries()) { - dom.style.setProperty(name, value); - } - - return dom; - } - - updateDOM(): boolean { - return true; - } - - exportJSON(): SerializedCustomTableNode { - return { - ...super.exportJSON(), - type: 'custom-table', - version: 1, - id: this.__id, - colWidths: this.__colWidths, - styles: Object.fromEntries(this.__styles), - alignment: this.__alignment, - inset: this.__inset, - }; - } - - static importJSON(serializedNode: SerializedCustomTableNode): CustomTableNode { - const node = $createCustomTableNode(); - deserializeCommonBlockNode(serializedNode, node); - node.setColWidths(serializedNode.colWidths); - node.setStyles(new Map(Object.entries(serializedNode.styles))); - return node; - } - - static importDOM(): DOMConversionMap|null { - return { - table(node: HTMLElement): DOMConversion|null { - return { - conversion: (element: HTMLElement): DOMConversionOutput|null => { - const node = $createCustomTableNode(); - setCommonBlockPropsFromElement(element, node); - - const colWidths = getTableColumnWidths(element as HTMLTableElement); - node.setColWidths(colWidths); - node.setStyles(extractStyleMapFromElement(element)); - - return {node}; - }, - priority: 1, - }; - }, - }; - } -} - -export function $createCustomTableNode(): CustomTableNode { - return new CustomTableNode(); -} - -export function $isCustomTableNode(node: LexicalNode | null | undefined): node is CustomTableNode { - return node instanceof CustomTableNode; -} diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index 7e0ce9daf2b..03213e2629a 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -11,14 +11,11 @@ import {ImageNode} from "./image"; import {DetailsNode, SummaryNode} from "./details"; import {ListItemNode, ListNode} from "@lexical/list"; import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; -import {CustomTableNode} from "./custom-table"; import {HorizontalRuleNode} from "./horizontal-rule"; import {CodeBlockNode} from "./code-block"; import {DiagramNode} from "./diagram"; import {EditorUiContext} from "../ui/framework/core"; import {MediaNode} from "./media"; -import {CustomTableCellNode} from "./custom-table-cell"; -import {CustomTableRowNode} from "./custom-table-row"; import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; @@ -32,9 +29,9 @@ export function getNodesForPageEditor(): (KlassConstructor | QuoteNode, ListNode, ListItemNode, - CustomTableNode, - CustomTableRowNode, - CustomTableCellNode, + TableNode, + TableRowNode, + TableCellNode, ImageNode, // TODO - Alignment HorizontalRuleNode, DetailsNode, SummaryNode, @@ -43,30 +40,6 @@ export function getNodesForPageEditor(): (KlassConstructor | MediaNode, // TODO - Alignment ParagraphNode, LinkNode, - { - replace: TableNode, - with(node: TableNode) { - return new CustomTableNode(); - } - }, - { - replace: TableRowNode, - with(node: TableRowNode) { - return new CustomTableRowNode(); - } - }, - { - replace: TableCellNode, - with: (node: TableCellNode) => { - const cell = new CustomTableCellNode( - node.__headerState, - node.__colSpan, - node.__width, - ); - cell.__rowSpan = node.__rowSpan; - return cell; - } - }, ]; } diff --git a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts index fc4196f0a08..2e4883d88e6 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts @@ -9,17 +9,15 @@ import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg"; import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg"; import {EditorUiContext} from "../../framework/core"; import {$getSelection, BaseSelection} from "lexical"; -import {$isCustomTableNode} from "../../../nodes/custom-table"; import { $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $insertTableColumn__EXPERIMENTAL, - $insertTableRow__EXPERIMENTAL, - $isTableNode, $isTableSelection, $unmergeCell, TableCellNode, + $insertTableRow__EXPERIMENTAL, $isTableCellNode, + $isTableNode, $isTableRowNode, $isTableSelection, $unmergeCell, TableCellNode, } from "@lexical/table"; import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection"; import {$getParentOfType} from "../../../utils/nodes"; -import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell"; import {$showCellPropertiesForm, $showRowPropertiesForm, $showTablePropertiesForm} from "../forms/tables"; import { $clearTableFormatting, @@ -27,7 +25,6 @@ import { $getTableRowsFromSelection, $mergeTableCellsInSelection } from "../../../utils/tables"; -import {$isCustomTableRowNode} from "../../../nodes/custom-table-row"; import { $copySelectedColumnsToClipboard, $copySelectedRowsToClipboard, @@ -41,7 +38,7 @@ import { } from "../../../utils/table-copy-paste"; const neverActive = (): boolean => false; -const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isCustomTableCellNode); +const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isTableCellNode); export const table: EditorBasicButtonDefinition = { label: 'Table', @@ -54,7 +51,7 @@ export const tableProperties: EditorButtonDefinition = { action(context: EditorUiContext) { context.editor.getEditorState().read(() => { const table = $getTableFromSelection($getSelection()); - if ($isCustomTableNode(table)) { + if ($isTableNode(table)) { $showTablePropertiesForm(table, context); } }); @@ -68,13 +65,13 @@ export const clearTableFormatting: EditorButtonDefinition = { format: 'long', action(context: EditorUiContext) { context.editor.update(() => { - const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); - if (!$isCustomTableCellNode(cell)) { + const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); + if (!$isTableCellNode(cell)) { return; } const table = $getParentOfType(cell, $isTableNode); - if ($isCustomTableNode(table)) { + if ($isTableNode(table)) { $clearTableFormatting(table); } }); @@ -88,13 +85,13 @@ export const resizeTableToContents: EditorButtonDefinition = { format: 'long', action(context: EditorUiContext) { context.editor.update(() => { - const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); - if (!$isCustomTableCellNode(cell)) { + const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); + if (!$isTableCellNode(cell)) { return; } - const table = $getParentOfType(cell, $isCustomTableNode); - if ($isCustomTableNode(table)) { + const table = $getParentOfType(cell, $isTableNode); + if ($isTableNode(table)) { $clearTableSizes(table); } }); @@ -108,7 +105,7 @@ export const deleteTable: EditorButtonDefinition = { icon: deleteIcon, action(context: EditorUiContext) { context.editor.update(() => { - const table = $getNodeFromSelection($getSelection(), $isCustomTableNode); + const table = $getNodeFromSelection($getSelection(), $isTableNode); if (table) { table.remove(); } @@ -169,7 +166,7 @@ export const rowProperties: EditorButtonDefinition = { action(context: EditorUiContext) { context.editor.getEditorState().read(() => { const rows = $getTableRowsFromSelection($getSelection()); - if ($isCustomTableRowNode(rows[0])) { + if ($isTableRowNode(rows[0])) { $showRowPropertiesForm(rows[0], context); } }); @@ -350,8 +347,8 @@ export const cellProperties: EditorButtonDefinition = { format: 'long', action(context: EditorUiContext) { context.editor.getEditorState().read(() => { - const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); - if ($isCustomTableCellNode(cell)) { + const cell = $getNodeFromSelection($getSelection(), $isTableCellNode); + if ($isTableCellNode(cell)) { $showCellPropertiesForm(cell, context); } }); @@ -387,7 +384,7 @@ export const splitCell: EditorButtonDefinition = { }, isActive: neverActive, isDisabled(selection) { - const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as TableCellNode|null; + const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode|null; if (cell) { const merged = cell.getRowSpan() > 1 || cell.getColSpan() > 1; return !merged; diff --git a/resources/js/wysiwyg/ui/defaults/forms/tables.ts b/resources/js/wysiwyg/ui/defaults/forms/tables.ts index 5a41c85b3da..3cfe9592ccb 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/tables.ts @@ -5,7 +5,6 @@ import { EditorSelectFormFieldDefinition } from "../../framework/forms"; import {EditorUiContext} from "../../framework/core"; -import {CustomTableCellNode} from "../../../nodes/custom-table-cell"; import {EditorFormModal} from "../../framework/modals"; import {$getSelection, ElementFormatType} from "lexical"; import { @@ -16,8 +15,8 @@ import { $setTableCellColumnWidth } from "../../../utils/tables"; import {formatSizeValue} from "../../../utils/dom"; -import {CustomTableRowNode} from "../../../nodes/custom-table-row"; -import {CustomTableNode} from "../../../nodes/custom-table"; +import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; +import {CommonBlockAlignment} from "../../../nodes/_common"; const borderStyleInput: EditorSelectFormFieldDefinition = { label: 'Border style', @@ -62,14 +61,14 @@ const alignmentInput: EditorSelectFormFieldDefinition = { } }; -export function $showCellPropertiesForm(cell: CustomTableCellNode, context: EditorUiContext): EditorFormModal { +export function $showCellPropertiesForm(cell: TableCellNode, context: EditorUiContext): EditorFormModal { const styles = cell.getStyles(); const modalForm = context.manager.createModal('cell_properties'); modalForm.show({ width: $getTableCellColumnWidth(context.editor, cell), height: styles.get('height') || '', type: cell.getTag(), - h_align: cell.getFormatType(), + h_align: cell.getAlignment(), v_align: styles.get('vertical-align') || '', border_width: styles.get('border-width') || '', border_style: styles.get('border-style') || '', @@ -89,7 +88,7 @@ export const cellProperties: EditorFormDefinition = { $setTableCellColumnWidth(cell, width); cell.updateTag(formData.get('type')?.toString() || ''); - cell.setFormat((formData.get('h_align')?.toString() || '') as ElementFormatType); + cell.setAlignment((formData.get('h_align')?.toString() || '') as CommonBlockAlignment); const styles = cell.getStyles(); styles.set('height', formatSizeValue(formData.get('height')?.toString() || '')); @@ -172,7 +171,7 @@ export const cellProperties: EditorFormDefinition = { ], }; -export function $showRowPropertiesForm(row: CustomTableRowNode, context: EditorUiContext): EditorFormModal { +export function $showRowPropertiesForm(row: TableRowNode, context: EditorUiContext): EditorFormModal { const styles = row.getStyles(); const modalForm = context.manager.createModal('row_properties'); modalForm.show({ @@ -216,7 +215,7 @@ export const rowProperties: EditorFormDefinition = { ], }; -export function $showTablePropertiesForm(table: CustomTableNode, context: EditorUiContext): EditorFormModal { +export function $showTablePropertiesForm(table: TableNode, context: EditorUiContext): EditorFormModal { const styles = table.getStyles(); const modalForm = context.manager.createModal('table_properties'); modalForm.show({ @@ -229,7 +228,7 @@ export function $showTablePropertiesForm(table: CustomTableNode, context: Editor border_color: styles.get('border-color') || '', background_color: styles.get('background-color') || '', // caption: '', TODO - align: table.getFormatType(), + align: table.getAlignment(), }); return modalForm; } @@ -253,12 +252,12 @@ export const tableProperties: EditorFormDefinition = { styles.set('background-color', formData.get('background_color')?.toString() || ''); table.setStyles(styles); - table.setFormat(formData.get('align') as ElementFormatType); + table.setAlignment(formData.get('align') as CommonBlockAlignment); const cellPadding = (formData.get('cell_padding')?.toString() || ''); if (cellPadding) { const cellPaddingFormatted = formatSizeValue(cellPadding); - $forEachTableCell(table, (cell: CustomTableCellNode) => { + $forEachTableCell(table, (cell: TableCellNode) => { const styles = cell.getStyles(); styles.set('padding', cellPaddingFormatted); cell.setStyles(styles); diff --git a/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts b/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts index 30ff3abc56c..6f026ca1895 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts @@ -1,6 +1,5 @@ import {EditorUiElement} from "../core"; import {$createTableNodeWithDimensions} from "@lexical/table"; -import {CustomTableNode} from "../../../nodes/custom-table"; import {$insertNewBlockNodeAtSelection} from "../../../utils/selection"; import {el} from "../../../utils/dom"; @@ -78,7 +77,7 @@ export class EditorTableCreator extends EditorUiElement { const colWidths = Array(columns).fill(targetColWidth + 'px'); this.getContext().editor.update(() => { - const table = $createTableNodeWithDimensions(rows, columns, false) as CustomTableNode; + const table = $createTableNodeWithDimensions(rows, columns, false); table.setColWidths(colWidths); $insertNewBlockNodeAtSelection(table); }); diff --git a/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts index 37f1b6f01ee..4256fdafc65 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts @@ -1,7 +1,6 @@ import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical"; import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker"; -import {CustomTableNode} from "../../../nodes/custom-table"; -import {TableRowNode} from "@lexical/table"; +import {TableNode, TableRowNode} from "@lexical/table"; import {el} from "../../../utils/dom"; import {$getTableColumnWidth, $setTableColumnWidth} from "../../../utils/tables"; @@ -148,7 +147,7 @@ class TableResizer { _this.editor.update(() => { const table = $getNearestNodeFromDOMNode(parentTable); - if (table instanceof CustomTableNode) { + if (table instanceof TableNode) { const originalWidth = $getTableColumnWidth(_this.editor, table, cellIndex); const newWidth = Math.max(originalWidth + change, 10); $setTableColumnWidth(table, cellIndex, newWidth); diff --git a/resources/js/wysiwyg/ui/framework/helpers/table-selection-handler.ts b/resources/js/wysiwyg/ui/framework/helpers/table-selection-handler.ts index f631fb804a5..d3d8925505f 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/table-selection-handler.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/table-selection-handler.ts @@ -1,12 +1,12 @@ import {$getNodeByKey, LexicalEditor} from "lexical"; import {NodeKey} from "lexical/LexicalNode"; import { + $isTableNode, applyTableHandlers, HTMLTableElementWithWithTableSelectionState, TableNode, TableObserver } from "@lexical/table"; -import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table"; // File adapted from logic in: // https://github.com/facebook/lexical/blob/f373759a7849f473d34960a6bf4e34b2a011e762/packages/lexical-react/src/LexicalTablePlugin.ts#L49 @@ -25,12 +25,12 @@ class TableSelectionHandler { } protected init() { - this.unregisterMutationListener = this.editor.registerMutationListener(CustomTableNode, (mutations) => { + this.unregisterMutationListener = this.editor.registerMutationListener(TableNode, (mutations) => { for (const [nodeKey, mutation] of mutations) { if (mutation === 'created') { this.editor.getEditorState().read(() => { - const tableNode = $getNodeByKey(nodeKey); - if ($isCustomTableNode(tableNode)) { + const tableNode = $getNodeByKey(nodeKey); + if ($isTableNode(tableNode)) { this.initializeTableNode(tableNode); } }); diff --git a/resources/js/wysiwyg/utils/table-copy-paste.ts b/resources/js/wysiwyg/utils/table-copy-paste.ts index 12c19b0fb80..1e024e4c758 100644 --- a/resources/js/wysiwyg/utils/table-copy-paste.ts +++ b/resources/js/wysiwyg/utils/table-copy-paste.ts @@ -1,24 +1,28 @@ import {NodeClipboard} from "./node-clipboard"; -import {CustomTableRowNode} from "../nodes/custom-table-row"; import {$getTableCellsFromSelection, $getTableFromSelection, $getTableRowsFromSelection} from "./tables"; import {$getSelection, BaseSelection, LexicalEditor} from "lexical"; -import {$createCustomTableCellNode, $isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell"; -import {CustomTableNode} from "../nodes/custom-table"; import {TableMap} from "./table-map"; -import {$isTableSelection} from "@lexical/table"; +import { + $createTableCellNode, + $isTableCellNode, + $isTableSelection, + TableCellNode, + TableNode, + TableRowNode +} from "@lexical/table"; import {$getNodeFromSelection} from "./selection"; -const rowClipboard: NodeClipboard = new NodeClipboard(); +const rowClipboard: NodeClipboard = new NodeClipboard(); export function isRowClipboardEmpty(): boolean { return rowClipboard.size() === 0; } -export function validateRowsToCopy(rows: CustomTableRowNode[]): void { +export function validateRowsToCopy(rows: TableRowNode[]): void { let commonRowSize: number|null = null; for (const row of rows) { - const cells = row.getChildren().filter(n => $isCustomTableCellNode(n)); + const cells = row.getChildren().filter(n => $isTableCellNode(n)); let rowSize = 0; for (const cell of cells) { rowSize += cell.getColSpan() || 1; @@ -35,10 +39,10 @@ export function validateRowsToCopy(rows: CustomTableRowNode[]): void { } } -export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: CustomTableNode): void { +export function validateRowsToPaste(rows: TableRowNode[], targetTable: TableNode): void { const tableColCount = (new TableMap(targetTable)).columnCount; for (const row of rows) { - const cells = row.getChildren().filter(n => $isCustomTableCellNode(n)); + const cells = row.getChildren().filter(n => $isTableCellNode(n)); let rowSize = 0; for (const cell of cells) { rowSize += cell.getColSpan() || 1; @@ -49,7 +53,7 @@ export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: Cus } while (rowSize < tableColCount) { - row.append($createCustomTableCellNode()); + row.append($createTableCellNode()); rowSize++; } } @@ -98,11 +102,11 @@ export function $pasteClipboardRowsAfter(editor: LexicalEditor): void { } } -const columnClipboard: NodeClipboard[] = []; +const columnClipboard: NodeClipboard[] = []; -function setColumnClipboard(columns: CustomTableCellNode[][]): void { +function setColumnClipboard(columns: TableCellNode[][]): void { const newClipboards = columns.map(cells => { - const clipboard = new NodeClipboard(); + const clipboard = new NodeClipboard(); clipboard.set(...cells); return clipboard; }); @@ -122,9 +126,9 @@ function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|nul return {from: shape.fromX, to: shape.toX}; } - const cell = $getNodeFromSelection(selection, $isCustomTableCellNode); + const cell = $getNodeFromSelection(selection, $isTableCellNode); const table = $getTableFromSelection(selection); - if (!$isCustomTableCellNode(cell) || !table) { + if (!$isTableCellNode(cell) || !table) { return null; } @@ -137,7 +141,7 @@ function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|nul return {from: range.fromX, to: range.toX}; } -function $getTableColumnCellsFromSelection(range: TableRange, table: CustomTableNode): CustomTableCellNode[][] { +function $getTableColumnCellsFromSelection(range: TableRange, table: TableNode): TableCellNode[][] { const map = new TableMap(table); const columns = []; for (let x = range.from; x <= range.to; x++) { @@ -148,7 +152,7 @@ function $getTableColumnCellsFromSelection(range: TableRange, table: CustomTable return columns; } -function validateColumnsToCopy(columns: CustomTableCellNode[][]): void { +function validateColumnsToCopy(columns: TableCellNode[][]): void { let commonColSize: number|null = null; for (const cells of columns) { @@ -203,7 +207,7 @@ export function $copySelectedColumnsToClipboard(): void { setColumnClipboard(columns); } -function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: CustomTableNode) { +function validateColumnsToPaste(columns: TableCellNode[][], targetTable: TableNode) { const tableRowCount = (new TableMap(targetTable)).rowCount; for (const cells of columns) { let colSize = 0; @@ -216,7 +220,7 @@ function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: C } while (colSize < tableRowCount) { - cells.push($createCustomTableCellNode()); + cells.push($createTableCellNode()); colSize++; } } diff --git a/resources/js/wysiwyg/utils/table-map.ts b/resources/js/wysiwyg/utils/table-map.ts index 607deffe1ca..dfe21b936f6 100644 --- a/resources/js/wysiwyg/utils/table-map.ts +++ b/resources/js/wysiwyg/utils/table-map.ts @@ -1,6 +1,4 @@ -import {CustomTableNode} from "../nodes/custom-table"; -import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell"; -import {$isTableRowNode} from "@lexical/table"; +import {$isTableCellNode, $isTableRowNode, TableCellNode, TableNode} from "@lexical/table"; export type CellRange = { fromX: number; @@ -16,15 +14,15 @@ export class TableMap { // Represents an array (rows*columns in length) of cell nodes from top-left to // bottom right. Cells may repeat where merged and covering multiple spaces. - cells: CustomTableCellNode[] = []; + cells: TableCellNode[] = []; - constructor(table: CustomTableNode) { + constructor(table: TableNode) { this.buildCellMap(table); } - protected buildCellMap(table: CustomTableNode) { - const rowsAndCells: CustomTableCellNode[][] = []; - const setCell = (x: number, y: number, cell: CustomTableCellNode) => { + protected buildCellMap(table: TableNode) { + const rowsAndCells: TableCellNode[][] = []; + const setCell = (x: number, y: number, cell: TableCellNode) => { if (typeof rowsAndCells[y] === 'undefined') { rowsAndCells[y] = []; } @@ -36,7 +34,7 @@ export class TableMap { const rowNodes = table.getChildren().filter(r => $isTableRowNode(r)); for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) { const rowNode = rowNodes[rowIndex]; - const cellNodes = rowNode.getChildren().filter(c => $isCustomTableCellNode(c)); + const cellNodes = rowNode.getChildren().filter(c => $isTableCellNode(c)); let targetColIndex: number = 0; for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) { const cellNode = cellNodes[cellIndex]; @@ -60,7 +58,7 @@ export class TableMap { this.columnCount = Math.max(...rowsAndCells.map(r => r.length)); const cells = []; - let lastCell: CustomTableCellNode = rowsAndCells[0][0]; + let lastCell: TableCellNode = rowsAndCells[0][0]; for (let y = 0; y < this.rowCount; y++) { for (let x = 0; x < this.columnCount; x++) { if (!rowsAndCells[y] || !rowsAndCells[y][x]) { @@ -75,7 +73,7 @@ export class TableMap { this.cells = cells; } - public getCellAtPosition(x: number, y: number): CustomTableCellNode { + public getCellAtPosition(x: number, y: number): TableCellNode { const position = (y * this.columnCount) + x; if (position >= this.cells.length) { throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`); @@ -84,13 +82,13 @@ export class TableMap { return this.cells[position]; } - public getCellsInRange(range: CellRange): CustomTableCellNode[] { + public getCellsInRange(range: CellRange): TableCellNode[] { const minX = Math.max(Math.min(range.fromX, range.toX), 0); const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1); const minY = Math.max(Math.min(range.fromY, range.toY), 0); const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1); - const cells = new Set(); + const cells = new Set(); for (let y = minY; y <= maxY; y++) { for (let x = minX; x <= maxX; x++) { @@ -101,7 +99,7 @@ export class TableMap { return [...cells.values()]; } - public getCellsInColumn(columnIndex: number): CustomTableCellNode[] { + public getCellsInColumn(columnIndex: number): TableCellNode[] { return this.getCellsInRange({ fromX: columnIndex, toX: columnIndex, @@ -110,7 +108,7 @@ export class TableMap { }); } - public getRangeForCell(cell: CustomTableCellNode): CellRange|null { + public getRangeForCell(cell: TableCellNode): CellRange|null { let range: CellRange|null = null; const cellKey = cell.getKey(); diff --git a/resources/js/wysiwyg/utils/tables.ts b/resources/js/wysiwyg/utils/tables.ts index aa8ec89ba77..ed947ddcdcb 100644 --- a/resources/js/wysiwyg/utils/tables.ts +++ b/resources/js/wysiwyg/utils/tables.ts @@ -1,15 +1,19 @@ import {BaseSelection, LexicalEditor} from "lexical"; -import {$isTableRowNode, $isTableSelection, TableRowNode, TableSelection, TableSelectionShape} from "@lexical/table"; -import {$isCustomTableNode, CustomTableNode} from "../nodes/custom-table"; -import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell"; +import { + $isTableCellNode, + $isTableNode, + $isTableRowNode, + $isTableSelection, TableCellNode, TableNode, + TableRowNode, + TableSelection, +} from "@lexical/table"; import {$getParentOfType} from "./nodes"; import {$getNodeFromSelection} from "./selection"; import {formatSizeValue} from "./dom"; import {TableMap} from "./table-map"; -import {$isCustomTableRowNode, CustomTableRowNode} from "../nodes/custom-table-row"; -function $getTableFromCell(cell: CustomTableCellNode): CustomTableNode|null { - return $getParentOfType(cell, $isCustomTableNode) as CustomTableNode|null; +function $getTableFromCell(cell: TableCellNode): TableNode|null { + return $getParentOfType(cell, $isTableNode) as TableNode|null; } export function getTableColumnWidths(table: HTMLTableElement): string[] { @@ -55,7 +59,7 @@ function extractWidthFromElement(element: HTMLElement): string { return width || ''; } -export function $setTableColumnWidth(node: CustomTableNode, columnIndex: number, width: number|string): void { +export function $setTableColumnWidth(node: TableNode, columnIndex: number, width: number|string): void { const rows = node.getChildren() as TableRowNode[]; let maxCols = 0; for (const row of rows) { @@ -78,7 +82,7 @@ export function $setTableColumnWidth(node: CustomTableNode, columnIndex: number, node.setColWidths(colWidths); } -export function $getTableColumnWidth(editor: LexicalEditor, node: CustomTableNode, columnIndex: number): number { +export function $getTableColumnWidth(editor: LexicalEditor, node: TableNode, columnIndex: number): number { const colWidths = node.getColWidths(); if (colWidths.length > columnIndex && colWidths[columnIndex].endsWith('px')) { return Number(colWidths[columnIndex].replace('px', '')); @@ -97,14 +101,14 @@ export function $getTableColumnWidth(editor: LexicalEditor, node: CustomTableNod return 0; } -function $getCellColumnIndex(node: CustomTableCellNode): number { +function $getCellColumnIndex(node: TableCellNode): number { const row = node.getParent(); if (!$isTableRowNode(row)) { return -1; } let index = 0; - const cells = row.getChildren(); + const cells = row.getChildren(); for (const cell of cells) { let colSpan = cell.getColSpan() || 1; index += colSpan; @@ -116,7 +120,7 @@ function $getCellColumnIndex(node: CustomTableCellNode): number { return index - 1; } -export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: string): void { +export function $setTableCellColumnWidth(cell: TableCellNode, width: string): void { const table = $getTableFromCell(cell) const index = $getCellColumnIndex(cell); @@ -125,7 +129,7 @@ export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: strin } } -export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTableCellNode): string { +export function $getTableCellColumnWidth(editor: LexicalEditor, cell: TableCellNode): string { const table = $getTableFromCell(cell) const index = $getCellColumnIndex(cell); if (!table) { @@ -136,13 +140,13 @@ export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTabl return (widths.length > index) ? widths[index] : ''; } -export function $getTableCellsFromSelection(selection: BaseSelection|null): CustomTableCellNode[] { +export function $getTableCellsFromSelection(selection: BaseSelection|null): TableCellNode[] { if ($isTableSelection(selection)) { const nodes = selection.getNodes(); - return nodes.filter(n => $isCustomTableCellNode(n)); + return nodes.filter(n => $isTableCellNode(n)); } - const cell = $getNodeFromSelection(selection, $isCustomTableCellNode) as CustomTableCellNode; + const cell = $getNodeFromSelection(selection, $isTableCellNode) as TableCellNode; return cell ? [cell] : []; } @@ -193,12 +197,12 @@ export function $mergeTableCellsInSelection(selection: TableSelection): void { firstCell.setRowSpan(newHeight); } -export function $getTableRowsFromSelection(selection: BaseSelection|null): CustomTableRowNode[] { +export function $getTableRowsFromSelection(selection: BaseSelection|null): TableRowNode[] { const cells = $getTableCellsFromSelection(selection); - const rowsByKey: Record = {}; + const rowsByKey: Record = {}; for (const cell of cells) { const row = cell.getParent(); - if ($isCustomTableRowNode(row)) { + if ($isTableRowNode(row)) { rowsByKey[row.getKey()] = row; } } @@ -206,28 +210,28 @@ export function $getTableRowsFromSelection(selection: BaseSelection|null): Custo return Object.values(rowsByKey); } -export function $getTableFromSelection(selection: BaseSelection|null): CustomTableNode|null { +export function $getTableFromSelection(selection: BaseSelection|null): TableNode|null { const cells = $getTableCellsFromSelection(selection); if (cells.length === 0) { return null; } - const table = $getParentOfType(cells[0], $isCustomTableNode); - if ($isCustomTableNode(table)) { + const table = $getParentOfType(cells[0], $isTableNode); + if ($isTableNode(table)) { return table; } return null; } -export function $clearTableSizes(table: CustomTableNode): void { +export function $clearTableSizes(table: TableNode): void { table.setColWidths([]); // TODO - Extra form things once table properties and extra things // are supported for (const row of table.getChildren()) { - if (!$isCustomTableRowNode(row)) { + if (!$isTableRowNode(row)) { continue; } @@ -236,7 +240,7 @@ export function $clearTableSizes(table: CustomTableNode): void { rowStyles.delete('width'); row.setStyles(rowStyles); - const cells = row.getChildren().filter(c => $isCustomTableCellNode(c)); + const cells = row.getChildren().filter(c => $isTableCellNode(c)); for (const cell of cells) { const cellStyles = cell.getStyles(); cellStyles.delete('height'); @@ -247,23 +251,21 @@ export function $clearTableSizes(table: CustomTableNode): void { } } -export function $clearTableFormatting(table: CustomTableNode): void { +export function $clearTableFormatting(table: TableNode): void { table.setColWidths([]); table.setStyles(new Map); for (const row of table.getChildren()) { - if (!$isCustomTableRowNode(row)) { + if (!$isTableRowNode(row)) { continue; } row.setStyles(new Map); - row.setFormat(''); - const cells = row.getChildren().filter(c => $isCustomTableCellNode(c)); + const cells = row.getChildren().filter(c => $isTableCellNode(c)); for (const cell of cells) { cell.setStyles(new Map); cell.clearWidth(); - cell.setFormat(''); } } } @@ -272,14 +274,14 @@ export function $clearTableFormatting(table: CustomTableNode): void { * Perform the given callback for each cell in the given table. * Returning false from the callback stops the function early. */ -export function $forEachTableCell(table: CustomTableNode, callback: (c: CustomTableCellNode) => void|false): void { +export function $forEachTableCell(table: TableNode, callback: (c: TableCellNode) => void|false): void { outer: for (const row of table.getChildren()) { - if (!$isCustomTableRowNode(row)) { + if (!$isTableRowNode(row)) { continue; } const cells = row.getChildren(); for (const cell of cells) { - if (!$isCustomTableCellNode(cell)) { + if (!$isTableCellNode(cell)) { return; } const result = callback(cell); @@ -290,10 +292,10 @@ export function $forEachTableCell(table: CustomTableNode, callback: (c: CustomTa } } -export function $getCellPaddingForTable(table: CustomTableNode): string { +export function $getCellPaddingForTable(table: TableNode): string { let padding: string|null = null; - $forEachTableCell(table, (cell: CustomTableCellNode) => { + $forEachTableCell(table, (cell: TableCellNode) => { const cellPadding = cell.getStyles().get('padding') || '' if (padding === null) { padding = cellPadding; From 9fdd100f2d989ddc30d9cbad4dadb1b98096edaf Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 4 Dec 2024 18:53:59 +0000 Subject: [PATCH 5/6] Lexical: Reorganised custom node code into lexical codebase Also cleaned up old unused imports. --- resources/js/wysiwyg/index.ts | 2 +- .../js/wysiwyg/lexical/core/LexicalMutations.ts | 1 - .../js/wysiwyg/lexical/core/LexicalReconciler.ts | 12 ++++++------ .../core/__tests__/unit/LexicalEditor.test.ts | 1 - .../lexical/core/nodes/CommonBlockNode.ts | 13 ++++++++++--- .../lexical/core/nodes/LexicalElementNode.ts | 4 ++-- .../lexical/core/nodes/LexicalParagraphNode.ts | 6 +++--- .../nodes/__tests__/unit/LexicalTabNode.test.ts | 10 +--------- .../nodes/__tests__/unit/LexicalTextNode.test.ts | 2 -- .../_common.ts => lexical/core/nodes/common.ts} | 13 +++---------- .../js/wysiwyg/lexical/list/LexicalListNode.ts | 2 +- resources/js/wysiwyg/lexical/readme.md | 2 +- .../rich-text/LexicalCalloutNode.ts} | 4 ++-- .../rich-text/LexicalCodeBlockNode.ts} | 6 +++--- .../rich-text/LexicalDetailsNode.ts} | 4 ++-- .../rich-text/LexicalDiagramNode.ts} | 4 ++-- .../lexical/rich-text/LexicalHeadingNode.ts | 7 +++---- .../rich-text/LexicalHorizontalRuleNode.ts} | 0 .../rich-text/LexicalImageNode.ts} | 4 ++-- .../rich-text/LexicalMediaNode.ts} | 8 ++++---- .../lexical/rich-text/LexicalQuoteNode.ts | 10 ++++------ .../lexical/table/LexicalTableCellNode.ts | 2 +- .../js/wysiwyg/lexical/table/LexicalTableNode.ts | 10 +++++----- .../table/LexicalTableSelectionHelpers.ts | 2 -- .../js/wysiwyg/{nodes/index.ts => nodes.ts} | 16 ++++++++-------- .../js/wysiwyg/services/drop-paste-handling.ts | 2 +- .../js/wysiwyg/services/keyboard-handling.ts | 4 ++-- resources/js/wysiwyg/ui/decorators/code-block.ts | 4 ++-- resources/js/wysiwyg/ui/decorators/diagram.ts | 2 +- .../js/wysiwyg/ui/defaults/buttons/alignments.ts | 4 ++-- .../wysiwyg/ui/defaults/buttons/block-formats.ts | 2 +- .../js/wysiwyg/ui/defaults/buttons/objects.ts | 15 +++++++-------- .../js/wysiwyg/ui/defaults/forms/objects.ts | 9 ++++----- resources/js/wysiwyg/ui/defaults/forms/tables.ts | 4 ++-- .../wysiwyg/ui/framework/helpers/node-resizer.ts | 6 +++--- resources/js/wysiwyg/ui/framework/manager.ts | 2 +- resources/js/wysiwyg/utils/diagrams.ts | 4 ++-- resources/js/wysiwyg/utils/formats.ts | 4 ++-- resources/js/wysiwyg/utils/images.ts | 2 +- resources/js/wysiwyg/utils/nodes.ts | 2 +- resources/js/wysiwyg/utils/selection.ts | 2 +- 41 files changed, 97 insertions(+), 116 deletions(-) rename resources/js/wysiwyg/{nodes/_common.ts => lexical/core/nodes/common.ts} (89%) rename resources/js/wysiwyg/{nodes/callout.ts => lexical/rich-text/LexicalCalloutNode.ts} (98%) rename resources/js/wysiwyg/{nodes/code-block.ts => lexical/rich-text/LexicalCodeBlockNode.ts} (97%) rename resources/js/wysiwyg/{nodes/details.ts => lexical/rich-text/LexicalDetailsNode.ts} (97%) rename resources/js/wysiwyg/{nodes/diagram.ts => lexical/rich-text/LexicalDiagramNode.ts} (97%) rename resources/js/wysiwyg/{nodes/horizontal-rule.ts => lexical/rich-text/LexicalHorizontalRuleNode.ts} (100%) rename resources/js/wysiwyg/{nodes/image.ts => lexical/rich-text/LexicalImageNode.ts} (98%) rename resources/js/wysiwyg/{nodes/media.ts => lexical/rich-text/LexicalMediaNode.ts} (97%) rename resources/js/wysiwyg/{nodes/index.ts => nodes.ts} (78%) diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index c4403773bf2..9066b402f33 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -1,4 +1,4 @@ -import {$getSelection, createEditor, CreateEditorArgs, isCurrentlyReadOnlyMode, LexicalEditor} from 'lexical'; +import {$getSelection, createEditor, CreateEditorArgs, LexicalEditor} from 'lexical'; import {createEmptyHistoryState, registerHistory} from '@lexical/history'; import {registerRichText} from '@lexical/rich-text'; import {mergeRegister} from '@lexical/utils'; diff --git a/resources/js/wysiwyg/lexical/core/LexicalMutations.ts b/resources/js/wysiwyg/lexical/core/LexicalMutations.ts index c24dc9ebb3c..80645205679 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalMutations.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalMutations.ts @@ -16,7 +16,6 @@ import { $getSelection, $isDecoratorNode, $isElementNode, - $isRangeSelection, $isTextNode, $setSelection, } from '.'; diff --git a/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts b/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts index 7843027d713..fccf1ae23a8 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts @@ -29,12 +29,12 @@ import { import { DOUBLE_LINE_BREAK, FULL_RECONCILE, - IS_ALIGN_CENTER, - IS_ALIGN_END, - IS_ALIGN_JUSTIFY, - IS_ALIGN_LEFT, - IS_ALIGN_RIGHT, - IS_ALIGN_START, + + + + + + } from './LexicalConstants'; import {EditorState} from './LexicalEditorState'; import { diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts index f3c6f710509..28a203100c4 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts @@ -47,7 +47,6 @@ import { import invariant from 'lexical/shared/invariant'; import { - $createTestDecoratorNode, $createTestElementNode, $createTestInlineElementNode, createTestEditor, diff --git a/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts b/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts index bf4fc08ca60..572c9448b0c 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts @@ -1,8 +1,15 @@ -import {ElementNode} from "./LexicalElementNode"; -import {CommonBlockAlignment, SerializedCommonBlockNode} from "../../../nodes/_common"; +import {ElementNode, type SerializedElementNode} from "./LexicalElementNode"; +import {CommonBlockAlignment, CommonBlockInterface} from "./common"; +import {Spread} from "lexical"; -export class CommonBlockNode extends ElementNode { +export type SerializedCommonBlockNode = Spread<{ + id: string; + alignment: CommonBlockAlignment; + inset: number; +}, SerializedElementNode> + +export class CommonBlockNode extends ElementNode implements CommonBlockInterface { __id: string = ''; __alignment: CommonBlockAlignment = ''; __inset: number = 0; diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts index 002d825d6ea..9624af67e7e 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalElementNode.ts @@ -19,8 +19,8 @@ import invariant from 'lexical/shared/invariant'; import {$isTextNode, TextNode} from '../index'; import { DOUBLE_LINE_BREAK, - ELEMENT_FORMAT_TO_TYPE, - ELEMENT_TYPE_TO_FORMAT, + + } from '../LexicalConstants'; import {LexicalNode} from '../LexicalNode'; import { diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts index 6517d939eda..f6f57c91c75 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts @@ -29,10 +29,10 @@ import { import {$isTextNode} from './LexicalTextNode'; import { commonPropertiesDifferent, deserializeCommonBlockNode, - SerializedCommonBlockNode, setCommonBlockPropsFromElement, + setCommonBlockPropsFromElement, updateElementWithCommonBlockProps -} from "../../../nodes/_common"; -import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; +} from "./common"; +import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; export type SerializedParagraphNode = Spread< { diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts index 9831114340d..d1ba5359752 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts @@ -10,21 +10,14 @@ import { $insertDataTransferForPlainText, $insertDataTransferForRichText, } from '@lexical/clipboard'; -import {$createListItemNode, $createListNode} from '@lexical/list'; -import {registerRichText} from '@lexical/rich-text'; import { $createParagraphNode, - $createRangeSelection, $createTabNode, - $createTextNode, $getRoot, $getSelection, $insertNodes, - $isElementNode, $isRangeSelection, - $isTextNode, - $setSelection, - KEY_TAB_COMMAND, + } from 'lexical'; import { @@ -32,7 +25,6 @@ import { initializeUnitTest, invariant, } from '../../../__tests__/utils'; -import {$createHeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; describe('LexicalTabNode tests', () => { initializeUnitTest((testEnv) => { diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts index b1ea099ac1e..c54760ff242 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts @@ -41,9 +41,7 @@ import { $setCompositionKey, getEditorStateTextContent, } from '../../../LexicalUtils'; -import {Text} from "@codemirror/state"; import {$generateHtmlFromNodes} from "@lexical/html"; -import {formatBold} from "@lexical/selection/__tests__/utils"; const editorConfig = Object.freeze({ namespace: '', diff --git a/resources/js/wysiwyg/nodes/_common.ts b/resources/js/wysiwyg/lexical/core/nodes/common.ts similarity index 89% rename from resources/js/wysiwyg/nodes/_common.ts rename to resources/js/wysiwyg/lexical/core/nodes/common.ts index 71849bb4589..eac9c829595 100644 --- a/resources/js/wysiwyg/nodes/_common.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/common.ts @@ -1,18 +1,11 @@ -import {LexicalNode, Spread} from "lexical"; -import type {SerializedElementNode} from "lexical/nodes/LexicalElementNode"; -import {el, sizeToPixels} from "../utils/dom"; +import {sizeToPixels} from "../../../utils/dom"; +import {SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; export type CommonBlockAlignment = 'left' | 'right' | 'center' | 'justify' | ''; const validAlignments: CommonBlockAlignment[] = ['left', 'right', 'center', 'justify']; type EditorNodeDirection = 'ltr' | 'rtl' | null; -export type SerializedCommonBlockNode = Spread<{ - id: string; - alignment: CommonBlockAlignment; - inset: number; -}, SerializedElementNode> - export interface NodeHasAlignment { readonly __alignment: CommonBlockAlignment; setAlignment(alignment: CommonBlockAlignment): void; @@ -37,7 +30,7 @@ export interface NodeHasDirection { getDirection(): EditorNodeDirection; } -interface CommonBlockInterface extends NodeHasId, NodeHasAlignment, NodeHasInset, NodeHasDirection {} +export interface CommonBlockInterface extends NodeHasId, NodeHasAlignment, NodeHasInset, NodeHasDirection {} export function extractAlignmentFromElement(element: HTMLElement): CommonBlockAlignment { const textAlignStyle: string = element.style.textAlign || ''; diff --git a/resources/js/wysiwyg/lexical/list/LexicalListNode.ts b/resources/js/wysiwyg/lexical/list/LexicalListNode.ts index 138c895e6b8..6edf0d64a2e 100644 --- a/resources/js/wysiwyg/lexical/list/LexicalListNode.ts +++ b/resources/js/wysiwyg/lexical/list/LexicalListNode.ts @@ -36,7 +36,7 @@ import { updateChildrenListItemValue, } from './formatList'; import {$getListDepth, $wrapInListItem} from './utils'; -import {extractDirectionFromElement} from "../../nodes/_common"; +import {extractDirectionFromElement} from "lexical/nodes/common"; export type SerializedListNode = Spread< { diff --git a/resources/js/wysiwyg/lexical/readme.md b/resources/js/wysiwyg/lexical/readme.md index 31db8fab1ca..24440ec8077 100644 --- a/resources/js/wysiwyg/lexical/readme.md +++ b/resources/js/wysiwyg/lexical/readme.md @@ -9,4 +9,4 @@ Only components used, or intended to be used, were copied in at this point. The original work built upon in this directory and below is under the copyright of Meta Platforms, Inc. and affiliates. The original license can be seen in the [ORIGINAL-LEXICAL-LICENSE](./ORIGINAL-LEXICAL-LICENSE) file. -Files may have since been modified with modifications being under the license and copyright of the BookStack project as a whole. \ No newline at end of file +Files may have since been added or modified with changes being under the license and copyright of the BookStack project as a whole. \ No newline at end of file diff --git a/resources/js/wysiwyg/nodes/callout.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalCalloutNode.ts similarity index 98% rename from resources/js/wysiwyg/nodes/callout.ts rename to resources/js/wysiwyg/lexical/rich-text/LexicalCalloutNode.ts index cfe32ec854c..6f97ba751ff 100644 --- a/resources/js/wysiwyg/nodes/callout.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalCalloutNode.ts @@ -11,10 +11,10 @@ import type {EditorConfig} from "lexical/LexicalEditor"; import type {RangeSelection} from "lexical/LexicalSelection"; import { CommonBlockAlignment, commonPropertiesDifferent, deserializeCommonBlockNode, - SerializedCommonBlockNode, setCommonBlockPropsFromElement, updateElementWithCommonBlockProps -} from "./_common"; +} from "lexical/nodes/common"; +import {SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success'; diff --git a/resources/js/wysiwyg/nodes/code-block.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalCodeBlockNode.ts similarity index 97% rename from resources/js/wysiwyg/nodes/code-block.ts rename to resources/js/wysiwyg/lexical/rich-text/LexicalCodeBlockNode.ts index 76c17197111..cbe69184887 100644 --- a/resources/js/wysiwyg/nodes/code-block.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalCodeBlockNode.ts @@ -8,9 +8,9 @@ import { Spread } from "lexical"; import type {EditorConfig} from "lexical/LexicalEditor"; -import {EditorDecoratorAdapter} from "../ui/framework/decorator"; -import {CodeEditor} from "../../components"; -import {el} from "../utils/dom"; +import {EditorDecoratorAdapter} from "../../ui/framework/decorator"; +import {CodeEditor} from "../../../components"; +import {el} from "../../utils/dom"; export type SerializedCodeBlockNode = Spread<{ language: string; diff --git a/resources/js/wysiwyg/nodes/details.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts similarity index 97% rename from resources/js/wysiwyg/nodes/details.ts rename to resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts index de87696f345..178b0d9531d 100644 --- a/resources/js/wysiwyg/nodes/details.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts @@ -8,8 +8,8 @@ import { EditorConfig, } from 'lexical'; -import {el} from "../utils/dom"; -import {extractDirectionFromElement} from "./_common"; +import {el} from "../../utils/dom"; +import {extractDirectionFromElement} from "lexical/nodes/common"; export type SerializedDetailsNode = Spread<{ id: string; diff --git a/resources/js/wysiwyg/nodes/diagram.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalDiagramNode.ts similarity index 97% rename from resources/js/wysiwyg/nodes/diagram.ts rename to resources/js/wysiwyg/lexical/rich-text/LexicalDiagramNode.ts index bd37b200c80..e69f97848ac 100644 --- a/resources/js/wysiwyg/nodes/diagram.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalDiagramNode.ts @@ -8,8 +8,8 @@ import { Spread } from "lexical"; import type {EditorConfig} from "lexical/LexicalEditor"; -import {EditorDecoratorAdapter} from "../ui/framework/decorator"; -import {el} from "../utils/dom"; +import {EditorDecoratorAdapter} from "../../ui/framework/decorator"; +import {el} from "../../utils/dom"; export type SerializedDiagramNode = Spread<{ id: string; diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts index 0f30263ba0e..30563c09d5a 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts @@ -11,16 +11,15 @@ import { type NodeKey, type ParagraphNode, type RangeSelection, - type SerializedElementNode, type Spread } from "lexical"; import {addClassNamesToElement} from "@lexical/utils"; -import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; +import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; import { commonPropertiesDifferent, deserializeCommonBlockNode, - SerializedCommonBlockNode, setCommonBlockPropsFromElement, + setCommonBlockPropsFromElement, updateElementWithCommonBlockProps -} from "../../nodes/_common"; +} from "lexical/nodes/common"; export type HeadingTagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; diff --git a/resources/js/wysiwyg/nodes/horizontal-rule.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalHorizontalRuleNode.ts similarity index 100% rename from resources/js/wysiwyg/nodes/horizontal-rule.ts rename to resources/js/wysiwyg/lexical/rich-text/LexicalHorizontalRuleNode.ts diff --git a/resources/js/wysiwyg/nodes/image.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalImageNode.ts similarity index 98% rename from resources/js/wysiwyg/nodes/image.ts rename to resources/js/wysiwyg/lexical/rich-text/LexicalImageNode.ts index b6d362b62c9..9f42ad73204 100644 --- a/resources/js/wysiwyg/nodes/image.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalImageNode.ts @@ -6,8 +6,8 @@ import { Spread } from "lexical"; import type {EditorConfig} from "lexical/LexicalEditor"; -import {CommonBlockAlignment, extractAlignmentFromElement} from "./_common"; -import {$selectSingleNode} from "../utils/selection"; +import {CommonBlockAlignment, extractAlignmentFromElement} from "lexical/nodes/common"; +import {$selectSingleNode} from "../../utils/selection"; import {SerializedElementNode} from "lexical/nodes/LexicalElementNode"; export interface ImageNodeOptions { diff --git a/resources/js/wysiwyg/nodes/media.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts similarity index 97% rename from resources/js/wysiwyg/nodes/media.ts rename to resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts index 64fe8f77b4d..a675665ac14 100644 --- a/resources/js/wysiwyg/nodes/media.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts @@ -8,14 +8,14 @@ import { } from 'lexical'; import type {EditorConfig} from "lexical/LexicalEditor"; -import {el, setOrRemoveAttribute, sizeToPixels} from "../utils/dom"; +import {el, setOrRemoveAttribute, sizeToPixels} from "../../utils/dom"; import { CommonBlockAlignment, deserializeCommonBlockNode, - SerializedCommonBlockNode, setCommonBlockPropsFromElement, updateElementWithCommonBlockProps -} from "./_common"; -import {$selectSingleNode} from "../utils/selection"; +} from "lexical/nodes/common"; +import {$selectSingleNode} from "../../utils/selection"; +import {SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; export type MediaNodeTag = 'iframe' | 'embed' | 'object' | 'video' | 'audio'; export type MediaNodeSource = { diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts index 53caca80115..f0d97fe9806 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts @@ -5,22 +5,20 @@ import { type DOMConversionOutput, type DOMExportOutput, type EditorConfig, - ElementNode, isHTMLElement, type LexicalEditor, LexicalNode, type NodeKey, type ParagraphNode, - type RangeSelection, - SerializedElementNode + type RangeSelection } from "lexical"; import {addClassNamesToElement} from "@lexical/utils"; -import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; +import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; import { commonPropertiesDifferent, deserializeCommonBlockNode, - SerializedCommonBlockNode, setCommonBlockPropsFromElement, + setCommonBlockPropsFromElement, updateElementWithCommonBlockProps -} from "../../nodes/_common"; +} from "lexical/nodes/common"; export type SerializedQuoteNode = SerializedCommonBlockNode; diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts index 72676b9bacb..1fc6b42bbeb 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts @@ -29,7 +29,7 @@ import { } from 'lexical'; import {extractStyleMapFromElement, StyleMap} from "../../utils/dom"; -import {CommonBlockAlignment, extractAlignmentFromElement} from "../../nodes/_common"; +import {CommonBlockAlignment, extractAlignmentFromElement} from "lexical/nodes/common"; export const TableCellHeaderStates = { BOTH: 3, diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts index ab163005370..9443747a6f7 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts @@ -15,25 +15,25 @@ import { LexicalEditor, LexicalNode, NodeKey, - SerializedElementNode, Spread, + Spread, } from 'lexical'; import {addClassNamesToElement, isHTMLElement} from '@lexical/utils'; import { $applyNodeReplacement, $getNearestNodeFromDOMNode, - ElementNode, + } from 'lexical'; import {$isTableCellNode} from './LexicalTableCellNode'; import {TableDOMCell, TableDOMTable} from './LexicalTableObserver'; import {getTable} from './LexicalTableSelectionHelpers'; -import {CommonBlockNode, copyCommonBlockProperties} from "lexical/nodes/CommonBlockNode"; +import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; import { commonPropertiesDifferent, deserializeCommonBlockNode, - SerializedCommonBlockNode, setCommonBlockPropsFromElement, + setCommonBlockPropsFromElement, updateElementWithCommonBlockProps -} from "../../nodes/_common"; +} from "lexical/nodes/common"; import {el, extractStyleMapFromElement, StyleMap} from "../../utils/dom"; import {getTableColumnWidths} from "../../utils/tables"; diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts b/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts index 6c3317c5dfa..e098a21e498 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableSelectionHelpers.ts @@ -16,7 +16,6 @@ import type { } from './LexicalTableSelection'; import type { BaseSelection, - ElementFormatType, LexicalCommand, LexicalEditor, LexicalNode, @@ -50,7 +49,6 @@ import { DELETE_LINE_COMMAND, DELETE_WORD_COMMAND, FOCUS_COMMAND, - FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, INSERT_PARAGRAPH_COMMAND, KEY_ARROW_DOWN_COMMAND, diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes.ts similarity index 78% rename from resources/js/wysiwyg/nodes/index.ts rename to resources/js/wysiwyg/nodes.ts index 03213e2629a..eb836bdce02 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes.ts @@ -1,4 +1,4 @@ -import {CalloutNode} from './callout'; +import {CalloutNode} from '@lexical/rich-text/LexicalCalloutNode'; import { ElementNode, KlassConstructor, @@ -7,15 +7,15 @@ import { ParagraphNode } from "lexical"; import {LinkNode} from "@lexical/link"; -import {ImageNode} from "./image"; -import {DetailsNode, SummaryNode} from "./details"; +import {ImageNode} from "@lexical/rich-text/LexicalImageNode"; +import {DetailsNode, SummaryNode} from "@lexical/rich-text/LexicalDetailsNode"; import {ListItemNode, ListNode} from "@lexical/list"; import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; -import {HorizontalRuleNode} from "./horizontal-rule"; -import {CodeBlockNode} from "./code-block"; -import {DiagramNode} from "./diagram"; -import {EditorUiContext} from "../ui/framework/core"; -import {MediaNode} from "./media"; +import {HorizontalRuleNode} from "@lexical/rich-text/LexicalHorizontalRuleNode"; +import {CodeBlockNode} from "@lexical/rich-text/LexicalCodeBlockNode"; +import {DiagramNode} from "@lexical/rich-text/LexicalDiagramNode"; +import {EditorUiContext} from "./ui/framework/core"; +import {MediaNode} from "@lexical/rich-text/LexicalMediaNode"; import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; diff --git a/resources/js/wysiwyg/services/drop-paste-handling.ts b/resources/js/wysiwyg/services/drop-paste-handling.ts index e049d5e7c9e..2ee831d74fc 100644 --- a/resources/js/wysiwyg/services/drop-paste-handling.ts +++ b/resources/js/wysiwyg/services/drop-paste-handling.ts @@ -8,7 +8,7 @@ import { import {$insertNewBlockNodesAtSelection, $selectSingleNode} from "../utils/selection"; import {$getNearestBlockNodeForCoords, $htmlToBlockNodes} from "../utils/nodes"; import {Clipboard} from "../../services/clipboard"; -import {$createImageNode} from "../nodes/image"; +import {$createImageNode} from "@lexical/rich-text/LexicalImageNode"; import {$createLinkNode} from "@lexical/link"; import {EditorImageData, uploadImageFile} from "../utils/images"; import {EditorUiContext} from "../ui/framework/core"; diff --git a/resources/js/wysiwyg/services/keyboard-handling.ts b/resources/js/wysiwyg/services/keyboard-handling.ts index 5f7f41ef02c..6a1345fac6d 100644 --- a/resources/js/wysiwyg/services/keyboard-handling.ts +++ b/resources/js/wysiwyg/services/keyboard-handling.ts @@ -10,8 +10,8 @@ import { LexicalEditor, LexicalNode } from "lexical"; -import {$isImageNode} from "../nodes/image"; -import {$isMediaNode} from "../nodes/media"; +import {$isImageNode} from "@lexical/rich-text/LexicalImageNode"; +import {$isMediaNode} from "@lexical/rich-text/LexicalMediaNode"; import {getLastSelection} from "../utils/selection"; import {$getNearestNodeBlockParent} from "../utils/nodes"; import {$setInsetForSelection} from "../utils/lists"; diff --git a/resources/js/wysiwyg/ui/decorators/code-block.ts b/resources/js/wysiwyg/ui/decorators/code-block.ts index 37d3df588c3..daae32e1982 100644 --- a/resources/js/wysiwyg/ui/decorators/code-block.ts +++ b/resources/js/wysiwyg/ui/decorators/code-block.ts @@ -1,7 +1,7 @@ import {EditorDecorator} from "../framework/decorator"; import {EditorUiContext} from "../framework/core"; -import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block"; -import {$isDecoratorNode, BaseSelection} from "lexical"; +import {$openCodeEditorForNode, CodeBlockNode} from "@lexical/rich-text/LexicalCodeBlockNode"; +import {BaseSelection} from "lexical"; import {$selectionContainsNode, $selectSingleNode} from "../../utils/selection"; diff --git a/resources/js/wysiwyg/ui/decorators/diagram.ts b/resources/js/wysiwyg/ui/decorators/diagram.ts index 44d332939e8..d53bcb482ec 100644 --- a/resources/js/wysiwyg/ui/decorators/diagram.ts +++ b/resources/js/wysiwyg/ui/decorators/diagram.ts @@ -1,7 +1,7 @@ import {EditorDecorator} from "../framework/decorator"; import {EditorUiContext} from "../framework/core"; import {BaseSelection} from "lexical"; -import {DiagramNode} from "../../nodes/diagram"; +import {DiagramNode} from "@lexical/rich-text/LexicalDiagramNode"; import {$selectionContainsNode, $selectSingleNode} from "../../utils/selection"; import {$openDrawingEditorForNode} from "../../utils/diagrams"; diff --git a/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts b/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts index f0f46ddc674..98edf44b3b9 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/alignments.ts @@ -9,9 +9,9 @@ import ltrIcon from "@icons/editor/direction-ltr.svg"; import rtlIcon from "@icons/editor/direction-rtl.svg"; import { $getBlockElementNodesInSelection, - $selectionContainsAlignment, $selectionContainsDirection, $selectSingleNode, $toggleSelection, getLastSelection + $selectionContainsAlignment, $selectionContainsDirection, $selectSingleNode, getLastSelection } from "../../../utils/selection"; -import {CommonBlockAlignment} from "../../../nodes/_common"; +import {CommonBlockAlignment} from "lexical/nodes/common"; import {nodeHasAlignment} from "../../../utils/nodes"; diff --git a/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts b/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts index e0d1e7077fa..b36fd1c4f0c 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts @@ -1,4 +1,4 @@ -import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../../../nodes/callout"; +import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "@lexical/rich-text/LexicalCalloutNode"; import {EditorButtonDefinition} from "../../framework/buttons"; import {EditorUiContext} from "../../framework/core"; import {$isParagraphNode, BaseSelection, LexicalNode} from "lexical"; diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts index fd95f9f35d2..f9c029ff14c 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts @@ -2,27 +2,26 @@ import {EditorButtonDefinition} from "../../framework/buttons"; import linkIcon from "@icons/editor/link.svg"; import {EditorUiContext} from "../../framework/core"; import { - $createTextNode, $getRoot, $getSelection, $insertNodes, BaseSelection, - ElementNode, isCurrentlyReadOnlyMode + ElementNode } from "lexical"; import {$isLinkNode, LinkNode} from "@lexical/link"; import unlinkIcon from "@icons/editor/unlink.svg"; import imageIcon from "@icons/editor/image.svg"; -import {$isImageNode, ImageNode} from "../../../nodes/image"; +import {$isImageNode, ImageNode} from "@lexical/rich-text/LexicalImageNode"; import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg"; -import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../../nodes/horizontal-rule"; +import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "@lexical/rich-text/LexicalHorizontalRuleNode"; import codeBlockIcon from "@icons/editor/code-block.svg"; -import {$isCodeBlockNode} from "../../../nodes/code-block"; +import {$isCodeBlockNode} from "@lexical/rich-text/LexicalCodeBlockNode"; import editIcon from "@icons/edit.svg"; import diagramIcon from "@icons/editor/diagram.svg"; -import {$createDiagramNode, DiagramNode} from "../../../nodes/diagram"; +import {$createDiagramNode, DiagramNode} from "@lexical/rich-text/LexicalDiagramNode"; import detailsIcon from "@icons/editor/details.svg"; import mediaIcon from "@icons/editor/media.svg"; -import {$createDetailsNode, $isDetailsNode} from "../../../nodes/details"; -import {$isMediaNode, MediaNode} from "../../../nodes/media"; +import {$createDetailsNode, $isDetailsNode} from "@lexical/rich-text/LexicalDetailsNode"; +import {$isMediaNode, MediaNode} from "@lexical/rich-text/LexicalMediaNode"; import { $getNodeFromSelection, $insertNewBlockNodeAtSelection, diff --git a/resources/js/wysiwyg/ui/defaults/forms/objects.ts b/resources/js/wysiwyg/ui/defaults/forms/objects.ts index 228566d442e..f00a08bb5f5 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/objects.ts @@ -5,11 +5,10 @@ import { EditorSelectFormFieldDefinition } from "../../framework/forms"; import {EditorUiContext} from "../../framework/core"; -import {$createNodeSelection, $createTextNode, $getSelection, $insertNodes, $setSelection} from "lexical"; -import {$isImageNode, ImageNode} from "../../../nodes/image"; -import {$createLinkNode, $isLinkNode, LinkNode} from "@lexical/link"; -import {$createMediaNodeFromHtml, $createMediaNodeFromSrc, $isMediaNode, MediaNode} from "../../../nodes/media"; -import {$insertNodeToNearestRoot} from "@lexical/utils"; +import {$createNodeSelection, $getSelection, $insertNodes, $setSelection} from "lexical"; +import {$isImageNode, ImageNode} from "@lexical/rich-text/LexicalImageNode"; +import {LinkNode} from "@lexical/link"; +import {$createMediaNodeFromHtml, $createMediaNodeFromSrc, $isMediaNode, MediaNode} from "@lexical/rich-text/LexicalMediaNode"; import {$getNodeFromSelection, getLastSelection} from "../../../utils/selection"; import {EditorFormModal} from "../../framework/modals"; import {EditorActionField} from "../../framework/blocks/action-field"; diff --git a/resources/js/wysiwyg/ui/defaults/forms/tables.ts b/resources/js/wysiwyg/ui/defaults/forms/tables.ts index 3cfe9592ccb..63fa24c800f 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/tables.ts @@ -6,7 +6,7 @@ import { } from "../../framework/forms"; import {EditorUiContext} from "../../framework/core"; import {EditorFormModal} from "../../framework/modals"; -import {$getSelection, ElementFormatType} from "lexical"; +import {$getSelection} from "lexical"; import { $forEachTableCell, $getCellPaddingForTable, $getTableCellColumnWidth, @@ -16,7 +16,7 @@ import { } from "../../../utils/tables"; import {formatSizeValue} from "../../../utils/dom"; import {TableCellNode, TableNode, TableRowNode} from "@lexical/table"; -import {CommonBlockAlignment} from "../../../nodes/_common"; +import {CommonBlockAlignment} from "lexical/nodes/common"; const borderStyleInput: EditorSelectFormFieldDefinition = { label: 'Border style', diff --git a/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts index 2e4f2939ca2..fa8ff48be56 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts @@ -1,10 +1,10 @@ import {BaseSelection, LexicalNode,} from "lexical"; import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker"; import {el} from "../../../utils/dom"; -import {$isImageNode} from "../../../nodes/image"; +import {$isImageNode} from "@lexical/rich-text/LexicalImageNode"; import {EditorUiContext} from "../core"; -import {NodeHasSize} from "../../../nodes/_common"; -import {$isMediaNode} from "../../../nodes/media"; +import {NodeHasSize} from "lexical/nodes/common"; +import {$isMediaNode} from "@lexical/rich-text/LexicalMediaNode"; function isNodeWithSize(node: LexicalNode): node is NodeHasSize&LexicalNode { return $isImageNode(node) || $isMediaNode(node); diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 7c0975da7e7..185cd5dccd0 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -1,7 +1,7 @@ import {EditorFormModal, EditorFormModalDefinition} from "./modals"; import {EditorContainerUiElement, EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./core"; import {EditorDecorator, EditorDecoratorAdapter} from "./decorator"; -import {$getSelection, BaseSelection, COMMAND_PRIORITY_LOW, LexicalEditor, SELECTION_CHANGE_COMMAND} from "lexical"; +import {BaseSelection, LexicalEditor} from "lexical"; import {DecoratorListener} from "lexical/LexicalEditor"; import type {NodeKey} from "lexical/LexicalNode"; import {EditorContextToolbar, EditorContextToolbarDefinition} from "./toolbars"; diff --git a/resources/js/wysiwyg/utils/diagrams.ts b/resources/js/wysiwyg/utils/diagrams.ts index fb5543005c2..ffd8e603b99 100644 --- a/resources/js/wysiwyg/utils/diagrams.ts +++ b/resources/js/wysiwyg/utils/diagrams.ts @@ -1,8 +1,8 @@ -import {$getSelection, $insertNodes, LexicalEditor, LexicalNode} from "lexical"; +import {$insertNodes, LexicalEditor, LexicalNode} from "lexical"; import {HttpError} from "../../services/http"; import {EditorUiContext} from "../ui/framework/core"; import * as DrawIO from "../../services/drawio"; -import {$createDiagramNode, DiagramNode} from "../nodes/diagram"; +import {$createDiagramNode, DiagramNode} from "@lexical/rich-text/LexicalDiagramNode"; import {ImageManager} from "../../components"; import {EditorImageData} from "./images"; import {$getNodeFromSelection, getLastSelection} from "./selection"; diff --git a/resources/js/wysiwyg/utils/formats.ts b/resources/js/wysiwyg/utils/formats.ts index 1be802ebf1c..a5f06f147d2 100644 --- a/resources/js/wysiwyg/utils/formats.ts +++ b/resources/js/wysiwyg/utils/formats.ts @@ -14,8 +14,8 @@ import { $toggleSelectionBlockNodeType, getLastSelection } from "./selection"; -import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../nodes/code-block"; -import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "../nodes/callout"; +import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "@lexical/rich-text/LexicalCodeBlockNode"; +import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "@lexical/rich-text/LexicalCalloutNode"; import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list"; import {$createLinkNode, $isLinkNode} from "@lexical/link"; import {$createHeadingNode, $isHeadingNode, HeadingTagType} from "@lexical/rich-text/LexicalHeadingNode"; diff --git a/resources/js/wysiwyg/utils/images.ts b/resources/js/wysiwyg/utils/images.ts index 2c13427d967..85bae18e5e5 100644 --- a/resources/js/wysiwyg/utils/images.ts +++ b/resources/js/wysiwyg/utils/images.ts @@ -1,5 +1,5 @@ import {ImageManager} from "../../components"; -import {$createImageNode} from "../nodes/image"; +import {$createImageNode} from "@lexical/rich-text/LexicalImageNode"; import {$createLinkNode, LinkNode} from "@lexical/link"; export type EditorImageData = { diff --git a/resources/js/wysiwyg/utils/nodes.ts b/resources/js/wysiwyg/utils/nodes.ts index 97634f96b4e..b5cc789550c 100644 --- a/resources/js/wysiwyg/utils/nodes.ts +++ b/resources/js/wysiwyg/utils/nodes.ts @@ -11,7 +11,7 @@ import { import {LexicalNodeMatcher} from "../nodes"; import {$generateNodesFromDOM} from "@lexical/html"; import {htmlToDom} from "./dom"; -import {NodeHasAlignment, NodeHasInset} from "../nodes/_common"; +import {NodeHasAlignment, NodeHasInset} from "lexical/nodes/common"; import {$findMatchingParent} from "@lexical/utils"; function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] { diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index 02838eba034..28e729e92ae 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -16,7 +16,7 @@ import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes"; import {$setBlocksType} from "@lexical/selection"; import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes"; -import {CommonBlockAlignment} from "../nodes/_common"; +import {CommonBlockAlignment} from "lexical/nodes/common"; const lastSelectionByEditor = new WeakMap; From d00cf6e1bac6165354f3b8a3a440f948941fe011 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 4 Dec 2024 20:03:05 +0000 Subject: [PATCH 6/6] Lexical: Updated tests for node changes --- .../__tests__/unit/HTMLCopyAndPaste.test.ts | 4 +- .../core/__tests__/unit/LexicalEditor.test.ts | 18 +- .../__tests__/unit/LexicalEditorState.test.ts | 12 +- .../unit/LexicalSerialization.test.ts | 4 +- .../__tests__/unit/LexicalElementNode.test.ts | 2 - .../unit/LexicalParagraphNode.test.ts | 21 +- .../__tests__/unit/LexicalRootNode.test.ts | 2 - .../unit/LexicalHeadlessEditor.test.ts | 2 +- .../html/__tests__/unit/LexicalHtml.test.ts | 4 +- .../unit/LexicalListItemNode.test.ts | 554 ++++++++---------- .../__tests__/unit/LexicalListNode.test.ts | 19 - .../__tests__/unit/LexicalSelection.test.ts | 6 +- .../__tests__/unit/LexicalTableNode.test.ts | 5 +- .../unit/LexicalTableSelection.test.ts | 8 +- .../unit/LexicalEventHelpers.test.ts | 12 +- .../unit/LexicalUtilsSplitNode.test.ts | 4 +- ...exlcaiUtilsInsertNodeToNearestRoot.test.ts | 4 +- 17 files changed, 301 insertions(+), 380 deletions(-) diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/HTMLCopyAndPaste.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/HTMLCopyAndPaste.test.ts index 534663a54b4..cdad252c92d 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/HTMLCopyAndPaste.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/HTMLCopyAndPaste.test.ts @@ -82,12 +82,12 @@ describe('HTMLCopyAndPaste tests', () => { pastedHTML: ` 123
456
`, }, { - expectedHTML: `
  • done
  • todo
    • done
    • todo
  • todo
`, + expectedHTML: `
  • done
  • todo
    • done
    • todo
  • todo
`, name: 'google doc checklist', pastedHTML: `
  • checked

    done

  • unchecked

    todo

    • checked

      done

    • unchecked

      todo

  • unchecked

    todo

`, }, { - expectedHTML: `

checklist

  • done
  • todo
`, + expectedHTML: `

checklist

  • done
  • todo
`, name: 'github checklist', pastedHTML: `

checklist

  • done
  • todo
`, }, diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts index 28a203100c4..5d763291972 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts @@ -974,7 +974,7 @@ describe('LexicalEditor tests', () => { editable ? 'editable' : 'non-editable' })`, async () => { const JSON_EDITOR_STATE = - '{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"123","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"root","version":1}}'; + '{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"123","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"textStyle":""}],"direction":null,"type":"root","version":1}}'; init(); const contentEditable = editor.getRootElement(); editor.setEditable(editable); @@ -1047,8 +1047,6 @@ describe('LexicalEditor tests', () => { __cachedText: null, __dir: null, __first: paragraphKey, - __format: 0, - __indent: 0, __key: 'root', __last: paragraphKey, __next: null, @@ -1059,10 +1057,11 @@ describe('LexicalEditor tests', () => { __type: 'root', }); expect(parsedParagraph).toEqual({ + "__alignment": "", __dir: null, __first: textKey, - __format: 0, - __indent: 0, + __id: '', + __inset: 0, __key: paragraphKey, __last: textKey, __next: null, @@ -1070,7 +1069,6 @@ describe('LexicalEditor tests', () => { __prev: null, __size: 1, __style: '', - __textFormat: 0, __textStyle: '', __type: 'paragraph', }); @@ -1129,8 +1127,6 @@ describe('LexicalEditor tests', () => { __cachedText: null, __dir: null, __first: paragraphKey, - __format: 0, - __indent: 0, __key: 'root', __last: paragraphKey, __next: null, @@ -1141,10 +1137,11 @@ describe('LexicalEditor tests', () => { __type: 'root', }); expect(parsedParagraph).toEqual({ + "__alignment": "", __dir: null, __first: textKey, - __format: 0, - __indent: 0, + __id: '', + __inset: 0, __key: paragraphKey, __last: textKey, __next: null, @@ -1152,7 +1149,6 @@ describe('LexicalEditor tests', () => { __prev: null, __size: 1, __style: '', - __textFormat: 0, __textStyle: '', __type: 'paragraph', }); diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditorState.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditorState.test.ts index 38ecf03bce2..97b63450323 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditorState.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditorState.test.ts @@ -54,8 +54,6 @@ describe('LexicalEditorState tests', () => { __cachedText: 'foo', __dir: null, __first: '1', - __format: 0, - __indent: 0, __key: 'root', __last: '1', __next: null, @@ -66,10 +64,11 @@ describe('LexicalEditorState tests', () => { __type: 'root', }); expect(paragraph).toEqual({ + "__alignment": "", __dir: null, __first: '2', - __format: 0, - __indent: 0, + __id: '', + __inset: 0, __key: '1', __last: '2', __next: null, @@ -77,7 +76,6 @@ describe('LexicalEditorState tests', () => { __prev: null, __size: 1, __style: '', - __textFormat: 0, __textStyle: '', __type: 'paragraph', }); @@ -113,7 +111,7 @@ describe('LexicalEditorState tests', () => { }); expect(JSON.stringify(editor.getEditorState().toJSON())).toEqual( - `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello world","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"root","version":1}}`, + `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello world","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"root","version":1}}`, ); }); @@ -140,8 +138,6 @@ describe('LexicalEditorState tests', () => { __cachedText: '', __dir: null, __first: null, - __format: 0, - __indent: 0, __key: 'root', __last: null, __next: null, diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts index 81eff674a99..e08547c13b6 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts @@ -107,7 +107,7 @@ describe('LexicalSerialization tests', () => { }); const stringifiedEditorState = JSON.stringify(editor.getEditorState()); - const expectedStringifiedEditorState = `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the playground","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"quote","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"The playground is a demo environment built with ","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"@lexical/react","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":". Try typing in ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"some text","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" with ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"different","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" formats.","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"If you'd like to find out more about Lexical, you can:","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Visit the ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lexical website","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://lexical.dev/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" for documentation and more information.","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Check out the code on our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"GitHub repository","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Playground code can be found ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"here","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical/tree/main/packages/lexical-playground"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Join our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Discord Server","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://discord.com/invite/KmG4wQnnD9"},{"detail":0,"format":0,"mode":"normal","style":"","text":" and chat with the team.","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":null,"format":"","indent":0,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1}],"direction":null,"format":"","indent":0,"type":"table","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}`; + const expectedStringifiedEditorState = `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the playground","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.","type":"text","version":1}],"direction":null,"type":"quote","version":1,"id":"","alignment":"","inset":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"The playground is a demo environment built with ","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"@lexical/react","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":". Try typing in ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"some text","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" with ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"different","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" formats.","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"If you'd like to find out more about Lexical, you can:","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Visit the ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lexical website","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://lexical.dev/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" for documentation and more information.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Check out the code on our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"GitHub repository","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Playground code can be found ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"here","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical/tree/main/packages/lexical-playground"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Join our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Discord Server","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://discord.com/invite/KmG4wQnnD9"},{"detail":0,"format":0,"mode":"normal","style":"","text":" and chat with the team.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":4}],"direction":null,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul","id":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0}],"direction":null,"type":"table","version":1,"id":"","alignment":"","inset":0,"colWidths":[],"styles":{}}],"direction":null,"type":"root","version":1}}`; expect(stringifiedEditorState).toBe(expectedStringifiedEditorState); @@ -116,7 +116,7 @@ describe('LexicalSerialization tests', () => { const otherStringifiedEditorState = JSON.stringify(editorState); expect(otherStringifiedEditorState).toBe( - `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the playground","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"quote","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"The playground is a demo environment built with ","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"@lexical/react","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":". Try typing in ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"some text","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" with ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"different","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" formats.","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"If you'd like to find out more about Lexical, you can:","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Visit the ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lexical website","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://lexical.dev/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" for documentation and more information.","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Check out the code on our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"GitHub repository","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Playground code can be found ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"here","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical/tree/main/packages/lexical-playground"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Join our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Discord Server","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://discord.com/invite/KmG4wQnnD9"},{"detail":0,"format":0,"mode":"normal","style":"","text":" and chat with the team.","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":null,"format":"","indent":0,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1},{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1}],"direction":null,"format":"","indent":0,"type":"table","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}`, + `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the playground","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.","type":"text","version":1}],"direction":null,"type":"quote","version":1,"id":"","alignment":"","inset":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"The playground is a demo environment built with ","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"@lexical/react","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":". Try typing in ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"some text","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" with ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"different","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" formats.","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"If you'd like to find out more about Lexical, you can:","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Visit the ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lexical website","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://lexical.dev/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" for documentation and more information.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Check out the code on our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"GitHub repository","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Playground code can be found ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"here","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical/tree/main/packages/lexical-playground"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Join our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Discord Server","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://discord.com/invite/KmG4wQnnD9"},{"detail":0,"format":0,"mode":"normal","style":"","text":" and chat with the team.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":4}],"direction":null,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul","id":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0}],"direction":null,"type":"table","version":1,"id":"","alignment":"","inset":0,"colWidths":[],"styles":{}}],"direction":null,"type":"root","version":1}}`, ); }); }); diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts index fb5c98f8a6c..6e3a3861ae5 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts @@ -84,8 +84,6 @@ describe('LexicalElementNode tests', () => { expect(node.exportJSON()).toStrictEqual({ children: [], direction: null, - format: '', - indent: 0, type: 'test_block', version: 1, }); diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalParagraphNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalParagraphNode.test.ts index 1f7c4cfc3a7..7bf485ca1d4 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalParagraphNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalParagraphNode.test.ts @@ -48,11 +48,11 @@ describe('LexicalParagraphNode tests', () => { // logic is in place in the corresponding importJSON method // to accomodate these changes. expect(node.exportJSON()).toStrictEqual({ + alignment: '', children: [], direction: null, - format: '', - indent: 0, - textFormat: 0, + id: '', + inset: 0, textStyle: '', type: 'paragraph', version: 1, @@ -127,6 +127,21 @@ describe('LexicalParagraphNode tests', () => { }); }); + test('id is supported', async () => { + const {editor} = testEnv; + let paragraphNode: ParagraphNode; + + await editor.update(() => { + paragraphNode = new ParagraphNode(); + paragraphNode.setId('testid') + $getRoot().append(paragraphNode); + }); + + expect(testEnv.innerHTML).toBe( + '


', + ); + }); + test('$createParagraphNode()', async () => { const {editor} = testEnv; diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalRootNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalRootNode.test.ts index 123cb3375d6..7ef370f4ba7 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalRootNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalRootNode.test.ts @@ -77,8 +77,6 @@ describe('LexicalRootNode tests', () => { expect(node.exportJSON()).toStrictEqual({ children: [], direction: null, - format: '', - indent: 0, type: 'root', version: 1, }); diff --git a/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts b/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts index c4dedd47d13..122516d45b6 100644 --- a/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts +++ b/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts @@ -206,7 +206,7 @@ describe('LexicalHeadlessEditor', () => { cleanup(); expect(html).toBe( - '

hello world

', + '

hello world

', ); }); }); diff --git a/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts b/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts index a4e2d231389..e5064121ab5 100644 --- a/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts +++ b/resources/js/wysiwyg/lexical/html/__tests__/unit/LexicalHtml.test.ts @@ -176,7 +176,7 @@ describe('HTML', () => { }); expect(html).toBe( - '

Hello world!

', + '

Hello world!

', ); }); @@ -206,7 +206,7 @@ describe('HTML', () => { }); expect(html).toBe( - '

Hello world!

', + '

Hello world!

', ); }); }); diff --git a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts index 523c7eb126e..567714bcd9f 100644 --- a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts +++ b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListItemNode.test.ts @@ -62,7 +62,7 @@ describe('LexicalListItemNode tests', () => { expectHtmlToBeEqual( listItemNode.createDOM(editorConfig).outerHTML, html` -
  • +
  • `, ); @@ -90,7 +90,7 @@ describe('LexicalListItemNode tests', () => { expectHtmlToBeEqual( domElement.outerHTML, html` -
  • +
  • `, ); const newListItemNode = new ListItemNode(); @@ -106,7 +106,7 @@ describe('LexicalListItemNode tests', () => { expectHtmlToBeEqual( domElement.outerHTML, html` -
  • +
  • `, ); }); @@ -125,7 +125,7 @@ describe('LexicalListItemNode tests', () => { expectHtmlToBeEqual( domElement.outerHTML, html` -
  • +
  • `, ); const nestedListNode = new ListNode('bullet', 1); @@ -142,7 +142,7 @@ describe('LexicalListItemNode tests', () => { expectHtmlToBeEqual( domElement.outerHTML, html` -
  • +
  • `, ); }); @@ -486,53 +486,43 @@ describe('LexicalListItemNode tests', () => { }); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A -
      • -
      -
    • -
    • - x -
    • -
    • - B -
    • -
    -
    +
      +
    • +
        +
      • + A +
      • +
      +
    • +
    • + x +
    • +
    • + B +
    • +
    `, ); await editor.update(() => x.remove()); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A -
      • -
      -
    • -
    • - B -
    • -
    -
    +
      +
    • +
        +
      • + A +
      • +
      +
    • +
    • + B +
    • +
    `, ); }); @@ -566,53 +556,43 @@ describe('LexicalListItemNode tests', () => { }); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • - A -
    • -
    • - x -
    • -
    • -
        -
      • - B -
      • -
      -
    • -
    -
    +
      +
    • + A +
    • +
    • + x +
    • +
    • +
        +
      • + B +
      • +
      +
    • +
    `, ); await editor.update(() => x.remove()); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • - A -
    • -
    • -
        -
      • - B -
      • -
      -
    • -
    -
    +
      +
    • + A +
    • +
    • +
        +
      • + B +
      • +
      +
    • +
    `, ); }); @@ -650,57 +630,47 @@ describe('LexicalListItemNode tests', () => { }); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A -
      • -
      -
    • -
    • - x -
    • -
    • -
        -
      • - B -
      • -
      -
    • -
    -
    +
      +
    • +
        +
      • + A +
      • +
      +
    • +
    • + x +
    • +
    • +
        +
      • + B +
      • +
      +
    • +
    `, ); await editor.update(() => x.remove()); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A -
      • -
      • - B -
      • -
      -
    • -
    -
    +
      +
    • +
        +
      • + A +
      • +
      • + B +
      • +
      +
    • +
    `, ); }); @@ -746,71 +716,61 @@ describe('LexicalListItemNode tests', () => { }); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A1 -
      • -
      • -
          -
        • - A2 -
        • -
        -
      • -
      -
    • -
    • - x -
    • -
    • -
        -
      • - B -
      • -
      -
    • -
    -
    +
      +
    • +
        +
      • + A1 +
      • +
      • +
          +
        • + A2 +
        • +
        +
      • +
      +
    • +
    • + x +
    • +
    • +
        +
      • + B +
      • +
      +
    • +
    `, ); await editor.update(() => x.remove()); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A1 -
      • -
      • -
          -
        • - A2 -
        • -
        -
      • -
      • - B -
      • -
      -
    • -
    -
    +
      +
    • +
        +
      • + A1 +
      • +
      • +
          +
        • + A2 +
        • +
        +
      • +
      • + B +
      • +
      +
    • +
    `, ); }); @@ -856,71 +816,61 @@ describe('LexicalListItemNode tests', () => { }); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A -
      • -
      -
    • -
    • - x -
    • -
    • -
        -
      • -
          -
        • - B1 -
        • -
        -
      • -
      • - B2 -
      • -
      -
    • -
    -
    +
      +
    • +
        +
      • + A +
      • +
      +
    • +
    • + x +
    • +
    • +
        +
      • +
          +
        • + B1 +
        • +
        +
      • +
      • + B2 +
      • +
      +
    • +
    `, ); await editor.update(() => x.remove()); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A -
      • -
      • -
          -
        • - B1 -
        • -
        -
      • -
      • - B2 -
      • -
      -
    • -
    -
    +
      +
    • +
        +
      • + A +
      • +
      • +
          +
        • + B1 +
        • +
        +
      • +
      • + B2 +
      • +
      +
    • +
    `, ); }); @@ -974,81 +924,71 @@ describe('LexicalListItemNode tests', () => { }); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A1 -
      • -
      • -
          -
        • - A2 -
        • -
        -
      • -
      -
    • -
    • - x -
    • -
    • -
        -
      • -
          -
        • - B1 -
        • -
        -
      • -
      • - B2 -
      • -
      -
    • -
    -
    +
      +
    • +
        +
      • + A1 +
      • +
      • +
          +
        • + A2 +
        • +
        +
      • +
      +
    • +
    • + x +
    • +
    • +
        +
      • +
          +
        • + B1 +
        • +
        +
      • +
      • + B2 +
      • +
      +
    • +
    `, ); await editor.update(() => x.remove()); expectHtmlToBeEqual( - testEnv.outerHTML, + testEnv.innerHTML, html` -
    -
      -
    • -
        -
      • - A1 -
      • -
      • -
          -
        • - A2 -
        • -
        • - B1 -
        • -
        -
      • -
      • - B2 -
      • -
      -
    • -
    -
    +
      +
    • +
        +
      • + A1 +
      • +
      • +
          +
        • + A2 +
        • +
        • + B1 +
        • +
        +
      • +
      • + B2 +
      • +
      +
    • +
    `, ); }); diff --git a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts index 497e096b1c2..8c7729dbff1 100644 --- a/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts +++ b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts @@ -294,24 +294,5 @@ describe('LexicalListNode tests', () => { expect(bulletList.__listType).toBe('bullet'); }); }); - - test('ListNode.clone() without list type (backward compatibility)', async () => { - const {editor} = testEnv; - - await editor.update(() => { - const olNode = ListNode.clone({ - __key: '1', - __start: 1, - __tag: 'ol', - } as unknown as ListNode); - const ulNode = ListNode.clone({ - __key: '1', - __start: 1, - __tag: 'ul', - } as unknown as ListNode); - expect(olNode.__listType).toBe('number'); - expect(ulNode.__listType).toBe('bullet'); - }); - }); }); }); diff --git a/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts index 466be7498de..cc09d1735cc 100644 --- a/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts +++ b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts @@ -2605,7 +2605,7 @@ describe('LexicalSelection tests', () => { return $createHeadingNode('h1'); }); expect(JSON.stringify(testEditor._pendingEditorState?.toJSON())).toBe( - '{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"},{"children":[{"children":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"},{"children":[],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"},{"children":[],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"},{"children":[],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"},{"children":[],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"}],"direction":null,"format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1}],"direction":null,"format":"","indent":0,"type":"tablerow","version":1}],"direction":null,"format":"","indent":0,"type":"table","version":1},{"children":[],"direction":null,"format":"","indent":0,"type":"heading","version":1,"tag":"h1"}],"direction":null,"format":"","indent":0,"type":"root","version":1}}', + '{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[{"children":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0}],"direction":null,"type":"table","version":1,"id":"","alignment":"","inset":0,"colWidths":[],"styles":{}},{"children":[],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"}],"direction":null,"type":"root","version":1}}', ); }); }); @@ -2695,7 +2695,7 @@ describe('LexicalSelection tests', () => { }); }); expect(element.innerHTML).toStrictEqual( - `

    1

    1.1

    `, + `

    1

    • 1.1

    `, ); }); @@ -2734,7 +2734,7 @@ describe('LexicalSelection tests', () => { }); }); expect(element.innerHTML).toStrictEqual( - `

    1.1

    `, + `
    • 1.1

    `, ); }); }); diff --git a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts index 6848e55325d..2879decdad7 100644 --- a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts +++ b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts @@ -113,9 +113,8 @@ describe('LexicalTableNode tests', () => { $insertDataTransferForRichText(dataTransfer, selection, editor); }); // Make sure paragraph is inserted inside empty cells - const emptyCell = '


    '; expect(testEnv.innerHTML).toBe( - `${emptyCell}

    Hello there

    General Kenobi!

    Lexical is nice

    `, + `

    Hello there

    General Kenobi!

    Lexical is nice


    `, ); }); @@ -136,7 +135,7 @@ describe('LexicalTableNode tests', () => { $insertDataTransferForRichText(dataTransfer, selection, editor); }); expect(testEnv.innerHTML).toBe( - `

    Surface

    MWP_WORK_LS_COMPOSER

    77349

    Lexical

    XDS_RICH_TEXT_AREA

    sdvd sdfvsfs

    `, + `

    Surface

    MWP_WORK_LS_COMPOSER

    77349

    Lexical

    XDS_RICH_TEXT_AREA

    sdvd sdfvsfs

    `, ); }); }, diff --git a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts index d5b85ccaaa0..1548216cf1a 100644 --- a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts +++ b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts @@ -101,8 +101,6 @@ describe('table selection', () => { __cachedText: null, __dir: null, __first: paragraphKey, - __format: 0, - __indent: 0, __key: 'root', __last: paragraphKey, __next: null, @@ -113,10 +111,11 @@ describe('table selection', () => { __type: 'root', }); expect(parsedParagraph).toEqual({ + __alignment: "", __dir: null, __first: textKey, - __format: 0, - __indent: 0, + __id: '', + __inset: 0, __key: paragraphKey, __last: textKey, __next: null, @@ -124,7 +123,6 @@ describe('table selection', () => { __prev: null, __size: 1, __style: '', - __textFormat: 0, __textStyle: '', __type: 'paragraph', }); diff --git a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalEventHelpers.test.ts b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalEventHelpers.test.ts index d76937ed606..cae4f1aae68 100644 --- a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalEventHelpers.test.ts +++ b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalEventHelpers.test.ts @@ -176,7 +176,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    • Other side
    • I must have called
    ', + '
    • Other side
    • I must have called
    ', inputs: [ pasteHTML( `
    • Other side
    • I must have called
    `, @@ -186,7 +186,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    1. To tell you
    2. I’m sorry
    ', + '
    1. To tell you
    2. I’m sorry
    ', inputs: [ pasteHTML( `
    1. To tell you
    2. I’m sorry
    `, @@ -266,7 +266,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    • Hello
    • from the other
    • side
    ', + '
    • Hello
    • from the other
    • side
    ', inputs: [ pasteHTML( `
    • Hello
    • from the other
    • side
    `, @@ -276,7 +276,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    • Hello
    • from the other
    • side
    ', + '
    • Hello
    • from the other
    • side
    ', inputs: [ pasteHTML( `
    • Hello
    • from the other
    • side
    `, @@ -611,7 +611,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    1. 1
      2

    2. 3
    ', + '
    1. 1
      2

    2. 3
    ', inputs: [ pasteHTML('
    1. 1
      2
    2. 3
    '), ], @@ -647,7 +647,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    1. 1

    2. 3
    ', + '
    1. 1

    2. 3
    ', inputs: [pasteHTML('
    1. 1

    2. 3
    ')], name: 'only br in a li', }, diff --git a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalUtilsSplitNode.test.ts b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalUtilsSplitNode.test.ts index a70200d6349..54cd8b54f1e 100644 --- a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalUtilsSplitNode.test.ts +++ b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexicalUtilsSplitNode.test.ts @@ -82,10 +82,10 @@ describe('LexicalUtils#splitNode', () => { expectedHtml: '
      ' + '
    • Before
    • ' + - '
      • Hello
    • ' + + '
      • Hello
    • ' + '
    ' + '
      ' + - '
      • world
    • ' + + '
      • world
    • ' + '
    • After
    • ' + '
    ', initialHtml: diff --git a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexlcaiUtilsInsertNodeToNearestRoot.test.ts b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexlcaiUtilsInsertNodeToNearestRoot.test.ts index fb04e628413..8c31496de5a 100644 --- a/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexlcaiUtilsInsertNodeToNearestRoot.test.ts +++ b/resources/js/wysiwyg/lexical/utils/__tests__/unit/LexlcaiUtilsInsertNodeToNearestRoot.test.ts @@ -56,11 +56,11 @@ describe('LexicalUtils#insertNodeToNearestRoot', () => { expectedHtml: '
      ' + '
    • Before
    • ' + - '
      • Hello
    • ' + + '
      • Hello
    • ' + '
    ' + '' + '
      ' + - '
      • world
    • ' + + '
      • world
    • ' + '
    • After
    • ' + '
    ', initialHtml: