facebookarchive / draft-js

A React framework for building text editors.
https://draftjs.org/
MIT License
22.57k stars 2.64k forks source link

Avoid empty unstyled blocks #636

Open adamtal3 opened 8 years ago

adamtal3 commented 8 years ago

I want to remove all empty blocks that has no style. My problem is that when the user clicks on backspace in a styled block with no text, the block is not deleted, it just loses his style and I wish to avoid having unstyled blocks.

I'm looking for some solution to this behavior.

I've tried using Modifier.removeRange (like suggested in #168) to delete all empty blocks (with no style and no text) but it just doesn't work.

Can you please tell me what I'm doing wrong?

const removeEmptyUnstyledBlocks = (editorState) => {
  let contentState = editorState.getCurrentContent();
  const blocks = contentState.getBlocksAsArray();
  const emptyBlocks = blocks.filter(b => b.get('type') === 'unstyled' && b.get('text') === '');
  emptyBlocks.forEach(b => {
    let removalRange = SelectionState.createEmpty().merge({ // I've tried many variations for this
      anchorKey: b.getKey(),
      focusKey: b.getKey(),
      focusOffset: 0,
      anchorOffset: 0,
      isBackward: true,
      hasFocus: true
    });

    contentState = Modifier.removeRange(contentState, removalRange, 'backward');
  });

  return emptyBlocks.length ? EditorState.push(editorState, contentState, 'remove-empty-blocks') : editorState;
};

This doesn't work (look at my comment for a working solution).

Is it even possible to simply remove a block from the editorState's block map? (e.g. using immutable-js deleteIn([...]) function)

Maybe the problem is that the selection is collapsed because the block has no text?

Surely, it would be better if I could totally avoid getting empty blocks instead of fixing the state in every onChange.

Update

I forgot to mention that I'm using this handleKeyCommand function (which handles the backspace command):

  handleKeyCommand(command) {
    const { editorState } = this.state;

    const newState = RichUtils.handleKeyCommand(editorState, command);

    if (newState) {
      this.onChange(newState);
    }

    return !!newState;
  }

And I think this is the right place to take care of my problem.

adamtal3 commented 8 years ago

After debugging what draft-js is doing when I hit backspace the second time (after the block becomes "unstyled") this is what I came up with (it's like simulating a second backspace when pressing backspace in an empty block):

handleKeyCommand(command) {
  const {editorState} = this.state;

  if (command === 'backspace') {
    const block = getCurrentBlock(editorState);

    if (block.get('text') === '') {
      if (getBlockIndex(editorState, block.get('key')) === 0) {
        // This ignores a backspace in case we're at the start of the content and the block is empty,
        // this prevents the creation of an empty unstyled block at the beginning of the content
        return editorState;
      }
      else {
        // This causes the RichUtils.handleKeyCommand to run twice and that way if the block is empty
        // instead of getting an unstyled block, we're removing the block
        editorState = RichUtils.handleKeyCommand(editorState, command);
      }
    }
  }

  const newState = RichUtils.handleKeyCommand(editorState, command);

  if (newState) {
    this.onChange(newState);
  }

  return !!newState;
};

This works.

I would really like to know if there's any way more elegant to take care of this. If this is the correct way, feel free to close this issue.

Thanks :)

jan4984 commented 8 years ago

when the user clicks on backspace in a styled block with no text, the block SHOULD be deleted, at least my application using draft work like this.

stopachka commented 8 years ago

hey @adamtal3

In this case, you would most likely want to use the default keyCommand for backspace, and RichTextUtils for the rest.

This is the backspace command you want:

https://github.com/facebook/draft-js/blob/a16bc55bf8297af627e8a4cfe5f283f88e7fc8bd/src/component/handlers/edit/commands/keyCommandPlainBackspace.js#L26

This is currently not exposed, but we'd be happy to have a PR that exposes this function.

Perhaps under keyCommandHandlers on the main namespace

karanjthakkar commented 8 years ago

@stopachka I'd like to help out. Can you help me understand a bit as to what needs to be done over here?

JonasMatos0 commented 3 years ago

Hello,

What's the current state of this issue? I'm taking a look on these really old GFI tickets. I can try to help, just need some details about how to proceed.

Thanks in advance.

bobber205 commented 3 years ago

@adamtal3 Looks like getCurrentBlock doesn't exist anymore (only getCurrentBlockType) and getBlockIndex is completely missing as well. Anyone have any ideas on replacement functions?