Hi,
First off, thanks for open sourcing this great editor. I've been able to do a lot with this package, but I have had mountains of trouble with one specific issue. I built a templating system in which the user enters some data in inputs (YouTube URLs, image uploads, etc.), from which I generate valid, semantic markup and insert it into the editor. The problem I have run into is that despite many attempts to configure Parchment blots, I cannot get block level elements like paragraph, H2, H3 sitting inside divs into the editor. Here is a sample of the type markup that I would like to insert into the editor:
<div class="col-lg-8 col-md-8 col-sm-12 col-xs-12">
<h2>Here is my headline</h2>
<p>Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit.</p>
<p>Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit.</p>
<iframe height="197" src="https://www.youtube.com/embed/2de13Kac4M0" width="350"></iframe>
</div>
<div class="col-lg-4 col-md-4 col-sm-12 col-xs-12">
<img src="/image.png" alt="image" />
<p class="caption">Caption</p>
</div>
And here is my custom Blot configuration. I think the issue is that Quill does not like block level elements inside block level elements, but I believe there HAS to be a way to do it. FYI, I am trying to insert this content via React props, as I am building off of the React-Quill package:
import ReactQuill from 'react-quill'
const Quill = ReactQuill.Quill
// import theme from 'react-quill/dist/quill.snow.css'
const BlockEmbed = Quill.import('blots/block/embed')
const Block = Quill.import('blots/block')
const Inline = Quill.import('blots/inline')
const Embed = Quill.import('blots/embed')
/* need this "blot" to allow for button links */
class LinkBlot extends Inline {
static create(value) {
let node = super.create();
if (value.href) {
if (value.href !== '') { node.setAttribute('href', value.href) }
if (value.class !== '') { node.setAttribute('class', value.class) }
if (value.style !== '') { node.setAttribute('style', value.style) }
node.setAttribute('target', '_blank')
} else {
node.setAttribute('href', value)
node.setAttribute('target', '_blank')
}
return node;
}
static formats(node) {
return {
href: node.getAttribute('href') ? node.getAttribute('href') : '',
class: node.getAttribute('class') ? node.getAttribute('class') : '',
style: node.getAttribute('style') ? node.getAttribute('style') : ''
}
}
}
LinkBlot.blotName = 'link';
LinkBlot.tagName = 'a';
Quill.register(LinkBlot);
/* we need to control the output of headings in order to have custom classes on templates */
class H1Blot extends Block {
static create(value) {
let node = super.create();
if (value.class) { node.setAttribute('class', value.class) }
if (value.style) { node.setAttribute('style', value.style) }
return node;
}
static formats(node) {
return {
class: node.getAttribute('class') ? node.getAttribute('class') : null,
style: node.getAttribute('style') ? node.getAttribute('style') : null
}
}
}
H1Blot.blotName = 'h1'
H1Blot.tagName = 'h1'
Quill.register(H1Blot)
class H2Blot extends Block {
static create(value) {
let node = super.create();
if (value.class) { node.setAttribute('class', value.class) }
if (value.style) { node.setAttribute('style', value.style) }
return node;
}
static formats(node) {
return {
class: node.getAttribute('class') ? node.getAttribute('class') : null,
style: node.getAttribute('style') ? node.getAttribute('style') : null
}
}
}
H2Blot.blotName = 'h2'
H2Blot.tagName = 'h2'
Quill.register(H2Blot)
class ParagraphBlot extends Block {
static create(value) {
let node = super.create()
if (value.class !== '') { node.setAttribute('class', value.class) }
if (value.style !== '') { node.setAttribute('style', value.style) }
return node
}
static formats(node) {
if (node) {
return {
class: node.getAttribute('class') ? node.getAttribute('class') : '',
style: node.getAttribute('style') ? node.getAttribute('style') : ''
}
}
}
}
ParagraphBlot.blotName = 'p'
ParagraphBlot.tagName = 'p'
Quill.register(ParagraphBlot)
class DivBlot extends Block {
static create(value) {
let node = super.create()
if (value.class !== '') { node.setAttribute('class', value.class) }
if (value.style !== '') { node.setAttribute('style', value.style) }
return node
}
static formats(node) {
if (node) {
return {
class: node.getAttribute('class') ? node.getAttribute('class') : '',
style: node.getAttribute('style') ? node.getAttribute('style') : ''
}
}
}
}
DivBlot.blotName = 'div'
DivBlot.tagName = 'div'
Quill.register(DivBlot)
class Image extends Embed {
static create(value) {
let node = super.create()
/* if the values are coming from one of the HTML templates */
if (value.url) {
node.setAttribute('alt', value.alt)
node.setAttribute('class', value.class)
node.setAttribute('src', value.url)
if (value['data-url']) {
node.setAttribute('data-url', value['data-url'])
}
if (value.style) {
node.setAttribute('style', value.style)
}
if (value.align) {
node.setAttribute('align', value.align)
}
} else {
/* otherwise, we need to set them manually */
node.setAttribute('src', value)
node.setAttribute('class', 'img-responsive center-block')
}
return node
}
static value(node) {
return {
align: node.getAttribute('align'),
alt: node.getAttribute('alt'),
class: node.getAttribute('class'),
'data-url': node.getAttribute('data-url'),
style: node.getAttribute('style'),
url: node.getAttribute('src')
}
}
}
Image.blotName = 'image'
Image.tagName = 'img'
Quill.register(Image)
class VideoBlot extends BlockEmbed {
static create(value) {
let node = super.create()
if (value.url) {
node.setAttribute('align', value.align)
node.setAttribute('border', 'none')
node.setAttribute('class', value.class)
node.setAttribute('height', value.height)
node.setAttribute('src', value.url)
node.setAttribute('style', value.style)
node.setAttribute('width', value.width)
} else {
node.setAttribute('src', value)
node.setAttribute('class', 'ql-video')
node.setAttribute('allowfullscreen', true)
}
return node
}
static value(node) {
return {
align: node.getAttribute('align'),
class: node.getAttribute('class'),
height: node.getAttribute('height'),
style: node.getAttribute('style'),
width: node.getAttribute('width'),
url: node.getAttribute('src')
}
}
}
VideoBlot.blotName = 'video'
VideoBlot.tagName = 'iframe'
Quill.register(VideoBlot)
So basically, I just need to be able to include paragraph tags, images, YouTube iframes, H2 tags inside DIV tags. If anyone can offer any insight at all, I'd be extremely appreciative. I've basically tried everything!! Thanks -- Steve
hi @svanetten1976 I also needed something similar to this but only for the headers and asked here but didn't get a reply. So I finally figured it out myself although not sure that it's an ok approach or not. Basically I created Header Blot and HeaderItem Blot separately and used the List and ListItem structure. So I have now H1 > DIV structure. Although wanted to do DIV > H1 but it was creating problems at a lot of places.
So I guess your best bet is to go with List, ListItem structure unless someone suggests a better alternative.
Thanks for the reply @akashkamboj but I'll need to see if I can figure out another way. I really need to be able to include some custom HTML with nested block elements, but I certainly appreciate your suggestion.
I also needed something similar to this
I also need something similar to this. For creating elements like:
<div class="video-container">
<iframe src="xxx">
</iframe>
<div>
The main purpose of adding one more layer is to apply some css styles to the div as the container.
For those who want iFrame to be inside a div with a class ( or any other element ):
import Quill from "quill";
const BlockEmbed = Quill.import("blots/block/embed");
class VideoBlot extends BlockEmbed {
static create(url) {
let node = super.create(url);
let iframe = document.createElement('iframe');
// Set styles for wrapper
node.setAttribute('class', 'embed-responsive embed-responsive-16by9');
// Set styles for iframe
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('allowfullscreen', true);
iframe.setAttribute('src', url);
// Append iframe as child to wrapper
node.appendChild(iframe);
return node;
}
static value(domNode) {
return domNode.firstChild.getAttribute('src');
}
}
VideoBlot.blotName = 'video';
VideoBlot.tagName = 'div';
Quill.register(VideoBlot, true);
@ArsalanSavand Thanks. For some reason, domNode.firstChild didn't work for me, so I used querySelector() to get the child instead https://github.com/quilljs/quill/issues/2380#issuecomment-433807279 Also, I sanitized the link.
@kkomelin You're welcome and yes you can also use querySelector() to get the child.
Did anyone finally get block elements inside other block elements? I really need to know that.
Did anyone finally get block elements inside other block elements? I really need to know that.
@loagit did you end up finding a solution? I'm trying to nest markup schema inside another but no dice
Is this issue not still fixed after 3 years?...
Most helpful comment
I also needed something similar to this