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 5599604c0..81eff674a 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 a4d74210e..e9d14ef11 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 37ca1cdef..bf4fc08ca 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 d8525fb36..983111434 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 947e591b4..a4e2d2313 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 000000000..0f30263ba --- /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 000000000..53caca801 --- /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 a94f9ee0b..be4b97ba3 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 66374bf5f..cf85045cd 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 bc5c3f1d2..c585c028a 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<Array<File>> = 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 <p> 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<typeof PASTE_COMMAND>, 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 5f2d9dcc0..466be7498 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 4d88bde0e..0523b7f71 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 fd7731f90..d76937ed6 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 5df6245f5..000000000 --- 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<SerializedCommonBlockNode, SerializedHeadingNode> - -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 39ae7bf8a..000000000 --- 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<SerializedCommonBlockNode, SerializedQuoteNode> - -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 062394a98..7b274eba1 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<typeof LexicalNode> | LexicalNodeReplacement)[] { return [ CalloutNode, - CustomHeadingNode, - CustomQuoteNode, + HeadingNode, + QuoteNode, CustomListNode, CustomListItemNode, // TODO - Alignment? CustomTableNode, @@ -46,18 +45,6 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> | 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 05bdb5dcc..0384a3bf1 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 f86e33c31..e0d1e7077 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 5a64cdc30..f88b22c3f 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<string, CustomHeadingNode>(); + protected headerMap = new Map<string, HeadingNode>(); 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 3cfc96442..d724730e3 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); }); }