lotion icon indicating copy to clipboard operation
lotion copied to clipboard

Can't select text for more than 1 line

Open CavalcanteLeo opened this issue 1 year ago • 15 comments

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

CavalcanteLeo avatar Jul 29 '22 15:07 CavalcanteLeo

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!

vvidday avatar Aug 01 '22 15:08 vvidday

have you seen editor.js?

they have a similar approach

CavalcanteLeo avatar Aug 01 '22 16:08 CavalcanteLeo

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.

DAlperin avatar Aug 01 '22 16:08 DAlperin

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

CavalcanteLeo avatar Aug 01 '22 16:08 CavalcanteLeo

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

DAlperin avatar Aug 01 '22 16:08 DAlperin

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

CavalcanteLeo avatar Aug 01 '22 16:08 CavalcanteLeo

image With a parent contenteditable though

DAlperin avatar Aug 01 '22 16:08 DAlperin

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.

greentfrapp avatar Aug 01 '22 17:08 greentfrapp

@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.

DAlperin avatar Aug 01 '22 17:08 DAlperin

@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?

greentfrapp avatar Aug 03 '22 05:08 greentfrapp

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.

johnpuddephatt avatar Sep 02 '22 14:09 johnpuddephatt

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 avatar May 22 '23 03:05 yuguaa

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

yuguaa avatar May 22 '23 06:05 yuguaa

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

aigcprompt avatar Jul 06 '23 13:07 aigcprompt

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

nsnl-coder avatar Dec 30 '23 20:12 nsnl-coder