From fc8cf099e3a15125652716ab94fc7d2c60f8bf4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kub=C3=A1t?= <36794259+skopz356@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:28:21 +0200 Subject: [PATCH] Allow exporting a document fragment from the exportDOM function (#6641) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomáš Kubát --- .../src/__tests__/unit/LexicalHtml.test.ts | 55 ++++++++++++++++++- packages/lexical-html/src/index.ts | 9 ++- packages/lexical/src/LexicalNode.ts | 4 +- packages/lexical/src/LexicalUtils.ts | 11 ++++ packages/lexical/src/index.ts | 1 + 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/packages/lexical-html/src/__tests__/unit/LexicalHtml.test.ts b/packages/lexical-html/src/__tests__/unit/LexicalHtml.test.ts index 5583cf94f5a..8759603419a 100644 --- a/packages/lexical-html/src/__tests__/unit/LexicalHtml.test.ts +++ b/packages/lexical-html/src/__tests__/unit/LexicalHtml.test.ts @@ -6,9 +6,6 @@ * */ -//@ts-ignore-next-line -import type {RangeSelection} from 'lexical'; - import {CodeNode} from '@lexical/code'; import {createHeadlessEditor} from '@lexical/headless'; import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html'; @@ -20,6 +17,8 @@ import { $createRangeSelection, $createTextNode, $getRoot, + ParagraphNode, + RangeSelection, } from 'lexical'; describe('HTML', () => { @@ -212,4 +211,54 @@ describe('HTML', () => { '

Hello world!

', ); }); + + test('It should output correctly nodes whose export is DocumentFragment', () => { + const editor = createHeadlessEditor({ + html: { + export: new Map([ + [ + ParagraphNode, + () => { + const element = document.createDocumentFragment(); + return { + element, + }; + }, + ], + ]), + }, + nodes: [], + }); + + editor.update( + () => { + const root = $getRoot(); + const p1 = $createParagraphNode(); + const text1 = $createTextNode('Hello'); + p1.append(text1); + const p2 = $createParagraphNode(); + const text2 = $createTextNode('World'); + p2.append(text2); + root.append(p1).append(p2); + // Root + // - ParagraphNode + // -- TextNode "Hello" + // - ParagraphNode + // -- TextNode "World" + }, + { + discrete: true, + }, + ); + + let html = ''; + + editor.update(() => { + html = $generateHtmlFromNodes(editor); + }); + + expect(html).toBe( + 'HelloWorld', + ); + }); }); diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts index 2975315cc35..732bab44048 100644 --- a/packages/lexical-html/src/index.ts +++ b/packages/lexical-html/src/index.ts @@ -29,6 +29,7 @@ import { $isTextNode, ArtificialNode__DO_NOT_USE, ElementNode, + isDocumentFragment, isInlineDomNode, } from 'lexical'; @@ -147,7 +148,7 @@ function $appendNodesToHTML( } if (shouldInclude && !shouldExclude) { - if (isHTMLElement(element)) { + if (isHTMLElement(element) || isDocumentFragment(element)) { element.append(fragment); } parentElement.append(element); @@ -155,7 +156,11 @@ function $appendNodesToHTML( if (after) { const newElement = after.call(target, element); if (newElement) { - element.replaceWith(newElement); + if (isDocumentFragment(element)) { + element.replaceChildren(newElement); + } else { + element.replaceWith(newElement); + } } } } else { diff --git a/packages/lexical/src/LexicalNode.ts b/packages/lexical/src/LexicalNode.ts index 5f70d3e36df..564989cdc2e 100644 --- a/packages/lexical/src/LexicalNode.ts +++ b/packages/lexical/src/LexicalNode.ts @@ -155,9 +155,9 @@ export type DOMExportOutputMap = Map< export type DOMExportOutput = { after?: ( - generatedElement: HTMLElement | Text | null | undefined, + generatedElement: HTMLElement | DocumentFragment | Text | null | undefined, ) => HTMLElement | Text | null | undefined; - element: HTMLElement | Text | null; + element: HTMLElement | DocumentFragment | Text | null; }; export type NodeKey = string; diff --git a/packages/lexical/src/LexicalUtils.ts b/packages/lexical/src/LexicalUtils.ts index 65b45cf7362..bf7904d3b90 100644 --- a/packages/lexical/src/LexicalUtils.ts +++ b/packages/lexical/src/LexicalUtils.ts @@ -1668,6 +1668,17 @@ export function isHTMLElement(x: Node | EventTarget): x is HTMLElement { return x.nodeType === 1; } +/** + * @param x - The element being testing + * @returns Returns true if x is a document fragment, false otherwise. + */ +export function isDocumentFragment( + x: Node | EventTarget, +): x is DocumentFragment { + // @ts-ignore-next-line - strict check on nodeType here should filter out non-Element EventTarget implementors + return x.nodeType === 11; +} + /** * * @param node - the Dom Node to check diff --git a/packages/lexical/src/index.ts b/packages/lexical/src/index.ts index 5ef926b5afc..b3f5013cdd7 100644 --- a/packages/lexical/src/index.ts +++ b/packages/lexical/src/index.ts @@ -179,6 +179,7 @@ export { getEditorPropertyFromDOMNode, getNearestEditorFromDOMNode, isBlockDomNode, + isDocumentFragment, isHTMLAnchorElement, isHTMLElement, isInlineDomNode,