From 71454c3aaab32c21aed71d57b529bb2c94341ea8 Mon Sep 17 00:00:00 2001 From: Bob Ippolito Date: Mon, 2 Dec 2024 14:46:11 -0800 Subject: [PATCH 01/25] [lexical-link] Bug Fix: Preserve the start/end of the selection for non-text points when creating a LinkNode (#6883) --- .../__tests__/unit/LexicalLinkNode.test.ts | 143 +++++++++--- packages/lexical-link/src/index.ts | 207 +++++++++++------- 2 files changed, 241 insertions(+), 109 deletions(-) diff --git a/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts b/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts index 94582207ffc..a4c49cb6dfd 100644 --- a/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts +++ b/packages/lexical-link/src/__tests__/unit/LexicalLinkNode.test.ts @@ -15,14 +15,19 @@ import { } from '@lexical/link'; import {$createMarkNode, $isMarkNode} from '@lexical/mark'; import { + $createLineBreakNode, $createParagraphNode, $createTextNode, $getRoot, + $getSelection, + $isLineBreakNode, + $isRangeSelection, + $isTextNode, $selectAll, ParagraphNode, + RangeSelection, SerializedParagraphNode, - TextNode, -} from 'lexical/src'; +} from 'lexical'; import {initializeUnitTest} from 'lexical/src/__tests__/utils'; const editorConfig = Object.freeze({ @@ -47,20 +52,20 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('/'); + const linkNode = $createLinkNode('/'); expect(linkNode.__type).toBe('link'); expect(linkNode.__url).toBe('/'); }); - expect(() => new LinkNode('')).toThrow(); + expect(() => $createLinkNode('')).toThrow(); }); test('LineBreakNode.clone()', async () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('/'); + const linkNode = $createLinkNode('/'); const linkNodeClone = LinkNode.clone(linkNode); @@ -73,7 +78,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo'); + const linkNode = $createLinkNode('https://example.com/foo'); expect(linkNode.getURL()).toBe('https://example.com/foo'); }); @@ -83,7 +88,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo'); + const linkNode = $createLinkNode('https://example.com/foo'); expect(linkNode.getURL()).toBe('https://example.com/foo'); @@ -97,7 +102,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { target: '_blank', }); @@ -109,7 +114,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { target: '_blank', }); @@ -125,7 +130,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { rel: 'noopener noreferrer', target: '_blank', }); @@ -138,7 +143,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { rel: 'noopener', target: '_blank', }); @@ -155,7 +160,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { title: 'Hello world', }); @@ -167,7 +172,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { title: 'Hello world', }); @@ -183,7 +188,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo'); + const linkNode = $createLinkNode('https://example.com/foo'); expect(linkNode.createDOM(editorConfig).outerHTML).toBe( '', @@ -201,7 +206,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { rel: 'noopener noreferrer', target: '_blank', title: 'Hello world', @@ -226,7 +231,7 @@ describe('LexicalLinkNode tests', () => { await editor.update(() => { // eslint-disable-next-line no-script-url - const linkNode = new LinkNode('javascript:alert(0)'); + const linkNode = $createLinkNode('javascript:alert(0)'); expect(linkNode.createDOM(editorConfig).outerHTML).toBe( '', ); @@ -237,7 +242,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo'); + const linkNode = $createLinkNode('https://example.com/foo'); const domElement = linkNode.createDOM(editorConfig); @@ -245,7 +250,7 @@ describe('LexicalLinkNode tests', () => { '', ); - const newLinkNode = new LinkNode('https://example.com/bar'); + const newLinkNode = $createLinkNode('https://example.com/bar'); const result = newLinkNode.updateDOM( linkNode, domElement, @@ -263,7 +268,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { rel: 'noopener noreferrer', target: '_blank', title: 'Hello world', @@ -275,7 +280,7 @@ describe('LexicalLinkNode tests', () => { '', ); - const newLinkNode = new LinkNode('https://example.com/bar', { + const newLinkNode = $createLinkNode('https://example.com/bar', { rel: 'noopener', target: '_self', title: 'World hello', @@ -297,7 +302,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { rel: 'noopener noreferrer', target: '_blank', title: 'Hello world', @@ -309,7 +314,7 @@ describe('LexicalLinkNode tests', () => { '', ); - const newLinkNode = new LinkNode('https://example.com/bar'); + const newLinkNode = $createLinkNode('https://example.com/bar'); const result = newLinkNode.updateDOM( linkNode, domElement, @@ -327,7 +332,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo'); + const linkNode = $createLinkNode('https://example.com/foo'); expect(linkNode.canInsertTextBefore()).toBe(false); }); @@ -337,7 +342,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo'); + const linkNode = $createLinkNode('https://example.com/foo'); expect(linkNode.canInsertTextAfter()).toBe(false); }); @@ -347,7 +352,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo'); + const linkNode = $createLinkNode('https://example.com/foo'); const createdLinkNode = $createLinkNode('https://example.com/foo'); @@ -362,7 +367,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode('https://example.com/foo', { + const linkNode = $createLinkNode('https://example.com/foo', { rel: 'noopener noreferrer', target: '_blank', title: 'Hello world', @@ -388,7 +393,7 @@ describe('LexicalLinkNode tests', () => { const {editor} = testEnv; await editor.update(() => { - const linkNode = new LinkNode(''); + const linkNode = $createLinkNode(''); expect($isLinkNode(linkNode)).toBe(true); }); @@ -397,14 +402,27 @@ describe('LexicalLinkNode tests', () => { test('$toggleLink applies the title attribute when creating', async () => { const {editor} = testEnv; await editor.update(() => { - const p = new ParagraphNode(); - p.append(new TextNode('Some text')); + const p = $createParagraphNode(); + const textNode = $createTextNode('Some text'); + p.append(textNode); $getRoot().append(p); - }); - - await editor.update(() => { $selectAll(); $toggleLink('https://lexical.dev/', {title: 'Lexical Website'}); + const linkNode = p.getFirstChild() as LinkNode; + expect($isLinkNode(linkNode)).toBe(true); + expect(linkNode.getTitle()).toBe('Lexical Website'); + const selection = $getSelection() as RangeSelection; + expect($isRangeSelection(selection)).toBe(true); + expect(selection.anchor).toMatchObject({ + key: textNode.getKey(), + offset: 0, + type: 'text', + }); + expect(selection.focus).toMatchObject({ + key: textNode.getKey(), + offset: textNode.getTextContentSize(), + type: 'text', + }); }); const paragraph = editor!.getEditorState().toJSON().root @@ -442,6 +460,7 @@ describe('LexicalLinkNode tests', () => { expect(textNode.getTextContent()).toBe('some '); // Check link node and its nested structure + expect($isLinkNode(linkNode)).toBe(true); if ($isLinkNode(linkNode)) { expect(linkNode.getURL()).toBe('https://example.com/foo'); expect(linkNode.getRel()).toBe('noreferrer'); @@ -470,6 +489,7 @@ describe('LexicalLinkNode tests', () => { expect(textNode.getTextContent()).toBe('some '); // Check mark node is preserved and moved up to paragraph level + expect($isMarkNode(markNode)).toBe(true); if ($isMarkNode(markNode)) { expect(markNode.getType()).toBe('mark'); expect(markNode.getIDs()).toEqual(['knetk']); @@ -477,5 +497,64 @@ describe('LexicalLinkNode tests', () => { } }); }); + + test('$toggleLink adds link with embedded LineBreakNode', async () => { + const {editor} = testEnv; + await editor.update(() => { + const paragraph = $createParagraphNode(); + const precedingText = $createTextNode('some '); // space after + const textNode = $createTextNode('text'); + paragraph.append(precedingText, textNode, $createLineBreakNode()); + $getRoot().clear().append(paragraph); + paragraph.select(1); + $toggleLink('https://example.com/foo', { + rel: 'noreferrer', + }); + }); + + editor.read(() => { + const paragraph = $getRoot().getFirstChild() as ParagraphNode; + const [precedingText, linkNode] = paragraph.getChildren(); + + // Check first text node + expect(precedingText.getTextContent()).toBe('some '); + + // Check link node and its nested structure + expect($isLinkNode(linkNode)).toBe(true); + if ($isLinkNode(linkNode)) { + expect(linkNode.getURL()).toBe('https://example.com/foo'); + expect(linkNode.getRel()).toBe('noreferrer'); + expect( + linkNode.getChildren().map((node) => node.getTextContent()), + ).toEqual(['text', '\n']); + expect($getSelection()).toMatchObject({ + anchor: { + key: linkNode.getFirstChildOrThrow().getKey(), + offset: 0, + type: 'text', + }, + focus: {key: linkNode.getKey(), offset: 2, type: 'element'}, + }); + } + }); + + await editor.update(() => { + $selectAll(); + $toggleLink(null); + }); + + // Verify structure after link removal + editor.read(() => { + const paragraph = $getRoot().getFirstChild() as ParagraphNode; + const children = paragraph.getChildren(); + expect(children.map((node) => node.getTextContent())).toEqual([ + 'some text', + '\n', + ]); + const [textNode, lineBreakNode] = children; + expect($isTextNode(textNode)).toBe(true); + expect($isLineBreakNode(lineBreakNode)).toBe(true); + }); + }); }); }); diff --git a/packages/lexical-link/src/index.ts b/packages/lexical-link/src/index.ts index 1ddb4dcec47..b2cdaefc89c 100644 --- a/packages/lexical-link/src/index.ts +++ b/packages/lexical-link/src/index.ts @@ -14,6 +14,7 @@ import type { LexicalCommand, LexicalNode, NodeKey, + Point, RangeSelection, SerializedElementNode, } from 'lexical'; @@ -28,10 +29,13 @@ import { $getSelection, $isElementNode, $isRangeSelection, + $normalizeSelection__EXPERIMENTAL, + $setSelection, createCommand, ElementNode, Spread, } from 'lexical'; +import invariant from 'shared/invariant'; export type LinkAttributes = { rel?: null | string; @@ -477,6 +481,66 @@ export const TOGGLE_LINK_COMMAND: LexicalCommand< string | ({url: string} & LinkAttributes) | null > = createCommand('TOGGLE_LINK_COMMAND'); +function $getPointNode(point: Point, offset: number): LexicalNode | null { + if (point.type === 'element') { + const node = point.getNode(); + invariant( + $isElementNode(node), + '$getPointNode: element point is not an ElementNode', + ); + const childNode = node.getChildren()[point.offset + offset]; + return childNode || null; + } + return null; +} + +/** + * Preserve the logical start/end of a RangeSelection in situations where + * the point is an element that may be reparented in the callback. + * + * @param $fn The function to run + * @returns The result of the callback + */ +function $withSelectedNodes($fn: () => T): T { + const initialSelection = $getSelection(); + if (!$isRangeSelection(initialSelection)) { + return $fn(); + } + const normalized = $normalizeSelection__EXPERIMENTAL(initialSelection); + const isBackwards = normalized.isBackward(); + const anchorNode = $getPointNode(normalized.anchor, isBackwards ? -1 : 0); + const focusNode = $getPointNode(normalized.focus, isBackwards ? 0 : -1); + const rval = $fn(); + if (anchorNode || focusNode) { + const updatedSelection = $getSelection(); + if ($isRangeSelection(updatedSelection)) { + const finalSelection = updatedSelection.clone(); + if (anchorNode) { + const anchorParent = anchorNode.getParent(); + if (anchorParent) { + finalSelection.anchor.set( + anchorParent.getKey(), + anchorNode.getIndexWithinParent() + (isBackwards ? 1 : 0), + 'element', + ); + } + } + if (focusNode) { + const focusParent = focusNode.getParent(); + if (focusParent) { + finalSelection.focus.set( + focusParent.getKey(), + focusNode.getIndexWithinParent() + (isBackwards ? 0 : 1), + 'element', + ); + } + } + $setSelection($normalizeSelection__EXPERIMENTAL(finalSelection)); + } + } + return rval; +} + /** * Generates or updates a LinkNode. It can also delete a LinkNode if the URL is null, * but saves any children and brings them up to the parent node. @@ -515,93 +579,82 @@ export function $toggleLink( parentLink.remove(); } }); - } else { - // Add or merge LinkNodes - if (nodes.length === 1) { - const firstNode = nodes[0]; - // if the first node is a LinkNode or if its - // parent is a LinkNode, we update the URL, target and rel. - const linkNode = $getAncestor(firstNode, $isLinkNode); - if (linkNode !== null) { - linkNode.setURL(url); - if (target !== undefined) { - linkNode.setTarget(target); - } - if (rel !== null) { - linkNode.setRel(rel); - } - if (title !== undefined) { - linkNode.setTitle(title); - } - return; - } + return; + } + const updatedNodes = new Set(); + const updateLinkNode = (linkNode: LinkNode) => { + if (updatedNodes.has(linkNode.getKey())) { + return; + } + updatedNodes.add(linkNode.getKey()); + linkNode.setURL(url); + if (target !== undefined) { + linkNode.setTarget(target); + } + if (rel !== undefined) { + linkNode.setRel(rel); } + if (title !== undefined) { + linkNode.setTitle(title); + } + }; + // Add or merge LinkNodes + if (nodes.length === 1) { + const firstNode = nodes[0]; + // if the first node is a LinkNode or if its + // parent is a LinkNode, we update the URL, target and rel. + const linkNode = $getAncestor(firstNode, $isLinkNode); + if (linkNode !== null) { + return updateLinkNode(linkNode); + } + } - let prevParent: ElementNode | LinkNode | null = null; + $withSelectedNodes(() => { let linkNode: LinkNode | null = null; - - nodes.forEach((node) => { - const parent = node.getParent(); - - if ( - parent === linkNode || - parent === null || - ($isElementNode(node) && !node.isInline()) - ) { - return; + for (const node of nodes) { + if (!node.isAttached()) { + continue; } - - if ($isLinkNode(parent)) { - linkNode = parent; - parent.setURL(url); - if (target !== undefined) { - parent.setTarget(target); - } - if (rel !== null) { - linkNode.setRel(rel); - } - if (title !== undefined) { - linkNode.setTitle(title); - } - return; + const parentLinkNode = $getAncestor(node, $isLinkNode); + if (parentLinkNode) { + updateLinkNode(parentLinkNode); + continue; } - - if (!parent.is(prevParent)) { - prevParent = parent; - linkNode = $createLinkNode(url, {rel, target, title}); - - if ($isLinkNode(parent)) { - if (node.getPreviousSibling() === null) { - parent.insertBefore(linkNode); - } else { - parent.insertAfter(linkNode); - } - } else { - node.insertBefore(linkNode); - } - } - - if ($isLinkNode(node)) { - if (node.is(linkNode)) { - return; + if ($isElementNode(node)) { + if (!node.isInline()) { + // Ignore block nodes, if there are any children we will see them + // later and wrap in a new LinkNode + continue; } - if (linkNode !== null) { - const children = node.getChildren(); - - for (let i = 0; i < children.length; i++) { - linkNode.append(children[i]); + if ($isLinkNode(node)) { + // If it's not an autolink node and we don't already have a LinkNode + // in this block then we can update it and re-use it + if ( + !$isAutoLinkNode(node) && + (linkNode === null || !linkNode.getParentOrThrow().isParentOf(node)) + ) { + updateLinkNode(node); + linkNode = node; + continue; + } + // Unwrap LinkNode, we already have one or it's an AutoLinkNode + for (const child of node.getChildren()) { + node.insertBefore(child); } + node.remove(); + continue; } - - node.remove(); - return; } - - if (linkNode !== null) { - linkNode.append(node); + const prevLinkNode = node.getPreviousSibling(); + if ($isLinkNode(prevLinkNode) && prevLinkNode.is(linkNode)) { + prevLinkNode.append(node); + continue; } - }); - } + linkNode = $createLinkNode(url, {rel, target, title}); + node.insertAfter(linkNode); + linkNode.append(node); + } + }); } /** @deprecated renamed to {@link $toggleLink} by @lexical/eslint-plugin rules-of-lexical */ export const toggleLink = $toggleLink; From a9dfc9359c82a4105dd30c24adbb885e6b97ae25 Mon Sep 17 00:00:00 2001 From: Ajaezo Kingsley <54126417+Kingscliq@users.noreply.github.com> Date: Tue, 3 Dec 2024 05:30:36 +0100 Subject: [PATCH 02/25] [lexical-website][lexical-react] Documentation Update: documentation for LexicalTreeView plugin (#6898) --- .../lexical-react/src/LexicalTreeView.tsx | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/lexical-react/src/LexicalTreeView.tsx b/packages/lexical-react/src/LexicalTreeView.tsx index db536de589b..1b7057b27ef 100644 --- a/packages/lexical-react/src/LexicalTreeView.tsx +++ b/packages/lexical-react/src/LexicalTreeView.tsx @@ -18,13 +18,31 @@ import {mergeRegister} from '@lexical/utils'; import * as React from 'react'; import {useEffect, useState} from 'react'; +/** + * TreeView is a React component that provides a visual representation of + * the Lexical editor's state and enables debugging features like time travel + * and custom tree node rendering. + * + * @param {Object} props - The properties passed to the TreeView component. + * @param {LexicalEditor} props.editor - The Lexical editor instance to be visualized and debugged. + * @param {string} [props.treeTypeButtonClassName] - Custom class name for the tree type toggle button. + * @param {string} [props.timeTravelButtonClassName] - Custom class name for the time travel toggle button. + * @param {string} [props.timeTravelPanelButtonClassName] - Custom class name for buttons inside the time travel panel. + * @param {string} [props.timeTravelPanelClassName] - Custom class name for the overall time travel panel container. + * @param {string} [props.timeTravelPanelSliderClassName] - Custom class name for the time travel slider in the panel. + * @param {string} [props.viewClassName] - Custom class name for the tree view container. + * @param {CustomPrintNodeFn} [props.customPrintNode] - A function for customizing the display of nodes in the tree. + * + * @returns {JSX.Element} - A React element that visualizes the editor's state and supports debugging interactions. + */ + export function TreeView({ treeTypeButtonClassName, timeTravelButtonClassName, timeTravelPanelSliderClassName, timeTravelPanelButtonClassName, - viewClassName, timeTravelPanelClassName, + viewClassName, editor, customPrintNode, }: { @@ -38,6 +56,7 @@ export function TreeView({ customPrintNode?: CustomPrintNodeFn; }): JSX.Element { const treeElementRef = React.createRef(); + const [editorCurrentState, setEditorCurrentState] = useState( editor.getEditorState(), ); @@ -45,6 +64,7 @@ export function TreeView({ const commandsLog = useLexicalCommandsLog(editor); useEffect(() => { + // Registers listeners to update the tree view when the editor state changes return mergeRegister( editor.registerUpdateListener(({editorState}) => { setEditorCurrentState(editorState); @@ -59,16 +79,23 @@ export function TreeView({ const element = treeElementRef.current; if (element !== null) { - // @ts-ignore Internal field + // Assigns the editor instance to the tree view DOM element for internal tracking + // @ts-ignore Internal field used by Lexical element.__lexicalEditor = editor; return () => { - // @ts-ignore Internal field + // Cleans up the reference when the component is unmounted + // @ts-ignore Internal field used by Lexical element.__lexicalEditor = null; }; } }, [editor, treeElementRef]); + /** + * Handles toggling the readonly state of the editor. + * + * @param {boolean} isReadonly - Whether the editor should be set to readonly. + */ const handleEditorReadOnly = (isReadonly: boolean) => { const rootElement = editor.getRootElement(); if (rootElement == null) { @@ -90,6 +117,7 @@ export function TreeView({ editorState={editorCurrentState} setEditorState={(state) => editor.setEditorState(state)} generateContent={async function (exportDOM) { + // Generates the content for the tree view, allowing customization with exportDOM and customPrintNode return generateContent(editor, commandsLog, exportDOM, customPrintNode); }} ref={treeElementRef} From b7fa4cf673869dac0c2e0c1fe667e71e72ff6adb Mon Sep 17 00:00:00 2001 From: Maksim Horbachevsky Date: Tue, 3 Dec 2024 11:57:49 -0500 Subject: [PATCH 03/25] Warn about "display: flex" container for the editor (#6901) --- packages/lexical/src/LexicalEditor.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/lexical/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts index 02be308ba84..1a67571c1a0 100644 --- a/packages/lexical/src/LexicalEditor.ts +++ b/packages/lexical/src/LexicalEditor.ts @@ -1070,6 +1070,19 @@ export class LexicalEditor { if (classNames != null) { nextRootElement.classList.add(...classNames); } + if (__DEV__) { + const nextRootElementParent = nextRootElement.parentElement; + if ( + nextRootElementParent != null && + ['flex', 'inline-flex'].includes( + getComputedStyle(nextRootElementParent).display, + ) + ) { + console.warn( + `When using "display: flex" or "display: inline-flex" on an element containing content editable, Chrome may have unwanted focusing behavior when clicking outside of it. Consider wrapping the content editable within a non-flex element.`, + ); + } + } } else { // If content editable is unmounted we'll reset editor state back to original // (or pending) editor state since there will be no reconciliation From bccfeeece345a7bc00ac14ca2670cab3eb887f32 Mon Sep 17 00:00:00 2001 From: Oluwasanya Olaoluwa Date: Thu, 5 Dec 2024 08:22:47 +1100 Subject: [PATCH 04/25] [lexical-website] Fix: /docs/react/ "next" button links to itself (#6911) --- .../docs/react/create_plugin.md | 6 +----- packages/lexical-website/docs/react/faq.md | 15 ++++++--------- packages/lexical-website/docs/react/index.md | 8 ++------ packages/lexical-website/docs/react/plugins.md | 18 ++++++++---------- packages/lexical-website/sidebars.js | 11 ++++++----- 5 files changed, 23 insertions(+), 35 deletions(-) diff --git a/packages/lexical-website/docs/react/create_plugin.md b/packages/lexical-website/docs/react/create_plugin.md index 310d964672c..6ebf19263fe 100644 --- a/packages/lexical-website/docs/react/create_plugin.md +++ b/packages/lexical-website/docs/react/create_plugin.md @@ -1,7 +1,3 @@ ---- -sidebar_position: 2 ---- - # Creating a React Plugin In addition to using the Lexical React plugins offered by the core library, you can make your own plugins to extend or alter Lexical's functionality to suit your own use cases. @@ -18,7 +14,7 @@ If the Plugin introduces new nodes, they have to be registered in `initialConfig ```js const initialConfig = { - namespace: "MyEditor", + namespace: 'MyEditor', nodes: [MyLexicalNode], }; ``` diff --git a/packages/lexical-website/docs/react/faq.md b/packages/lexical-website/docs/react/faq.md index c98dce9be91..b63a2295e5a 100644 --- a/packages/lexical-website/docs/react/faq.md +++ b/packages/lexical-website/docs/react/faq.md @@ -1,6 +1,3 @@ ---- ---- - # React FAQ ## My app does not work in dev when using StrictMode, help!? @@ -14,18 +11,18 @@ conventions and guidelines. This is a great place to start: Some Lexical-specific concerns (which are consequences of React's concurrent and StrictMode semantics, not due to anything unusual in Lexical): -* In React 19, `useMemo` calls are cached across StrictMode re-renders, so +- In React 19, `useMemo` calls are cached across StrictMode re-renders, so only one editor will be used for both renders. If you have a `useEffect` call with side-effects (such as updating the document when a plug-in initializes), then you should first check to make sure that this effect has not already occurred (e.g. by checking the state of the document or undoing the change as a cleanup function returned by the effect) -* `LexicalComposer`'s initialConfig prop is only considered once during +- `LexicalComposer`'s initialConfig prop is only considered once during the first render (`useMemo` is used to create the `LexicalComposerContext` which includes the editor and theme) -* If you are using an `editorState` argument in the config when creating the +- If you are using an `editorState` argument in the config when creating the editor, it will only be called once when the editor is created. -* You should generally prefer to use hooks that return state such as +- You should generally prefer to use hooks that return state such as `useLexicalEditable` (`useLexicalSubscription` is a generalization of this style) rather than manually registering the listeners and expecting a particular sequence of triggers to be called, especially @@ -45,10 +42,10 @@ build of Lexical that the hook was imported from. The most common root causes of this issue are: -* You are trying to use `useLexicalComposerContext()` in a component that is +- You are trying to use `useLexicalComposerContext()` in a component that is not a child of the `LexicalComposer`. If you need to do that, you need to pass the context or editor up the tree with something like `EditorRefPlugin`. -* You have multiple builds of Lexical in your project. This could be because +- You have multiple builds of Lexical in your project. This could be because you have a dependency that has a direct dependency on some other version of Lexical (these packages should have Lexical as `peerDependencies`, but not all do), or because your project mixes import and require statements diff --git a/packages/lexical-website/docs/react/index.md b/packages/lexical-website/docs/react/index.md index b0150b10116..1b811e5b517 100644 --- a/packages/lexical-website/docs/react/index.md +++ b/packages/lexical-website/docs/react/index.md @@ -1,9 +1,5 @@ --- -id: "index" -title: "Lexical API" -sidebar_label: "Introduction" -sidebar_position: 0 -custom_edit_url: null +sidebar_label: 'Introduction' --- # Lexical + React @@ -13,7 +9,7 @@ To make it easier for React users to implement rich-text editors, Lexical expose - {`Getting Started Guide`} +{`Getting Started Guide`} ## Supported Versions diff --git a/packages/lexical-website/docs/react/plugins.md b/packages/lexical-website/docs/react/plugins.md index c40f8c22251..3f6d24cc9d5 100644 --- a/packages/lexical-website/docs/react/plugins.md +++ b/packages/lexical-website/docs/react/plugins.md @@ -1,7 +1,3 @@ ---- -sidebar_position: 1 ---- - # Lexical Plugins React-based plugins are using Lexical editor instance from `` context: @@ -29,7 +25,7 @@ const initialConfig = { ... - +; ``` > Note: Many plugins might require you to register the one or many Lexical nodes in order for the plugin to work. You can do this by passing a reference to the node to the `nodes` array in your initial editor configuration. @@ -184,7 +180,9 @@ In order to use `TableOfContentsPlugin`, you need to pass a callback function in ```jsx {(tableOfContentsArray) => { - return ; + return ( + + ); }} ``` @@ -195,8 +193,8 @@ Allows you to get a ref to the underlying editor instance outside of LexicalComp from a separate part of your application. ```jsx - const editorRef = useRef(null); - +const editorRef = useRef(null); +; ``` ### `LexicalSelectionAlwaysOnDisplay` @@ -204,5 +202,5 @@ from a separate part of your application. By default, browser text selection becomes invisible when clicking away from the editor. This plugin ensures the selection remains visible. ```jsx - -``` \ No newline at end of file + +``` diff --git a/packages/lexical-website/sidebars.js b/packages/lexical-website/sidebars.js index d1f61b1374a..523933de4c2 100644 --- a/packages/lexical-website/sidebars.js +++ b/packages/lexical-website/sidebars.js @@ -71,12 +71,13 @@ const sidebars = { type: 'category', }, { - items: [{dirName: 'react', type: 'autogenerated'}], + items: [ + 'react/index', + 'react/plugins', + 'react/create_plugin', + 'react/faq', + ], label: 'React', - link: { - id: 'react/index', - type: 'doc', - }, type: 'category', }, { From 97481c9d1035c8759f2b94897847e7d0e2b0c12a Mon Sep 17 00:00:00 2001 From: Bob Ippolito Date: Fri, 6 Dec 2024 06:56:51 -0800 Subject: [PATCH 05/25] [lexical-table][lexical-utils][lexical-react]: Bug Fix: Enforce table integrity with transforms and move non-React plugin code to @lexical/table (#6914) --- .../__tests__/e2e/Tables.spec.mjs | 12 +- .../src/plugins/TablePlugin.tsx | 46 +-- .../lexical-react/src/LexicalTablePlugin.ts | 211 +----------- .../src/shared/useCharacterLimit.ts | 14 +- .../lexical-table/flow/LexicalTable.js.flow | 14 +- .../lexical-table/src/LexicalTableCellNode.ts | 2 +- .../lexical-table/src/LexicalTableNode.ts | 9 +- .../src/LexicalTablePluginHelpers.ts | 275 +++++++++++++++ .../lexical-table/src/LexicalTableRowNode.ts | 8 +- .../__tests__/unit/LexicalTableNode.test.tsx | 312 ++++++++++++++++++ packages/lexical-table/src/index.ts | 5 + .../lexical-utils/flow/LexicalUtils.js.flow | 11 + .../unit/descendantsMatching.test.tsx | 84 +++++ .../src/__tests__/unit/iterators.test.tsx | 216 ++++++++++++ .../unit/unwrapAndFilterDescendants.test.tsx | 101 ++++++ packages/lexical-utils/src/index.ts | 155 +++++++++ .../src/__tests__/unit/LexicalEditor.test.tsx | 4 +- 17 files changed, 1217 insertions(+), 262 deletions(-) create mode 100644 packages/lexical-table/src/LexicalTablePluginHelpers.ts create mode 100644 packages/lexical-utils/src/__tests__/unit/descendantsMatching.test.tsx create mode 100644 packages/lexical-utils/src/__tests__/unit/iterators.test.tsx create mode 100644 packages/lexical-utils/src/__tests__/unit/unwrapAndFilterDescendants.test.tsx diff --git a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs index 4fd7ca25b30..1cf4e25a7b1 100644 --- a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs @@ -3737,7 +3737,9 @@ test.describe.parallel('Tables', () => { Hello world

-
+ +


+

{ -
-
+ +


+ + +


+

; -export const INSERT_NEW_TABLE_COMMAND: LexicalCommand = - createCommand('INSERT_NEW_TABLE_COMMAND'); - export const CellContext = createContext({ cellEditorConfig: null, cellEditorPlugins: null, @@ -155,28 +143,16 @@ export function TablePlugin({ }): JSX.Element | null { const [editor] = useLexicalComposerContext(); const cellContext = useContext(CellContext); - useEffect(() => { - if (!editor.hasNodes([TableNode])) { - invariant(false, 'TablePlugin: TableNode is not registered on editor'); + if (!editor.hasNodes([TableNode, TableRowNode, TableCellNode])) { + invariant( + false, + 'TablePlugin: TableNode, TableRowNode, or TableCellNode is not registered on editor', + ); } - + }, [editor]); + useEffect(() => { cellContext.set(cellEditorConfig, children); - - return editor.registerCommand( - INSERT_NEW_TABLE_COMMAND, - ({columns, rows, includeHeaders}) => { - const tableNode = $createTableNodeWithDimensions( - Number(rows), - Number(columns), - includeHeaders, - ); - $insertNodes([tableNode]); - return true; - }, - COMMAND_PRIORITY_EDITOR, - ); - }, [cellContext, cellEditorConfig, children, editor]); - + }, [cellContext, cellEditorConfig, children]); return null; } diff --git a/packages/lexical-react/src/LexicalTablePlugin.ts b/packages/lexical-react/src/LexicalTablePlugin.ts index a5c43d17c65..7d91b0b576c 100644 --- a/packages/lexical-react/src/LexicalTablePlugin.ts +++ b/packages/lexical-react/src/LexicalTablePlugin.ts @@ -6,43 +6,15 @@ * */ -import type { - HTMLTableElementWithWithTableSelectionState, - InsertTableCommandPayload, - TableObserver, -} from '@lexical/table'; -import type {NodeKey} from 'lexical'; - import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import { - $computeTableMap, - $computeTableMapSkipCellCheck, - $createTableCellNode, - $createTableNodeWithDimensions, - $getNodeTriplet, - $getTableAndElementByKey, - $isTableCellNode, - $isTableRowNode, - applyTableHandlers, - getTableElement, - INSERT_TABLE_COMMAND, + registerTableCellUnmergeTransform, + registerTablePlugin, + registerTableSelectionObserver, setScrollableTablesActive, TableCellNode, - TableNode, - TableRowNode, } from '@lexical/table'; -import { - $insertFirst, - $insertNodeToNearestRoot, - mergeRegister, -} from '@lexical/utils'; -import { - $createParagraphNode, - $isTextNode, - COMMAND_PRIORITY_EDITOR, -} from 'lexical'; import {useEffect} from 'react'; -import invariant from 'shared/invariant'; export interface TablePluginProps { /** @@ -82,181 +54,18 @@ export function TablePlugin({ setScrollableTablesActive(editor, hasHorizontalScroll); }, [editor, hasHorizontalScroll]); - useEffect(() => { - if (!editor.hasNodes([TableNode, TableCellNode, TableRowNode])) { - invariant( - false, - 'TablePlugin: TableNode, TableCellNode or TableRowNode not registered on editor', - ); - } - - return mergeRegister( - editor.registerCommand( - INSERT_TABLE_COMMAND, - ({columns, rows, includeHeaders}) => { - const tableNode = $createTableNodeWithDimensions( - Number(rows), - Number(columns), - includeHeaders, - ); - $insertNodeToNearestRoot(tableNode); - - const firstDescendant = tableNode.getFirstDescendant(); - if ($isTextNode(firstDescendant)) { - firstDescendant.select(); - } - - return true; - }, - COMMAND_PRIORITY_EDITOR, - ), - editor.registerNodeTransform(TableNode, (node) => { - const [gridMap] = $computeTableMapSkipCellCheck(node, null, null); - const maxRowLength = gridMap.reduce((curLength, row) => { - return Math.max(curLength, row.length); - }, 0); - const rowNodes = node.getChildren(); - for (let i = 0; i < gridMap.length; ++i) { - const rowNode = rowNodes[i]; - if (!rowNode) { - continue; - } - const rowLength = gridMap[i].reduce( - (acc, cell) => (cell ? 1 + acc : acc), - 0, - ); - if (rowLength === maxRowLength) { - continue; - } - for (let j = rowLength; j < maxRowLength; ++j) { - // TODO: inherit header state from another header or body - const newCell = $createTableCellNode(0); - newCell.append($createParagraphNode()); - (rowNode as TableRowNode).append(newCell); - } - } - }), - ); - }, [editor]); + useEffect(() => registerTablePlugin(editor), [editor]); - useEffect(() => { - const tableSelections = new Map< - NodeKey, - [TableObserver, HTMLTableElementWithWithTableSelectionState] - >(); - - const initializeTableNode = ( - tableNode: TableNode, - nodeKey: NodeKey, - dom: HTMLElement, - ) => { - const tableElement = getTableElement(tableNode, dom); - const tableSelection = applyTableHandlers( - tableNode, - tableElement, - editor, - hasTabHandler, - ); - tableSelections.set(nodeKey, [tableSelection, tableElement]); - }; - - const unregisterMutationListener = editor.registerMutationListener( - TableNode, - (nodeMutations) => { - editor.getEditorState().read( - () => { - for (const [nodeKey, mutation] of nodeMutations) { - const tableSelection = tableSelections.get(nodeKey); - if (mutation === 'created' || mutation === 'updated') { - const {tableNode, tableElement} = - $getTableAndElementByKey(nodeKey); - if (tableSelection === undefined) { - initializeTableNode(tableNode, nodeKey, tableElement); - } else if (tableElement !== tableSelection[1]) { - // The update created a new DOM node, destroy the existing TableObserver - tableSelection[0].removeListeners(); - tableSelections.delete(nodeKey); - initializeTableNode(tableNode, nodeKey, tableElement); - } - } else if (mutation === 'destroyed') { - if (tableSelection !== undefined) { - tableSelection[0].removeListeners(); - tableSelections.delete(nodeKey); - } - } - } - }, - {editor}, - ); - }, - {skipInitialization: false}, - ); - - return () => { - unregisterMutationListener(); - // Hook might be called multiple times so cleaning up tables listeners as well, - // as it'll be reinitialized during recurring call - for (const [, [tableSelection]] of tableSelections) { - tableSelection.removeListeners(); - } - }; - }, [editor, hasTabHandler]); + useEffect( + () => registerTableSelectionObserver(editor, hasTabHandler), + [editor, hasTabHandler], + ); // Unmerge cells when the feature isn't enabled useEffect(() => { - if (hasCellMerge) { - return; + if (!hasCellMerge) { + return registerTableCellUnmergeTransform(editor); } - return editor.registerNodeTransform(TableCellNode, (node) => { - if (node.getColSpan() > 1 || node.getRowSpan() > 1) { - // When we have rowSpan we have to map the entire Table to understand where the new Cells - // fit best; let's analyze all Cells at once to save us from further transform iterations - const [, , gridNode] = $getNodeTriplet(node); - const [gridMap] = $computeTableMap(gridNode, node, node); - // TODO this function expects Tables to be normalized. Look into this once it exists - const rowsCount = gridMap.length; - const columnsCount = gridMap[0].length; - let row = gridNode.getFirstChild(); - invariant( - $isTableRowNode(row), - 'Expected TableNode first child to be a RowNode', - ); - const unmerged = []; - for (let i = 0; i < rowsCount; i++) { - if (i !== 0) { - row = row.getNextSibling(); - invariant( - $isTableRowNode(row), - 'Expected TableNode first child to be a RowNode', - ); - } - let lastRowCell: null | TableCellNode = null; - for (let j = 0; j < columnsCount; j++) { - const cellMap = gridMap[i][j]; - const cell = cellMap.cell; - if (cellMap.startRow === i && cellMap.startColumn === j) { - lastRowCell = cell; - unmerged.push(cell); - } else if (cell.getColSpan() > 1 || cell.getRowSpan() > 1) { - invariant( - $isTableCellNode(cell), - 'Expected TableNode cell to be a TableCellNode', - ); - const newCell = $createTableCellNode(cell.__headerState); - if (lastRowCell !== null) { - lastRowCell.insertAfter(newCell); - } else { - $insertFirst(row, newCell); - } - } - } - } - for (const cell of unmerged) { - cell.setColSpan(1); - cell.setRowSpan(1); - } - } - }); }, [editor, hasCellMerge]); // Remove cell background color when feature is disabled diff --git a/packages/lexical-react/src/shared/useCharacterLimit.ts b/packages/lexical-react/src/shared/useCharacterLimit.ts index 8e1e4f813c0..75d8e58040b 100644 --- a/packages/lexical-react/src/shared/useCharacterLimit.ts +++ b/packages/lexical-react/src/shared/useCharacterLimit.ts @@ -14,7 +14,7 @@ import { OverflowNode, } from '@lexical/overflow'; import {$rootTextContent} from '@lexical/text'; -import {$dfs, mergeRegister} from '@lexical/utils'; +import {$dfs, $unwrapNode, mergeRegister} from '@lexical/utils'; import { $getSelection, $isElementNode, @@ -254,18 +254,6 @@ function $wrapNode(node: LexicalNode): OverflowNode { return overflowNode; } -function $unwrapNode(node: OverflowNode): LexicalNode | null { - const children = node.getChildren(); - const childrenLength = children.length; - - for (let i = 0; i < childrenLength; i++) { - node.insertBefore(children[i]); - } - - node.remove(); - return childrenLength > 0 ? children[childrenLength - 1] : null; -} - export function $mergePrevious(overflowNode: OverflowNode): void { const previousNode = overflowNode.getPreviousSibling(); diff --git a/packages/lexical-table/flow/LexicalTable.js.flow b/packages/lexical-table/flow/LexicalTable.js.flow index 2674a125f50..0d3af559ed3 100644 --- a/packages/lexical-table/flow/LexicalTable.js.flow +++ b/packages/lexical-table/flow/LexicalTable.js.flow @@ -75,7 +75,7 @@ declare export class TableCellNode extends ElementNode { canBeEmpty(): false; } declare export function $createTableCellNode( - headerState: TableCellHeaderState, + headerState?: TableCellHeaderState, colSpan?: number, width?: ?number, ): TableCellNode; @@ -350,4 +350,14 @@ export type InsertTableCommandPayload = $ReadOnly<{ includeHeaders?: InsertTableCommandPayloadHeaders; }>; -declare export var INSERT_TABLE_COMMAND: LexicalCommand; \ No newline at end of file +declare export var INSERT_TABLE_COMMAND: LexicalCommand; + +/** + * LexicalTablePluginHelpers + */ + +declare export function registerTableCellUnmergeTransform(editor: LexicalEditor): () => void; + +declare export function registerTablePlugin(editor: LexicalEditor): () => void; + +declare export function registerTableSelectionObserver(editor: LexicalEditor, hasTabHandler?: boolean): () => void; diff --git a/packages/lexical-table/src/LexicalTableCellNode.ts b/packages/lexical-table/src/LexicalTableCellNode.ts index 2b00b8dfb6e..795779c4990 100644 --- a/packages/lexical-table/src/LexicalTableCellNode.ts +++ b/packages/lexical-table/src/LexicalTableCellNode.ts @@ -366,7 +366,7 @@ export function $convertTableCellNodeElement( } export function $createTableCellNode( - headerState: TableCellHeaderState, + headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS, colSpan = 1, width?: number, ): TableCellNode { diff --git a/packages/lexical-table/src/LexicalTableNode.ts b/packages/lexical-table/src/LexicalTableNode.ts index 4a4a2c970fa..636613346b3 100644 --- a/packages/lexical-table/src/LexicalTableNode.ts +++ b/packages/lexical-table/src/LexicalTableNode.ts @@ -6,9 +6,8 @@ * */ -import type {TableRowNode} from './LexicalTableRowNode'; - import { + $descendantsMatching, addClassNamesToElement, isHTMLElement, removeClassNamesFromElement, @@ -36,6 +35,7 @@ import invariant from 'shared/invariant'; import {PIXEL_VALUE_REG_EXP} from './constants'; import {$isTableCellNode, type TableCellNode} from './LexicalTableCellNode'; import {TableDOMCell, TableDOMTable} from './LexicalTableObserver'; +import {$isTableRowNode, type TableRowNode} from './LexicalTableRowNode'; import { $getNearestTableCellInTableFromDOMNode, getTable, @@ -498,7 +498,10 @@ export function $convertTableElement( tableNode.setColWidths(columns); } } - return {node: tableNode}; + return { + after: (children) => $descendantsMatching(children, $isTableRowNode), + node: tableNode, + }; } export function $createTableNode(): TableNode { diff --git a/packages/lexical-table/src/LexicalTablePluginHelpers.ts b/packages/lexical-table/src/LexicalTablePluginHelpers.ts new file mode 100644 index 00000000000..ae7ee4547e2 --- /dev/null +++ b/packages/lexical-table/src/LexicalTablePluginHelpers.ts @@ -0,0 +1,275 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + $insertFirst, + $insertNodeToNearestRoot, + $unwrapAndFilterDescendants, + mergeRegister, +} from '@lexical/utils'; +import { + $createParagraphNode, + $isTextNode, + COMMAND_PRIORITY_EDITOR, + LexicalEditor, + NodeKey, +} from 'lexical'; +import invariant from 'shared/invariant'; + +import { + $createTableCellNode, + $isTableCellNode, + TableCellNode, +} from './LexicalTableCellNode'; +import { + INSERT_TABLE_COMMAND, + InsertTableCommandPayload, +} from './LexicalTableCommands'; +import {$isTableNode, TableNode} from './LexicalTableNode'; +import {$getTableAndElementByKey, TableObserver} from './LexicalTableObserver'; +import {$isTableRowNode, TableRowNode} from './LexicalTableRowNode'; +import { + applyTableHandlers, + getTableElement, + HTMLTableElementWithWithTableSelectionState, +} from './LexicalTableSelectionHelpers'; +import { + $computeTableMap, + $computeTableMapSkipCellCheck, + $createTableNodeWithDimensions, + $getNodeTriplet, +} from './LexicalTableUtils'; + +function $insertTableCommandListener({ + rows, + columns, + includeHeaders, +}: InsertTableCommandPayload): boolean { + const tableNode = $createTableNodeWithDimensions( + Number(rows), + Number(columns), + includeHeaders, + ); + $insertNodeToNearestRoot(tableNode); + + const firstDescendant = tableNode.getFirstDescendant(); + if ($isTextNode(firstDescendant)) { + firstDescendant.select(); + } + + return true; +} + +function $tableCellTransform(node: TableCellNode) { + if (!$isTableRowNode(node.getParent())) { + // TableCellNode must be a child of TableRowNode. + node.remove(); + } else if (node.isEmpty()) { + // TableCellNode should never be empty + node.append($createParagraphNode()); + } +} + +function $tableRowTransform(node: TableRowNode) { + if (!$isTableNode(node.getParent())) { + // TableRowNode must be a child of TableNode. + // TODO: Future support of tbody/thead/tfoot may change this + node.remove(); + } else { + $unwrapAndFilterDescendants(node, $isTableCellNode); + } +} + +function $tableTransform(node: TableNode) { + // TableRowNode is the only valid child for TableNode + // TODO: Future support of tbody/thead/tfoot/caption may change this + $unwrapAndFilterDescendants(node, $isTableRowNode); + + const [gridMap] = $computeTableMapSkipCellCheck(node, null, null); + const maxRowLength = gridMap.reduce((curLength, row) => { + return Math.max(curLength, row.length); + }, 0); + const rowNodes = node.getChildren(); + for (let i = 0; i < gridMap.length; ++i) { + const rowNode = rowNodes[i]; + if (!rowNode) { + continue; + } + invariant( + $isTableRowNode(rowNode), + 'TablePlugin: Expecting all children of TableNode to be TableRowNode, found %s (type %s)', + rowNode.constructor.name, + rowNode.getType(), + ); + const rowLength = gridMap[i].reduce( + (acc, cell) => (cell ? 1 + acc : acc), + 0, + ); + if (rowLength === maxRowLength) { + continue; + } + for (let j = rowLength; j < maxRowLength; ++j) { + // TODO: inherit header state from another header or body + const newCell = $createTableCellNode(); + newCell.append($createParagraphNode()); + rowNode.append(newCell); + } + } +} + +/** + * Register a transform to ensure that all TableCellNode have a colSpan and rowSpan of 1. + * This should only be registered when you do not want to support merged cells. + * + * @param editor The editor + * @returns An unregister callback + */ +export function registerTableCellUnmergeTransform( + editor: LexicalEditor, +): () => void { + return editor.registerNodeTransform(TableCellNode, (node) => { + if (node.getColSpan() > 1 || node.getRowSpan() > 1) { + // When we have rowSpan we have to map the entire Table to understand where the new Cells + // fit best; let's analyze all Cells at once to save us from further transform iterations + const [, , gridNode] = $getNodeTriplet(node); + const [gridMap] = $computeTableMap(gridNode, node, node); + // TODO this function expects Tables to be normalized. Look into this once it exists + const rowsCount = gridMap.length; + const columnsCount = gridMap[0].length; + let row = gridNode.getFirstChild(); + invariant( + $isTableRowNode(row), + 'Expected TableNode first child to be a RowNode', + ); + const unmerged = []; + for (let i = 0; i < rowsCount; i++) { + if (i !== 0) { + row = row.getNextSibling(); + invariant( + $isTableRowNode(row), + 'Expected TableNode first child to be a RowNode', + ); + } + let lastRowCell: null | TableCellNode = null; + for (let j = 0; j < columnsCount; j++) { + const cellMap = gridMap[i][j]; + const cell = cellMap.cell; + if (cellMap.startRow === i && cellMap.startColumn === j) { + lastRowCell = cell; + unmerged.push(cell); + } else if (cell.getColSpan() > 1 || cell.getRowSpan() > 1) { + invariant( + $isTableCellNode(cell), + 'Expected TableNode cell to be a TableCellNode', + ); + const newCell = $createTableCellNode(cell.__headerState); + if (lastRowCell !== null) { + lastRowCell.insertAfter(newCell); + } else { + $insertFirst(row, newCell); + } + } + } + } + for (const cell of unmerged) { + cell.setColSpan(1); + cell.setRowSpan(1); + } + } + }); +} + +export function registerTableSelectionObserver( + editor: LexicalEditor, + hasTabHandler: boolean = true, +): () => void { + const tableSelections = new Map< + NodeKey, + [TableObserver, HTMLTableElementWithWithTableSelectionState] + >(); + + const initializeTableNode = ( + tableNode: TableNode, + nodeKey: NodeKey, + dom: HTMLElement, + ) => { + const tableElement = getTableElement(tableNode, dom); + const tableSelection = applyTableHandlers( + tableNode, + tableElement, + editor, + hasTabHandler, + ); + tableSelections.set(nodeKey, [tableSelection, tableElement]); + }; + + const unregisterMutationListener = editor.registerMutationListener( + TableNode, + (nodeMutations) => { + editor.getEditorState().read( + () => { + for (const [nodeKey, mutation] of nodeMutations) { + const tableSelection = tableSelections.get(nodeKey); + if (mutation === 'created' || mutation === 'updated') { + const {tableNode, tableElement} = + $getTableAndElementByKey(nodeKey); + if (tableSelection === undefined) { + initializeTableNode(tableNode, nodeKey, tableElement); + } else if (tableElement !== tableSelection[1]) { + // The update created a new DOM node, destroy the existing TableObserver + tableSelection[0].removeListeners(); + tableSelections.delete(nodeKey); + initializeTableNode(tableNode, nodeKey, tableElement); + } + } else if (mutation === 'destroyed') { + if (tableSelection !== undefined) { + tableSelection[0].removeListeners(); + tableSelections.delete(nodeKey); + } + } + } + }, + {editor}, + ); + }, + {skipInitialization: false}, + ); + + return () => { + unregisterMutationListener(); + // Hook might be called multiple times so cleaning up tables listeners as well, + // as it'll be reinitialized during recurring call + for (const [, [tableSelection]] of tableSelections) { + tableSelection.removeListeners(); + } + }; +} + +/** + * Register the INSERT_TABLE_COMMAND listener and the table integrity transforms. The + * table selection observer should be registered separately after this with + * {@link registerTableSelectionObserver}. + * + * @param editor The editor + * @returns An unregister callback + */ +export function registerTablePlugin(editor: LexicalEditor): () => void { + if (!editor.hasNodes([TableNode])) { + invariant(false, 'TablePlugin: TableNode is not registered on editor'); + } + return mergeRegister( + editor.registerCommand( + INSERT_TABLE_COMMAND, + $insertTableCommandListener, + COMMAND_PRIORITY_EDITOR, + ), + editor.registerNodeTransform(TableNode, $tableTransform), + editor.registerNodeTransform(TableRowNode, $tableRowTransform), + editor.registerNodeTransform(TableCellNode, $tableCellTransform), + ); +} diff --git a/packages/lexical-table/src/LexicalTableRowNode.ts b/packages/lexical-table/src/LexicalTableRowNode.ts index fd8bcb8fa0a..9a7d5c99c88 100644 --- a/packages/lexical-table/src/LexicalTableRowNode.ts +++ b/packages/lexical-table/src/LexicalTableRowNode.ts @@ -8,7 +8,7 @@ import type {BaseSelection, Spread} from 'lexical'; -import {addClassNamesToElement} from '@lexical/utils'; +import {$descendantsMatching, addClassNamesToElement} from '@lexical/utils'; import { $applyNodeReplacement, DOMConversionMap, @@ -21,6 +21,7 @@ import { } from 'lexical'; import {PIXEL_VALUE_REG_EXP} from './constants'; +import {$isTableCellNode} from './LexicalTableCellNode'; export type SerializedTableRowNode = Spread< { @@ -124,7 +125,10 @@ export function $convertTableRowElement(domNode: Node): DOMConversionOutput { height = parseFloat(domNode_.style.height); } - return {node: $createTableRowNode(height)}; + return { + after: (children) => $descendantsMatching(children, $isTableCellNode), + node: $createTableRowNode(height), + }; } export function $createTableRowNode(height?: number): TableRowNode { diff --git a/packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx b/packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx index 96ca3c7e426..38755a841e2 100644 --- a/packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx +++ b/packages/lexical-table/src/__tests__/unit/LexicalTableNode.test.tsx @@ -317,6 +317,318 @@ describe('LexicalTableNode tests', () => { ); }); + test('Copy table with caption/tbody/thead/tfoot from an external source', async () => { + const {editor} = testEnv; + + const dataTransfer = new DataTransferMock(); + dataTransfer.setData( + 'text/html', + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead + html` + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Council budget (in £) 2018 +
+ Items + + Expenditure +
+ Donuts + + 3,000 +
+ Stationery + + 18,000 +
+ Totals + + 21,000 +
+ `, + ); + await editor.update(() => { + const selection = $getSelection(); + invariant( + $isRangeSelection(selection), + 'isRangeSelection(selection)', + ); + $insertDataTransferForRichText(dataTransfer, selection, editor); + }); + // Here we are testing the createDOM, not the exportDOM, so the tbody is not there + expectTableHtmlToBeEqual( + testEnv.innerHTML, + html` + + + + + + + + + + + + + + + + + + + + + +
+

+ Items +

+
+

+ Expenditure +

+
+

+ Donuts +

+
+

+ 3,000 +

+
+

+ Stationery +

+
+

+ 18,000 +

+
+

+ Totals +

+
+

+ 21,000 +

+
+ `, + ); + }); + + test('Copy table with caption from an external source', async () => { + const {editor} = testEnv; + + const dataTransfer = new DataTransferMock(); + dataTransfer.setData( + 'text/html', + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption + html` + + + + + + + + + + + + + + + + + + + + + + + + + +
+ He-Man and Skeletor facts +
+ He-Man + + Skeletor +
+ Role + + Hero + + Villain +
+ Weapon + + Power Sword + + Havoc Staff +
+ Dark secret + + Expert florist + + Cries at romcoms +
+ `, + ); + await editor.update(() => { + const selection = $getSelection(); + invariant( + $isRangeSelection(selection), + 'isRangeSelection(selection)', + ); + $insertDataTransferForRichText(dataTransfer, selection, editor); + }); + // Here we are testing the createDOM, not the exportDOM, so the tbody is not there + expectTableHtmlToBeEqual( + testEnv.innerHTML, + html` + + + + + + + + + + + + + + + + + + + + + + + + + + +
+


+
+

+ He-Man +

+
+

+ Skeletor +

+
+

+ Role +

+
+

+ Hero +

+
+

+ Villain +

+
+

+ Weapon +

+
+

+ Power Sword +

+
+

+ Havoc Staff +

+
+

+ Dark secret +

+
+

+ Expert florist +

+
+

+ Cries at romcoms +

+
+ `, + ); + }); + test('Copy table from an external source like gdoc with formatting', async () => { const {editor} = testEnv; diff --git a/packages/lexical-table/src/index.ts b/packages/lexical-table/src/index.ts index be452681b98..c4fe6ace096 100644 --- a/packages/lexical-table/src/index.ts +++ b/packages/lexical-table/src/index.ts @@ -29,6 +29,11 @@ export { } from './LexicalTableNode'; export type {TableDOMCell} from './LexicalTableObserver'; export {$getTableAndElementByKey, TableObserver} from './LexicalTableObserver'; +export { + registerTableCellUnmergeTransform, + registerTablePlugin, + registerTableSelectionObserver, +} from './LexicalTablePluginHelpers'; export type {SerializedTableRowNode} from './LexicalTableRowNode'; export { $createTableRowNode, diff --git a/packages/lexical-utils/flow/LexicalUtils.js.flow b/packages/lexical-utils/flow/LexicalUtils.js.flow index 11524eee950..958dd8acfa7 100644 --- a/packages/lexical-utils/flow/LexicalUtils.js.flow +++ b/packages/lexical-utils/flow/LexicalUtils.js.flow @@ -125,3 +125,14 @@ declare export function $splitNode( declare export function calculateZoomLevel(element: Element | null): number; declare export function $isEditorIsNestedEditor(editor: LexicalEditor): boolean; + +declare export function $unwrapAndFilterDescendants( + root: ElementNode, + $predicate: (node: LexicalNode) => boolean, +): boolean; + +declare export function $firstToLastIterator(node: ElementNode): Iterable; + +declare export function $lastToFirstIterator(node: ElementNode): Iterable; + +declare export function $unwrapNode(node: ElementNode): void; diff --git a/packages/lexical-utils/src/__tests__/unit/descendantsMatching.test.tsx b/packages/lexical-utils/src/__tests__/unit/descendantsMatching.test.tsx new file mode 100644 index 00000000000..3a4e596626f --- /dev/null +++ b/packages/lexical-utils/src/__tests__/unit/descendantsMatching.test.tsx @@ -0,0 +1,84 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import type {Klass, LexicalEditor, LexicalNode} from 'lexical'; + +import {$descendantsMatching} from '@lexical/utils'; +import { + $createParagraphNode, + $createTextNode, + $getRoot, + $isTextNode, + ParagraphNode, +} from 'lexical'; +import {createTestEditor} from 'lexical/src/__tests__/utils'; + +function assertClass(v: unknown, klass: Klass): T { + if (v instanceof klass) { + return v as T; + } + throw new Error(`Value does not extend ${klass.name}`); +} + +function $createTextAndParagraphWithDepth(depth: number): LexicalNode[] { + if (depth <= 0) { + return [$createTextNode(`<${depth} />`)]; + } + return [ + $createTextNode(`<${depth}>`), + $createParagraphNode().append( + ...$createTextAndParagraphWithDepth(depth - 1), + ), + $createTextNode(``), + ]; +} + +function textContentForDepth(i: number): string { + return i > 0 ? `<${i}>${textContentForDepth(i - 1)}` : `<${i} />`; +} + +describe('$descendantsMatching', () => { + let editor: LexicalEditor; + + beforeEach(async () => { + editor = createTestEditor(); + editor._headless = true; + }); + + [0, 1, 2].forEach((depth) => + it(`Can un-nest children at depth ${depth}`, () => { + editor.update( + () => { + const firstNode = $createParagraphNode(); + $getRoot() + .clear() + .append( + firstNode.append(...$createTextAndParagraphWithDepth(depth)), + ); + }, + {discrete: true}, + ); + editor.update( + () => { + const firstNode = assertClass( + $getRoot().getFirstChildOrThrow(), + ParagraphNode, + ); + expect(firstNode.getChildren().every($isTextNode)).toBe(depth === 0); + firstNode.splice( + 0, + firstNode.getChildrenSize(), + $descendantsMatching(firstNode.getChildren(), $isTextNode), + ); + expect(firstNode.getChildren().every($isTextNode)).toBe(true); + expect(firstNode.getTextContent()).toBe(textContentForDepth(depth)); + }, + {discrete: true}, + ); + }), + ); +}); diff --git a/packages/lexical-utils/src/__tests__/unit/iterators.test.tsx b/packages/lexical-utils/src/__tests__/unit/iterators.test.tsx new file mode 100644 index 00000000000..f7042e14e0e --- /dev/null +++ b/packages/lexical-utils/src/__tests__/unit/iterators.test.tsx @@ -0,0 +1,216 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import type {Klass, LexicalEditor, LexicalNode} from 'lexical'; + +import {$firstToLastIterator, $lastToFirstIterator} from '@lexical/utils'; +import {$createParagraphNode, $createTextNode, TextNode} from 'lexical'; +import {createTestEditor} from 'lexical/src/__tests__/utils'; + +function assertClass(v: unknown, klass: Klass): T { + if (v instanceof klass) { + return v as T; + } + throw new Error(`Value does not extend ${klass.name}`); +} + +describe('$firstToLastIterator', () => { + let editor: LexicalEditor; + + beforeEach(async () => { + editor = createTestEditor(); + editor._headless = true; + }); + + it(`Iterates from first to last`, () => { + editor.update( + () => { + const parent = $createParagraphNode().splice( + 0, + 0, + Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)), + ); + // Check initial state + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + expect( + Array.from($firstToLastIterator(parent), (node) => { + return assertClass(node, TextNode).getTextContent(); + }), + ).toEqual(['0', '1', '2', '3', '4']); + // Parent was not affected + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + }, + {discrete: true}, + ); + }); + it(`Can handle node removal`, () => { + editor.update( + () => { + const parent = $createParagraphNode().splice( + 0, + 0, + Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)), + ); + // Check initial state + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + expect( + Array.from($firstToLastIterator(parent), (node) => { + const rval = assertClass(node, TextNode).getTextContent(); + node.remove(); + return rval; + }), + ).toEqual(['0', '1', '2', '3', '4']); + expect(parent.getChildren()).toEqual([]); + }, + {discrete: true}, + ); + }); + it(`Detects cycles when nodes move incorrectly`, () => { + editor.update( + () => { + const parent = $createParagraphNode().splice( + 0, + 0, + Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)), + ); + // Check initial state + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + expect(() => + Array.from($firstToLastIterator(parent), (node) => { + const rval = assertClass(node, TextNode).getTextContent(); + parent.append(node); + return rval; + }), + ).toThrow(/\$childIterator: Cycle detected/); + }, + {discrete: true}, + ); + }); + it(`Can handle nodes moving in the other direction`, () => { + editor.update( + () => { + const parent = $createParagraphNode().splice( + 0, + 0, + Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)), + ); + // Check initial state + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + expect( + Array.from($firstToLastIterator(parent), (node) => { + const rval = assertClass(node, TextNode).getTextContent(); + if (node.getPreviousSibling() !== null) { + parent.splice(0, 0, [node]); + } + return rval; + }), + ).toEqual(['0', '1', '2', '3', '4']); + // This mutation reversed the nodes while traversing + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['4', '3', '2', '1', '0']); + }, + {discrete: true}, + ); + }); +}); + +describe('$lastToFirstIterator', () => { + let editor: LexicalEditor; + + beforeEach(async () => { + editor = createTestEditor(); + editor._headless = true; + }); + + it(`Iterates from last to first`, () => { + editor.update( + () => { + const parent = $createParagraphNode().splice( + 0, + 0, + Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)), + ); + // Check initial state + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + expect( + Array.from($lastToFirstIterator(parent), (node) => { + return assertClass(node, TextNode).getTextContent(); + }), + ).toEqual(['4', '3', '2', '1', '0']); + // Parent was not affected + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + }, + {discrete: true}, + ); + }); + it(`Can handle node removal`, () => { + editor.update( + () => { + const parent = $createParagraphNode().splice( + 0, + 0, + Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)), + ); + // Check initial state + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + expect( + Array.from($lastToFirstIterator(parent), (node) => { + const rval = assertClass(node, TextNode).getTextContent(); + node.remove(); + return rval; + }), + ).toEqual(['4', '3', '2', '1', '0']); + expect(parent.getChildren()).toEqual([]); + }, + {discrete: true}, + ); + }); + it(`Can handle nodes moving in the other direction`, () => { + editor.update( + () => { + const parent = $createParagraphNode().splice( + 0, + 0, + Array.from({length: 5}, (_v, i) => $createTextNode(`${i}`)), + ); + // Check initial state + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['0', '1', '2', '3', '4']); + expect( + Array.from($lastToFirstIterator(parent), (node) => { + const rval = assertClass(node, TextNode).getTextContent(); + parent.append(node); + return rval; + }), + ).toEqual(['4', '3', '2', '1', '0']); + // This mutation reversed the nodes while traversing + expect( + parent.getAllTextNodes().map((node) => node.getTextContent()), + ).toEqual(['4', '3', '2', '1', '0']); + }, + {discrete: true}, + ); + }); +}); diff --git a/packages/lexical-utils/src/__tests__/unit/unwrapAndFilterDescendants.test.tsx b/packages/lexical-utils/src/__tests__/unit/unwrapAndFilterDescendants.test.tsx new file mode 100644 index 00000000000..cb42d0fe643 --- /dev/null +++ b/packages/lexical-utils/src/__tests__/unit/unwrapAndFilterDescendants.test.tsx @@ -0,0 +1,101 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import type {Klass, LexicalEditor, LexicalNode} from 'lexical'; + +import {$unwrapAndFilterDescendants} from '@lexical/utils'; +import { + $createParagraphNode, + $createTextNode, + $getRoot, + $isParagraphNode, + $isTextNode, + ParagraphNode, +} from 'lexical'; +import {createTestEditor} from 'lexical/src/__tests__/utils'; + +function assertClass(v: unknown, klass: Klass): T { + if (v instanceof klass) { + return v as T; + } + throw new Error(`Value does not extend ${klass.name}`); +} + +function $createTextAndParagraphWithDepth(depth: number): LexicalNode[] { + if (depth <= 0) { + return [$createTextNode(`<${depth} />`)]; + } + return [ + $createTextNode(`<${depth}>`), + $createParagraphNode().append( + ...$createTextAndParagraphWithDepth(depth - 1), + ), + $createTextNode(``), + ]; +} + +function textContentForDepth(i: number): string { + return i > 0 ? `<${i}>${textContentForDepth(i - 1)}` : `<${i} />`; +} + +describe('$unwrapAndFilterDescendants', () => { + let editor: LexicalEditor; + + beforeEach(async () => { + editor = createTestEditor(); + editor._headless = true; + }); + + it('Is a no-op with valid children', () => { + editor.update( + () => { + $getRoot().clear().append($createParagraphNode()); + }, + {discrete: true}, + ); + editor.update( + () => { + expect($unwrapAndFilterDescendants($getRoot(), $isParagraphNode)).toBe( + false, + ); + expect($getRoot().getChildrenSize()).toBe(1); + expect($isParagraphNode($getRoot().getFirstChild())).toBe(true); + }, + {discrete: true}, + ); + }); + [0, 1, 2].forEach((depth) => + it(`Can un-nest children at depth ${depth}`, () => { + editor.update( + () => { + const firstNode = $createParagraphNode(); + $getRoot() + .clear() + .append( + firstNode.append(...$createTextAndParagraphWithDepth(depth)), + ); + }, + {discrete: true}, + ); + editor.update( + () => { + const firstNode = assertClass( + $getRoot().getFirstChildOrThrow(), + ParagraphNode, + ); + expect(firstNode.getChildren().every($isTextNode)).toBe(depth === 0); + expect($unwrapAndFilterDescendants(firstNode, $isTextNode)).toBe( + depth > 0, + ); + expect(firstNode.getChildren().every($isTextNode)).toBe(true); + expect(firstNode.getTextContent()).toBe(textContentForDepth(depth)); + }, + {discrete: true}, + ); + }), + ); +}); diff --git a/packages/lexical-utils/src/index.ts b/packages/lexical-utils/src/index.ts index 8994e3dad65..0a758f40f2f 100644 --- a/packages/lexical-utils/src/index.ts +++ b/packages/lexical-utils/src/index.ts @@ -23,6 +23,7 @@ import { Klass, LexicalEditor, LexicalNode, + NodeKey, } from 'lexical'; // This underscore postfixing is used as a hotfix so we do not // export shared types from this module #5918 @@ -690,3 +691,157 @@ export function calculateZoomLevel(element: Element | null): number { export function $isEditorIsNestedEditor(editor: LexicalEditor): boolean { return editor._parentEditor !== null; } + +/** + * A depth first last-to-first traversal of root that stops at each node that matches + * $predicate and ensures that its parent is root. This is typically used to discard + * invalid or unsupported wrapping nodes. For example, a TableNode must only have + * TableRowNode as children, but an importer might add invalid nodes based on + * caption, tbody, thead, etc. and this will unwrap and discard those. + * + * @param root The root to start the traversal + * @param $predicate Should return true for nodes that are permitted to be children of root + * @returns true if this unwrapped or removed any nodes + */ +export function $unwrapAndFilterDescendants( + root: ElementNode, + $predicate: (node: LexicalNode) => boolean, +): boolean { + return $unwrapAndFilterDescendantsImpl(root, $predicate, null); +} + +function $unwrapAndFilterDescendantsImpl( + root: ElementNode, + $predicate: (node: LexicalNode) => boolean, + $onSuccess: null | ((node: LexicalNode) => void), +): boolean { + let didMutate = false; + for (const node of $lastToFirstIterator(root)) { + if ($predicate(node)) { + if ($onSuccess !== null) { + $onSuccess(node); + } + continue; + } + didMutate = true; + if ($isElementNode(node)) { + $unwrapAndFilterDescendantsImpl( + node, + $predicate, + $onSuccess ? $onSuccess : (child) => node.insertAfter(child), + ); + } + node.remove(); + } + return didMutate; +} + +/** + * A depth first traversal of the children array that stops at and collects + * each node that `$predicate` matches. This is typically used to discard + * invalid or unsupported wrapping nodes on a children array in the `after` + * of an {@link lexical!DOMConversionOutput}. For example, a TableNode must only have + * TableRowNode as children, but an importer might add invalid nodes based on + * caption, tbody, thead, etc. and this will unwrap and discard those. + * + * This function is read-only and performs no mutation operations, which makes + * it suitable for import and export purposes but likely not for any in-place + * mutation. You should use {@link $unwrapAndFilterDescendants} for in-place + * mutations such as node transforms. + * + * @param children The children to traverse + * @param $predicate Should return true for nodes that are permitted to be children of root + * @returns The children or their descendants that match $predicate + */ +export function $descendantsMatching( + children: LexicalNode[], + $predicate: (node: LexicalNode) => node is T, +): T[]; +export function $descendantsMatching( + children: LexicalNode[], + $predicate: (node: LexicalNode) => boolean, +): LexicalNode[] { + const result: LexicalNode[] = []; + const stack = [...children].reverse(); + for (let child = stack.pop(); child !== undefined; child = stack.pop()) { + if ($predicate(child)) { + result.push(child); + } else if ($isElementNode(child)) { + for (const grandchild of $lastToFirstIterator(child)) { + stack.push(grandchild); + } + } + } + return result; +} + +/** + * Return an iterator that yields each child of node from first to last, taking + * care to preserve the next sibling before yielding the value in case the caller + * removes the yielded node. + * + * @param node The node whose children to iterate + * @returns An iterator of the node's children + */ +export function $firstToLastIterator(node: ElementNode): Iterable { + return { + [Symbol.iterator]: () => + $childIterator(node.getFirstChild(), (child) => child.getNextSibling()), + }; +} + +/** + * Return an iterator that yields each child of node from last to first, taking + * care to preserve the previous sibling before yielding the value in case the caller + * removes the yielded node. + * + * @param node The node whose children to iterate + * @returns An iterator of the node's children + */ +export function $lastToFirstIterator(node: ElementNode): Iterable { + return { + [Symbol.iterator]: () => + $childIterator(node.getLastChild(), (child) => + child.getPreviousSibling(), + ), + }; +} + +function $childIterator( + initialNode: LexicalNode | null, + nextNode: (node: LexicalNode) => LexicalNode | null, +): Iterator { + let state = initialNode; + const seen = __DEV__ ? new Set() : null; + return { + next() { + if (state === null) { + return iteratorDone; + } + const rval = iteratorNotDone(state); + if (__DEV__ && seen !== null) { + const key = state.getKey(); + invariant( + !seen.has(key), + '$childIterator: Cycle detected, node with key %s has already been traversed', + String(key), + ); + seen.add(key); + } + state = nextNode(state); + return rval; + }, + }; +} + +/** + * Insert all children before this node, and then remove it. + * + * @param node The ElementNode to unwrap and remove + */ +export function $unwrapNode(node: ElementNode): void { + for (const child of $firstToLastIterator(node)) { + node.insertBefore(child); + } + node.remove(); +} diff --git a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx index cf33a568d3f..3986f27806f 100644 --- a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx +++ b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx @@ -2210,7 +2210,7 @@ describe('LexicalEditor tests', () => { await editor.update(() => { const root = $getRoot(); - const tableCell = $createTableCellNode(0); + const tableCell = $createTableCellNode(); const tableRow = $createTableRowNode(); const table = $createTableNode(); @@ -2225,7 +2225,7 @@ describe('LexicalEditor tests', () => { await editor.update(() => { const tableRow = $getNodeByKey(tableRowKey) as TableRowNode; - const tableCell = $createTableCellNode(0); + const tableCell = $createTableCellNode(); tableRow.append(tableCell); }); From 55ef7cad49c303a343575745d311fb1a3d05c15b Mon Sep 17 00:00:00 2001 From: Hadi Elghoul <113323394+elgh0ul@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:25:05 -0500 Subject: [PATCH 06/25] [lexical][@lexical/selection] Feature: Unify $selectAll Implementations (#6902) Co-authored-by: Hadi Elghoul --- packages/lexical-selection/src/index.ts | 4 +-- .../lexical-selection/src/range-selection.ts | 35 ------------------- packages/lexical/src/LexicalUtils.ts | 21 +++++++++-- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/packages/lexical-selection/src/index.ts b/packages/lexical-selection/src/index.ts index d901ab4d4d9..8d9d47ce635 100644 --- a/packages/lexical-selection/src/index.ts +++ b/packages/lexical-selection/src/index.ts @@ -18,7 +18,6 @@ import { $isParentElementRTL, $moveCaretSelection, $moveCharacter, - $selectAll, $setBlocksType, $shouldOverrideDefaultCharacterSelection, $wrapNodes, @@ -32,7 +31,9 @@ import { export { /** @deprecated moved to the lexical package */ $cloneWithProperties, + /** @deprecated moved to the lexical package */ $selectAll, } from 'lexical'; + export { $addNodeStyle, $isAtNodeEnd, @@ -48,7 +49,6 @@ export { $isParentElementRTL, $moveCaretSelection, $moveCharacter, - $selectAll, $setBlocksType, $shouldOverrideDefaultCharacterSelection, $wrapNodes, diff --git a/packages/lexical-selection/src/range-selection.ts b/packages/lexical-selection/src/range-selection.ts index e92a81b6188..15b4e66c5c7 100644 --- a/packages/lexical-selection/src/range-selection.ts +++ b/packages/lexical-selection/src/range-selection.ts @@ -452,41 +452,6 @@ export function $moveCharacter( ); } -/** - * Expands the current Selection to cover all of the content in the editor. - * @param selection - The current selection. - */ -export function $selectAll(selection: RangeSelection): void { - const anchor = selection.anchor; - const focus = selection.focus; - const anchorNode = anchor.getNode(); - const topParent = anchorNode.getTopLevelElementOrThrow(); - const root = topParent.getParentOrThrow(); - let firstNode = root.getFirstDescendant(); - let lastNode = root.getLastDescendant(); - let firstType: 'element' | 'text' = 'element'; - let lastType: 'element' | 'text' = 'element'; - let lastOffset = 0; - - if ($isTextNode(firstNode)) { - firstType = 'text'; - } else if (!$isElementNode(firstNode) && firstNode !== null) { - firstNode = firstNode.getParentOrThrow(); - } - - if ($isTextNode(lastNode)) { - lastType = 'text'; - lastOffset = lastNode.getTextContentSize(); - } else if (!$isElementNode(lastNode) && lastNode !== null) { - lastNode = lastNode.getParentOrThrow(); - } - - if (firstNode && lastNode) { - anchor.set(firstNode.getKey(), 0, firstType); - focus.set(lastNode.getKey(), lastOffset, lastType); - } -} - /** * Returns the current value of a CSS property for Nodes, if set. If not set, it returns the defaultValue. * @param node - The node whose style value to get. diff --git a/packages/lexical/src/LexicalUtils.ts b/packages/lexical/src/LexicalUtils.ts index f4cc88ac86a..d4bc2d1ea46 100644 --- a/packages/lexical/src/LexicalUtils.ts +++ b/packages/lexical/src/LexicalUtils.ts @@ -1084,10 +1084,25 @@ export function isSelectAll( return key.toLowerCase() === 'a' && controlOrMeta(metaKey, ctrlKey); } -export function $selectAll(): void { +export function $selectAll(selection?: RangeSelection | null): RangeSelection { const root = $getRoot(); - const selection = root.select(0, root.getChildrenSize()); - $setSelection($normalizeSelection(selection)); + + if ($isRangeSelection(selection)) { + const anchor = selection.anchor; + const focus = selection.focus; + const anchorNode = anchor.getNode(); + const topParent = anchorNode.getTopLevelElementOrThrow(); + const rootNode = topParent.getParentOrThrow(); + anchor.set(rootNode.getKey(), 0, 'element'); + focus.set(rootNode.getKey(), rootNode.getChildrenSize(), 'element'); + $normalizeSelection(selection); + return selection; + } else { + // Create a new RangeSelection + const newSelection = root.select(0, root.getChildrenSize()); + $setSelection($normalizeSelection(newSelection)); + return newSelection; + } } export function getCachedClassNameArray( From 7776cea0f1862cb57b3c2d2118da629e098b784c Mon Sep 17 00:00:00 2001 From: Basile Savouret <47100280+basile-savouret@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:26:53 +0100 Subject: [PATCH 07/25] [lexical-playground]: Fix empty layout item causes 100% CPU usage (#6906) Co-authored-by: Ivaylo Pavlov --- .../src/plugins/LayoutPlugin/LayoutPlugin.tsx | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/lexical-playground/src/plugins/LayoutPlugin/LayoutPlugin.tsx b/packages/lexical-playground/src/plugins/LayoutPlugin/LayoutPlugin.tsx index cbdeee1fe23..dd226849e62 100644 --- a/packages/lexical-playground/src/plugins/LayoutPlugin/LayoutPlugin.tsx +++ b/packages/lexical-playground/src/plugins/LayoutPlugin/LayoutPlugin.tsx @@ -97,6 +97,25 @@ export function LayoutPlugin(): null { return false; }; + const $fillLayoutItemIfEmpty = (node: LayoutItemNode) => { + if (node.isEmpty()) { + node.append($createParagraphNode()); + } + }; + + const $removeIsolatedLayoutItem = (node: LayoutItemNode): boolean => { + const parent = node.getParent(); + if (!$isLayoutContainerNode(parent)) { + const children = node.getChildren(); + for (const child of children) { + node.insertBefore(child); + } + node.remove(); + return true; + } + return false; + }; + return mergeRegister( // When layout is the last child pressing down/right arrow will insert paragraph // below it to allow adding more content. It's similar what $insertBlockNode @@ -186,17 +205,17 @@ export function LayoutPlugin(): null { }, COMMAND_PRIORITY_EDITOR, ), - // Structure enforcing transformers for each node type. In case nesting structure is not - // "Container > Item" it'll unwrap nodes and convert it back - // to regular content. + editor.registerNodeTransform(LayoutItemNode, (node) => { - const parent = node.getParent(); - if (!$isLayoutContainerNode(parent)) { - const children = node.getChildren(); - for (const child of children) { - node.insertBefore(child); - } - node.remove(); + // Structure enforcing transformers for each node type. In case nesting structure is not + // "Container > Item" it'll unwrap nodes and convert it back + // to regular content. + const isRemoved = $removeIsolatedLayoutItem(node); + + if (!isRemoved) { + // Layout item should always have a child. this function will listen + // for any empty layout item and fill it with a paragraph node + $fillLayoutItemIfEmpty(node); } }), editor.registerNodeTransform(LayoutContainerNode, (node) => { From 05fa244bd0f6043114ffb8feab2922d8e4de7e6f Mon Sep 17 00:00:00 2001 From: daichan132 <71433925+daichan132@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:42:29 +0900 Subject: [PATCH 08/25] [lexical-playground] Chore: Update Prettier to v3 (#6920) --- package-lock.json | 30 ++++++- .../__tests__/utils/index.mjs | 26 +++--- packages/lexical-playground/package.json | 2 +- .../components/PrettierButton/index.tsx | 85 ++++++++++--------- 4 files changed, 86 insertions(+), 57 deletions(-) diff --git a/package-lock.json b/package-lock.json index e5387c0ffdc..4488fab50c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30584,6 +30584,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, "bin": { "prettier": "bin-prettier.js" }, @@ -39153,7 +39154,7 @@ "katex": "^0.16.10", "lexical": "0.21.0", "lodash-es": "^4.17.21", - "prettier": "^2.3.2", + "prettier": "^3.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", @@ -39173,6 +39174,21 @@ "vite-plugin-static-copy": "^2.1.0" } }, + "packages/lexical-playground/node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "packages/lexical-react": { "name": "@lexical/react", "version": "0.21.0", @@ -56265,7 +56281,7 @@ "katex": "^0.16.10", "lexical": "0.21.0", "lodash-es": "^4.17.21", - "prettier": "^2.3.2", + "prettier": "^3.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", @@ -56275,6 +56291,13 @@ "vite-plugin-static-copy": "^2.1.0", "y-websocket": "^1.5.4", "yjs": ">=13.5.42" + }, + "dependencies": { + "prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==" + } } }, "lib0": { @@ -60208,7 +60231,8 @@ "prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==" + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true }, "prettier-plugin-hermes-parser": { "version": "0.20.1", diff --git a/packages/lexical-playground/__tests__/utils/index.mjs b/packages/lexical-playground/__tests__/utils/index.mjs index ed81a581866..a9e13d587ec 100644 --- a/packages/lexical-playground/__tests__/utils/index.mjs +++ b/packages/lexical-playground/__tests__/utils/index.mjs @@ -225,7 +225,7 @@ async function assertHTMLOnPageOrFrame( frameName, actualHtmlModificationsCallback = (actualHtml) => actualHtml, ) { - const expected = prettifyHTML(expectedHtml.replace(/\n/gm, ''), { + const expected = await prettifyHTML(expectedHtml.replace(/\n/gm, ''), { ignoreClasses, ignoreInlineStyles, }); @@ -236,7 +236,7 @@ async function assertHTMLOnPageOrFrame( .first() .innerHTML(), ); - let actual = prettifyHTML(actualHtml.replace(/\n/gm, ''), { + let actual = await prettifyHTML(actualHtml.replace(/\n/gm, ''), { ignoreClasses, ignoreInlineStyles, }); @@ -780,7 +780,10 @@ export async function dragImage( ); } -export function prettifyHTML(string, {ignoreClasses, ignoreInlineStyles} = {}) { +export async function prettifyHTML( + string, + {ignoreClasses, ignoreInlineStyles} = {}, +) { let output = string; if (ignoreClasses) { @@ -793,15 +796,14 @@ export function prettifyHTML(string, {ignoreClasses, ignoreInlineStyles} = {}) { output = output.replace(/\s__playwright_target__="[^"]+"/, ''); - return prettier - .format(output, { - attributeGroups: ['$DEFAULT', '^data-'], - attributeSort: 'ASC', - bracketSameLine: true, - htmlWhitespaceSensitivity: 'ignore', - parser: 'html', - }) - .trim(); + return await prettier.format(output, { + attributeGroups: ['$DEFAULT', '^data-'], + attributeSort: 'asc', + bracketSameLine: true, + htmlWhitespaceSensitivity: 'ignore', + parser: 'html', + plugins: ['prettier-plugin-organize-attributes'], + }); } // This function does not suppose to do anything, it's only used as a trigger diff --git a/packages/lexical-playground/package.json b/packages/lexical-playground/package.json index cd7ced6303f..5a2751577cf 100644 --- a/packages/lexical-playground/package.json +++ b/packages/lexical-playground/package.json @@ -29,7 +29,7 @@ "katex": "^0.16.10", "lexical": "0.21.0", "lodash-es": "^4.17.21", - "prettier": "^2.3.2", + "prettier": "^3.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", diff --git a/packages/lexical-playground/src/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx b/packages/lexical-playground/src/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx index 203e63702b9..c1e47f23ec4 100644 --- a/packages/lexical-playground/src/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +++ b/packages/lexical-playground/src/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx @@ -10,7 +10,6 @@ import './index.css'; import {$isCodeNode} from '@lexical/code'; import {$getNearestNodeFromDOMNode, LexicalEditor} from 'lexical'; import {Options} from 'prettier'; -import * as React from 'react'; import {useState} from 'react'; interface Props { @@ -20,17 +19,27 @@ interface Props { } const PRETTIER_PARSER_MODULES = { - css: () => import('prettier/parser-postcss'), - html: () => import('prettier/parser-html'), - js: () => import('prettier/parser-babel'), - markdown: () => import('prettier/parser-markdown'), + css: [() => import('prettier/parser-postcss')], + html: [() => import('prettier/parser-html')], + js: [ + () => import('prettier/parser-babel'), + () => import('prettier/plugins/estree'), + ], + markdown: [() => import('prettier/parser-markdown')], + typescript: [ + () => import('prettier/parser-typescript'), + () => import('prettier/plugins/estree'), + ], } as const; type LanguagesType = keyof typeof PRETTIER_PARSER_MODULES; async function loadPrettierParserByLang(lang: string) { - const dynamicImport = PRETTIER_PARSER_MODULES[lang as LanguagesType]; - return await dynamicImport(); + const dynamicImports = PRETTIER_PARSER_MODULES[lang as LanguagesType]; + const modules = await Promise.all( + dynamicImports.map((dynamicImport) => dynamicImport()), + ); + return modules; } async function loadPrettierFormat() { @@ -39,18 +48,11 @@ async function loadPrettierFormat() { } const PRETTIER_OPTIONS_BY_LANG: Record = { - css: { - parser: 'css', - }, - html: { - parser: 'html', - }, - js: { - parser: 'babel', - }, - markdown: { - parser: 'markdown', - }, + css: {parser: 'css'}, + html: {parser: 'html'}, + js: {parser: 'babel'}, + markdown: {parser: 'markdown'}, + typescript: {parser: 'typescript'}, }; const LANG_CAN_BE_PRETTIER = Object.keys(PRETTIER_OPTIONS_BY_LANG); @@ -76,36 +78,37 @@ export function PrettierButton({lang, editor, getCodeDOMNode}: Props) { async function handleClick(): Promise { const codeDOMNode = getCodeDOMNode(); + if (!codeDOMNode) { + return; + } + + let content = ''; + editor.update(() => { + const codeNode = $getNearestNodeFromDOMNode(codeDOMNode); + if ($isCodeNode(codeNode)) { + content = codeNode.getTextContent(); + } + }); + if (content === '') { + return; + } try { const format = await loadPrettierFormat(); const options = getPrettierOptions(lang); - options.plugins = [await loadPrettierParserByLang(lang)]; - - if (!codeDOMNode) { - return; - } + const prettierParsers = await loadPrettierParserByLang(lang); + options.plugins = prettierParsers.map( + (parser) => parser.default || parser, + ); + const formattedCode = await format(content, options); editor.update(() => { const codeNode = $getNearestNodeFromDOMNode(codeDOMNode); - if ($isCodeNode(codeNode)) { - const content = codeNode.getTextContent(); - - let parsed = ''; - - try { - parsed = format(content, options); - } catch (error: unknown) { - setError(error); - } - - if (parsed !== '') { - const selection = codeNode.select(0); - selection.insertText(parsed); - setSyntaxError(''); - setTipsVisible(false); - } + const selection = codeNode.select(0); + selection.insertText(formattedCode); + setSyntaxError(''); + setTipsVisible(false); } }); } catch (error: unknown) { From 4e8d5352f5c629195670317bd202cb77d33b5e8a Mon Sep 17 00:00:00 2001 From: Gerard Rovira Date: Mon, 9 Dec 2024 16:49:59 +0000 Subject: [PATCH 09/25] Doc nits (#6927) --- packages/lexical-website/docs/react/plugins.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/lexical-website/docs/react/plugins.md b/packages/lexical-website/docs/react/plugins.md index 3f6d24cc9d5..ca97f7935ca 100644 --- a/packages/lexical-website/docs/react/plugins.md +++ b/packages/lexical-website/docs/react/plugins.md @@ -41,7 +41,7 @@ const initialConfig = { ### `LexicalPlainTextPlugin` -React wrapper for `@lexical/plain-text` that adds major features for plain text editing, including typing, deletion and copy/pasting +React wrapper for `@lexical/plain-text` that adds major features for plain text editing, including typing, deletion and copy/pasting. ```jsx @@ -73,7 +73,7 @@ Plugin that calls `onChange` whenever Lexical state is updated. Using `ignoreHis ### `LexicalHistoryPlugin` -React wrapper for `@lexical/history` that adds support for history stack management and `undo` / `redo` commands +React wrapper for `@lexical/history` that adds support for history stack management and `undo` / `redo` commands. ```jsx @@ -81,7 +81,7 @@ React wrapper for `@lexical/history` that adds support for history stack managem ### `LexicalLinkPlugin` -React wrapper for `@lexical/link` that adds support for links, including `$toggleLink` command support that toggles link for selected text +React wrapper for `@lexical/link` that adds support for links, including `$toggleLink` command support that toggles link for selected text. ```jsx @@ -107,7 +107,7 @@ React wrapper for `@lexical/list` that adds support for check lists. Note that i [![See API Documentation](/img/see-api-documentation.svg)](/docs/api/modules/lexical_react_LexicalTablePlugin) -React wrapper for `@lexical/table` that adds support for tables +React wrapper for `@lexical/table` that adds support for tables. ```jsx @@ -153,7 +153,7 @@ const MATCHERS = [ ### `LexicalClearEditorPlugin` -Adds `clearEditor` command support to clear editor's content +Adds `clearEditor` command support to clear editor's content. ```jsx @@ -161,7 +161,7 @@ Adds `clearEditor` command support to clear editor's content ### `LexicalMarkdownShortcutPlugin` -Adds markdown shortcut support: headings, lists, code blocks, quotes, links and inline styles (bold, italic, strikethrough) +Adds markdown shortcut support: headings, lists, code blocks, quotes, links and inline styles (bold, italic, strikethrough). ```jsx From 09779cb2cc8461b1245d4aea0db77e5b205a33e5 Mon Sep 17 00:00:00 2001 From: Gerard Rovira Date: Mon, 9 Dec 2024 19:26:45 +0000 Subject: [PATCH 10/25] Update core-tests workflow triggers (#6928) --- .github/workflows/tests.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5f5074869c1..d3826cbbdc0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,9 +9,6 @@ on: - 'packages/lexical-website/**' pull_request: types: [opened, synchronize, reopened] - paths-ignore: - - 'examples/**' - - 'packages/lexical-website/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 9198eb0f6980c6736a7bac688725c62ebc7dbe81 Mon Sep 17 00:00:00 2001 From: Bob Ippolito Date: Tue, 10 Dec 2024 01:26:57 -0800 Subject: [PATCH 11/25] [*] Bug Fix: add merge_group to the tests workflow (#6932) --- .github/workflows/tests.yml | 1 + .../__tests__/e2e/List.spec.mjs | 103 +++++++++--------- 2 files changed, 53 insertions(+), 51 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d3826cbbdc0..431c342226a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,7 @@ on: - 'packages/lexical-website/**' pull_request: types: [opened, synchronize, reopened] + merge_group: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/packages/lexical-playground/__tests__/e2e/List.spec.mjs b/packages/lexical-playground/__tests__/e2e/List.spec.mjs index 8c1703c15c9..f6d46104fc2 100644 --- a/packages/lexical-playground/__tests__/e2e/List.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/List.spec.mjs @@ -28,7 +28,6 @@ import { focusEditor, html, initialize, - IS_LINUX, pasteFromClipboard, repeat, selectFromAlignDropdown, @@ -72,60 +71,62 @@ test.beforeEach(({isPlainText}) => { test.describe.parallel('Nested List', () => { test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); - test(`Can create a list and partially copy some content out of it`, async ({ - page, - isCollab, - }) => { - test.fixme(isCollab && IS_LINUX, 'Flaky on Linux + Collab'); - await focusEditor(page); - await page.keyboard.type( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam venenatis risus ac cursus efficitur. Cras efficitur magna odio, lacinia posuere mauris placerat in. Etiam eu congue nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla vulputate justo id eros convallis, vel pellentesque orci hendrerit. Pellentesque accumsan molestie eros, vitae tempor nisl semper sit amet. Sed vulputate leo dolor, et bibendum quam feugiat eget. Praesent vestibulum libero sed enim ornare, in consequat dui posuere. Maecenas ornare vestibulum felis, non elementum urna imperdiet sit amet.', - ); - await toggleBulletList(page); - await moveToEditorBeginning(page); - await moveRight(page, 6); - await selectCharacters(page, 'right', 11); + test( + `Can create a list and partially copy some content out of it`, + { + tag: '@flaky', + }, + async ({page, isCollab}) => { + await focusEditor(page); + await page.keyboard.type( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam venenatis risus ac cursus efficitur. Cras efficitur magna odio, lacinia posuere mauris placerat in. Etiam eu congue nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla vulputate justo id eros convallis, vel pellentesque orci hendrerit. Pellentesque accumsan molestie eros, vitae tempor nisl semper sit amet. Sed vulputate leo dolor, et bibendum quam feugiat eget. Praesent vestibulum libero sed enim ornare, in consequat dui posuere. Maecenas ornare vestibulum felis, non elementum urna imperdiet sit amet.', + ); + await toggleBulletList(page); + await moveToEditorBeginning(page); + await moveRight(page, 6); + await selectCharacters(page, 'right', 11); - await withExclusiveClipboardAccess(async () => { - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await moveToEditorEnd(page); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); + await moveToEditorEnd(page); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); - await pasteFromClipboard(page, clipboard); - }); + await pasteFromClipboard(page, clipboard); + }); - await assertHTML( - page, - html` -

    -
  • - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam - venenatis risus ac cursus efficitur. Cras efficitur magna odio, - lacinia posuere mauris placerat in. Etiam eu congue nisl. - Vestibulum ante ipsum primis in faucibus orci luctus et ultrices - posuere cubilia curae; Nulla vulputate justo id eros convallis, - vel pellentesque orci hendrerit. Pellentesque accumsan molestie - eros, vitae tempor nisl semper sit amet. Sed vulputate leo dolor, - et bibendum quam feugiat eget. Praesent vestibulum libero sed enim - ornare, in consequat dui posuere. Maecenas ornare vestibulum - felis, non elementum urna imperdiet sit amet. - -
  • -
-

- ipsum dolor -

- `, - ); - }); + await assertHTML( + page, + html` +
    +
  • + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam + venenatis risus ac cursus efficitur. Cras efficitur magna odio, + lacinia posuere mauris placerat in. Etiam eu congue nisl. + Vestibulum ante ipsum primis in faucibus orci luctus et ultrices + posuere cubilia curae; Nulla vulputate justo id eros convallis, + vel pellentesque orci hendrerit. Pellentesque accumsan molestie + eros, vitae tempor nisl semper sit amet. Sed vulputate leo + dolor, et bibendum quam feugiat eget. Praesent vestibulum libero + sed enim ornare, in consequat dui posuere. Maecenas ornare + vestibulum felis, non elementum urna imperdiet sit amet. + +
  • +
+

+ ipsum dolor +

+ `, + ); + }, + ); test('Should outdent if indented when the backspace key is pressed', async ({ page, From 49580d4212437e31e5f81b5388da065022252b7b Mon Sep 17 00:00:00 2001 From: "C." <106287207+citruscai@users.noreply.github.com> Date: Tue, 10 Dec 2024 07:47:02 -0600 Subject: [PATCH 12/25] [Breaking Change][lexical-list] Fix: Preserve original format after indenting list item (#6912) Co-authored-by: Bob Ippolito Co-authored-by: Gerard Rovira --- .../lexical-list/src/LexicalListItemNode.ts | 95 +++++++++---------- packages/lexical-list/src/formatList.ts | 11 +-- packages/lexical-list/src/utils.ts | 3 +- .../__tests__/e2e/List.spec.mjs | 45 +++++++++ .../src/__tests__/unit/LexicalEditor.test.tsx | 2 +- .../unit/LexicalSerialization.test.ts | 4 +- .../lexical/src/nodes/LexicalParagraphNode.ts | 13 ++- 7 files changed, 105 insertions(+), 68 deletions(-) diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index f4fafcba71a..5581aba730b 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -7,20 +7,6 @@ */ import type {ListNode, ListType} from './'; -import type { - BaseSelection, - DOMConversionMap, - DOMConversionOutput, - DOMExportOutput, - EditorConfig, - EditorThemeClasses, - LexicalNode, - NodeKey, - ParagraphNode, - RangeSelection, - SerializedElementNode, - Spread, -} from 'lexical'; import { addClassNamesToElement, @@ -29,11 +15,24 @@ import { import { $applyNodeReplacement, $createParagraphNode, + $getSelection, $isElementNode, $isParagraphNode, $isRangeSelection, + BaseSelection, + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + EditorThemeClasses, ElementNode, LexicalEditor, + LexicalNode, + NodeKey, + ParagraphNode, + RangeSelection, + SerializedParagraphNode, + Spread, } from 'lexical'; import invariant from 'shared/invariant'; import normalizeClassNames from 'shared/normalizeClassNames'; @@ -47,11 +46,11 @@ export type SerializedListItemNode = Spread< checked: boolean | undefined; value: number; }, - SerializedElementNode + SerializedParagraphNode >; /** @noInheritDoc */ -export class ListItemNode extends ElementNode { +export class ListItemNode extends ParagraphNode { /** @internal */ __value: number; /** @internal */ @@ -81,12 +80,11 @@ export class ListItemNode extends ElementNode { $setListItemThemeClassNames(element, config.theme, this); return element; } + updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean { + if (super.updateDOM(prevNode, dom, config)) { + return true; + } - updateDOM( - prevNode: ListItemNode, - dom: HTMLElement, - config: EditorConfig, - ): boolean { const parent = this.getParent(); if ($isListNode(parent) && parent.getListType() === 'check') { updateListItemChecked(dom, this, prevNode, parent); @@ -94,7 +92,6 @@ export class ListItemNode extends ElementNode { // @ts-expect-error - this is always HTMLListItemElement dom.value = this.__value; $setListItemThemeClassNames(dom, config.theme, this); - return false; } @@ -128,6 +125,12 @@ export class ListItemNode extends ElementNode { node.setValue(serializedNode.value); node.setFormat(serializedNode.format); node.setDirection(serializedNode.direction); + if (typeof serializedNode.textFormat === 'number') { + node.setTextFormat(serializedNode.textFormat); + } + if (typeof serializedNode.textStyle === 'string') { + node.setTextStyle(serializedNode.textStyle); + } return node; } @@ -224,15 +227,11 @@ export class ListItemNode extends ElementNode { } const siblings = this.getNextSiblings(); - - // Split the lists and insert the node in between them listNode.insertAfter(node, restoreSelection); if (siblings.length !== 0) { const newListNode = $createListNode(listNode.getListType()); - siblings.forEach((sibling) => newListNode.append(sibling)); - node.insertAfter(newListNode, restoreSelection); } @@ -256,51 +255,49 @@ export class ListItemNode extends ElementNode { } insertNewAfter( - _: RangeSelection, + selection: RangeSelection, restoreSelection = true, ): ListItemNode | ParagraphNode { const newElement = $createListItemNode( this.__checked == null ? undefined : false, ); + + const format = selection.format; + newElement.setTextFormat(format); + + newElement.setFormat(this.getFormatType()); this.insertAfter(newElement, restoreSelection); return newElement; } - collapseAtStart(selection: RangeSelection): true { + collapseAtStart(): boolean { + const selection = $getSelection(); + + if (!$isRangeSelection(selection)) { + return false; + } + const paragraph = $createParagraphNode(); const children = this.getChildren(); children.forEach((child) => paragraph.append(child)); + const listNode = this.getParentOrThrow(); - const listNodeParent = listNode.getParentOrThrow(); - const isIndented = $isListItemNode(listNodeParent); + const listNodeParent = listNode.getParent(); + + if (!$isListNode(listNode)) { + return false; + } if (listNode.getChildrenSize() === 1) { - if (isIndented) { - // if the list node is nested, we just want to remove it, - // effectively unindenting it. + if ($isListItemNode(listNodeParent)) { listNode.remove(); listNodeParent.select(); } else { listNode.insertBefore(paragraph); listNode.remove(); - // If we have selection on the list item, we'll need to move it - // to the paragraph - const anchor = selection.anchor; - const focus = selection.focus; - const key = paragraph.getKey(); - - if (anchor.type === 'element' && anchor.getNode().is(this)) { - anchor.set(key, anchor.offset, 'element'); - } - - if (focus.type === 'element' && focus.getNode().is(this)) { - focus.set(key, focus.offset, 'element'); - } + paragraph.select(); } - } else { - listNode.insertBefore(paragraph); - this.remove(); } return true; diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts index 3dc4a22ea20..83172275908 100644 --- a/packages/lexical-list/src/formatList.ts +++ b/packages/lexical-list/src/formatList.ts @@ -243,7 +243,6 @@ export function removeList(editor: LexicalEditor): void { if ($isLeafNode(node)) { const listItemNode = $getNearestNodeOfType(node, ListItemNode); - if (listItemNode != null) { listNodes.add($getTopListNode(listItemNode)); } @@ -479,11 +478,13 @@ export function $handleListInsertParagraph(): boolean { return false; } // Only run this code on empty list items + const anchor = selection.anchor.getNode(); if (!$isListItemNode(anchor) || anchor.getChildrenSize() !== 0) { return false; } + const topListNode = $getTopListNode(anchor); const parent = anchor.getParent(); @@ -493,8 +494,7 @@ export function $handleListInsertParagraph(): boolean { ); const grandparent = parent.getParent(); - - let replacementNode; + let replacementNode: ElementNode; if ($isRootOrShadowRoot(grandparent)) { replacementNode = $createParagraphNode(); @@ -505,13 +505,12 @@ export function $handleListInsertParagraph(): boolean { } else { return false; } + replacementNode.select(); const nextSiblings = anchor.getNextSiblings(); - if (nextSiblings.length > 0) { const newList = $createListNode(parent.getListType()); - if ($isParagraphNode(replacementNode)) { replacementNode.insertAfter(newList); } else { @@ -524,9 +523,7 @@ export function $handleListInsertParagraph(): boolean { newList.append(sibling); }); } - // Don't leave hanging nested empty lists $removeHighestEmptyListParent(anchor); - return true; } diff --git a/packages/lexical-list/src/utils.ts b/packages/lexical-list/src/utils.ts index 9c9b1bf9af1..9b443f76205 100644 --- a/packages/lexical-list/src/utils.ts +++ b/packages/lexical-list/src/utils.ts @@ -6,9 +6,8 @@ * */ -import type {LexicalNode, Spread} from 'lexical'; - import {$findMatchingParent} from '@lexical/utils'; +import {type LexicalNode, type Spread} from 'lexical'; import invariant from 'shared/invariant'; import { diff --git a/packages/lexical-playground/__tests__/e2e/List.spec.mjs b/packages/lexical-playground/__tests__/e2e/List.spec.mjs index f6d46104fc2..e1173a1f66f 100644 --- a/packages/lexical-playground/__tests__/e2e/List.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/List.spec.mjs @@ -17,6 +17,7 @@ import { redo, selectAll, selectCharacters, + toggleBold, undo, } from '../keyboardShortcuts/index.mjs'; import { @@ -1881,4 +1882,48 @@ test.describe.parallel('Nested List', () => { }); }, ); + test('new list item should preserve format from previous list item even after new list item is indented', async ({ + page, + }) => { + await focusEditor(page); + await toggleBulletList(page); + await toggleBold(page); + await page.keyboard.type('MLH Fellowship'); + await page.keyboard.press('Enter'); + await clickIndentButton(page); + await page.keyboard.type('Fall 2024'); + await assertHTML( + page, + html` +
    +
  • + + MLH Fellowship + +
  • +
  • +
      +
    • + + Fall 2024 + +
    • +
    +
  • +
+ `, + ); + }); }); diff --git a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx index 3986f27806f..7f7be6d9c5f 100644 --- a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx +++ b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx @@ -1026,7 +1026,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,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"root","version":1}}'; init(); const contentEditable = editor.getRootElement(); editor.setEditable(editable); diff --git a/packages/lexical/src/__tests__/unit/LexicalSerialization.test.ts b/packages/lexical/src/__tests__/unit/LexicalSerialization.test.ts index 9237bc9d3dd..96820722dcd 100644 --- a/packages/lexical/src/__tests__/unit/LexicalSerialization.test.ts +++ b/packages/lexical/src/__tests__/unit/LexicalSerialization.test.ts @@ -110,7 +110,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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":"ltr","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":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"const lexical = \\"awesome\\"","type":"code-highlight","version":1}],"direction":"ltr","format":"","indent":0,"type":"code","version":1,"language":"javascript"},{"children":[{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1}],"direction":"ltr","format":"","indent":0,"type":"table","version":1}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","value":4}],"direction":"ltr","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":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"const lexical = \\"awesome\\"","type":"code-highlight","version":1}],"direction":"ltr","format":"","indent":0,"type":"code","version":1,"language":"javascript"},{"children":[{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1}],"direction":"ltr","format":"","indent":0,"type":"table","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}`; expect(stringifiedEditorState).toBe(expectedStringifiedEditorState); @@ -119,7 +119,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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":"ltr","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":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"const lexical = \\"awesome\\"","type":"code-highlight","version":1}],"direction":"ltr","format":"","indent":0,"type":"code","version":1,"language":"javascript"},{"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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","value":4}],"direction":"ltr","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":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"const lexical = \\"awesome\\"","type":"code-highlight","version":1}],"direction":"ltr","format":"","indent":0,"type":"code","version":1,"language":"javascript"},{"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":"ltr","format":"","indent":0,"type":"root","version":1}}`, ); }); }); diff --git a/packages/lexical/src/nodes/LexicalParagraphNode.ts b/packages/lexical/src/nodes/LexicalParagraphNode.ts index c1250aeae16..036799b3b71 100644 --- a/packages/lexical/src/nodes/LexicalParagraphNode.ts +++ b/packages/lexical/src/nodes/LexicalParagraphNode.ts @@ -120,11 +120,7 @@ export class ParagraphNode extends ElementNode { } return dom; } - updateDOM( - prevNode: ParagraphNode, - dom: HTMLElement, - config: EditorConfig, - ): boolean { + updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean { return false; } @@ -164,7 +160,9 @@ export class ParagraphNode extends ElementNode { node.setFormat(serializedNode.format); node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); - node.setTextFormat(serializedNode.textFormat); + if (typeof serializedNode.textFormat === 'number') { + node.setTextFormat(serializedNode.textFormat); + } return node; } @@ -190,7 +188,8 @@ export class ParagraphNode extends ElementNode { const direction = this.getDirection(); newElement.setDirection(direction); newElement.setFormat(this.getFormatType()); - newElement.setStyle(this.getTextStyle()); + newElement.setStyle(this.getStyle()); + this.insertAfter(newElement, restoreSelection); return newElement; } From df15f5398542f9bc5aae0a3212ab3f33cc5a4462 Mon Sep 17 00:00:00 2001 From: Oluwasanya Olaoluwa Date: Wed, 11 Dec 2024 03:54:55 +1100 Subject: [PATCH 13/25] [lexical-playground] Fix: tabs do not show strikethrough/underline (#6811) Co-authored-by: Bob Ippolito --- .../__tests__/e2e/CodeBlock.spec.mjs | 60 ++++++++++++++----- .../__tests__/e2e/Tab.spec.mjs | 8 ++- .../src/themes/PlaygroundEditorTheme.css | 44 ++++++++++++++ .../src/themes/PlaygroundEditorTheme.ts | 1 + packages/lexical/src/LexicalEditor.ts | 1 + packages/lexical/src/nodes/LexicalTabNode.ts | 14 ++++- 6 files changed, 109 insertions(+), 19 deletions(-) diff --git a/packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs b/packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs index 34dd9972778..dab99d16c5d 100644 --- a/packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/CodeBlock.spec.mjs @@ -366,7 +366,9 @@ test.describe('CodeBlock', () => { ;
- + @@ -393,7 +395,9 @@ test.describe('CodeBlock', () => { ;
- + @@ -453,7 +457,9 @@ test.describe('CodeBlock', () => { {
- + @@ -501,8 +507,12 @@ test.describe('CodeBlock', () => { data-gutter="123" data-highlight-language="javascript" data-language="javascript"> - - + + @@ -527,9 +537,15 @@ test.describe('CodeBlock', () => { {
- - - + + + @@ -551,8 +567,12 @@ test.describe('CodeBlock', () => { ;
- - + + @@ -575,7 +595,9 @@ test.describe('CodeBlock', () => { data-gutter="123" data-highlight-language="javascript" data-language="javascript"> - + @@ -600,8 +622,12 @@ test.describe('CodeBlock', () => { {
- - + + @@ -623,7 +649,9 @@ test.describe('CodeBlock', () => { ;
- + @@ -913,10 +941,10 @@ test.describe('CodeBlock', () => { data-gutter="12" data-language="javascript" data-highlight-language="javascript"> - + a b
- + c d `, diff --git a/packages/lexical-playground/__tests__/e2e/Tab.spec.mjs b/packages/lexical-playground/__tests__/e2e/Tab.spec.mjs index 118e39536d4..b9198e193de 100644 --- a/packages/lexical-playground/__tests__/e2e/Tab.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Tab.spec.mjs @@ -81,7 +81,9 @@ test.describe('Tab', () => { dir="ltr" style="padding-inline-start: calc(40px)"> すし - + すし

`, @@ -106,7 +108,9 @@ test.describe('Tab', () => { data-gutter="1" data-highlight-language="javascript" data-language="javascript"> - + diff --git a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css index 527085b7539..031173135f9 100644 --- a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css +++ b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css @@ -57,12 +57,56 @@ .PlaygroundEditorTheme__textUnderline { text-decoration: underline; } + .PlaygroundEditorTheme__textStrikethrough { text-decoration: line-through; } + .PlaygroundEditorTheme__textUnderlineStrikethrough { text-decoration: underline line-through; } + +.PlaygroundEditorTheme__tabNode { + position: relative; + text-decoration: none; +} + +.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderline::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: 0.15em; + border-bottom: 0.1em solid currentColor; +} + +.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textStrikethrough::before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0.69em; + border-top: 0.1em solid currentColor; +} + +.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderlineStrikethrough::before, +.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderlineStrikethrough::after { + content: ''; + position: absolute; + left: 0; + right: 0; +} + +.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderlineStrikethrough::before { + top: 0.69em; + border-top: 0.1em solid currentColor; +} + +.PlaygroundEditorTheme__tabNode.PlaygroundEditorTheme__textUnderlineStrikethrough::after { + bottom: 0.05em; + border-bottom: 0.1em solid currentColor; +} + .PlaygroundEditorTheme__textSubscript { font-size: 0.8em; vertical-align: sub !important; diff --git a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.ts b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.ts index 0b45916782b..e473ada671c 100644 --- a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.ts +++ b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.ts @@ -91,6 +91,7 @@ const theme: EditorThemeClasses = { quote: 'PlaygroundEditorTheme__quote', rtl: 'PlaygroundEditorTheme__rtl', specialText: 'PlaygroundEditorTheme__specialText', + tab: 'PlaygroundEditorTheme__tabNode', table: 'PlaygroundEditorTheme__table', tableCell: 'PlaygroundEditorTheme__tableCell', tableCellActionButton: 'PlaygroundEditorTheme__tableCellActionButton', diff --git a/packages/lexical/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts index 1a67571c1a0..dcc91e85658 100644 --- a/packages/lexical/src/LexicalEditor.ts +++ b/packages/lexical/src/LexicalEditor.ts @@ -131,6 +131,7 @@ export type EditorThemeClasses = { quote?: EditorThemeClassName; root?: EditorThemeClassName; rtl?: EditorThemeClassName; + tab?: EditorThemeClassName; table?: EditorThemeClassName; tableAddColumns?: EditorThemeClassName; tableAddRows?: EditorThemeClassName; diff --git a/packages/lexical/src/nodes/LexicalTabNode.ts b/packages/lexical/src/nodes/LexicalTabNode.ts index d3182e40df0..8c5999b33c2 100644 --- a/packages/lexical/src/nodes/LexicalTabNode.ts +++ b/packages/lexical/src/nodes/LexicalTabNode.ts @@ -11,8 +11,9 @@ import type {DOMConversionMap, NodeKey} from '../LexicalNode'; import invariant from 'shared/invariant'; import {IS_UNMERGEABLE} from '../LexicalConstants'; +import {EditorConfig} from '../LexicalEditor'; import {LexicalNode} from '../LexicalNode'; -import {$applyNodeReplacement} from '../LexicalUtils'; +import {$applyNodeReplacement, getCachedClassNameArray} from '../LexicalUtils'; import { SerializedTextNode, TextDetailType, @@ -47,6 +48,17 @@ export class TabNode extends TextNode { return null; } + createDOM(config: EditorConfig): HTMLElement { + const dom = super.createDOM(config); + const classNames = getCachedClassNameArray(config.theme, 'tab'); + + if (classNames !== undefined) { + const domClassList = dom.classList; + domClassList.add(...classNames); + } + return dom; + } + static importJSON(serializedTabNode: SerializedTabNode): TabNode { const node = $createTabNode(); node.setFormat(serializedTabNode.format); From dba254081e59ae7f49e3b3a838f6f8958381cc2e Mon Sep 17 00:00:00 2001 From: Syed Umar Anis Date: Wed, 11 Dec 2024 03:55:40 +1100 Subject: [PATCH 14/25] [lexical-playground] Refactor: editor styles should in PlaygroundEditorTheme.css (#6934) --- packages/lexical-playground/src/index.css | 22 ------------------- .../src/themes/PlaygroundEditorTheme.css | 19 ++++++++++++++++ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/packages/lexical-playground/src/index.css b/packages/lexical-playground/src/index.css index 87fb8fdbbff..8ad3159aeeb 100644 --- a/packages/lexical-playground/src/index.css +++ b/packages/lexical-playground/src/index.css @@ -1766,28 +1766,6 @@ button.item.dropdown-item-active i { z-index: 3; } -.PlaygroundEditorTheme__blockCursor { - display: block; - pointer-events: none; - position: absolute; -} - -.PlaygroundEditorTheme__blockCursor:after { - content: ''; - display: block; - position: absolute; - top: -2px; - width: 20px; - border-top: 1px solid black; - animation: CursorBlink 1.1s steps(2, start) infinite; -} - -@keyframes CursorBlink { - to { - visibility: hidden; - } -} - .dialog-dropdown { background-color: #eee !important; margin-bottom: 10px; diff --git a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css index 031173135f9..2e985c4dc8d 100644 --- a/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css +++ b/packages/lexical-playground/src/themes/PlaygroundEditorTheme.css @@ -133,6 +133,25 @@ text-decoration: underline; cursor: pointer; } +.PlaygroundEditorTheme__blockCursor { + display: block; + pointer-events: none; + position: absolute; +} +.PlaygroundEditorTheme__blockCursor:after { + content: ''; + display: block; + position: absolute; + top: -2px; + width: 20px; + border-top: 1px solid black; + animation: CursorBlink 1.1s steps(2, start) infinite; +} +@keyframes CursorBlink { + to { + visibility: hidden; + } +} .PlaygroundEditorTheme__code { background-color: rgb(240, 242, 245); font-family: Menlo, Consolas, Monaco, monospace; From 42bd45d7e71bff9a2fe44aff18f50be3b3db1a14 Mon Sep 17 00:00:00 2001 From: lin-mt Date: Wed, 11 Dec 2024 00:58:35 +0800 Subject: [PATCH 15/25] [lexical-react]Bug Fix: the location of draggable-block-menu cannot be calculated #6818 (#6915) Co-authored-by: Bob Ippolito --- .../lexical-react/src/LexicalDraggableBlockPlugin.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx b/packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx index d37bd69f3c9..ef7303ee281 100644 --- a/packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx +++ b/packages/lexical-react/src/LexicalDraggableBlockPlugin.tsx @@ -191,9 +191,15 @@ function setMenuPosition( const floatingElemRect = floatingElem.getBoundingClientRect(); const anchorElementRect = anchorElem.getBoundingClientRect(); + // top left + let targetCalculateHeight: number = parseInt(targetStyle.lineHeight, 10); + if (isNaN(targetCalculateHeight)) { + // middle + targetCalculateHeight = targetRect.bottom - targetRect.top; + } const top = targetRect.top + - (parseInt(targetStyle.lineHeight, 10) - floatingElemRect.height) / 2 - + (targetCalculateHeight - floatingElemRect.height) / 2 - anchorElementRect.top; const left = SPACE; From 3ba07059a909ea497113f1b1c905065cbd946f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandr=20Lapu=C5=A1kin?= Date: Tue, 10 Dec 2024 19:42:50 +0200 Subject: [PATCH 16/25] [lexical-list] Bug Fix: Ensure new paragraph node retains selection styling when exiting list (#6917) Co-authored-by: Aleksandr Lapuskin Co-authored-by: Bob Ippolito --- .../lexical-list/src/LexicalListItemNode.ts | 2 +- packages/lexical-list/src/formatList.ts | 16 +++++++--------- .../__tests__/e2e/List.spec.mjs | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index 5581aba730b..252c03704d0 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -382,7 +382,7 @@ export class ListItemNode extends ParagraphNode { } canMergeWith(node: LexicalNode): boolean { - return $isParagraphNode(node) || $isListItemNode(node); + return $isListItemNode(node) || $isParagraphNode(node); } extractWithChild(child: LexicalNode, selection: BaseSelection): boolean { diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts index 83172275908..469d623590a 100644 --- a/packages/lexical-list/src/formatList.ts +++ b/packages/lexical-list/src/formatList.ts @@ -12,7 +12,6 @@ import { $getSelection, $isElementNode, $isLeafNode, - $isParagraphNode, $isRangeSelection, $isRootOrShadowRoot, ElementNode, @@ -494,10 +493,12 @@ export function $handleListInsertParagraph(): boolean { ); const grandparent = parent.getParent(); - let replacementNode: ElementNode; + let replacementNode: ParagraphNode | ListItemNode; if ($isRootOrShadowRoot(grandparent)) { replacementNode = $createParagraphNode(); + replacementNode.setTextStyle(selection.style); + replacementNode.setTextFormat(selection.format); topListNode.insertAfter(replacementNode); } else if ($isListItemNode(grandparent)) { replacementNode = $createListItemNode(); @@ -511,17 +512,14 @@ export function $handleListInsertParagraph(): boolean { const nextSiblings = anchor.getNextSiblings(); if (nextSiblings.length > 0) { const newList = $createListNode(parent.getListType()); - if ($isParagraphNode(replacementNode)) { - replacementNode.insertAfter(newList); - } else { + if ($isListItemNode(replacementNode)) { const newListItem = $createListItemNode(); newListItem.append(newList); replacementNode.insertAfter(newListItem); + } else { + replacementNode.insertAfter(newList); } - nextSiblings.forEach((sibling) => { - sibling.remove(); - newList.append(sibling); - }); + newList.append(...nextSiblings); } // Don't leave hanging nested empty lists $removeHighestEmptyListParent(anchor); diff --git a/packages/lexical-playground/__tests__/e2e/List.spec.mjs b/packages/lexical-playground/__tests__/e2e/List.spec.mjs index e1173a1f66f..d29495ee708 100644 --- a/packages/lexical-playground/__tests__/e2e/List.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/List.spec.mjs @@ -32,6 +32,7 @@ import { pasteFromClipboard, repeat, selectFromAlignDropdown, + selectFromColorPicker, selectFromFormatDropdown, test, waitForSelector, @@ -160,6 +161,24 @@ test.describe.parallel('Nested List', () => { ); }); + test('Should retain selection style when exiting list', async ({page}) => { + await focusEditor(page); + await toggleBulletList(page); + + await selectFromColorPicker(page); + await toggleBold(page); + await page.keyboard.type('Hello'); + //Double-enter to exit list + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.type('World'); + + await assertHTML( + page, + '
  • Hello

World

', + ); + }); + test(`Can indent/outdent mutliple list nodes in a list with multiple levels of indentation`, async ({ page, }) => { From 28c33d64d5a645010bb47062521f5b02d1ed6fcc Mon Sep 17 00:00:00 2001 From: Vinay Kushwaha <51132814+iamvinayvk@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:54:49 +0530 Subject: [PATCH 17/25] [lexical-table] Fix: Delete table row in merge cells (#6922) --- .../__tests__/e2e/Tables.spec.mjs | 443 ++++++++++++++++++ .../lexical-table/src/LexicalTableUtils.ts | 24 +- 2 files changed, 463 insertions(+), 4 deletions(-) diff --git a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs index 1cf4e25a7b1..1b1ba63cd8b 100644 --- a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs @@ -4231,4 +4231,447 @@ test.describe.parallel('Tables', () => { `, ); }); + + test('Can delete table row when previous cell is a merged cell', async ({ + page, + isCollab, + isPlainText, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + + await insertTable(page, 5, 5); + + await selectCellsFromTableCords( + page, + {x: 1, y: 1}, + {x: 1, y: 3}, + false, + false, + ); + await mergeTableCells(page); + await selectCellsFromTableCords( + page, + {x: 1, y: 2}, + {x: 2, y: 4}, + false, + false, + ); + await mergeTableCells(page); + await assertHTML( + page, + html` +


+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+ `, + ); + + await selectCellsFromTableCords( + page, + {x: 0, y: 2}, + {x: 0, y: 2}, + true, + true, + ); + + await deleteTableRows(page); + + await assertHTML( + page, + html` +


+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+ `, + ); + }); + + test('Can delete table row when siblings are merged cell', async ({ + page, + isCollab, + isPlainText, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + + await insertTable(page, 5, 5); + + await selectCellsFromTableCords( + page, + {x: 0, y: 0}, + {x: 0, y: 3}, + true, + true, + ); + await mergeTableCells(page); + await selectCellsFromTableCords( + page, + {x: 2, y: 0}, + {x: 1, y: 2}, + true, + false, + ); + await mergeTableCells(page); + await assertHTML( + page, + html` +


+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+ `, + ); + + await selectCellsFromTableCords( + page, + {x: 0, y: 2}, + {x: 0, y: 2}, + false, + false, + ); + + await deleteTableRows(page); + + await assertHTML( + page, + html` +


+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+ `, + ); + }); }); diff --git a/packages/lexical-table/src/LexicalTableUtils.ts b/packages/lexical-table/src/LexicalTableUtils.ts index e1c0c0884cd..6c354285ec4 100644 --- a/packages/lexical-table/src/LexicalTableUtils.ts +++ b/packages/lexical-table/src/LexicalTableUtils.ts @@ -548,6 +548,7 @@ export function $deleteTableRow__EXPERIMENTAL(): void { return; } const columnCount = gridMap[0].length; + const selectedRowCount = anchorCell.__rowSpan; const nextRow = gridMap[focusEndRow + 1]; const nextRowNode: null | TableRowNode = grid.getChildAtIndex( focusEndRow + 1, @@ -565,7 +566,11 @@ export function $deleteTableRow__EXPERIMENTAL(): void { } // Rows overflowing top have to be trimmed if (row === anchorStartRow && cellStartRow < anchorStartRow) { - cell.setRowSpan(cell.__rowSpan - (cellStartRow - anchorStartRow)); + const overflowTop = anchorStartRow - cellStartRow; + cell.setRowSpan( + cell.__rowSpan - + Math.min(selectedRowCount, cell.__rowSpan - overflowTop), + ); } // Rows overflowing bottom have to be trimmed and moved to the next row if ( @@ -574,11 +579,22 @@ export function $deleteTableRow__EXPERIMENTAL(): void { ) { cell.setRowSpan(cell.__rowSpan - (focusEndRow - cellStartRow + 1)); invariant(nextRowNode !== null, 'Expected nextRowNode not to be null'); - if (column === 0) { + let insertAfterCell: null | TableCellNode = null; + for (let columnIndex = 0; columnIndex < column; columnIndex++) { + const currentCellMap = nextRow[columnIndex]; + const currentCell = currentCellMap.cell; + // Checking the cell having startRow as same as nextRow + if (currentCellMap.startRow === row + 1) { + insertAfterCell = currentCell; + } + if (currentCell.__colSpan > 1) { + columnIndex += currentCell.__colSpan - 1; + } + } + if (insertAfterCell === null) { $insertFirst(nextRowNode, cell); } else { - const {cell: previousCell} = nextRow[column - 1]; - previousCell.insertAfter(cell); + insertAfterCell.insertAfter(cell); } } } From 6243c4b442b19e92aa6f9dd8c2cc629ba9460028 Mon Sep 17 00:00:00 2001 From: Luis Silva Date: Wed, 11 Dec 2024 19:29:02 +0000 Subject: [PATCH 18/25] [scripts-integration-fixtures] Address GitHub detected a vulnerability in the @sveltejs/kit dependency (#6943) Co-authored-by: Luis Fetzner da Silva --- .../package-lock.json | 812 +++++++++--------- .../package.json | 2 +- 2 files changed, 406 insertions(+), 408 deletions(-) diff --git a/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package-lock.json b/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package-lock.json index 113ad945203..c0bf25d4f21 100644 --- a/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package-lock.json +++ b/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package-lock.json @@ -1,24 +1,24 @@ { "name": "lexical-sveltekit-vanilla-js", - "version": "0.17.1", + "version": "0.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lexical-sveltekit-vanilla-js", - "version": "0.17.1", + "version": "0.21.0", "devDependencies": { - "@lexical/dragon": "0.17.1", - "@lexical/history": "0.17.1", - "@lexical/rich-text": "0.17.1", - "@lexical/utils": "0.17.1", + "@lexical/dragon": "0.21.0", + "@lexical/history": "0.21.0", + "@lexical/rich-text": "0.21.0", + "@lexical/utils": "0.21.0", "@playwright/test": "^1.28.1", "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-node": "^5.0.1", "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/kit": "^2.0.0", + "@sveltejs/kit": "^2.10.1", "@sveltejs/vite-plugin-svelte": "^3.0.0", - "lexical": "0.17.1", + "lexical": "0.21.0", "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", "svelte": "^4.2.19", @@ -41,9 +41,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -57,9 +57,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -73,9 +73,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -89,9 +89,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -105,9 +105,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -121,9 +121,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -137,9 +137,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -153,9 +153,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -169,9 +169,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -185,9 +185,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -201,9 +201,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -217,9 +217,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -233,9 +233,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -249,9 +249,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -265,9 +265,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -281,9 +281,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -297,9 +297,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -313,9 +313,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -329,9 +329,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -345,9 +345,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -361,9 +361,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -377,9 +377,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -393,9 +393,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -457,108 +457,100 @@ } }, "node_modules/@lexical/clipboard": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.17.1.tgz", - "integrity": "sha512-OVqnEfWX8XN5xxuMPo6BfgGKHREbz++D5V5ISOiml0Z8fV/TQkdgwqbBJcUdJHGRHWSUwdK7CWGs/VALvVvZyw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.21.0.tgz", + "integrity": "sha512-3lNMlMeUob9fcnRXGVieV/lmPbmet/SVWckNTOwzfKrZ/YW5HiiyJrWviLRVf50dGXTbmBGt7K/2pfPYvWCHFA==", "dev": true, - "license": "MIT", "dependencies": { - "@lexical/html": "0.17.1", - "@lexical/list": "0.17.1", - "@lexical/selection": "0.17.1", - "@lexical/utils": "0.17.1", - "lexical": "0.17.1" + "@lexical/html": "0.21.0", + "@lexical/list": "0.21.0", + "@lexical/selection": "0.21.0", + "@lexical/utils": "0.21.0", + "lexical": "0.21.0" } }, "node_modules/@lexical/dragon": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.17.1.tgz", - "integrity": "sha512-lhBRKP7RlhiVCLtF0qiNqmMhEO6cQB43sMe7d4bvuY1G2++oKY/XAJPg6QJZdXRrCGRQ6vZ26QRNhRPmCxL5Ng==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.21.0.tgz", + "integrity": "sha512-ahTCaOtRFNauEzplN1qVuPjyGAlDd+XcVM5FQCdxVh/1DvqmBxEJRVuCBqatzUUVb89jRBekYUcEdnY9iNjvEQ==", "dev": true, - "license": "MIT", "dependencies": { - "lexical": "0.17.1" + "lexical": "0.21.0" } }, "node_modules/@lexical/history": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.17.1.tgz", - "integrity": "sha512-OU/ohajz4FXchUhghsWC7xeBPypFe50FCm5OePwo767G7P233IztgRKIng2pTT4zhCPW7S6Mfl53JoFHKehpWA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.21.0.tgz", + "integrity": "sha512-Sv2sici2NnAfHYHYRSjjS139MDT8fHP6PlYM2hVr+17dOg7/fJl22VBLRgQ7/+jLtAPxQjID69jvaMlOvt4Oog==", "dev": true, - "license": "MIT", "dependencies": { - "@lexical/utils": "0.17.1", - "lexical": "0.17.1" + "@lexical/utils": "0.21.0", + "lexical": "0.21.0" } }, "node_modules/@lexical/html": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.17.1.tgz", - "integrity": "sha512-yGG+K2DXl7Wn2DpNuZ0Y3uCHJgfHkJN3/MmnFb4jLnH1FoJJiuy7WJb/BRRh9H+6xBJ9v70iv+kttDJ0u1xp5w==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.21.0.tgz", + "integrity": "sha512-UGahVsGz8OD7Ya39qwquE+JPStTxCw/uaQrnUNorCM7owtPidO2H+tsilAB3A1GK3ksFGdHeEjBjG0Gf7gOg+Q==", "dev": true, - "license": "MIT", "dependencies": { - "@lexical/selection": "0.17.1", - "@lexical/utils": "0.17.1", - "lexical": "0.17.1" + "@lexical/selection": "0.21.0", + "@lexical/utils": "0.21.0", + "lexical": "0.21.0" } }, "node_modules/@lexical/list": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.17.1.tgz", - "integrity": "sha512-k9ZnmQuBvW+xVUtWJZwoGtiVG2cy+hxzkLGU4jTq1sqxRIoSeGcjvhFAK8JSEj4i21SgkB1FmkWXoYK5kbwtRA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.21.0.tgz", + "integrity": "sha512-WItGlwwNJCS8b6SO1QPKzArShmD+OXQkLbhBcAh+EfpnkvmCW5T5LqY+OfIRmEN1dhDOnwqCY7mXkivWO8o5tw==", "dev": true, - "license": "MIT", "dependencies": { - "@lexical/utils": "0.17.1", - "lexical": "0.17.1" + "@lexical/utils": "0.21.0", + "lexical": "0.21.0" } }, "node_modules/@lexical/rich-text": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.17.1.tgz", - "integrity": "sha512-T3kvj4P1OpedX9jvxN3WN8NP1Khol6mCW2ScFIRNRz2dsXgyN00thH1Q1J/uyu7aKyGS7rzcY0rb1Pz1qFufqQ==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.21.0.tgz", + "integrity": "sha512-+pvEKUneEkGfWOSTl9jU58N9knePilMLxxOtppCAcgnaCdilOh3n5YyRppXhvmprUe0JaTseCMoik2LP51G/JA==", "dev": true, - "license": "MIT", "dependencies": { - "@lexical/clipboard": "0.17.1", - "@lexical/selection": "0.17.1", - "@lexical/utils": "0.17.1", - "lexical": "0.17.1" + "@lexical/clipboard": "0.21.0", + "@lexical/selection": "0.21.0", + "@lexical/utils": "0.21.0", + "lexical": "0.21.0" } }, "node_modules/@lexical/selection": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.17.1.tgz", - "integrity": "sha512-qBKVn+lMV2YIoyRELNr1/QssXx/4c0id9NCB/BOuYlG8du5IjviVJquEF56NEv2t0GedDv4BpUwkhXT2QbNAxA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.21.0.tgz", + "integrity": "sha512-4u53bc8zlPPF0rnHjsGQExQ1St8NafsDd70/t1FMw7yvoMtUsKdH7+ap00esLkJOMv45unJD7UOzKRqU1X0sEA==", "dev": true, - "license": "MIT", "dependencies": { - "lexical": "0.17.1" + "lexical": "0.21.0" } }, "node_modules/@lexical/table": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.17.1.tgz", - "integrity": "sha512-2fUYPmxhyuMQX3MRvSsNaxbgvwGNJpHaKx1Ldc+PT2MvDZ6ALZkfsxbi0do54Q3i7dOon8/avRp4TuVaCnqvoA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.21.0.tgz", + "integrity": "sha512-JhylAWcf4qKD4FmxMUt3YzH5zg2+baBr4+/haLZL7178hMvUzJwGIiWk+3hD3phzmW3WrP49uFXzM7DMSCkE8w==", "dev": true, - "license": "MIT", "dependencies": { - "@lexical/utils": "0.17.1", - "lexical": "0.17.1" + "@lexical/clipboard": "0.21.0", + "@lexical/utils": "0.21.0", + "lexical": "0.21.0" } }, "node_modules/@lexical/utils": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.17.1.tgz", - "integrity": "sha512-jCQER5EsvhLNxKH3qgcpdWj/necUb82Xjp8qWQ3c0tyL07hIRm2tDRA/s9mQmvcP855HEZSmGVmR5SKtkcEAVg==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.21.0.tgz", + "integrity": "sha512-YzsNOAiLkCy6R3DuP18gtseDrzgx+30lFyqRvp5M7mckeYgQElwdfG5biNFDLv7BM9GjSzgU5Cunjycsx6Sjqg==", "dev": true, - "license": "MIT", "dependencies": { - "@lexical/list": "0.17.1", - "@lexical/selection": "0.17.1", - "@lexical/table": "0.17.1", - "lexical": "0.17.1" + "@lexical/list": "0.21.0", + "@lexical/selection": "0.21.0", + "@lexical/table": "0.21.0", + "lexical": "0.21.0" } }, "node_modules/@playwright/test": { @@ -577,26 +569,27 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.25", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", "dev": true }, "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz", - "integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==", + "version": "28.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz", + "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", - "glob": "^8.0.3", + "fdir": "^6.2.0", "is-reference": "1.2.1", - "magic-string": "^0.30.3" + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0 || 14 >= 14.17" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" @@ -607,6 +600,32 @@ } } }, + "node_modules/@rollup/plugin-commonjs/node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", @@ -628,15 +647,14 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", + "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", "is-module": "^1.0.0", "resolve": "^1.22.1" }, @@ -675,9 +693,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", "cpu": [ "arm" ], @@ -688,9 +706,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", "cpu": [ "arm64" ], @@ -701,9 +719,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", "cpu": [ "arm64" ], @@ -714,9 +732,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", "cpu": [ "x64" ], @@ -726,10 +744,49 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", "cpu": [ "arm" ], @@ -740,9 +797,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", "cpu": [ "arm64" ], @@ -753,9 +810,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", "cpu": [ "arm64" ], @@ -765,10 +822,36 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", "cpu": [ "riscv64" ], @@ -778,10 +861,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", "cpu": [ "x64" ], @@ -792,9 +888,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", "cpu": [ "x64" ], @@ -805,9 +901,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", "cpu": [ "arm64" ], @@ -818,9 +914,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", "cpu": [ "ia32" ], @@ -831,9 +927,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", "cpu": [ "x64" ], @@ -844,26 +940,26 @@ ] }, "node_modules/@sveltejs/adapter-auto": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.1.1.tgz", - "integrity": "sha512-6LeZft2Fo/4HfmLBi5CucMYmgRxgcETweQl/yQoZo/895K3S9YWYN4Sfm/IhwlIpbJp3QNvhKmwCHbsqQNYQpw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz", + "integrity": "sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ==", "dev": true, "dependencies": { - "import-meta-resolve": "^4.0.0" + "import-meta-resolve": "^4.1.0" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "node_modules/@sveltejs/adapter-node": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.0.1.tgz", - "integrity": "sha512-eYdmxdUWMW+dad1JfMsWBPY2vjXz9eE+52A2AQnXPScPJlIxIVk5mmbaEEzrZivLfO2wEcLTZ5vdC03W69x+iA==", + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.9.tgz", + "integrity": "sha512-51euNrx0AcaTu8//wDfVh7xmqQSVgU52rfinE/MwvGkJa4nHPJMHmzv6+OIpmxg7gZaF6+5NVlxnieCzxLD59g==", "dev": true, "dependencies": { - "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-node-resolve": "^15.3.0", "rollup": "^4.9.5" }, "peerDependencies": { @@ -871,32 +967,32 @@ } }, "node_modules/@sveltejs/adapter-static": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz", - "integrity": "sha512-6lMvf7xYEJ+oGeR5L8DFJJrowkefTK6ZgA4JiMqoClMkKq0s6yvsd3FZfCFvX1fQ0tpCD7fkuRVHsnUVgsHyNg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz", + "integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==", "dev": true, "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "node_modules/@sveltejs/kit": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.4.tgz", - "integrity": "sha512-eDxK2d4EGzk99QsZNoPXe7jlzA5EGqfcCpUwZ912bhnalsZ2ZsG5wGRthkydupVjYyqdmzEanVKFhLxU2vkPSQ==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.10.1.tgz", + "integrity": "sha512-2aormKTn94aU8Lfxj4gcbRGh1Dyw0hCFlNo51+njdRDn9P2ERuWC4bOtTuoy5HJpPYR3AH8oaaEjKDWUHbi1OA==", "dev": true, "hasInstallScript": true, "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", - "devalue": "^4.3.2", - "esm-env": "^1.0.0", - "import-meta-resolve": "^4.0.0", + "devalue": "^5.1.0", + "esm-env": "^1.2.1", + "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", - "sirv": "^2.0.4", + "sirv": "^3.0.0", "tiny-glob": "^0.2.9" }, "bin": { @@ -906,9 +1002,9 @@ "node": ">=18.13" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3" + "vite": "^5.0.3 || ^6.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte": { @@ -957,9 +1053,9 @@ "dev": true }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/resolve": { @@ -998,33 +1094,6 @@ "dequal": "^2.0.3" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/code-red": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", @@ -1111,15 +1180,15 @@ } }, "node_modules/devalue": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", - "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", "dev": true }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -1129,35 +1198,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/esm-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", - "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz", + "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==", "dev": true }, "node_modules/estree-walker": { @@ -1166,12 +1235,6 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -1195,37 +1258,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/globalyzer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", @@ -1251,53 +1283,25 @@ } }, "node_modules/import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { - "builtin-modules": "^3.3.0" + "hasown": "^2.0.2" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1328,11 +1332,10 @@ } }, "node_modules/lexical": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.17.1.tgz", - "integrity": "sha512-72/MhR7jqmyqD10bmJw8gztlCm4KDDT+TPtU4elqXrEvHoO5XENi34YAEUD9gIkPfqSwyLa9mwAX1nKzIr5xEA==", - "dev": true, - "license": "MIT" + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.21.0.tgz", + "integrity": "sha512-Dxc5SCG4kB+wF+Rh55ism3SuecOKeOtCtGHFGKd6pj2QKVojtjkxGTQPMt7//2z5rMSue4R+hmRM0pCEZflupA==", + "dev": true }, "node_modules/locate-character": { "version": "3.0.0", @@ -1383,9 +1386,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -1400,15 +1403,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -1445,9 +1439,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { @@ -1493,9 +1487,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -1513,8 +1507,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1563,12 +1557,12 @@ } }, "node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", "dev": true, "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -1578,19 +1572,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", "fsevents": "~2.3.2" } }, @@ -1613,9 +1613,9 @@ "dev": true }, "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", + "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", "dev": true, "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -1623,13 +1623,13 @@ "totalist": "^3.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1742,14 +1742,14 @@ } }, "node_modules/vite": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz", - "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -1768,6 +1768,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -1785,6 +1786,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -1823,12 +1827,6 @@ "optional": true } } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true } } } diff --git a/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package.json b/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package.json index 79ae305e4b2..acb21924aa5 100644 --- a/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package.json +++ b/scripts/__tests__/integration/fixtures/lexical-esm-sveltekit-vanilla-js/package.json @@ -17,7 +17,7 @@ "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-node": "^5.0.1", "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/kit": "^2.0.0", + "@sveltejs/kit": "^2.10.1", "@sveltejs/vite-plugin-svelte": "^3.0.0", "lexical": "0.21.0", "prettier": "^3.1.1", From 61e17c8489968e821148890a8d132c76e97ef3cc Mon Sep 17 00:00:00 2001 From: Sherry Date: Wed, 11 Dec 2024 23:36:58 -0800 Subject: [PATCH 19/25] [lexical-list] Revert PR 6912 (#6944) --- .../lexical-list/src/LexicalListItemNode.ts | 95 ++++++++++--------- packages/lexical-list/src/formatList.ts | 8 +- packages/lexical-list/src/utils.ts | 3 +- .../__tests__/e2e/List.spec.mjs | 44 --------- .../src/__tests__/unit/LexicalEditor.test.tsx | 2 +- .../unit/LexicalSerialization.test.ts | 4 +- .../lexical/src/nodes/LexicalParagraphNode.ts | 13 +-- 7 files changed, 66 insertions(+), 103 deletions(-) diff --git a/packages/lexical-list/src/LexicalListItemNode.ts b/packages/lexical-list/src/LexicalListItemNode.ts index 252c03704d0..9dade3ae0a3 100644 --- a/packages/lexical-list/src/LexicalListItemNode.ts +++ b/packages/lexical-list/src/LexicalListItemNode.ts @@ -7,6 +7,20 @@ */ import type {ListNode, ListType} from './'; +import type { + BaseSelection, + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + EditorThemeClasses, + LexicalNode, + NodeKey, + ParagraphNode, + RangeSelection, + SerializedElementNode, + Spread, +} from 'lexical'; import { addClassNamesToElement, @@ -15,24 +29,11 @@ import { import { $applyNodeReplacement, $createParagraphNode, - $getSelection, $isElementNode, $isParagraphNode, $isRangeSelection, - BaseSelection, - DOMConversionMap, - DOMConversionOutput, - DOMExportOutput, - EditorConfig, - EditorThemeClasses, ElementNode, LexicalEditor, - LexicalNode, - NodeKey, - ParagraphNode, - RangeSelection, - SerializedParagraphNode, - Spread, } from 'lexical'; import invariant from 'shared/invariant'; import normalizeClassNames from 'shared/normalizeClassNames'; @@ -46,11 +47,11 @@ export type SerializedListItemNode = Spread< checked: boolean | undefined; value: number; }, - SerializedParagraphNode + SerializedElementNode >; /** @noInheritDoc */ -export class ListItemNode extends ParagraphNode { +export class ListItemNode extends ElementNode { /** @internal */ __value: number; /** @internal */ @@ -80,11 +81,12 @@ export class ListItemNode extends ParagraphNode { $setListItemThemeClassNames(element, config.theme, this); return element; } - updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean { - if (super.updateDOM(prevNode, dom, config)) { - return true; - } + updateDOM( + prevNode: ListItemNode, + dom: HTMLElement, + config: EditorConfig, + ): boolean { const parent = this.getParent(); if ($isListNode(parent) && parent.getListType() === 'check') { updateListItemChecked(dom, this, prevNode, parent); @@ -92,6 +94,7 @@ export class ListItemNode extends ParagraphNode { // @ts-expect-error - this is always HTMLListItemElement dom.value = this.__value; $setListItemThemeClassNames(dom, config.theme, this); + return false; } @@ -125,12 +128,6 @@ export class ListItemNode extends ParagraphNode { node.setValue(serializedNode.value); node.setFormat(serializedNode.format); node.setDirection(serializedNode.direction); - if (typeof serializedNode.textFormat === 'number') { - node.setTextFormat(serializedNode.textFormat); - } - if (typeof serializedNode.textStyle === 'string') { - node.setTextStyle(serializedNode.textStyle); - } return node; } @@ -227,11 +224,15 @@ export class ListItemNode extends ParagraphNode { } const siblings = this.getNextSiblings(); + + // Split the lists and insert the node in between them listNode.insertAfter(node, restoreSelection); if (siblings.length !== 0) { const newListNode = $createListNode(listNode.getListType()); + siblings.forEach((sibling) => newListNode.append(sibling)); + node.insertAfter(newListNode, restoreSelection); } @@ -255,49 +256,51 @@ export class ListItemNode extends ParagraphNode { } insertNewAfter( - selection: RangeSelection, + _: RangeSelection, restoreSelection = true, ): ListItemNode | ParagraphNode { const newElement = $createListItemNode( this.__checked == null ? undefined : false, ); - - const format = selection.format; - newElement.setTextFormat(format); - - newElement.setFormat(this.getFormatType()); this.insertAfter(newElement, restoreSelection); return newElement; } - collapseAtStart(): boolean { - const selection = $getSelection(); - - if (!$isRangeSelection(selection)) { - return false; - } - + collapseAtStart(selection: RangeSelection): true { const paragraph = $createParagraphNode(); const children = this.getChildren(); children.forEach((child) => paragraph.append(child)); - const listNode = this.getParentOrThrow(); - const listNodeParent = listNode.getParent(); - - if (!$isListNode(listNode)) { - return false; - } + const listNodeParent = listNode.getParentOrThrow(); + const isIndented = $isListItemNode(listNodeParent); if (listNode.getChildrenSize() === 1) { - if ($isListItemNode(listNodeParent)) { + if (isIndented) { + // if the list node is nested, we just want to remove it, + // effectively unindenting it. listNode.remove(); listNodeParent.select(); } else { listNode.insertBefore(paragraph); listNode.remove(); - paragraph.select(); + // If we have selection on the list item, we'll need to move it + // to the paragraph + const anchor = selection.anchor; + const focus = selection.focus; + const key = paragraph.getKey(); + + if (anchor.type === 'element' && anchor.getNode().is(this)) { + anchor.set(key, anchor.offset, 'element'); + } + + if (focus.type === 'element' && focus.getNode().is(this)) { + focus.set(key, focus.offset, 'element'); + } } + } else { + listNode.insertBefore(paragraph); + this.remove(); } return true; diff --git a/packages/lexical-list/src/formatList.ts b/packages/lexical-list/src/formatList.ts index 469d623590a..46694253ebb 100644 --- a/packages/lexical-list/src/formatList.ts +++ b/packages/lexical-list/src/formatList.ts @@ -242,6 +242,7 @@ export function removeList(editor: LexicalEditor): void { if ($isLeafNode(node)) { const listItemNode = $getNearestNodeOfType(node, ListItemNode); + if (listItemNode != null) { listNodes.add($getTopListNode(listItemNode)); } @@ -477,13 +478,11 @@ export function $handleListInsertParagraph(): boolean { return false; } // Only run this code on empty list items - const anchor = selection.anchor.getNode(); if (!$isListItemNode(anchor) || anchor.getChildrenSize() !== 0) { return false; } - const topListNode = $getTopListNode(anchor); const parent = anchor.getParent(); @@ -493,6 +492,7 @@ export function $handleListInsertParagraph(): boolean { ); const grandparent = parent.getParent(); + let replacementNode: ParagraphNode | ListItemNode; if ($isRootOrShadowRoot(grandparent)) { @@ -506,10 +506,10 @@ export function $handleListInsertParagraph(): boolean { } else { return false; } - replacementNode.select(); const nextSiblings = anchor.getNextSiblings(); + if (nextSiblings.length > 0) { const newList = $createListNode(parent.getListType()); if ($isListItemNode(replacementNode)) { @@ -521,7 +521,9 @@ export function $handleListInsertParagraph(): boolean { } newList.append(...nextSiblings); } + // Don't leave hanging nested empty lists $removeHighestEmptyListParent(anchor); + return true; } diff --git a/packages/lexical-list/src/utils.ts b/packages/lexical-list/src/utils.ts index 9b443f76205..9c9b1bf9af1 100644 --- a/packages/lexical-list/src/utils.ts +++ b/packages/lexical-list/src/utils.ts @@ -6,8 +6,9 @@ * */ +import type {LexicalNode, Spread} from 'lexical'; + import {$findMatchingParent} from '@lexical/utils'; -import {type LexicalNode, type Spread} from 'lexical'; import invariant from 'shared/invariant'; import { diff --git a/packages/lexical-playground/__tests__/e2e/List.spec.mjs b/packages/lexical-playground/__tests__/e2e/List.spec.mjs index d29495ee708..df81baee7a2 100644 --- a/packages/lexical-playground/__tests__/e2e/List.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/List.spec.mjs @@ -1901,48 +1901,4 @@ test.describe.parallel('Nested List', () => { }); }, ); - test('new list item should preserve format from previous list item even after new list item is indented', async ({ - page, - }) => { - await focusEditor(page); - await toggleBulletList(page); - await toggleBold(page); - await page.keyboard.type('MLH Fellowship'); - await page.keyboard.press('Enter'); - await clickIndentButton(page); - await page.keyboard.type('Fall 2024'); - await assertHTML( - page, - html` -
    -
  • - - MLH Fellowship - -
  • -
  • -
      -
    • - - Fall 2024 - -
    • -
    -
  • -
- `, - ); - }); }); diff --git a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx index 7f7be6d9c5f..3986f27806f 100644 --- a/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx +++ b/packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx @@ -1026,7 +1026,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,"textFormat":0,"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,"format":"","indent":0,"type":"paragraph","version":1,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"root","version":1}}'; init(); const contentEditable = editor.getRootElement(); editor.setEditable(editable); diff --git a/packages/lexical/src/__tests__/unit/LexicalSerialization.test.ts b/packages/lexical/src/__tests__/unit/LexicalSerialization.test.ts index 96820722dcd..9237bc9d3dd 100644 --- a/packages/lexical/src/__tests__/unit/LexicalSerialization.test.ts +++ b/packages/lexical/src/__tests__/unit/LexicalSerialization.test.ts @@ -110,7 +110,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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","value":4}],"direction":"ltr","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":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"const lexical = \\"awesome\\"","type":"code-highlight","version":1}],"direction":"ltr","format":"","indent":0,"type":"code","version":1,"language":"javascript"},{"children":[{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1}],"direction":"ltr","format":"","indent":0,"type":"table","version":1}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":"ltr","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":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"const lexical = \\"awesome\\"","type":"code-highlight","version":1}],"direction":"ltr","format":"","indent":0,"type":"code","version":1,"language":"javascript"},{"children":[{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1},{"children":[{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1}],"direction":"ltr","format":"","indent":0,"type":"tablerow","version":1}],"direction":"ltr","format":"","indent":0,"type":"table","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}`; expect(stringifiedEditorState).toBe(expectedStringifiedEditorState); @@ -119,7 +119,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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"textFormat":0,"textStyle":"","value":4}],"direction":"ltr","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":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"const lexical = \\"awesome\\"","type":"code-highlight","version":1}],"direction":"ltr","format":"","indent":0,"type":"code","version":1,"language":"javascript"},{"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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","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":"ltr","format":"","indent":0,"type":"listitem","version":1,"value":4}],"direction":"ltr","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":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"const lexical = \\"awesome\\"","type":"code-highlight","version":1}],"direction":"ltr","format":"","indent":0,"type":"code","version":1,"language":"javascript"},{"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":"ltr","format":"","indent":0,"type":"root","version":1}}`, ); }); }); diff --git a/packages/lexical/src/nodes/LexicalParagraphNode.ts b/packages/lexical/src/nodes/LexicalParagraphNode.ts index 036799b3b71..c1250aeae16 100644 --- a/packages/lexical/src/nodes/LexicalParagraphNode.ts +++ b/packages/lexical/src/nodes/LexicalParagraphNode.ts @@ -120,7 +120,11 @@ export class ParagraphNode extends ElementNode { } return dom; } - updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean { + updateDOM( + prevNode: ParagraphNode, + dom: HTMLElement, + config: EditorConfig, + ): boolean { return false; } @@ -160,9 +164,7 @@ export class ParagraphNode extends ElementNode { node.setFormat(serializedNode.format); node.setIndent(serializedNode.indent); node.setDirection(serializedNode.direction); - if (typeof serializedNode.textFormat === 'number') { - node.setTextFormat(serializedNode.textFormat); - } + node.setTextFormat(serializedNode.textFormat); return node; } @@ -188,8 +190,7 @@ export class ParagraphNode extends ElementNode { const direction = this.getDirection(); newElement.setDirection(direction); newElement.setFormat(this.getFormatType()); - newElement.setStyle(this.getStyle()); - + newElement.setStyle(this.getTextStyle()); this.insertAfter(newElement, restoreSelection); return newElement; } From f49f068b96c218d92d1ab719ffeebb5346707304 Mon Sep 17 00:00:00 2001 From: "Niels Y." Date: Thu, 12 Dec 2024 11:38:10 -0800 Subject: [PATCH 20/25] [lexical-onboarding] testing sev mitigation (#6952) --- packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow b/packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow index d7ce0ef3f68..c6b84a047c2 100644 --- a/packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow +++ b/packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow @@ -40,3 +40,5 @@ declare export function registerLexicalCommandLogger( declare export function useLexicalCommandsLog( editor: LexicalEditor, ): LexicalCommandLog; + +// test comment \ No newline at end of file From f989d198043980ad62fb4ca2e55166a9ac0f87f5 Mon Sep 17 00:00:00 2001 From: bailey-meta Date: Thu, 12 Dec 2024 11:42:59 -0800 Subject: [PATCH 21/25] Test comment for pr testing (#6953) Co-authored-by: bailey-meta Co-authored-by: Niels Y. --- packages/lexical-dragon/flow/LexicalDragon.js.flow | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/lexical-dragon/flow/LexicalDragon.js.flow b/packages/lexical-dragon/flow/LexicalDragon.js.flow index 179ab470c54..2db9d522ecc 100644 --- a/packages/lexical-dragon/flow/LexicalDragon.js.flow +++ b/packages/lexical-dragon/flow/LexicalDragon.js.flow @@ -11,3 +11,5 @@ import {$getSelection, $isRangeSelection, $isTextNode} from 'lexical'; declare export function registerDragonSupport( editor: LexicalEditor, ): () => void; + +// test comment for pr testing From b2d8c697d8987617a9e47ecc05b858a627104c8b Mon Sep 17 00:00:00 2001 From: Tranquiliz00 Date: Thu, 12 Dec 2024 12:08:43 -0800 Subject: [PATCH 22/25] Create a test PR (#6955) Co-authored-by: Niels Y. --- packages/lexical-utils/flow/LexicalUtils.js.flow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lexical-utils/flow/LexicalUtils.js.flow b/packages/lexical-utils/flow/LexicalUtils.js.flow index 958dd8acfa7..692edf9116c 100644 --- a/packages/lexical-utils/flow/LexicalUtils.js.flow +++ b/packages/lexical-utils/flow/LexicalUtils.js.flow @@ -135,4 +135,4 @@ declare export function $firstToLastIterator(node: ElementNode): Iterable; -declare export function $unwrapNode(node: ElementNode): void; +declare export function $unwrapNode(node: ElementNode): void; \ No newline at end of file From 89ca73099fe9cded1955d2d2ea7a17df8b11915e Mon Sep 17 00:00:00 2001 From: "Niels Y." Date: Thu, 12 Dec 2024 12:22:39 -0800 Subject: [PATCH 23/25] Revert "[lexical-onboarding] testing sev mitigation (#6952)" (#6956) --- packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow b/packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow index c6b84a047c2..d7ce0ef3f68 100644 --- a/packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow +++ b/packages/lexical-devtools-core/flow/LexicalDevtoolsCore.js.flow @@ -40,5 +40,3 @@ declare export function registerLexicalCommandLogger( declare export function useLexicalCommandsLog( editor: LexicalEditor, ): LexicalCommandLog; - -// test comment \ No newline at end of file From b72d8538a5c7984d648b3357e88c28886ce09d5e Mon Sep 17 00:00:00 2001 From: bailey-meta Date: Thu, 12 Dec 2024 12:22:54 -0800 Subject: [PATCH 24/25] Revert "Test comment for pr testing (#6953)" (#6957) Co-authored-by: bailey-meta --- packages/lexical-dragon/flow/LexicalDragon.js.flow | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/lexical-dragon/flow/LexicalDragon.js.flow b/packages/lexical-dragon/flow/LexicalDragon.js.flow index 2db9d522ecc..179ab470c54 100644 --- a/packages/lexical-dragon/flow/LexicalDragon.js.flow +++ b/packages/lexical-dragon/flow/LexicalDragon.js.flow @@ -11,5 +11,3 @@ import {$getSelection, $isRangeSelection, $isTextNode} from 'lexical'; declare export function registerDragonSupport( editor: LexicalEditor, ): () => void; - -// test comment for pr testing From 0f3ff1569ba3a8b1ed853d0278a44409f265f515 Mon Sep 17 00:00:00 2001 From: Tranquiliz00 Date: Thu, 12 Dec 2024 12:23:00 -0800 Subject: [PATCH 25/25] Test234 (#6958) --- packages/lexical-utils/flow/LexicalUtils.js.flow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lexical-utils/flow/LexicalUtils.js.flow b/packages/lexical-utils/flow/LexicalUtils.js.flow index 692edf9116c..958dd8acfa7 100644 --- a/packages/lexical-utils/flow/LexicalUtils.js.flow +++ b/packages/lexical-utils/flow/LexicalUtils.js.flow @@ -135,4 +135,4 @@ declare export function $firstToLastIterator(node: ElementNode): Iterable; -declare export function $unwrapNode(node: ElementNode): void; \ No newline at end of file +declare export function $unwrapNode(node: ElementNode): void;