From 1b05850988d1c596b37b23deca4f5ac9149cda53 Mon Sep 17 00:00:00 2001 From: levensta Date: Fri, 26 Jun 2026 04:13:45 +0300 Subject: [PATCH] preverse last non apple interchange br --- .../lexical-html/src/import/coreImportRules.ts | 4 +++- .../__tests__/unit/LexicalEventHelpers.test.tsx | 4 ++-- packages/lexical/src/index.ts | 1 + packages/lexical/src/nodes/LexicalLineBreakNode.ts | 14 +++++++++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/lexical-html/src/import/coreImportRules.ts b/packages/lexical-html/src/import/coreImportRules.ts index 87c4adc6c65..6b6f8b5a2de 100644 --- a/packages/lexical-html/src/import/coreImportRules.ts +++ b/packages/lexical-html/src/import/coreImportRules.ts @@ -27,6 +27,7 @@ import { IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, + isAppleInterchangeNewline, isBlockDomNode, isDOMTextNode, isLastChildInBlockNode, @@ -453,7 +454,8 @@ const LineBreakRule = /* @__PURE__ */ defineImportRule({ // would otherwise survive as a LineBreakNode and tack an extra blank // line onto the imported content. $import: (_ctx, el) => - isOnlyChildInBlockNode(el) || isLastChildInBlockNode(el) + isOnlyChildInBlockNode(el) || + (isAppleInterchangeNewline(el) && isLastChildInBlockNode(el)) ? [] : [$createLineBreakNode()], match: sel.tag('br'), diff --git a/packages/lexical-utils/src/__tests__/unit/LexicalEventHelpers.test.tsx b/packages/lexical-utils/src/__tests__/unit/LexicalEventHelpers.test.tsx index 4ba20a998a0..56693485852 100644 --- a/packages/lexical-utils/src/__tests__/unit/LexicalEventHelpers.test.tsx +++ b/packages/lexical-utils/src/__tests__/unit/LexicalEventHelpers.test.tsx @@ -737,9 +737,9 @@ describe('LexicalEventHelpers', () => { }, { expectedHTML: - '

1

2

3

', + '

1

2

3

', inputs: [pasteHTML('1

2

3')], - name: 'last br in a block node is ignored', + name: 'last br in a block node is preserved', }, ]; diff --git a/packages/lexical/src/index.ts b/packages/lexical/src/index.ts index 282400a275e..7b87a99202f 100644 --- a/packages/lexical/src/index.ts +++ b/packages/lexical/src/index.ts @@ -386,6 +386,7 @@ export type {SerializedLineBreakNode} from './nodes/LexicalLineBreakNode'; export { $createLineBreakNode, $isLineBreakNode, + isAppleInterchangeNewline, isLastChildInBlockNode, isOnlyChildInBlockNode, LineBreakNode, diff --git a/packages/lexical/src/nodes/LexicalLineBreakNode.ts b/packages/lexical/src/nodes/LexicalLineBreakNode.ts index 376ae273ad7..3e90e83dfc5 100644 --- a/packages/lexical/src/nodes/LexicalLineBreakNode.ts +++ b/packages/lexical/src/nodes/LexicalLineBreakNode.ts @@ -19,6 +19,7 @@ import { $applyNodeReplacement, isBlockDomNode, isDOMTextNode, + isHTMLElement, } from '../LexicalUtils'; export type SerializedLineBreakNode = SerializedLexicalNode; @@ -58,7 +59,10 @@ export class LineBreakNode extends LexicalNode { static importDOM(): DOMConversionMap | null { return { br: (node: Node) => { - if (isOnlyChildInBlockNode(node) || isLastChildInBlockNode(node)) { + if ( + isOnlyChildInBlockNode(node) || + (isAppleInterchangeNewline(node) && isLastChildInBlockNode(node)) + ) { return null; } return { @@ -151,6 +155,14 @@ export function isLastChildInBlockNode(node: Node): boolean { return false; } +export function isAppleInterchangeNewline(node: Node): boolean { + return ( + node.nodeName === 'BR' && + isHTMLElement(node) && + node.getAttribute('class') === 'Apple-interchange-newline' + ); +} + function isWhitespaceDomTextNode(node: Node): boolean { return isDOMTextNode(node) && /^( |\t|\r?\n)+$/.test(node.textContent || ''); }