diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index c4403773b..9066b402f 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/LexicalEvents.ts b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts index 5fd671a76..c70a906a0 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 56f364501..806452056 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 '.'; @@ -96,15 +95,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 09d01bffd..fccf1ae23 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, @@ -30,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 { @@ -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__/unit/HTMLCopyAndPaste.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/HTMLCopyAndPaste.test.ts index 534663a54..cdad252c9 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: ``, + expectedHTML: ``, name: 'google doc checklist', pastedHTML: ``, }, { - expectedHTML: `

checklist

`, + expectedHTML: `

checklist

`, name: 'github checklist', pastedHTML: `

checklist

`, }, 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 f3c6f7105..5d7632919 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, @@ -975,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); @@ -1048,8 +1047,6 @@ describe('LexicalEditor tests', () => { __cachedText: null, __dir: null, __first: paragraphKey, - __format: 0, - __indent: 0, __key: 'root', __last: paragraphKey, __next: null, @@ -1060,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, @@ -1071,7 +1069,6 @@ describe('LexicalEditor tests', () => { __prev: null, __size: 1, __style: '', - __textFormat: 0, __textStyle: '', __type: 'paragraph', }); @@ -1130,8 +1127,6 @@ describe('LexicalEditor tests', () => { __cachedText: null, __dir: null, __first: paragraphKey, - __format: 0, - __indent: 0, __key: 'root', __last: paragraphKey, __next: null, @@ -1142,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, @@ -1153,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 38ecf03bc..97b634503 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 5599604c0..e08547c13 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(); @@ -106,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); @@ -115,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/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index f7230595a..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 = { @@ -129,8 +130,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 +194,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 +238,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 +317,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 000000000..572c9448b --- /dev/null +++ b/resources/js/wysiwyg/lexical/core/nodes/CommonBlockNode.ts @@ -0,0 +1,61 @@ +import {ElementNode, type SerializedElementNode} from "./LexicalElementNode"; +import {CommonBlockAlignment, CommonBlockInterface} from "./common"; +import {Spread} from "lexical"; + + +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; + + 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 88c6d5678..9624af67e 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 { @@ -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 4e69dc21c..f6f57c91c 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, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "./common"; +import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} 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 74c8d5a7f..a1c8813c3 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/core/nodes/__tests__/unit/LexicalElementNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts index fb5c98f8a..6e3a3861a 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 1f7c4cfc3..7bf485ca1 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 123cb3375..7ef370f4b 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/core/nodes/__tests__/unit/LexicalTabNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTabNode.test.ts index d8525fb36..d1ba53597 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 {$createHeadingNode, registerRichText} from '@lexical/rich-text'; import { $createParagraphNode, - $createRangeSelection, $createTabNode, - $createTextNode, $getRoot, $getSelection, $insertNodes, - $isElementNode, $isRangeSelection, - $isTextNode, - $setSelection, - KEY_TAB_COMMAND, + } from 'lexical'; import { 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 b1ea099ac..c54760ff2 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 71849bb45..eac9c8295 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/headless/__tests__/unit/LexicalHeadlessEditor.test.ts b/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts index c4dedd47d..122516d45 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 947e591b4..e5064121a 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<{ @@ -175,7 +176,7 @@ describe('HTML', () => { }); expect(html).toBe( - '

Hello world!

', + '

Hello world!

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

Hello world!

', + '

Hello world!

', ); }); }); diff --git a/resources/js/wysiwyg/lexical/html/index.ts b/resources/js/wysiwyg/lexical/html/index.ts index 2975315cc..3e962ec72 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 fe2b97570..884fe9153 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 5026a0129..33b021298 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; } @@ -126,14 +128,26 @@ 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(); + + 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, }; @@ -172,7 +186,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 +364,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); @@ -428,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; +function $hasNestedListWithoutLabel(node: ListItemNode): boolean { + const children = node.getChildren(); + let hasLabel = false; + let hasNestedList = false; - 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); - } - - 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 e22fbf771..6edf0d64a 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 "lexical/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 581db0294..567714bcd 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 +
      • +
      +
    • +
    `, ); }); @@ -1265,99 +1205,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/lexical/list/__tests__/unit/LexicalListNode.test.ts b/resources/js/wysiwyg/lexical/list/__tests__/unit/LexicalListNode.test.ts index 497e096b1..8c7729dbf 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/list/formatList.ts b/resources/js/wysiwyg/lexical/list/formatList.ts index b9ca01169..aa0d5d611 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/readme.md b/resources/js/wysiwyg/lexical/readme.md index 31db8fab1..24440ec80 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 cfe32ec85..6f97ba751 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 76c171971..cbe691848 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 de87696f3..178b0d953 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 bd37b200c..e69f97848 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 new file mode 100644 index 000000000..30563c09d --- /dev/null +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalHeadingNode.ts @@ -0,0 +1,201 @@ +import { + $applyNodeReplacement, + $createParagraphNode, + type DOMConversionMap, + DOMConversionOutput, + type DOMExportOutput, + type EditorConfig, + isHTMLElement, + type LexicalEditor, + type LexicalNode, + type NodeKey, + type ParagraphNode, + type RangeSelection, + type Spread +} from "lexical"; +import {addClassNamesToElement} from "@lexical/utils"; +import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; +import { + commonPropertiesDifferent, deserializeCommonBlockNode, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "lexical/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/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 b6d362b62..9f42ad732 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 64fe8f77b..a675665ac 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 new file mode 100644 index 000000000..f0d97fe98 --- /dev/null +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalQuoteNode.ts @@ -0,0 +1,127 @@ +import { + $applyNodeReplacement, + $createParagraphNode, + type DOMConversionMap, + type DOMConversionOutput, + type DOMExportOutput, + type EditorConfig, + isHTMLElement, + type LexicalEditor, + LexicalNode, + type NodeKey, + type ParagraphNode, + type RangeSelection +} from "lexical"; +import {addClassNamesToElement} from "@lexical/utils"; +import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; +import { + commonPropertiesDifferent, deserializeCommonBlockNode, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "lexical/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 d937060c6..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,344 +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')); - } - - const formatType = this.getFormatType(); - element.style.textAlign = formatType; - } - - return { - element, - }; - } - - static importJSON(serializedNode: SerializedQuoteNode): QuoteNode { - const node = $createQuoteNode(); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); - 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')); - } - - const formatType = this.getFormatType(); - element.style.textAlign = formatType; - } - - return { - element, - }; - } - - static importJSON(serializedNode: SerializedHeadingNode): HeadingNode { - const node = $createHeadingNode(serializedNode.tag); - node.setFormat(serializedNode.format); - node.setIndent(serializedNode.indent); - 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 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); - 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}; -} - -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, @@ -651,9 +299,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 +336,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 +469,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/__tests__/unit/LexicalSelection.test.ts b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts index 5f2d9dcc0..cc09d1735 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[]; @@ -2604,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}}', ); }); }); @@ -2694,7 +2695,7 @@ describe('LexicalSelection tests', () => { }); }); expect(element.innerHTML).toStrictEqual( - `

    1

    1.1

    `, + `

    1

    • 1.1

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

    1.1

    `, + `
    • 1.1

    `, ); }); }); 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/selection/range-selection.ts b/resources/js/wysiwyg/lexical/selection/range-selection.ts index dbadaf346..542eae4db 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/lexical/table/LexicalTableCellNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableCellNode.ts index 455d39bf6..1fc6b42bb 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 "lexical/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 357ba3e73..9443747a6 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,31 +15,48 @@ import type { LexicalEditor, LexicalNode, NodeKey, - SerializedElementNode, + 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 {$isTableRowNode, TableRowNode} from './LexicalTableRowNode'; import {getTable} from './LexicalTableSelectionHelpers'; +import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; +import { + commonPropertiesDifferent, deserializeCommonBlockNode, + setCommonBlockPropsFromElement, + updateElementWithCommonBlockProps +} from "lexical/nodes/common"; +import {el, extractStyleMapFromElement, StyleMap} from "../../utils/dom"; +import {getTableColumnWidths} from "../../utils/tables"; -export type SerializedTableNode = SerializedElementNode; +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 eddea69a2..07db2b65d 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; - if (PIXEL_VALUE_REG_EXP.test(domNode_.style.height)) { - height = parseFloat(domNode_.style.height); + const height = sizeToPixels(domNode_.style.height); + rowNode.setHeight(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 812cccc0d..e098a21e4 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, @@ -438,59 +436,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/LexicalTableNode.test.ts b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts index 6848e5532..2879decda 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/LexicalTableRowNode.test.ts b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableRowNode.test.ts index 285d587bf..5dbf03d9e 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/lexical/table/__tests__/unit/LexicalTableSelection.test.ts b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts index d5b85ccaa..1548216cf 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 fd7731f90..cae4f1aae 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'); @@ -174,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
    `, @@ -184,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
    `, @@ -264,7 +266,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    • Hello
    • from the other
    • side
    ', + '
    • Hello
    • from the other
    • side
    ', inputs: [ pasteHTML( `
    • Hello
    • from the other
    • side
    `, @@ -274,7 +276,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    • Hello
    • from the other
    • side
    ', + '
    • Hello
    • from the other
    • side
    ', inputs: [ pasteHTML( `
    • Hello
    • from the other
    • side
    `, @@ -609,7 +611,7 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '
    1. 1
      2

    2. 3
    ', + '
    1. 1
      2

    2. 3
    ', inputs: [ pasteHTML('
    1. 1
      2
    2. 3
    '), ], @@ -645,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 a70200d63..54cd8b54f 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 fb04e6284..8c31496de 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: diff --git a/resources/js/wysiwyg/nodes.ts b/resources/js/wysiwyg/nodes.ts new file mode 100644 index 000000000..eb836bdce --- /dev/null +++ b/resources/js/wysiwyg/nodes.ts @@ -0,0 +1,67 @@ +import {CalloutNode} from '@lexical/rich-text/LexicalCalloutNode'; +import { + ElementNode, + KlassConstructor, + LexicalNode, + LexicalNodeReplacement, NodeMutation, + ParagraphNode +} from "lexical"; +import {LinkNode} from "@lexical/link"; +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 "@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"; + +/** + * Load the nodes for lexical. + */ +export function getNodesForPageEditor(): (KlassConstructor | LexicalNodeReplacement)[] { + return [ + CalloutNode, + HeadingNode, + QuoteNode, + ListNode, + ListItemNode, + TableNode, + TableRowNode, + TableCellNode, + ImageNode, // TODO - Alignment + HorizontalRuleNode, + DetailsNode, SummaryNode, + CodeBlockNode, + DiagramNode, + MediaNode, // TODO - Alignment + ParagraphNode, + LinkNode, + ]; +} + +export function registerCommonNodeMutationListeners(context: EditorUiContext): void { + const decorated = [ImageNode, CodeBlockNode, DiagramNode]; + + const decorationDestroyListener = (mutations: Map): void => { + for (let [nodeKey, mutation] of mutations) { + if (mutation === "destroyed") { + const decorator = context.manager.getDecoratorByNodeKey(nodeKey); + if (decorator) { + decorator.destroy(context); + } + } + } + }; + + for (let decoratedNode of decorated) { + // Have to pass a unique function here since they are stored by lexical keyed on listener function. + context.editor.registerMutationListener(decoratedNode, (mutations) => decorationDestroyListener(mutations)); + } +} + +export type LexicalNodeMatcher = (node: LexicalNode|null|undefined) => boolean; +export type LexicalElementNodeCreator = () => ElementNode; \ No newline at end of file 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 - -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-list-item.ts b/resources/js/wysiwyg/nodes/custom-list-item.ts deleted file mode 100644 index 11887b436..000000000 --- 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 4b05fa62e..000000000 --- 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/custom-paragraph.ts b/resources/js/wysiwyg/nodes/custom-paragraph.ts deleted file mode 100644 index 3adc10d0e..000000000 --- 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/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 - -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/custom-table-cell.ts b/resources/js/wysiwyg/nodes/custom-table-cell.ts deleted file mode 100644 index 793302cfe..000000000 --- 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 f4702f36d..000000000 --- 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 c25c06c65..000000000 --- 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 deleted file mode 100644 index b5483c500..000000000 --- a/resources/js/wysiwyg/nodes/index.ts +++ /dev/null @@ -1,128 +0,0 @@ -import {HeadingNode, QuoteNode} from '@lexical/rich-text'; -import {CalloutNode} from './callout'; -import { - ElementNode, - KlassConstructor, - LexicalNode, - LexicalNodeReplacement, NodeMutation, - ParagraphNode -} from "lexical"; -import {CustomParagraphNode} from "./custom-paragraph"; -import {LinkNode} from "@lexical/link"; -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 {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"; - -/** - * Load the nodes for lexical. - */ -export function getNodesForPageEditor(): (KlassConstructor | LexicalNodeReplacement)[] { - return [ - CalloutNode, - CustomHeadingNode, - CustomQuoteNode, - CustomListNode, - CustomListItemNode, // TODO - Alignment? - CustomTableNode, - CustomTableRowNode, - CustomTableCellNode, - ImageNode, // TODO - Alignment - HorizontalRuleNode, - DetailsNode, SummaryNode, - CodeBlockNode, - DiagramNode, - MediaNode, // TODO - Alignment - CustomParagraphNode, - LinkNode, - { - replace: ParagraphNode, - with: (node: ParagraphNode) => { - return new CustomParagraphNode(); - } - }, - { - replace: HeadingNode, - with: (node: HeadingNode) => { - return new CustomHeadingNode(node.__tag); - } - }, - { - replace: QuoteNode, - with: (node: QuoteNode) => { - return new CustomQuoteNode(); - } - }, - { - 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) { - 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; - } - }, - ]; -} - -export function registerCommonNodeMutationListeners(context: EditorUiContext): void { - const decorated = [ImageNode, CodeBlockNode, DiagramNode]; - - const decorationDestroyListener = (mutations: Map): void => { - for (let [nodeKey, mutation] of mutations) { - if (mutation === "destroyed") { - const decorator = context.manager.getDecoratorByNodeKey(nodeKey); - if (decorator) { - decorator.destroy(context); - } - } - } - }; - - for (let decoratedNode of decorated) { - // Have to pass a unique function here since they are stored by lexical keyed on listener function. - context.editor.registerMutationListener(decoratedNode, (mutations) => decorationDestroyListener(mutations)); - } -} - -export type LexicalNodeMatcher = (node: LexicalNode|null|undefined) => boolean; -export type LexicalElementNodeCreator = () => ElementNode; \ No newline at end of file diff --git a/resources/js/wysiwyg/services/drop-paste-handling.ts b/resources/js/wysiwyg/services/drop-paste-handling.ts index 07e35d443..2ee831d74 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, @@ -7,8 +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 {$createCustomParagraphNode} from "../nodes/custom-paragraph"; +import {$createImageNode} from "@lexical/rich-text/LexicalImageNode"; 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 2c7bfdbba..6a1345fac 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, @@ -9,13 +10,12 @@ 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 {$createCustomParagraphNode} from "../nodes/custom-paragraph"; -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) { @@ -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(); }); @@ -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/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/todo.md b/resources/js/wysiwyg/todo.md index a49cccd26..817a235a7 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/ui/decorators/code-block.ts b/resources/js/wysiwyg/ui/decorators/code-block.ts index 37d3df588..daae32e19 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 44d332939..d53bcb482 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 f0f46ddc6..98edf44b3 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 f86e33c31..b36fd1c4f 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/block-formats.ts @@ -1,19 +1,15 @@ -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"; -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/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts index fd95f9f35..f9c029ff1 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/buttons/tables.ts b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts index fc4196f0a..2e4883d88 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/objects.ts b/resources/js/wysiwyg/ui/defaults/forms/objects.ts index 228566d44..f00a08bb5 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 5a41c85b3..63fa24c80 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/tables.ts @@ -5,9 +5,8 @@ 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 {$getSelection} from "lexical"; import { $forEachTableCell, $getCellPaddingForTable, $getTableCellColumnWidth, @@ -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 "lexical/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/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(); + 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/ui/framework/blocks/table-creator.ts b/resources/js/wysiwyg/ui/framework/blocks/table-creator.ts index 30ff3abc5..6f026ca18 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/node-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts index 2e4f2939c..fa8ff48be 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/helpers/table-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts index 37f1b6f01..4256fdafc 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 f631fb804..d3d892550 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/ui/framework/helpers/task-list-handler.ts b/resources/js/wysiwyg/ui/framework/helpers/task-list-handler.ts index da8c0eae3..62a784d83 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/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 7c0975da7..185cd5dcc 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 fb5543005..ffd8e603b 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 0ec9220dd..a5f06f147 100644 --- a/resources/js/wysiwyg/utils/formats.ts +++ b/resources/js/wysiwyg/utils/formats.ts @@ -1,5 +1,12 @@ -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, @@ -7,37 +14,35 @@ import { $toggleSelectionBlockNodeType, 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"; -import {insertList, ListNode, ListType, removeList} from "@lexical/list"; -import {$isCustomListNode} from "../nodes/custom-list"; +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"; +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), ) }); } export function toggleSelectionAsParagraph(editor: LexicalEditor) { editor.update(() => { - $toggleSelectionBlockNodeType($isCustomParagraphNode, $createCustomParagraphNode); + $toggleSelectionBlockNodeType($isParagraphNode, $createParagraphNode); }); } export function toggleSelectionAsBlockquote(editor: LexicalEditor) { editor.update(() => { - $toggleSelectionBlockNodeType($isQuoteNode, $createCustomQuoteNode); + $toggleSelectionBlockNodeType($isQuoteNode, $createQuoteNode); }); } @@ -45,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/images.ts b/resources/js/wysiwyg/utils/images.ts index 2c13427d9..85bae18e5 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/lists.ts b/resources/js/wysiwyg/utils/lists.ts index 30a97cbc1..646f341c2 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) { diff --git a/resources/js/wysiwyg/utils/nodes.ts b/resources/js/wysiwyg/utils/nodes.ts index 2dd99d369..b5cc78955 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,16 +9,15 @@ 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"; +import {NodeHasAlignment, NodeHasInset} from "lexical/nodes/common"; 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 67c2d91b2..28e729e92 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -7,18 +7,16 @@ 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"; +import {CommonBlockAlignment} from "lexical/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); } diff --git a/resources/js/wysiwyg/utils/table-copy-paste.ts b/resources/js/wysiwyg/utils/table-copy-paste.ts index 12c19b0fb..1e024e4c7 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 607deffe1..dfe21b936 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 aa8ec89ba..ed947ddcd 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;