ianstormtaylor / slate

A completely customizable framework for building rich text editors. (Currently in beta.)
http://slatejs.org
MIT License
29.64k stars 3.23k forks source link

Copying empty non-void element does not work #3954

Open kamilmielnik opened 3 years ago

kamilmielnik commented 3 years ago

Do you want to request a feature or report a bug?

Bug.

What's the current behavior?

Copying (Ctrl+C) empty non-void element does not work.

Reproduction steps:

  1. Open https://codesandbox.io/s/slate-reproductions-forked-emloe?file=/index.js
  2. Wait for it load (there's an image)
  3. Focus the bordered element with image
  4. Hit Ctrl+C

Actual result: Clipboard is not modified. Range.isCollapsed(editor, editor.selection) condition causes setFragmentData to return without modifying clipboard: https://github.com/ianstormtaylor/slate/blob/97dbab1/packages/slate-react/src/plugin/with-react.ts#L71-L73

Slate: 0.59.0 Browser: All OS: All

What's the expected behavior?

application/x-slate-fragment data is set in the clipboard (the empty selected element is not meaningless since there's other data on the it, e.g. imageUrl property).


I'm currently working around this issue with this plugin:

import { Editor, Range } from 'slate';
import { ReactEditor } from 'slate-react';

const convertToHtml = (anything: any): string => {
    const div = document.createElement('div');
    div.appendChild(anything);
    div.setAttribute('hidden', 'true');
    document.body.appendChild(div);
    const html = div.innerHTML;
    document.body.removeChild(div);
    return html;
};

const withFixedCopying = <T extends ReactEditor>(editor: T): T => {
    const { setFragmentData } = editor;

    editor.setFragmentData = (data): void => {
        if (editor.selection && Range.isCollapsed(editor.selection)) {
            const [currentNode] = Editor.node(editor, editor.selection, { depth: 1 });

            if (
                currentNode &&
                Editor.isEmpty(editor, currentNode) &&
                !Editor.isVoid(editor, currentNode)
            ) {
                const domRange = ReactEditor.toDOMRange(editor, editor.selection);
                const contents = domRange.cloneContents();
                const encodedFragment = window.btoa(encodeURIComponent(JSON.stringify(editor.getFragment())));
                data.setData('application/x-slate-fragment', encodedFragment);
                data.setData('text/html', convertToHtml(contents));
                return;
            }
        }

        setFragmentData(data);
    };

    return editor;
};

export default withFixedCopying;
bryanph commented 3 years ago

Slate's selections work with text so I feel like this is expected behavior. If you want the image to be selectable you gotta make it a void node so that a zero-width-space is inserted that can be selected.