Search code examples
reactjsquillreact-quilljs

How do you insert direct html through react-quill handlers?


I created the following react component. It uses quill js and has a custom button to insert a line. The problem is I can't find a way to to insert an <hr> tag through the insertLine function. Is there a way to insert direct html?

import ReactQuill, { Quill } from 'react-quill'
import 'react-quill/dist/quill.snow.css'

function insertLine() {
  
}

const icons = Quill.import('ui/icons')
icons['code-block'] = '<svg strokeWidth="0" viewBox="0 0 16 16" height="15" width="15" xmlns="http://www.w3.org/2000/svg"><path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0zM9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1zM6.646 7.646a.5.5 0 1 1 .708.708L5.707 10l1.647 1.646a.5.5 0 0 1-.708.708l-2-2a.5.5 0 0 1 0-.708l2-2zm2.708 0 2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 10 8.646 8.354a.5.5 0 1 1 .708-.708z"></path></svg>'

const modules = {
  toolbar: {
    container: '#toolbar',
    handlers: {
      insertLine: insertLine
    }
  },
  clipboard: {
    matchVisual: false,
  }
}

export default function TextEditor({ body, setBody }) {
  return (
    <div>
      <div id='toolbar' style={{borderBottomWidth: '0'}}>
        <span className='ql-formats'>
          <select className='ql-header' defaultValue='7' onChange={e => e.persist()}>
            <option value='1' />
            <option value='2' />
            <option value='7' />
          </select>
        </span>
        <span className='ql-formats'>
          <button className='ql-bold' />
          <button className='ql-italic' />
          <button className='ql-underline' />
          <button className='ql-strike' />
        </span>
        <span className='ql-formats'>
          <button className='ql-blockquote' />
          <button className='ql-link' />
          <button className='ql-image' />
          <button className='ql-code' />
          <button className='ql-code-block' />
        </span>
        <span className='ql-formats'>
          <button className='ql-list' value='ordered' />
          <button className='ql-list' value='bullet' />
          <button className='ql-insertLine' style={{transform: 'rotate(90deg)'}}>
            <svg stroke="currentColor" strokeWidth="0" viewBox="0 0 24 24" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0z"></path><path d="M4 22H2V2h2v20zM22 2h-2v20h2V2zm-8.5 5h-3v10h3V7z"></path></svg>       
          </button>
        </span>
      </div>
      <ReactQuill theme='snow' value={body} onChange={setBody} modules={modules} />
    </div>
  )
}

Solution

  • After some trial and error, this worked for me.

    import React, { useRef, useEffect } from 'react'
    import ReactQuill, { Quill } from 'react-quill'
    import 'react-quill/dist/quill.snow.css'
    
    const DividerBlot = Quill.import('blots/block')
    class Divider extends DividerBlot {}
    Divider.blotName = 'divider'
    Divider.tagName = 'hr'
    Quill.register(Divider)
    
    const icons = Quill.import('ui/icons')
    icons['code-block'] = '<svg strokeWidth="0" viewBox="0 0 16 16" height="15" width="15" xmlns="http://www.w3.org/2000/svg"><path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0zM9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1zM6.646 7.646a.5.5 0 1 1 .708.708L5.707 10l1.647 1.646a.5.5 0 0 1-.708.708l-2-2a.5.5 0 0 1 0-.708l2-2zm2.708 0 2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 10 8.646 8.354a.5.5 0 1 1 .708-.708z"></path></svg>'
    
    const modules = {
      toolbar: {
        container: '#toolbar',
        handlers: {
          insertLine: insertLine
        }
      },
      clipboard: {
        matchVisual: false
      }
    }
    
    function insertLine() {
      const editor = this.quill
      const range = editor.getSelection()
      const cursorPosition = range ? range.index : 0
      editor.insertEmbed(cursorPosition, 'divider', true, Quill.sources.USER)
      editor.setSelection(cursorPosition + 1, Quill.sources.SILENT)
    }
    
    export default function TextEditor({ body, setBody }) {
      const quillRef = useRef()
    
      useEffect(() => {
        if (quillRef.current) {
          const editor = quillRef.current.getEditor()
    
          editor.getModule('clipboard').addMatcher(Node.TEXT_NODE, function (node, delta) {
            const text = node.data
            if (text === '<hr>') {
              const divider = new Divider()
              const index = delta.ops.length - 1
              delta.ops[index].insert = { divider }
            }
            return delta
          })
    
          editor.getModule('toolbar').addHandler('divider', insertLine)
        }
      }, [])
    
      return (
        <div>
          <div id='toolbar' style={{borderBottomWidth: '0'}}>
            <span className='ql-formats'>
              <select className='ql-header' defaultValue='7' onChange={e => e.persist()}>
                <option value='1' />
                <option value='2' />
                <option value='7' />
              </select>
            </span>
            <span className='ql-formats'>
              <button className='ql-bold' />
              <button className='ql-italic' />
              <button className='ql-underline' />
              <button className='ql-strike' />
            </span>
            <span className='ql-formats'>
              <button className='ql-blockquote' />
              <button className='ql-link' />
              <button className='ql-image' />
              <button className='ql-code' />
              <button className='ql-code-block' />
            </span>
            <span className='ql-formats'>
              <button className='ql-list' value='ordered' />
              <button className='ql-list' value='bullet' />
              <button className='ql-insertLine' style={{transform: 'rotate(90deg)'}}>
                <svg stroke="currentColor" strokeWidth="0" viewBox="0 0 24 24" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0z"></path><path d="M4 22H2V2h2v20zM22 2h-2v20h2V2zm-8.5 5h-3v10h3V7z"></path></svg>       
              </button>
            </span>
          </div>
          <ReactQuill ref={quillRef} theme='snow' value={body} onChange={setBody} modules={modules} />
        </div>
      )
    }