question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Question: Select the content Copy Paste and Delete in editor doesn't work with error currentNode.extractWithChild is not a function

See original GitHub issue

Lexical version:0.2.5

Steps To Reproduce

  1. input some content in editor and submit
  2. modify the content in Editor which has initialEditorState
  3. select some content to copy and try to paste
  4. select some content and try to delete

https://user-images.githubusercontent.com/46723838/169605141-cb98848b-d517-4681-b798-c8d0939a4b2f.mov

The current behavior

  • copy some contents from out side of editor and paste it in the editor works but once I tried to select the input to copy and paste it it stopped working. I cannot deleted, copy and paste anymore.

error Screen Shot 2022-05-20 at 1 08 02 PM

editState saved in BE value: "{\"_nodeMap\":[[\"root\",{\"__children\":[\"299\"],\"__dir\":\"ltr\",\"__format\":0,\"__indent\":0,\"__key\":\"root\",\"__parent\":null,\"__type\":\"root\"}],[\"299\",{\"__type\":\"paragraph\",\"__parent\":\"root\",\"__key\":\"299\",\"__children\":[\"325\"],\"__format\":0,\"__indent\":0,\"__dir\":\"ltr\"}],[\"325\",{\"__type\":\"text\",\"__parent\":\"299\",\"__key\":\"325\",\"__text\":\"copy and paste issue example\",\"__format\":0,\"__style\":\"\",\"__mode\":0,\"__detail\":0,\"__marks\":null}]],\"_selection\":{\"anchor\":{\"key\":\"325\",\"offset\":28,\"type\":\"text\"},\"focus\":{\"key\":\"325\",\"offset\":0,\"type\":\"text\"},\"type\":\"range\"}}"

code Editor.tsx

export const customTheme = {
    ltr: 'ltr',
    rtl: 'rtl',
    placeholder: 'editor-placeholder',
    paragraph: 'editor-paragraph',
    list: {
        nested: {
            listitem: 'editor-nested-listitem',
        },
        ol: 'editor-list-ol',
        ul: 'editor-list-ul',
        listitem: 'editor-listitem',
    },
    link: 'editor-link',
    text: {
        bold: 'editor-text-bold',
        italic: 'editor-text-italic',
    },
};

const useStyles = makeStyles((theme) => ({
    editorContainer: {
        borderRadius: '2px',
        fontWeight: 400,
        textAlign: 'left',
        borderTopLeftRadius: '10px',
        borderTopRightRadius: '10px',
        border: `2px solid ${theme.palette.grey[300]}`,
        maxWidth: '550px',
    },

    editorInner: {
        background: '#fff',
        position: 'relative',
    },

    editorInput: {
        minHeight: '184px',
        maxHeight: '2500px',
        resize: 'none',
        fontSize: '1rem',
        position: 'relative',
        tabSize: '1',
        outline: 0,
        padding: theme.spacing(2, 1.5),
        overflowY: 'scroll',
    },
    editor: {
        paddingTop: theme.spacing(1),
    },
}));
interface Error {
    name: string;
    message: string;
    stack?: string;
}
interface Props {
    onChange: (editorState: EditorState, editor: LexicalEditor) => void;
    initialValue: EditorState | undefined;
}

const editorConfig = {
    // The editor theme
    theme: customTheme,
    // Handling of errors during update
    onError(error: Error) {
        throw error;
    },
    // Any custom nodes go here
    nodes: [ListNode, ListItemNode, AutoLinkNode, LinkNode],
};

const Editor = ({ onChange, initialValue }: Props): JSX.Element => {
    const classes = useStyles();

    return (
        <Grid container>
            <Grid item xs={12} className={classes.editor}>
                <LexicalComposer initialConfig={editorConfig}>
                    <div className={classes.editorContainer}>
                        <ToolbarPlugin />
                        <div className="editor-inner">
                            <RichTextPlugin
                                contentEditable={
                                    <ContentEditable
                                        className={classes.editorInput}
                                    />
                                }
                                initialEditorState={
                                    initialValue ? initialValue : null
                                }
                                placeholder={null}
                            />
                            <OnChangePlugin onChange={onChange} />
                            <AutoFocusPlugin />
                            <ListPlugin />
                            <LinkPlugin />
                        </div>
                    </div>
                </LexicalComposer>
            </Grid>
        </Grid>
    );
};

ToolbarPlugin.js https://codesandbox.io/s/lexical-rich-text-example-5tncvy?file=/src/plugins/ToolbarPlugin.js mostly same as this example. thank you for making this

import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import React, { useCallback, useEffect, useRef, useState } from 'react';

import `{`
    SELECTION_CHANGE_COMMAND,
    FORMAT_TEXT_COMMAND,
    FORMAT_ELEMENT_COMMAND,
    $getSelection,
    $isRangeSelection,
    INDENT_CONTENT_COMMAND,
    OUTDENT_CONTENT_COMMAND,
} from 'lexical';

import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
import { $isAtNodeEnd } from '@lexical/selection';
import { $getNearestNodeOfType, mergeRegister } from '@lexical/utils';
import {
    INSERT_ORDERED_LIST_COMMAND,
    INSERT_UNORDERED_LIST_COMMAND,
    REMOVE_LIST_COMMAND,
    $isListNode,
    ListNode,
} from '@lexical/list';
import { createPortal } from 'react-dom';
import { $isHeadingNode } from '@lexical/rich-text';

import './style.css';
import { Grid } from '@mui/material';
import LinkIcon from '@mui/icons-material/Link';
import FormatAlignJustifyIcon from '@mui/icons-material/FormatAlignJustify';
import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft';
import FormatAlignRightIcon from '@mui/icons-material/FormatAlignRight';
import FormatAlignCenterIcon from '@mui/icons-material/FormatAlignCenter';
import FormatItalicIcon from '@mui/icons-material/FormatItalic';
import FormatBoldIcon from '@mui/icons-material/FormatBold';
import FormatIndentDecreaseIcon from '@mui/icons-material/FormatIndentDecrease';
import FormatIndentIncreaseIcon from '@mui/icons-material/FormatIndentIncrease';
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';

import Edit from '@mui/icons-material/Edit';

const LowPriority = 1;

function Divider() {
    return <div className="divider" />;
}

function positionEditorElement(editor, rect) {
    if (rect === null) {
        editor.style.opacity = '0';
        editor.style.top = '-1000px';
        editor.style.left = '-1000px';
    } else {
        editor.style.opacity = '1';
        editor.style.top = `${
            rect.top + rect.height + window.pageYOffset + 10
        }px`;
        editor.style.left = `${
            rect.left +
            window.pageXOffset -
            editor.offsetWidth / 25 +
            rect.width / 25
        }px`;
    }
}
function getSelectedNode(selection) {
    const anchor = selection.anchor;
    const focus = selection.focus;
    const anchorNode = selection.anchor.getNode();
    const focusNode = selection.focus.getNode();
    if (anchorNode === focusNode) {
        return anchorNode;
    }
    const isBackward = selection.isBackward();
    if (isBackward) {
        return $isAtNodeEnd(focus) ? anchorNode : focusNode;
    } else {
        return $isAtNodeEnd(anchor) ? focusNode : anchorNode;
    }
}
function FloatingLinkEditor({ editor }) {
    const editorRef = useRef(null);
    const inputRef = useRef(null);
    const mouseDownRef = useRef(false);
    const [linkUrl, setLinkUrl] = useState('');
    const [isEditMode, setEditMode] = useState(false);
    const [lastSelection, setLastSelection] = useState(null);

    const updateLinkEditor = useCallback(() => {
        const selection = $getSelection();
        if ($isRangeSelection(selection)) {
            const node = getSelectedNode(selection);
            const parent = node.getParent();
            if ($isLinkNode(parent)) {
                setLinkUrl(parent.getURL());
            } else if ($isLinkNode(node)) {
                setLinkUrl(node.getURL());
            } else {
                setLinkUrl('');
            }
        }
        const editorElem = editorRef.current;
        const nativeSelection = window.getSelection();
        const activeElement = document.activeElement;

        if (editorElem === null) {
            return;
        }

        const rootElement = editor.getRootElement();
        if (
            selection !== null &&
            !nativeSelection.isCollapsed &&
            rootElement !== null &&
            rootElement.contains(nativeSelection.anchorNode)
        ) {
            const domRange = nativeSelection.getRangeAt(0);
            let rect;
            if (nativeSelection.anchorNode === rootElement) {
                let inner = rootElement;
                while (inner.firstElementChild != null) {
                    inner = inner.firstElementChild;
                }
                rect = inner.getBoundingClientRect();
            } else {
                rect = domRange.getBoundingClientRect();
            }

            if (!mouseDownRef.current) {
                positionEditorElement(editorElem, rect);
            }
            setLastSelection(selection);
        } else if (!activeElement || activeElement.className !== 'link-input') {
            positionEditorElement(editorElem, null);
            setLastSelection(null);
            setEditMode(false);
            setLinkUrl('');
        }

        return true;
    }, [editor]);

    useEffect(() => {
        return mergeRegister(
            editor.registerUpdateListener(({ editorState }) => {
                editorState.read(() => {
                    updateLinkEditor();
                });
            }),

            editor.registerCommand(
                SELECTION_CHANGE_COMMAND,
                () => {
                    updateLinkEditor();
                    return true;
                },
                LowPriority,
            ),
        );
    }, [editor, updateLinkEditor]);

    useEffect(() => {
        editor.getEditorState().read(() => {
            updateLinkEditor();
        });
    }, [editor, updateLinkEditor]);

    useEffect(() => {
        if (isEditMode && inputRef.current) {
            inputRef.current.focus();
        }
    }, [isEditMode]);

    return (
        <div ref={editorRef} className="link-editor">
            {isEditMode ? (
                <input
                    ref={inputRef}
                    className="link-input"
                    value={linkUrl}
                    onChange={(event) => {
                        setLinkUrl(event.target.value);
                    }}
                    onKeyDown={(event) => {
                        if (event.key === 'Enter') {
                            event.preventDefault();
                            if (lastSelection !== null) {
                                if (linkUrl !== '') {
                                    editor.dispatchCommand(
                                        TOGGLE_LINK_COMMAND,
                                        linkUrl,
                                    );
                                }
                                setEditMode(false);
                            }
                        } else if (event.key === 'Escape') {
                            event.preventDefault();
                            setEditMode(false);
                        }
                    }}
                />
            ) : (
                <>
                    <div className="link-input">
                        <a
                            href={linkUrl}
                            target="_blank"
                            rel="noopener noreferrer"
                        >
                            {linkUrl}
                        </a>
                        <div
                            className="link-edit"
                            tabIndex={0}
                            onClick={() => {
                                setEditMode(true);
                            }}
                            onMouseDown={(event) => event.preventDefault()}
                        >
                            <Edit />
                        </div>
                    </div>
                </>
            )}
        </div>
    );
}

export default function ToolbarPlugin() {
    const [editor] = useLexicalComposerContext();
    const toolbarRef = useRef(null);

    const [blockType, setBlockType] = useState('paragraph');

    const [isLink, setIsLink] = useState(false);
    const [isBold, setIsBold] = useState(false);
    const [isItalic, setIsItalic] = useState(false);

    const updateToolbar = useCallback(() => {
        const selection = $getSelection();

        if ($isRangeSelection(selection)) {
            const anchorNode = selection.anchor.getNode();
            const element =
                anchorNode.getKey() === 'root'
                    ? anchorNode
                    : anchorNode.getTopLevelElementOrThrow();
            const elementKey = element.getKey();
            const elementDOM = editor.getElementByKey(elementKey);
            if (elementDOM !== null) {
                if ($isListNode(element)) {
                    const parentList = $getNearestNodeOfType(
                        anchorNode,
                        ListNode,
                    );
                    const type = parentList
                        ? parentList.getTag()
                        : element.getTag();
                    setBlockType(type);
                } else {
                    const type = $isHeadingNode(element)
                        ? element.getTag()
                        : element.getType();
                    setBlockType(type);
                }
            }
            // Update text format
            setIsBold(selection.hasFormat('bold'));
            setIsItalic(selection.hasFormat('italic'));

            // Update links
            const node = getSelectedNode(selection);
            const parent = node.getParent();
            if ($isLinkNode(parent) || $isLinkNode(node)) {
                setIsLink(true);
            } else {
                setIsLink(false);
            }
        }
    }, [editor]);

    useEffect(() => {
        return mergeRegister(
            editor.registerUpdateListener(({ editorState }) => {
                editorState.read(() => {
                    updateToolbar();
                });
            }),
            editor.registerCommand(
                SELECTION_CHANGE_COMMAND,
                (_payload, newEditor) => {
                    updateToolbar();
                    return false;
                },
                LowPriority,
            ),
        );
    }, [editor, updateToolbar]);

    const insertLink = useCallback(() => {
        if (!isLink) {
            editor.dispatchCommand(TOGGLE_LINK_COMMAND, 'https://');
        } else {
            editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
        }
    }, [editor, isLink]);

    const formatBulletList = () => {
        if (blockType !== 'ul') {
            editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND);
        } else {
            editor.dispatchCommand(REMOVE_LIST_COMMAND);
        }
    };

    const formatNumberedList = () => {
        if (blockType !== 'ol') {
            editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND);
        } else {
            editor.dispatchCommand(REMOVE_LIST_COMMAND);
        }
    };

    return (
        <Grid container direction="row" ref={toolbarRef}>
            <Grid item sm={12} md={12} lg={5} className="toolbar">
                <span
                    onClick={() => {
                        editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
                    }}
                    className={
                        'toolbar-item spaced ' + (isBold ? 'active' : '')
                    }
                    aria-label="Format Bold"
                >
                    <FormatBoldIcon />
                </span>
                <span
                    onClick={() => {
                        editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
                    }}
                    className={
                        'toolbar-item spaced ' + (isItalic ? 'active' : '')
                    }
                    aria-label="Format Italics"
                >
                    <FormatItalicIcon />
                </span>
                <span
                    onClick={insertLink}
                    className={
                        'toolbar-item spaced ' + (isLink ? 'active' : '')
                    }
                    aria-label="Insert Link"
                >
                    <LinkIcon />
                </span>

                {isLink &&
                    createPortal(
                        <FloatingLinkEditor editor={editor} />,
                        document.body,
                    )}

                <Divider />
                <span
                    onClick={formatBulletList}
                    className={'toolbar-item spaced '}
                >
                    <FormatListBulletedIcon />
                </span>
                <span
                    onClick={formatNumberedList}
                    className={'toolbar-item spaced '}
                >
                    <FormatListNumberedIcon />
                </span>
            </Grid>

            <Grid item sm={12} md={12} lg={7} className="toolbar">
                <span
                    onClick={() => {
                        editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left');
                    }}
                    className="toolbar-item spaced"
                    aria-label="Left Align"
                >
                    <FormatAlignLeftIcon />
                </span>
                <span
                    onClick={() => {
                        editor.dispatchCommand(
                            FORMAT_ELEMENT_COMMAND,
                            'center',
                        );
                    }}
                    className="toolbar-item spaced"
                    aria-label="Center Align"
                >
                    <FormatAlignCenterIcon />
                </span>
                <span
                    onClick={() => {
                        editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right');
                    }}
                    className="toolbar-item spaced"
                    aria-label="Right Align"
                >
                    <FormatAlignRightIcon />
                </span>
                <span
                    onClick={() => {
                        editor.dispatchCommand(
                            FORMAT_ELEMENT_COMMAND,
                            'justify',
                        );
                    }}
                    className="toolbar-item spaced"
                    aria-label="Justify Align"
                >
                    <FormatAlignJustifyIcon />
                </span>
                <Divider />
                <span
                    onClick={() => {
                        editor.dispatchCommand(INDENT_CONTENT_COMMAND);
                    }}
                    className={'toolbar-item spaced '}
                    aria-label="Format Indent"
                >
                    <FormatIndentIncreaseIcon />
                </span>
                <span
                    onClick={() => {
                        editor.dispatchCommand(OUTDENT_CONTENT_COMMAND);
                    }}
                    className={'toolbar-item spaced '}
                    aria-label="Format Outdent"
                >
                    <FormatIndentDecreaseIcon />
                </span>
            </Grid>
        </Grid>
    );
}

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
tylerjbainbridgecommented, May 23, 2022

Hi @yuuminakamura! I’m unable to repro this on the current Lexical Playground, could you attach a screen recording of you reproducing this bug on our playground?

1reaction
acywatsoncommented, May 22, 2022

@tylerjbainbridge can you take a look?

Also, it looks like you’re on an older version of Lexical - it might be a good idea to try upgrading to the latest and ensuring that this still repros.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Copy and paste not working after update? - MATLAB Answers
Hi, I think my matlab updated to the latest version this morning. Now the copy and paste functions wont work from other applications...
Read more >
Uncaught TypeError: text.select is not a function - Stack Overflow
After some additional thinking i realised my problem was trying to paste into paragraph element <p></p> . When I changed it to textarea...
Read more >
Copy & Paste Problem with SPSS 21.0 and the Syntax Editor
You are copying from another app but pasting into the syntax editor pastes something stale. The problem could be that the copy in...
Read more >
Fix!!! Copy Paste not Working on Windows 10 - YouTube
Understanding the issueHere's how the copy paste - function works. Whenever we copy a text or an image, it is saved on a...
Read more >
How to Fix Microsoft Excel Cannot Paste the Data Error?
Are there any solutions to excel copy paste not working properly? ... The problem occurs when you try to copy data from one...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found