i am creating this post as my experience. sometime we need to create custom block in quill to create block for our need. i create some block.
index.html
<div class="" id="editor-wrapper" (keyup)="keyup($event)" (click)="keyup($event)">
</div>
<div><button (click)="download()">Download</button></div>
<div><button (click)="insert()">Insert Hr</button></div>
<div id="customDropdown" style="display: none;">
<!-- Add your dropdown options here -->
<button>Option 1</button>
<button>Option 2</button>
<button>Option 3</button>
</div>
editor.ts
declare const Quill: any;
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
declare const quillBetterTable: any;
declare const htmlToPdfmake: any;
declare const pdfMake: any;
import jsPDF from 'jspdf';
Quill.register({
'modules/better-table': quillBetterTable
}, true);
const Link = Quill.import("formats/link");
const BlockEmbed = Quill.import("blots/block/embed");
class VideoBlot extends BlockEmbed {
static create(obj:any) {
console.log(obj)
let node = super.create(obj?.url?obj?.url:obj);
let iframe = document.createElement('iframe');
let con = document.createElement('span');
con.setAttribute('style', 'display:flex;');
con.setAttribute('class', 'resize-container');
con.innerHTML=`
<button data-size="1" class="re-1">1x</button>
<button data-size="2" class="re-2">2x</button>
<button data-size="3" class="re-3">3x</button>
<button data-size="4" class="re-4">4x</button>
`
// con.addEventListener('click',(e:any)=>{
// node.setAttribute('data-width', 'embed-responsive-'+e.target.dataset.size);
// console.log("first",e.target.dataset.size)
// })
node.setAttribute('data-width', obj?.size?obj?.size:'embed-responsive-'+3);
// Set styles for wrapper
node.setAttribute('class', 'embed-responsive embed-responsive-16by9');
node.appendChild(con)
// Set styles for iframe
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('allowfullscreen', 'true');
iframe.setAttribute('src', obj?.url?obj?.url:obj);
// Append iframe as child to wrapper
node.appendChild(iframe);
return node;
}
static value(domNode:any) {
const url=domNode.getElementsByTagName('iframe')[0].getAttribute('src');
const size=domNode.getAttribute('data-width');
return {url,size}
}
// format(name:any, value:any) {
// // Override the format method to handle your custom attribute
// if (name === 'custom-attribute') {
// const previousValue = this['domNode'].getAttribute('data-custom-attribute');
// if (value) {
// this['domNode'].setAttribute('data-custom-attribute', value);
// } else {
// this['domNode'].removeAttribute('data-custom-attribute');
// }
// // Use the previousValue as needed
// } else {
// super.format(name, value);
// }
// console.log('Previous value:', name,value);
// }
}
VideoBlot['blotName'] = 'video';
VideoBlot['tagName'] = 'div';
Quill.register(VideoBlot, true);
var CustomLink = Quill.import('formats/link');
CustomLink.sanitize = function(url:any) {
return url; // Customize the URL sanitization if needed
};
class CustomLinkFormat extends CustomLink {
static create(value:any) {
const node = super.create(value.url);
console.log(value);
node.setAttribute('data-custom-attribute', value.customAttribute); // Set the custom attribute
return node;
}
static formats(node:any) {
const format = super.formats(node);
console.log(node)
format.customAttribute = node.getAttribute('data-custom-attribute')||'1'; // Get the custom attribute
return format;
}
}
Quill.register(CustomLinkFormat, true);
var Inline = Quill.import('blots/inline');
// Define the custom format class
class CustomFormat extends Inline {
static create(v:any) {
const node = super.create();
const d=document.createElement('sup');
d.classList.add('custom-format');
d.appendChild(node)
return d;
}
}
// Assign a CSS class name to the custom format
CustomFormat['blotName'] = 'highlight';
CustomFormat['tagName'] = 'span';
// Register the custom format with Quill
Quill.register(CustomFormat);
// Extend ListContainer module
// const Block = Quill.import('blots/block');
// class CustomListContainer extends Block {
// static create(value:any) {
// const node = super.create(value);
// node.classList.add('custom-list-container');
// return node;
// }
// }
// CustomListContainer['tagName'] = 'div';
// CustomListContainer['allowedChildren'] = [Block, CustomListContainer];
// CustomListContainer['scope'] = Block.scope;
// CustomListContainer['defaultChild'] = 'block';
// // Override the default ListItem module to use the custom list container
// const ListItem = Quill.import('formats/list');
// class CustomListItem extends ListItem {
// format(name:any, value:any) {
// if (name === 'list' && value) {
// const isOrdered = value === 'ordered';
// const CustomContainer:any = isOrdered ? 'OL' : 'UL';
// const container:any = this['parent'];
// if (!(container instanceof CustomContainer)) {
// const newContainer = this['scroll'].create(CustomContainer);
// container.replaceWith(newContainer);
// newContainer.appendChild(this['domNode']);
// }
// }
// super.format(name, value);
// }
// }
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-rich-editor',
templateUrl: './rich-editor.component.html',
styleUrls: ['./rich-editor.component.css']
})
export class RichEditorComponent implements OnInit{
data:any=`<p><br></p>`
quill:any;
constructor(public sanitizer: DomSanitizer){
}
ngOnInit(): void {
setTimeout(()=>{
this.initEditor()
},1000)
// document.querySelector('.re-1')?.addEventListener('click',()=>{
// console.log('sadasa')
// })
}
initEditor(){
this.quill = new Quill('#editor-wrapper', {
theme: 'snow',
modules: {
toolbar:{
container:[
'video',
'image',
'link',
'align',
'customLink',
{ 'script': 'sub'}, { 'script': 'super' },
{'list':'ordered'},{'list':'bullet'}
],
handlers:{
'customLink':(v:any)=>{
console.log(v)
this.quill.format('link', {
url: 'https://example.com',
customAttribute: 'custom value'
});
},
},
},
table: false, // disable table module
'better-table': {
operationMenu: {
items: {
unmergeCells: {
text: 'Another unmerge cells name'
}
}
}
},
keyboard: {
bindings: quillBetterTable.keyboardBindings
},
},
})
var customDropdown:any = document.getElementById('customDropdown');
var dropdownOpen = false;
this.quill.on('text-change', (delta:any, oldDelta:any, source:any)=> {
console.log(delta,oldDelta,source)
var range = this.quill.getSelection();
if (range && range.length === 0) {
var cursorPosition = range.index;
var lineText = this.quill.getText(0, cursorPosition);
// Define the trigger text or condition to open the dropdown
var triggerText = '/'; // Example: Open the dropdown when the user types '@dropdown'
if (lineText.endsWith(triggerText)) {
if (!dropdownOpen) {
// Get the bounds of the current cursor position
var bounds = this.quill.getBounds(range.index);
// Get the offset position of the Quill editor
var editorBounds:any = document.getElementById('editor-wrapper')?.getBoundingClientRect();
var editorOffsetTop = editorBounds.top + window.pageYOffset;
var editorOffsetLeft = editorBounds.left + window.pageXOffset;
// Position the dropdown below the cursor, considering the editor offset
customDropdown.style.left = (bounds.left - editorOffsetLeft) + 'px';
customDropdown.style.top = (bounds.top - editorOffsetTop + bounds.height) + 'px';
customDropdown.style.display = 'block';
dropdownOpen = true;
}
} else {
if (dropdownOpen) {
// Close the dropdown
customDropdown.style.display = 'none';
dropdownOpen = false;
}
}
}
});
document.addEventListener('click', function(event) {
// Close the dropdown if a click event occurs outside the dropdown
if (!customDropdown.contains(event.target)) {
customDropdown.style.display = 'none';
dropdownOpen = false;
}
});
}
click(x:any){
console.log(x)
}
download(){
let x=this.quill.root.innerHTML;
const htmlString = '<ol><li class="child">Item 1</li><li>Item 2</li></ol><ol><li class="chi">Item 1</li><li>Item 2</li></ol>';
// Replace <ol> tags with <ul> tags for child elements with the class "child"
x= x.replaceAll(/<ol\b([^>]*)>(.*?<li\s+data-list="bullet">.*?<\/li>.*?)<\/ol>/gi, '<ul $1>$2</ul>')
var html = htmlToPdfmake(x);
// console.log(html)
var docDefinition = {
content: [
html
],
styles:{
}
};
var pdfDocGenerator = pdfMake.createPdf(docDefinition);
pdfDocGenerator.download()
// var doc:any = new jsPDF();
// doc.html(this.quill.root.innerHTML, {
// callback: function (docs:any) {
// docs.save('quill_content.pdf');
// }
// });
}
insert(){
const range=this.quill.getSelection();
const is=this.quill.getFormat(range.index,range.length)
console.log(is)
if(is.highlight)
this.quill.format('highlight', false);
else this.quill.format('highlight', true);
}
keyup(event:any){
console.log(event)
}
}
style.css
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-toggle {
padding: 10px 15px;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
display: none;
min-width: 160px;
padding: 5px 0;
background-color: #fff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
z-index: 1;
}
.dropdown-menu.show {
display: block;
}
.dropdown-item {
display: block;
padding: 5px 10px;
color: #333;
text-decoration: none;
transition: background-color 0.3s;
}
.dropdown-item:hover {
background-color: #f5f5f5;
}
Top comments (1)
I am added block. if anyone need any custom block comment here i will give you best answer from our side. thank you.