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

Is there a way to support multiline blockquotes? #1056

Closed
eamodio opened this issue Oct 16, 2016 · 10 comments
Closed

Is there a way to support multiline blockquotes? #1056

eamodio opened this issue Oct 16, 2016 · 10 comments

Comments

@eamodio
Copy link

eamodio commented Oct 16, 2016

Currently on a blockquote line if you hit enter, quill starts a new blockquote (rather than what I expected -- just adding a new line into the current blockquote). This feels like a bug imo -- the blockquote should end on 2 enters (i.e. a blank line between them).

Although if you consider this expected behavior is there a way to create a new blockquote blot that would behave this way? Or any other way?

Expected behavior:
Support for multiline blockquote

Actual behavior:
Multiple blockquote blocks

Platforms: Any
Version: 1.0.6

@jhchen
Copy link
Member

jhchen commented Oct 20, 2016

This is behaving as designed. You can create your own blockquote format that behaves as you'd like and there's a large body of documentation on the topic of custom formats. A starting point is http://quilljs.com/guides/cloning-medium-with-parchment/ and a good reference is https://github.com/quilljs/parchment/. You may also find the code block's behavior / code informative as it does something similar.

@vadimasadchi
Copy link

vadimasadchi commented Nov 5, 2018

Did someone achieve multiline blockquote? It is not so easy as @jhchen said. It is always easy to say: "it is not a bug, it is a feature"

@journey-ad
Copy link

Hello everyone, I find a simple way to do this. with pure CSS.

This is the code

@elijahmurray
Copy link

I figured it out!

  1. Open Medium Editor
  2. Select "Markdown"
  3. Paste your multiline blockquote into the embedded Markdown editor
  4. Select the pasted multiline quote
  5. Click the quote mark icon
  6. Profit!

@Intrepidd
Copy link

Intrepidd commented Mar 11, 2021

I'm also super interested in this and after reading the documentation and linked guides I am not comfortable with implementing my own

@amedveshchek
Copy link

Seems it's really easy achievable by just customizing the CSS style for <blockquote>. There's a blockquote style:

.ql-snow .ql-editor blockquote {
  border-left: 4px solid #ccc;
  margin-bottom: 5px;
  margin-top: 5px;
  padding-left: 16px;
}

So we should simply replace margin-bottom and margin-top with padding-bottom and padding-top, and we've got what we want:

.ql-snow .ql-editor blockquote {
  border-left: 4px solid #ccc;
  padding-bottom: 5px;
  padding-top: 5px;
  padding-left: 16px;
}

The con here is that you need to host the .css file on your side; alternatively -- overwrite it after the main css is loaded.

@leventdeniz
Copy link

Seems it's really easy achievable by just customizing the CSS style

This still produces multiple <blockquote> Tags.

I actually kind of managed to write something custom... its basically a slightly modified code block. It definitely wasn't easy and I don't really understand what I did here. You also can't escape the blockquote anymore, maybe someone with more knowledge than me on quill can help. Here's what I got:

import { Quill } from 'react-quill';

const BlockQuote = Quill.import('formats/blockquote');
const Parchment = Quill.import('parchment');
const Delta = Quill.import('delta');
const TextBlot = Quill.import('blots/text');

class CustomBlockQuote extends BlockQuote {
  static formats() {
    return true;
  }

  delta() {
    let text = this.domNode.textContent;
    // Should always be true
    if (text.endsWith('\n')) {
      text = text.slice(0, -1);
    }
    return text.split('\n').reduce((delta, frag) => {
      return delta.insert(frag).insert('\n', this.formats());
    }, new Delta());
  }

  format(name, value) {
    if (name === this.statics.blotName && value) return;
    let [text, ] = this.descendant(TextBlot, this.length() - 1);
    if (text != null) {
      text.deleteAt(text.length() - 1, 1);
    }
    super.format(name, value);
  }

  formatAt(index, length, name, value) {
    if (length === 0) return;
    if (Parchment.query(name, Parchment.Scope.BLOCK) == null ||
      (name === this.statics.blotName && value === this.statics.formats(this.domNode))) {
      return;
    }
    let nextNewline = this.newlineIndex(index);
    if (nextNewline < 0 || nextNewline >= index + length) return;
    let prevNewline = this.newlineIndex(index, true) + 1;
    let isolateLength = nextNewline - prevNewline + 1;
    let blot = this.isolate(prevNewline, isolateLength);
    let next = blot.next;
    blot.format(name, value);
    if (next instanceof BlockQuote) {
      next.formatAt(0, index - prevNewline + length - isolateLength, name, value);
    }
  }

  insertAt(index, value, def) {
    if (def != null) return;
    let [text, offset] = this.descendant(TextBlot, index);
    text.insertAt(offset, value);
  }

  length() {
    let length = this.domNode.textContent.length;
    if (!this.domNode.textContent.endsWith('\n')) {
      return length + 1;
    }
    return length;
  }

  newlineIndex(searchIndex, reverse = false) {
    if (!reverse) {
      let offset = this.domNode.textContent.slice(searchIndex).indexOf('\n');
      return offset > -1 ? searchIndex + offset : -1;
    } else {
      return this.domNode.textContent.slice(0, searchIndex).lastIndexOf('\n');
    }
  }

  optimize(context) {
    if (!this.domNode.textContent.endsWith('\n')) {
      this.appendChild(Parchment.create('text', '\n'));
    }
    super.optimize(context);
    let next = this.next;
    if (next != null && next.prev === this &&
      next.statics.blotName === this.statics.blotName &&
      this.statics.formats(this.domNode) === next.statics.formats(next.domNode)) {
      next.optimize(context);
      next.moveChildren(this);
      next.remove();
    }
  }

  replace(target) {
    super.replace(target);
    [].slice.call(this.domNode.querySelectorAll('*')).forEach(function(node) {
      let blot = Parchment.find(node);
      if (blot == null) {
        node.parentNode.removeChild(node);
      } else if (blot instanceof Parchment.Embed) {
        blot.remove();
      } else {
        blot.unwrap();
      }
    });
  }
}

CustomBlockQuote.blotName = 'blockquote';
CustomBlockQuote.tagName = 'blockquote';

export default CustomBlockQuote;

Can't quite understand why there is no option for this - it could have been done like css and inline-styles can be opt-in.

@Superfish83
Copy link

I wanted to solved this too but coudn't find a satisfactory solution...
So I just used css to make multiple blockquotes look like they are in one continuous block.

@StephanieTM
Copy link

Hello everyone, I find a simple way to do this. with pure CSS.

This is the code

I really like 嘉然's CSS solution above because it's super simple. But my quote block needs to start and end with quotation marks, so I need the quote lines to be wrapped in a container.

image

I tried digging around with custom blots, but it was way too complicated. It didn't seem right to build a quote block from scratch. Then I realized the built-in code-block tool fits my needs perfectly. So, I grabbed some source code and made a custom quote block. Check it out in the codesandbox.

Demo video:

Kapture.2024-06-26.at.16.59.59.mp4

@fpalab
Copy link

fpalab commented Aug 15, 2024

Just stumbled on this thread and I'll leave another solution for posterity.

In my case adding this to my CSS worked:

.ql-snow .ql-editor blockquote+blockquote{ margin-top:-5px; }

The idea is to cancel the margin-top in the second blockquote of a sequence of two or more. That makes multiple blockquotes look like they belong to single one. Fully supports indents and works on Chrome and Firefox. But, it may have other problems I'm not aware of.

One issue is that it is dependent on the snow theme: other themes might define a different margin-top and require some trimming. Unfortunately the selector must use the theme class otherwise it would be not specific enough to get any changes done to the style.

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