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.