Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't select text for more than 1 line #13

Open
CavalcanteLeo opened this issue Jul 29, 2022 · 15 comments
Open

Can't select text for more than 1 line #13

CavalcanteLeo opened this issue Jul 29, 2022 · 15 comments

Comments

@CavalcanteLeo
Copy link

CavalcanteLeo commented Jul 29, 2022

few bugs found

  • can't select text for more than 1 line
  • if you delete some word in the middle of a sentence until it deletes the sentence in the line above, when you do a cmd+z (control+z) the undo gets lost
@vvidday
Copy link
Contributor

vvidday commented Aug 1, 2022

Hi, unfortunately I think this issue is quite complicated... The base reason for not being able to select multiple lines is due to the blocks having contenteditable=true. This can be circumvented by wrapping the parent element with contenteditable=true.

However, this raised some other problems -

  1. It seems like the parent div having contenteditable=true "blocks" all listeners in child components, e.g. pressing enter no longer creates a new block, deleting no longer triggers a merge, because the listeners in Block never get executed
  2. Tiptap editor becomes buggy, e.g. inserting bold/italic text moves the cursor to the front

One potential solution is to dynamically set the contenteditable property to only be true when the user is making a selection. This seems to work but there might be caveats that I'm not taking into account. However, even assuming that we can make it so that a user can make multiple line selections, it creates a bigger issue with regard to how we are going to deal with these selections. (e.g. deleting multiple lines)

To my knowledge, the way the project is currently set up is that individual Blocks are "responsible" for managing their state and data, however a deletion of multiple lines would require multiple blocks being changed based on one action.

Here's my rough attempt at dealing with these actions. The general idea is to "lift" the logic for multiple line events to the Lotion component, where we use the user's selection (from window.getSelection()) to traverse the DOM and determine the appropriate changes to the data. However, this approach seems rather hacky and is likely to be very error prone, especially since we're kind of "taking over" handling events from the default browser implementation.

The above code is deployed here - it's not finished and is still buggy for 'enter', breaks on non-text blocks and doesn't handle bold or italic text.

I would greatly appreciate input on the above approach, or if anyone has a better way to approach the issue!

@CavalcanteLeo
Copy link
Author

CavalcanteLeo commented Aug 1, 2022

have you seen editor.js?

they have a similar approach

@DAlperin
Copy link

DAlperin commented Aug 1, 2022

Yeah, I have taken several cracks at this kind of problem before and there is no great answer. I think The way notion does it is the technical best, but also by far the hardest way. As far as I can tell, notion renders all the blocks into one big contenteditable with many nested contenteditables inside? Then instead of making a new "editor" per "block" the render/editing engine keeps track of the data model and seamlessly displays it as one document.

@CavalcanteLeo
Copy link
Author

CavalcanteLeo commented Aug 1, 2022

Probable notion renders all blocks as you said... You can see that is possible to select texts like this, which is impossible with editorjs

Screen Shot 2022-08-01 at 13 44 19

@DAlperin
Copy link

DAlperin commented Aug 1, 2022

Exactly, it's all "one" editor. Even though the data model is blocks it gets rendered as a single logical editor.

@CavalcanteLeo
Copy link
Author

multiple contenteditable Screen Shot 2022-08-01 at 13 48 37

@DAlperin
Copy link

DAlperin commented Aug 1, 2022

image
With a parent contenteditable though

@greentfrapp
Copy link
Contributor

Hey everyone, thanks for the active discussion on this topic! We've also done a little bit of investigation into this and while having a parent contenteditable might work on Chrome, it still breaks on the latest version of Firefox. Interestingly, Notion does not use a parent contenteditable on Firefox, but we haven't dug into how it's implemented yet.

@DAlperin
Copy link

DAlperin commented Aug 1, 2022

@greentfrapp if you ever want to chat, I'd love to pick your brain/share some of my notes on the topic from the last time I faced this issue a little while ago. I come from the React side of things, so I don't have any good insight into the implementation side of things with Vue, but I do have some abstract knowledge built up that might be useful.

@greentfrapp
Copy link
Contributor

@DAlperin I'll love to take you up on that offer! Could you drop an email to [email protected] and we'll set up some time for a video call?

@johnpuddephatt
Copy link

One potential solution is to dynamically set the contenteditable property to only be true when the user is making a selection. This seems to work but there might be caveats that I'm not taking into account.

I've just taken a look at how Wordpress/Gutenberg handles this and this is the approach they take. In both Chrome and Firefox, I can see the contenteditable attribute being toggled on the parent element.

I've created a simple test case without Lotion and it looks like it's working fine in Chrome/Firefox/Safari.

@yuguaa
Copy link

yuguaa commented May 22, 2023

In recent days, I have been using contenteditable to write similar requirements, but my requirements include the requirement to use the mouse to select, right-click to merge two blocks, so that they are displayed in one piece, similar to seamless text, but at the same time to support A single block clicks and triggers a response event, which makes me miserable

@yuguaa
Copy link

yuguaa commented May 22, 2023

In recent days, I have been using contenteditable to write similar requirements, but my requirements include the requirement to use the mouse to select, right-click to merge two blocks, so that they are displayed in one piece, similar to seamless text, but at the same time to support A single block clicks and triggers a response event, which makes me miserable

Slatejs might work, let me try it

@aigcprompt
Copy link

according to https://www.blocknotejs.org/

@nsnl-coder
Copy link

nsnl-coder commented Dec 30, 2023

import { useEffect } from 'react';

const useToggleContentEditable = (
  editorRef: React.RefObject<HTMLDivElement>,
) => {
  useEffect(() => {
    if (!editorRef.current) return;

    const onMouseDown = () => {
      const selection = window.getSelection();
      if (!selection) return;

      selection.removeAllRanges();
      editorRef.current!.contentEditable = 'true';
    };

    const onMouseUp = () => {
      const selection = window.getSelection();
      if (!selection) return;

      const range = selection.getRangeAt(0);

      editorRef.current!.contentEditable = 'false';

      selection.removeAllRanges();
      selection.addRange(range);
    };

    editorRef.current.addEventListener('mousedown', onMouseDown);
    editorRef.current.addEventListener('mouseup', onMouseUp);

    return () => {
      editorRef.current?.removeEventListener('mousedown', onMouseDown);
      editorRef.current?.removeEventListener('mouseup', onMouseUp);
    };
  }, []);
};

export default useToggleContentEditable;

Hi guys,
I try to implement @johnpuddephatt solution but when i click on child Content Editable element, it losts the focus.
Here is my implementation to restore the cursor position when user click on child content editable element using Reactjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants