'react-quill How to properly add attribute to a specific html tag

Here is what I want to achieve. My user is typing a text in a react-quill component and each time the user type a hashtag like "#S1 " (the space is important, it allow to detect when the user has finished to type his tag) I want to replace the tag #S1 by a specific text containing some value.

I don't use delta, but HTML string for the value of the reat-quill component. So far I have succeed to replace the hastag by the proper value I need, and add a color to the span inserted. What I want to also add is a title attribute that will allow to show the corresponding tag name (i.e. #S1) and an id that will uniquely identify the content of the tag. But when I tried to add these attribute in my span, it's systematically strip.

I understood that I have to create my own parchment, but I' didn't succeed to get it work. Here is my code.

const modules = {
  toolbar: [
    [{header: [1, 2, false]}],
    ['bold', 'italic', 'underline', 'strike', 'blockquote'],
    [{'script': 'sub'}, {'script': 'super'}],
    [{'list': 'ordered'}, {'list': 'bullet'}, {'indent': '-1'}, {'indent': '+1'}],
    ['link', 'image'],
    ['clean'],
    [{color: []}, {background: []}],
  ],
};
const formats = [
  'header',
  'bold', 'italic', 'underline', 'strike', 'blockquote',
  'script', 'sub', 'super',
  'list', 'bullet', 'indent',
  'link', 'image',
  'color', 'background',

];
/**
 * Inline Em Tag from Quill Docs:
 * https://quilljs.com/guides/cloning-medium-with-parchment/
 */
let Inline = ReactQuill.Quill.import('blots/inline');
class EmphBlot extends Inline {
  static create(value) {
    console.log(value);
    let node = super.create();
    node.setAttribute('style', 'color: rgb(230, 0, 0)');
    node.setAttribute('title', value.title);
    
    return node;
  }

  static value(node) {
    return {
      title: node.getAttribute('title'),
      style: node.getAttribute('style')
    };
  }
}

EmphBlot.blotName = 'tag';
EmphBlot.tagName = 'span';
ReactQuill.Quill.register('formats/em', EmphBlot);

class Editor extends React.Component {
  constructor (props) {
    super(props)
    this.state = { editorHtml: '<p>Start here by typing a hashtag</p>' }
    this.quillRef = null;
    this.reactQuillRef = null;
    this.handleChange = this.handleChange.bind(this)
    this.handleClickEmbed = this.handleClickEmbed.bind(this)
    this.handleClickFormat = this.handleClickFormat.bind(this)
    this.registerFormats = this.registerFormats.bind(this)
  }
  
  componentDidMount () {
    this.registerFormats()
    this.setState({
      editorHtml: '' // trigger update
    })
  }
  
  componentDidUpdate () {
    this.registerFormats()
  }
  
  registerFormats () {
    // Ensure React-Quill references is available:
    if (typeof this.reactQuillRef.getEditor !== 'function') return;
    // Skip if Quill reference is defined:
    if (this.quillRef != null) return;
    
    console.log('Registering formats...', this.reactQuillRef)
    const quillRef = this.reactQuillRef.getEditor() // could still be null
    
    if (quillRef != null) {
      this.quillRef = quillRef;
      // console.log(Quill.imports)
    }
  }
  
  handleClickFormat () {
    var range = this.quillRef.getSelection();
    if (range) {      
      this.quillRef.format('em', true);
    }
  }
  
  handleClickEmbed () {
    var range = this.quillRef.getSelection();
    if (range) {      
      this.quillRef.insertEmbed(range.index,"hr","null")
    }
  }
  
  checkForTag(content, indexPosition){
  let tag = /#(\w+)\s/;
  let find = content.match(tag);
  let indexAfterInsertion = indexPosition;
  let updated = false;
  let comment;
  if (find !== null) {
    indexAfterInsertion = indexPosition - find[0].length;
    let replace = "My new data (1, 5, 3)"
    indexAfterInsertion += replace.length + 1;
    updated = true;
    if (replace !== '#' + find[1]) {
      let toReplacer = '<span style="color:red" title="tag-' + find[1].toUpperCase() + '">' + replace + '</span>&nbsp;';
      // console.log(toReplacer);
      comment = content.replace(/#(\w+)\s/, toReplacer);
    } else {
      comment = content.replace(/#(\w+)\s/, find[1] + "&nbsp;");
    }
  }
  // console.log(comment);
  return {content: comment, updated, indexAfterInsertion}
  }
  
  handleChange (content, delta, source, editor) {
    let editorHtml = content
    let newHtml = this.checkForTag(content);
    if(newHtml.updated === true){
      console.log(newHtml);
      editorHtml = newHtml.content
    }
    console.log(editorHtml)
    this.setState({editorHtml});
  }
  
  render () {
    return (
      <div>
        <p>Enter a text containing #S1 (or any other tag, the space must be placed after the tag). It sould be replaced by an other data, but missing the attribute title.</p>
        
        <ReactQuill 
          ref={(el) => { this.reactQuillRef = el }}
          value={this.state.editorHtml}
          onChange={this.handleChange.bind(this)}
          modules={modules}
          formats={formats}
          />
       </div>
     )
  }
}

Editor.propTypes = {
  placeholder: React.PropTypes.string,
}

ReactDOM.render(
  <div>
    <Editor/>
   </div>, 
  document.querySelector('.app')
)

Here is a codepen showing how it work so far https://codepen.io/FLCcrakers/pen/JZVeZE?editors=0111

I surely miss something, but don't understand what.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source