Lexical: Fixed code in lists, removed extra old alignment code

Code in lists could throw error on parse due to inner <code> tag being
parsed but not actually used within a <pre>, so this updates the
importDOM to disregard childdren for code blocks.

This also improves the invariant implementation to not be so
dev/debugger based, and to include vars in the output.
This commit is contained in:
Dan Brown 2025-02-16 15:09:33 +00:00
parent 7e03a973d8
commit 2b746425c9
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
10 changed files with 25 additions and 68 deletions

View File

@ -8,7 +8,6 @@
import type { import type {
BaseSelection, BaseSelection,
ElementFormatType,
LexicalCommand, LexicalCommand,
LexicalNode, LexicalNode,
TextFormatType, TextFormatType,
@ -91,8 +90,6 @@ export const OUTDENT_CONTENT_COMMAND: LexicalCommand<void> = createCommand(
); );
export const DROP_COMMAND: LexicalCommand<DragEvent> = export const DROP_COMMAND: LexicalCommand<DragEvent> =
createCommand('DROP_COMMAND'); createCommand('DROP_COMMAND');
export const FORMAT_ELEMENT_COMMAND: LexicalCommand<ElementFormatType> =
createCommand('FORMAT_ELEMENT_COMMAND');
export const DRAGSTART_COMMAND: LexicalCommand<DragEvent> = export const DRAGSTART_COMMAND: LexicalCommand<DragEvent> =
createCommand('DRAGSTART_COMMAND'); createCommand('DRAGSTART_COMMAND');
export const DRAGOVER_COMMAND: LexicalCommand<DragEvent> = export const DRAGOVER_COMMAND: LexicalCommand<DragEvent> =

View File

@ -6,7 +6,6 @@
* *
*/ */
import type {ElementFormatType} from './nodes/LexicalElementNode';
import type { import type {
TextDetailType, TextDetailType,
TextFormatType, TextFormatType,
@ -111,27 +110,6 @@ export const DETAIL_TYPE_TO_DETAIL: Record<TextDetailType | string, number> = {
unmergeable: IS_UNMERGEABLE, unmergeable: IS_UNMERGEABLE,
}; };
export const ELEMENT_TYPE_TO_FORMAT: Record<
Exclude<ElementFormatType, ''>,
number
> = {
center: IS_ALIGN_CENTER,
end: IS_ALIGN_END,
justify: IS_ALIGN_JUSTIFY,
left: IS_ALIGN_LEFT,
right: IS_ALIGN_RIGHT,
start: IS_ALIGN_START,
};
export const ELEMENT_FORMAT_TO_TYPE: Record<number, ElementFormatType> = {
[IS_ALIGN_CENTER]: 'center',
[IS_ALIGN_END]: 'end',
[IS_ALIGN_JUSTIFY]: 'justify',
[IS_ALIGN_LEFT]: 'left',
[IS_ALIGN_RIGHT]: 'right',
[IS_ALIGN_START]: 'start',
};
export const TEXT_MODE_TO_TYPE: Record<TextModeType, 0 | 1 | 2> = { export const TEXT_MODE_TO_TYPE: Record<TextModeType, 0 | 1 | 2> = {
normal: IS_NORMAL, normal: IS_NORMAL,
segmented: IS_SEGMENTED, segmented: IS_SEGMENTED,

View File

@ -146,6 +146,12 @@ type NodeName = string;
* Output for a DOM conversion. * Output for a DOM conversion.
* Node can be set to 'ignore' to ignore the conversion and handling of the DOMNode * Node can be set to 'ignore' to ignore the conversion and handling of the DOMNode
* including all its children. * including all its children.
*
* You can specify a function to run for each converted child (forChild) or on all
* the child nodes after the conversion is complete (after).
* The key difference here is that forChild runs for every deeply nested child node
* of the current node, whereas after will run only once after the
* transformation of the node and all its children is complete.
*/ */
export type DOMConversionOutput = { export type DOMConversionOutput = {
after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>; after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>;

View File

@ -49,15 +49,12 @@ export type {
} from './LexicalNode'; } from './LexicalNode';
export type { export type {
BaseSelection, BaseSelection,
ElementPointType as ElementPoint,
NodeSelection, NodeSelection,
Point, Point,
PointType, PointType,
RangeSelection, RangeSelection,
TextPointType as TextPoint,
} from './LexicalSelection'; } from './LexicalSelection';
export type { export type {
ElementFormatType,
SerializedElementNode, SerializedElementNode,
} from './nodes/LexicalElementNode'; } from './nodes/LexicalElementNode';
export type {SerializedRootNode} from './nodes/LexicalRootNode'; export type {SerializedRootNode} from './nodes/LexicalRootNode';
@ -87,7 +84,6 @@ export {
DRAGSTART_COMMAND, DRAGSTART_COMMAND,
DROP_COMMAND, DROP_COMMAND,
FOCUS_COMMAND, FOCUS_COMMAND,
FORMAT_ELEMENT_COMMAND,
FORMAT_TEXT_COMMAND, FORMAT_TEXT_COMMAND,
INDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND,
INSERT_LINE_BREAK_COMMAND, INSERT_LINE_BREAK_COMMAND,

View File

@ -46,15 +46,6 @@ export type SerializedElementNode<
SerializedLexicalNode SerializedLexicalNode
>; >;
export type ElementFormatType =
| 'left'
| 'start'
| 'center'
| 'right'
| 'end'
| 'justify'
| '';
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export interface ElementNode { export interface ElementNode {
getTopLevelElement(): ElementNode | null; getTopLevelElement(): ElementNode | null;

View File

@ -1314,6 +1314,11 @@ const nodeNameToTextFormat: Record<string, TextFormatType> = {
function convertTextFormatElement(domNode: HTMLElement): DOMConversionOutput { function convertTextFormatElement(domNode: HTMLElement): DOMConversionOutput {
const format = nodeNameToTextFormat[domNode.nodeName.toLowerCase()]; const format = nodeNameToTextFormat[domNode.nodeName.toLowerCase()];
if (format === 'code' && domNode.closest('pre')) {
return {node: null};
}
if (format === undefined) { if (format === undefined) {
return {node: null}; return {node: null};
} }

View File

@ -18,9 +18,9 @@ export default function invariant(
return; return;
} }
throw new Error( for (const arg of args) {
'Internal Lexical error: invariant() is meant to be replaced at compile ' + message = (message || '').replace('%s', arg);
'time. There is no runtime version. Error: ' + }
message,
); throw new Error(message);
} }

View File

@ -11,7 +11,6 @@ import type {
DOMChildConversion, DOMChildConversion,
DOMConversion, DOMConversion,
DOMConversionFn, DOMConversionFn,
ElementFormatType,
LexicalEditor, LexicalEditor,
LexicalNode, LexicalNode,
} from 'lexical'; } from 'lexical';
@ -58,6 +57,7 @@ export function $generateNodesFromDOM(
} }
} }
} }
$unwrapArtificalNodes(allArtificialNodes); $unwrapArtificalNodes(allArtificialNodes);
return lexicalNodes; return lexicalNodes;
@ -324,8 +324,6 @@ function wrapContinuousInlines(
nodes: Array<LexicalNode>, nodes: Array<LexicalNode>,
createWrapperFn: () => ElementNode, createWrapperFn: () => ElementNode,
): Array<LexicalNode> { ): Array<LexicalNode> {
const textAlign = (domNode as HTMLElement).style
.textAlign as ElementFormatType;
const out: Array<LexicalNode> = []; const out: Array<LexicalNode> = [];
let continuousInlines: Array<LexicalNode> = []; let continuousInlines: Array<LexicalNode> = [];
// wrap contiguous inline child nodes in para // wrap contiguous inline child nodes in para

View File

@ -145,7 +145,14 @@ export class CodeBlockNode extends DecoratorNode<EditorDecoratorAdapter> {
node.setId(element.id); node.setId(element.id);
} }
return { node }; return {
node,
after(childNodes): LexicalNode[] {
// Remove any child nodes that may get parsed since we're manually
// controlling the code contents.
return [];
},
};
}, },
priority: 3, priority: 3,
}; };

View File

@ -8,7 +8,6 @@
import type { import type {
CommandPayloadType, CommandPayloadType,
ElementFormatType,
LexicalCommand, LexicalCommand,
LexicalEditor, LexicalEditor,
PasteCommandType, PasteCommandType,
@ -44,7 +43,6 @@ import {
DRAGSTART_COMMAND, DRAGSTART_COMMAND,
DROP_COMMAND, DROP_COMMAND,
ElementNode, ElementNode,
FORMAT_ELEMENT_COMMAND,
FORMAT_TEXT_COMMAND, FORMAT_TEXT_COMMAND,
INSERT_LINE_BREAK_COMMAND, INSERT_LINE_BREAK_COMMAND,
INSERT_PARAGRAPH_COMMAND, INSERT_PARAGRAPH_COMMAND,
@ -285,25 +283,6 @@ export function registerRichText(editor: LexicalEditor): () => void {
}, },
COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_EDITOR,
), ),
editor.registerCommand<ElementFormatType>(
FORMAT_ELEMENT_COMMAND,
(format) => {
const selection = $getSelection();
if (!$isRangeSelection(selection) && !$isNodeSelection(selection)) {
return false;
}
const nodes = selection.getNodes();
for (const node of nodes) {
const element = $findMatchingParent(
node,
(parentNode): parentNode is ElementNode =>
$isElementNode(parentNode) && !parentNode.isInline(),
);
}
return true;
},
COMMAND_PRIORITY_EDITOR,
),
editor.registerCommand<boolean>( editor.registerCommand<boolean>(
INSERT_LINE_BREAK_COMMAND, INSERT_LINE_BREAK_COMMAND,
(selectStart) => { (selectStart) => {