Skip to content

Commit

Permalink
fix: properly handle VariableNode deletion in Lexical
Browse files Browse the repository at this point in the history
  • Loading branch information
loicguillois committed Feb 5, 2025
1 parent 54b63d7 commit fb1ff88
Showing 1 changed file with 79 additions and 48 deletions.
127 changes: 79 additions & 48 deletions frontend/src/components/RichEditor/useVariablePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,63 +14,94 @@ import { useEffect } from 'react';

import { $createVariableNode, VariableNode } from './nodes/VariableNode';
import { Variable } from './Variable';
import { $getNodeByKey } from 'lexical';

interface Props {
editor: LexicalEditor;
}

export const INSERT_VARIABLE_COMMAND: LexicalCommand<Variable> =
createCommand();
export function useVariablePlugin(props: Props) {
const { editor } = props;

export function useVariablePlugin(props: Props) {
const { editor } = props;

useEffect(() => {
return editor.registerCommand(
INSERT_VARIABLE_COMMAND,
(variable): boolean => {
const selection = $getSelection();
if (selection && $isNodeSelection(selection)) {
return false;
}

const node = $createVariableNode(variable);
$insertNodes([node]);

return true;
},
COMMAND_PRIORITY_EDITOR
);
}, [editor]);

useEffect(() => {
return editor.registerNodeTransform(VariableNode, (node) => {
const before = node.getPreviousSibling();

if (!isWhitespaceBefore(before)) {
node.insertBefore($createTextNode(' '));
}

const after = node.getNextSibling();
if (!isWhitespaceAfter(after)) {
node.insertAfter($createTextNode(' '));
}
});
}, [editor]);

function insertVariable(variable: Variable): void {
editor.dispatchCommand(INSERT_VARIABLE_COMMAND, variable);
useEffect(() => {
return editor.registerCommand(
INSERT_VARIABLE_COMMAND,
(variable): boolean => {
const selection = $getSelection();
if (selection && $isNodeSelection(selection)) {
return false;
}

const node = $createVariableNode(variable);
const selectionNode = selection?.getNodes()[0];

if (!isWhitespaceBefore(selectionNode ?? null)) {
$insertNodes([$createTextNode(' ')]);
}

$insertNodes([node]);

const after = node.getNextSibling();
if (!isWhitespaceAfter(after)) {
node.insertAfter($createTextNode(' '));
}

return true;
},
COMMAND_PRIORITY_EDITOR
);
}, [editor]);

useEffect(() => {
return editor.registerMutationListener(VariableNode, (nodes) => {
editor.update(() => {
nodes.forEach((mutation, nodeKey) => {
const node = editor.getEditorState().read(() => $getNodeByKey(nodeKey));
if (node instanceof VariableNode && !node.isAttached()) {
cleanUpWhitespace(node);
}
});
});
});
}, [editor]);


function insertVariable(variable: Variable): void {
editor.dispatchCommand(INSERT_VARIABLE_COMMAND, variable);
}

return {
insertVariable,
};
}

return {
insertVariable,
};
}
function isWhitespaceBefore(node: LexicalNode | null): boolean {
return !node || ($isTextNode(node) && /\s$/.test(node.getTextContent()));
}

function isWhitespaceBefore(node: LexicalNode | null): boolean {
return $isTextNode(node) && node.getTextContent().endsWith(' ');
}
function isWhitespaceAfter(node: LexicalNode | null): boolean {
return !node || ($isTextNode(node) && /^\s/.test(node.getTextContent()));
}

function isWhitespaceAfter(node: LexicalNode | null): boolean {
return $isTextNode(node) && node.getTextContent().startsWith(' ');
}
function cleanUpWhitespace(node: VariableNode) {
const prev = node.getPreviousSibling();
const next = node.getNextSibling();

if ($isTextNode(prev) && $isTextNode(next)) {
const prevText = prev.getTextContent().trimEnd();
const nextText = next.getTextContent().trimStart();

prev.setTextContent(`${prevText} ${nextText}`);
next.remove();
}

if ($isTextNode(prev) && prev.getTextContent().trim() === '') {
prev.remove();
}

if ($isTextNode(next) && next.getTextContent().trim() === '') {
next.remove();
}
}

0 comments on commit fb1ff88

Please sign in to comment.