slab / quill

Quill is a modern WYSIWYG editor built for compatibility and extensibility
https://quilljs.com
BSD 3-Clause "New" or "Revised" License
43.66k stars 3.39k forks source link

How to add difference between ENTER and SHIFT+ENTER #1187

Closed fpavlic closed 7 years ago

fpavlic commented 7 years ago

Hi Jason,

Just wonder how can I add difference between paragraph and new line entries.

For example ENTER should always create new paragraph, but SHIFT+ENTER should add brake-line.

Thanks for you help, Filip

jhchen commented 7 years ago

252

VaelVictus commented 5 years ago

2019 Developers: Your best shot is here https://codepen.io/mackermedia/pen/gmNwZP

Quill really is great. But, from the blog:

Quill aims to be the defacto rich text editor for the web.

Yet the document model you are so proud of cannot simply produce a <br> with a shift-enter. Please reconsider this feature.

robertu7 commented 5 years ago

@VaelVictus your sample is buggy.

You can reproduce with:

  1. clear up all content
  2. hit Enter
  3. Newline is appended but cursor isn't moved

image

VaelVictus commented 5 years ago

Which, indeed, is why I said it was your best shot in 2019.

robertu7 commented 5 years ago

@VaelVictus you should try this workaround, works fine for me :)

tomasbonco commented 5 years ago

Original thread is locked now, but I had issues with workarounds and Deltas they produced. When I did setContents and getContents I got a different version compared to the original. So I tried to solve the issue myself and came with a solution that works pretty well for me. The whole idea is to place an empty <div></div> there. Reasons:

  1. A div is a block element and will cause text to break.
  2. It's a paired tag. So we can use standard Quill's Embed functionality.

The only issue I had, was forcing Quill not to place any content into the <div>. That's why I set height to 0 and that solved the issue.

So the whole code looks like this:

import Quill from 'quill'

const Embed = Quill.import('blots/embed');

export class SmartBreakBlot extends Embed
{
    static create()
    {
        const node: HTMLElement = super.create();
        node.style.height = '0px';
        return node
    }
}

SmartBreakBlot.blotName = 'smartbreak';
SmartBreakBlot.tagName = 'div';
Quill.register( 'formats/smartbreak', SmartBreakBlot )

...

keyboard:
{
    bindings:
    {
        shiftEnter:
        {
            key: 13,
            shiftKey: true,
            handler: ( range ) =>
            {
                this.editor.insertEmbed(range.index, 'smartbreak', true, 'user');
                this.editor.setSelection( range.index + 1, 'silent' );

                return false
            }
        }
    }
}

Because the original issue is opened for almost 4-5 years now, I hope this helps.

q2apro commented 4 years ago

Shift + Enter should 100 % come by default.

Quill gets hyped but is still missing standards that other editors have out-of-the-box.

And this issues was brought up already in 2014 ...

Personally I am sticking to SCEditor as long as this is not default.


This "heavy" workaround seems to work: https://codepen.io/mackermedia/pen/gmNwZP

VaelVictus commented 4 years ago

Thanks for that heavy workaround link. I have actually since adopted Pell and this was a driver to do so.

Mistralys commented 4 years ago

I must admit I was surprised when I realized that Shift + Enter does not work out of the box. Differentiating between newlines and paragraphs is why there are <br> and <p> tags in the first place, and they are not interchangeable. <p> tags can be styled, while <br> tags cannot. The difference between the two is a big part in styling HTML documents, and since Quill produces HTML, I expected it to support both.

I quite like Quill, and how easy it is to integrate - I will have a look at the solutions proposed so far to add this functionality, as everyone who will be using it in my project know how to use and need both paragraphs and newlines.

I really think it should be added to the core :+1:

vasva commented 4 years ago

I have same issue course I tried to use Devextreme HTMLEditor which is using Quill, there I see no change to replace it easily. When I try hack it via direct DOM injection Quill removes BR immediately. I hate it boths :(

vasva commented 4 years ago

@VaelVictus you should try this workaround, works fine for me :)

It wourks when u use pure Quiil, but no chance when it is wrapped

IcyFoxe commented 4 years ago

@VaelVictus your sample is buggy.

You can reproduce with:

  1. clear up all content
  2. hit Enter
  3. Newline is appended but cursor isn't moved

image

I might not understand that code entirely, but changing:

length() {
  return 1
}

inside the class to:

length() {
  return 0
}

fixed the issue for me.

mylesdawson commented 3 years ago

For anyone using React-Quill this worked for me:

React-Quill Shift+Enter

The only problem is that after pressing enter, backspace has to be pressed twice to return to the previous line. If anyone has a solution for that please let me know!

NODESPLIT commented 3 years ago

Not sure why answers here are so complicated.

I just did this in the Quill config:

keyboard: {
    bindings: {
        linebreak: {
            key: 13,
            shiftKey: true,
            handler: function(range) {
                this.quill.insertEmbed(range.index, 'breaker', true, Quill.sources.USER);
                this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
                return false;
            }
        }
    }
}

For the Breaker blot I did:

let Embed = Quill.import('blots/embed');

class Breaker extends Embed {
    static tagName = 'br';
    static blotName = 'breaker';
}

Quill.register(Breaker);

I had to also add the blotName 'breaker' to the list of formats in the Quill config.

I am using react-quill, so you might need to translate to vanilla usage if that's what you're doing but this very simple setup worked fine for me, no complicated selections, insertions or anything. Just an embed with a tag of br, inserted on shift-return.

I would say that if you're having issues with the way br tags and p tags are rendered in your editor and your actual display of the data then you should look into manually theming quill. I found the quill.core.css file was styling a lot of tags and I wanted a context agnostic editor that left styling to the page it's on.

korneSt commented 3 years ago

@NODESPLIT

Not sure why answers here are so complicated.

I just did this in the Quill config:

keyboard: {
    bindings: {
        linebreak: {
            key: 13,
            shiftKey: true,
            handler: function(range) {
                this.quill.insertEmbed(range.index, 'breaker', true, Quill.sources.USER);
                this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
                return false;
            }
        }
    }
}

For the Breaker blot I did:

let Embed = Quill.import('blots/embed');

class Breaker extends Embed {
    static tagName = 'br';
    static blotName = 'breaker';
}

Quill.register(Breaker);

I had to also add the blotName 'breaker' to the list of formats in the Quill config.

I am using react-quill, so you might need to translate to vanilla usage if that's what you're doing but this very simple setup worked fine for me, no complicated selections, insertions or anything. Just an embed with a tag of br, inserted on shift-return.

I would say that if you're having issues with the way br tags and p tags are rendered in your editor and your actual display of the data then you should look into manually theming quill. I found the quill.core.css file was styling a lot of tags and I wanted a context agnostic editor that left styling to the page it's on.

This works great when writing text, but not on copy-paste, do you have any fix for that?

Edit: this solution seems to work https://github.com/zenoamaro/react-quill/issues/513#issuecomment-523783053

HaloElite commented 1 year ago

As for the buggy but useful example provided by @VaelVictus - I improved it by adding/extending a couple of handlers, taking care of the following problems:

The only problem that remains and that I have not been able to solve is the "double backspace" tap that's needed to remove the <p><br></p> tags that quill won't delete in one tap now.

The example sadly had to be written in old coffee script but just throw it in an online converter and you're ready to go. I hope this helps someone struggling with this feature as much as I did. =)

 keyboard: bindings:
          handleEnter:
            key: 13
            handler: (range, context) ->
              if range.length > 0
                try
                  that.yquill.scroll.deleteAt range.index, range.length
                catch exceptionVar
                   that.quill.deleteText(0, that.quill.getLength())
                   return

              # So we do not trigger text-change
              lineFormats = Object.keys(context.format).reduce(((lineFormats, format) ->
                if Parchment.query(format, Parchment.Scope.BLOCK) and !Array.isArray(context.format[format])
                  lineFormats[format] = context.format[format]
                lineFormats
              ), {})
              previousChar = that.quill.getText(range.index - 1, 1)

              # Earlier scroll.deleteAt might have messed up our selection,
              # so insertText's built in selection preservation is not reliable
              that.quill.insertText range.index, '\n', lineFormats, Quill.sources.USER
              if previousChar == '' or previousChar == '\n'
                that.quill.setSelection range.index + 2, Quill.sources.SILENT
              else
                that.quill.setSelection range.index + 1, Quill.sources.SILENT
              # that.quill.selection.scrollIntoView()
              Object.keys(context.format).forEach (name) ->
                if lineFormats[name] != null
                  return
                if Array.isArray(context.format[name])
                  return
                if name == 'link'
                  return
                that.quill.format name, context.format[name], Quill.sources.USER
                return

              if that.quill.getLeaf(range.index)[0].domNode.nodeName == "BR"
                that.quill.insertEmbed range.index, 'break', true, 'user'  
                that.quill.setSelection range.index + 2, Quill.sources.SIL
              return
          linebreak:
            key: 13
            shiftKey: true
            handler: (range) ->
              currentRange = range.index
              currentLeaf = that.quill.getLeaf(range.index)[0]
              nextLeaf = that.quill.getLeaf(range.index + 1)[0]

              # Prohibit soft break when no text is inserted before or when inside of hard-break
              if currentLeaf and currentLeaf.domNode.nodeName == "BR" and currentLeaf.parent and currentLeaf.parent.domNode.nodeName == "P" and currentLeaf.next == null
                return

              else if currentLeaf and currentLeaf.domNode.nodeName == "BR" and currentLeaf.next and currentLeaf.next.domNode.nodeName == "#text"
                return

              else if currentLeaf and currentLeaf.domNode.nodeName == "BR" and currentLeaf.next and currentLeaf.next.domNode.nodeName == "BR"
                return

              # Insert first break
              that.quill.insertEmbed currentRange, 'break', true, 'user'

              # Insert a second break if:
              # At the end of the editor, OR next leaf has a different parent (<p>)
              if nextLeaf == null or currentLeaf.parent != nextLeaf.parent
                that.quill.insertEmbed currentRange, 'break', true, 'user'

              # Now that we've inserted a line break, move the cursor forward
              that.quill.setSelection currentRange + 1, Quill.sources.SILENT
              return
          deleteHandler:
              key: 'Delete',
              handler: (range, context) -> 
                  currentLeaf = that.quill.getLeaf(range.index)[0]
                  nextLeaf = that.quill.getLeaf(range.index + 1)[0]

                  if currentLeaf.domNode.nodeName == "BR" and (nextLeaf and nextLeaf.domNode.nodeName == "BR") and range.index > 0
                    that.quill.deleteText range.index, 1, Quill.sources.USER
                    that.quill.setSelection range.index, Quill.sources.SILENT

                  if range.index == 0 and (nextLeaf and nextLeaf.domNode.nodeName != "BR")
                    that.quill.deleteText range.index, 1, Quill.sources.USER
                    that.quill.setSelection range.index, Quill.sources.SILENT
                  else if range.index == 0 and (nextLeaf and nextLeaf.domNode.nodeName == "BR")
                    that.quill.deleteText range.index - 1, 1, Quill.sources.USER
                    that.quill.deleteText range.index, 1, Quill.sources.USER
                    that.quill.deleteText range.index + 1, 1, Quill.sources.USER
                    that.quill.setSelection range.index + 1, Quill.sources.SILENT
                    that.quill.deleteText range.index, 1, Quill.sources.USER
                  else if currentLeaf.domNode.nodeName == "BR" and currentLeaf.parent.domNode.nodeName == "P" and (nextLeaf and nextLeaf.domNode.nodeName == "BR")
                    if range.index > 0
                      that.quill.deleteText range.index - 1, 1, Quill.sources.USER
                    that.quill.setSelection range.index, Quill.sources.SILENT
                  else if currentLeaf.domNode.nodeName == "BR" and currentLeaf.parent.domNode.nodeName == "P" and (nextLeaf and nextLeaf.domNode.nodeName != "#text")
                    if range.index > 0
                      that.quill.deleteText range.index - 1, 1, Quill.sources.USER
                    that.quill.setSelection range.index, Quill.sources.SILENT
                  else if range.length == 0 and range.index != 0
                    that.quill.deleteText range.index, 1, Quill.sources.USER
                  else
                    that.quill.deleteText range, Quill.sources.USER
herrbasan commented 1 year ago

This is such a hacky solution, if one could call it a solution at all. I guess the problem is that the author didn't anticipate this while constructing the inner logic of the project, so it probably isn't trivial to add this in a robust manner. Unfortunately the project i'm working on requires this functionality so i'm giving up on Quill for now.

ShaneJohnsonCC commented 8 months ago

How in the world is this still an issue? This is literally one of the most obvious features a wysiwyg editor should have. Can someone on the dev team PLEASE explain why you refuse to add this after almost 10 years of requests?

VaelVictus commented 8 months ago

Just use TinyMCE 6. :} You can test for this functionality right here: https://www.tiny.cloud/docs/tinymce/6/

DmitriyKochergin commented 7 months ago

This is by far the best implementation of Shift + Enter for Quill https://codesandbox.io/embed/quill-line-break-br-fully-working-djyoc2?codemirror=1

0) use Enter to insert paragraph, use Shift+Enter to insert break 1) works on paragraphs and bullet lists 2) both Enter and Shift+Enter preserve styles that were applied on previous line 3) When copypasting into editor html with newlines - they are automatically converted to
as one would expect

uniquejava commented 5 months ago

This is by far the best implementation of Shift + Enter for Quill https://codesandbox.io/embed/quill-line-break-br-fully-working-djyoc2?codemirror=1

0. use Enter to insert paragraph, use Shift+Enter to insert break

1. works on paragraphs and bullet lists

2. both Enter and Shift+Enter preserve styles that were applied on previous line

3. When copypasting into editor html with newlines - they are automatically converted to  as one would expect

Worked like charm for quill 1.3.7, but not for quill 2.0.2, anyone can do a upgrade?