Shift + Enter Keyboard Shortcut Not Working in Quill 2.0.2
Hi Quill Team,
I am currently using Quill 2.0.2 on Windows 10 with Firefox 126.0.1. I encountered an issue with the "Shift + Enter" keyboard shortcut: it does not seem to work as expected. Specifically, when I press "Shift + Enter" while writing text in the editor, it always creates a new paragraph (
) instead of inserting a line break (
).
Here is a snippet of my current implementation:
var quill = new Quill('#editor', {
theme: 'snow'
});
quill.keyboard.addBinding({
key: 13, // Enter key
shiftKey: true
}, function(range, context) {
quill.insertText(range.index, '\n');
quill.setSelection(range.index + 1);
});
Could you help me understand what might be going wrong?
https://quilljs.com/docs/modules/keyboard#configuration
Isn’t this more about the fact that Quill doesn’t support line breaks (<br> tags) as any \n will be recognized as a new ‘row’ in Parchment?
I found #2872. This appears to be a long standing issue. Quite unfortunate, that was the last piece of the puzzle to migrate away from CKEditor5. :/
Try CodeMirror…
@lutzissler Thanks, but CodeMirror doesn't appear to have a toolbar for Bold, List, Headers, etc. It's specialized for code output. That's not my use case.
Sorry, meant the sister project, ProseMirror.
Is there any similar ProseMirror (or tiptap) based OpenSource solution?
We use an editor to manage our email templates. I resolved the visual alignment issue in the application by removing the top padding from the following CSS rule:
css
.quill-editor .ql-editor p {
font-size: 14px;
}
This adjustment corrects the display issue. When rendering or sending the content, I apply the following transformations to format the output as desired:
php
// Replace <p><br></p> with <br> for standalone line breaks
$body = preg_replace('/<p><br><\/p>/', '<br>', $body);
// Replace </p><p> with <br> to combine paragraphs on the same line
$body = preg_replace('/<\/p>\s*<p>/', '<br>', $body);
This approach visually aligns the editor content while also formatting the HTML appropriately for rendering and sending.
Thanks for the workaround, but it is horrible that we have to do such a thing!
Following @Diego-Franke solution, please see here our poor man's approach:
We wanted to keep some of the <p> tags and change the </p><p> to <br>s.
// Replace <p><br></p> with <br> for standalone line breaks
$regexText = preg_replace('/<p><br><\/p>/', '<br />', $textFromQuill);
// Replace empty <p></p> tags with a placeholder
$regexText = preg_replace('/<p>\s*<\/p>/', '<!--EMPTYBYQUILL-->', $regexText);
// Replace </p><p> with <br />
$regexText = preg_replace('/<\/p>\s*<p>/', '<br />', $regexText);
// Remove the placeholder
$regexText = preg_replace('/<!--EMPTYBYQUILL-->/', '', $regexText);
Why This Approach?
We needed something quick and dirty to process our HTML without pulling in a full parser. While regex for HTML is less than ideal, it works in our specific use case.
Disclaimer:
We know it’s not elegant, but maybe it’ll help someone or inspire a better solution. Suggestions are more than welcome!
Or Quill will accept <br>, as it should!!!
Yeah, I had shift+enter working with Quill v1.3, but the upgrade to Quill v2 and Angular 19 broke it. The fix was to change the keyboard binding as follows:
onEditorCreated(editor: Quill) {
editor.scroll.register(SoftLineBreakBlot);
editor.keyboard.addBinding({ key: 'Enter', shiftKey: true }, shiftEnterHandler); <<<<<
editor.clipboard.addMatcher('BR', brMatcher);
}
Using 'Enter' instead of 13.
@hhubik Really? Can you share more of your code, maybe your whole editor configuration? Because I cannot get the handler to fire with Shift + Enter.
quill.keyboard.addBinding({ key: 'Enter', shiftKey: true }, () => { console.log('shift enter handler'); });
This never fires for me.
Update
Maybe it's because I am using ngx-quill with Angular that quill.keyboard.addBinding() does not work, but I have found a potential solution from here and here.
Here is the relevant code:
public modules = {
uploader: false,
toolbar: [
...
],
clipboard: {
matchers: [
['BR', lineBreakMatcher]
]
},
keyboard: {
bindings: {
"shift enter": {
key: 'Enter',
shiftKey: true,
handler: function (range) {
shiftEnterHandler(this.quill, range);
}
}
}
}
}
export function shiftEnterHandler(quill: Quill, range) {
const currentLeaf = quill.getLeaf(range.index)[0];
const nextLeaf = quill.getLeaf(range.index + 1)[0];
quill.insertEmbed(range.index, "sbreak", true, Quill.sources.USER);
// Insert a second break if:
// At the end of the editor, OR next leaf has a different parent (<p>)
if (nextLeaf === null || currentLeaf.parent !== nextLeaf.parent) {
quill.insertEmbed(range.index, "sbreak", true, Quill.sources.USER);
}
// Now that we've inserted a line break, move the cursor forward
quill.setSelection(range.index + 1, Quill.sources.SILENT);
}
function lineBreakMatcher(): Delta {
const newDelta = new Delta();
newDelta.insert({'sbreak': ''});
return newDelta;
}
export class SoftLineBreakBlot extends Embed {
static blotName = 'sbreak';
static tagName = 'br';
}
Quill.register(SoftLineBreakBlot);
@dstj looks like you have found another way of doing this in the meantime, which looks a bit different from what I did. I am hosting the editor in a standalone Angular 19 component and do all the editor initialization in that component like this:
import { Component, ChangeDetectionStrategy, OnDestroy, ElementRef, Input, HostBinding, LOCALE_ID, inject } from '@angular/core';
...
import Quill, { Delta } from 'quill';
import Embed from 'quill/blots/embed';
const SOFTBREAK_KEYWORD = 'softbreak';
export class SoftLineBreakBlot extends Embed {
static override blotName = SOFTBREAK_KEYWORD;
static override tagName = 'br';
static override className = SOFTBREAK_KEYWORD;
}
export function shiftEnterHandler(this: any, range: { index: number }) {
// console.log('keybinding enter + shift', range, this);
const currentLeaf = this.quill.getLeaf(range.index)[0];
const nextLeaf = this.quill.getLeaf(range.index + 1)[0];
this.quill.insertEmbed(range.index, SOFTBREAK_KEYWORD, true, Quill.sources.USER);
// Insert a second break if:
// At the end of the editor, OR next leaf has a different parent (<p>)
if (nextLeaf === null || currentLeaf.parent !== nextLeaf.parent) {
this.quill.insertEmbed(range.index, SOFTBREAK_KEYWORD, true, Quill.sources.USER);
}
// // Now that we've inserted a line break, move the cursor forward
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
}
/**
* Converts HTML <br> elements to softbreak embeds when pasting content
* @param node The DOM node being matched
* @returns Delta containing a softbreak embed
*/
export function brMatcher(node: HTMLElement): Delta {
if (!(node instanceof HTMLBRElement)) {
return new Delta();
}
return new Delta().insert({ [SOFTBREAK_KEYWORD]: true });
}
@Component({
selector: 'my-comp',
templateUrl: './my-comp.html',
styleUrls: ['./my-comp.scss'],
providers: [{ provide: MatFormFieldControl, useExisting: InfoDocumentFormControlComponent }],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
...
QuillEditorComponent,
...
]
})
export class MyComponent implements ControlValueAccessor, MatFormFieldControl<xxxx>, OnDestroy {
...
quillModules: QuillModules = {};
quillCustomOptions: CustomOption[] = [];
constructor() {
...
this.quillModules = {
keyboard: {
bindings: {
'shift+enter': {
key: ['Enter'],
shiftKey: true,
handler: shiftEnterHandler
}
}
},
// see https://quilljs.com/docs/modules/toolbar/ for more info
toolbar: [
[{ header: [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ align: [] }],
['image']
]
};
this.quillCustomOptions = [
{
import: 'formats/font',
whitelist: ['mirza', 'roboto', 'aref', 'serif', 'sansserif', 'monospace']
}
];
}
...
onEditorCreated(editor: Quill) {
editor.scroll.register(SoftLineBreakBlot);
editor.keyboard.addBinding({ key: 'Enter', shiftKey: true }, shiftEnterHandler);
editor.clipboard.addMatcher('BR', brMatcher);
// set editor content if a value is provided
if (this.editorContent) {
editor.clipboard.dangerouslyPasteHTML(this.editorContent);
}
console.log('editor created', editor);
}
Here is the relevant part of the component template:
<quill-editor
...
[sanitize]="true"
formControlName="xxx"
[modules]="quillModules"
(onEditorCreated)="onEditorCreated($event)"
>
</quill-editor>