Create Custom Block in Quill like Video, Link, Banner.

create-custom-block-in-quill-like-video,-link,-banner.

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

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=`
        
        
        
        
    `
    // 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=`


` 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 = '
  1. Item 1
  2. Item 2
  1. Item 1
  2. Item 2
'; // Replace
    tags with
      tags for child elements with the class "child" x= x.replaceAll(/]*)>(.*?.*?.*?)
/gi, '
    $2
') 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;
}
Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
product-marketing-awards-2023:-get-your-ballots-ready

Product Marketing Awards 2023: get your ballots ready

Next Post
react-custom-hook:-usedeepcompareeffect

React Custom Hook: useDeepCompareEffect

Related Posts