silencedogood
silencedogood

Reputation: 3299

Dynamically wrap text in an anchor tag in Quilljs Error: addRange(): The given range isn't in document

I'm looking to search the editor text for any hashtags, and wrap the hashtag in an anchor tag to make it a link. So this: #whatsup would be this: <a href='http://help.com'>#whatsup</a>.

Here's what I have so far, which modifies the text correctly but results in this error. addRange(): The given range isn't in document And the cursor is removed, disallowing me to type anything else.

I get the same error when using setContents rather than dangerouslyPasteHTML

Just type any hashtag value such as #foo into the code snippet and look at your console to reproduce the error. Any help much appreciated.

class RichEditor extends React.Component {
    constructor (props) {
        super(props)
        this.state = { 
            editorHtml: '',
            hashTags: [] 
        }
        this.modules = {
            toolbar: [
                [{ header: [1, 2, false] }],
                ['bold', 'italic', 'underline'],
                ['image', 'code-block']
              ]
        }

        this.editor = null;
        this.quillRef = null;
        this.handleChange = this.handleChange.bind(this)
    }

    handleChange (html) {
        var blurtHashtags = this.editor.getText().match(/(?:^|\B)#[a-zA-Z][\w]{1,19}\b/g);

        if (blurtHashtags) {
            var modHtml = html.replace(blurtHashtags[0], "<a href='http://help.com'>" + blurtHashtags[0] + "</a>");
                 this.editor.clipboard.dangerouslyPasteHTML(modHtml);
        } else {
            this.setState(prevState => ({
                editorHtml: html 
            }));
        }
    }
    
    componentDidMount() {
        if (typeof this.quillRef.getEditor !== 'function') return;
        this.editor = this.quillRef.getEditor();
    }
    
    render() {
        return (
            <div>
                <ReactQuill
                    ref={(el) => { this.quillRef = el; }} 
                    onChange={this.handleChange} 
                    modules={this.modules}
                />
            </div>
        );
    }
}

ReactDOM.render( <
  RichEditor / > ,
  document.getElementById("quill")
);
<div id="quill"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/prop-types/prop-types.js"></script>
<script src="https://unpkg.com/react-quill@latest/dist/react-quill.js"></script>
<link href="//cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">

Upvotes: 3

Views: 2455

Answers (1)

norbitrial
norbitrial

Reputation: 15166

It's pretty interesting what is going on in the background. The solution what you find down there uses setTimeout with 0ms delay to call dangerouslyPasteHTML function.

The reason behind why it is working now as expected that is technically not a good practice to modify the content in the handler event of the input field and by adding the delay the event loop will execute the input field modification - dangerouslyPasteHTML function - after the handler event.

The following code snippet resolves your issue, please take a look:

class RichEditor extends React.Component {
    constructor (props) {
        super(props)
        this.state = { 
            editorHtml: '',
            hashTags: [] 
        }
        this.modules = {
            toolbar: [
                [{ header: [1, 2, false] }],
                ['bold', 'italic', 'underline'],
                ['image', 'code-block']
              ]
        }

        this.editor = null;
        this.quillRef = null;
        this.handleChange = this.handleChange.bind(this)
    }

    handleChange (html) {
        const linkStart = `<a href="http://help.com" rel="noopener noreferrer" target="_blank">`;
        const linkEnd = `</a>`;

        // remove anchor tag around the hashtag value
        // in order to identify hashtag properly
        let modHtml = html.replace(linkStart, '');
        modHtml = modHtml.replace(linkEnd, '');
        const blurtHashtags = modHtml.match(/(?:^|\B)#[a-zA-Z][\w]{1,19}\b/g);

        if (blurtHashtags) {
            // rebuild link with anchor tag
            const link = `${linkStart}${blurtHashtags[0]}${linkEnd}`;
            modHtml = modHtml.replace(blurtHashtags[0], link);

            let editor = this.editor;
        
            setTimeout(() => {
                editor.clipboard.dangerouslyPasteHTML(modHtml);

                let selection = editor.getSelection();
                selection.index = modHtml.length;
                editor.setSelection(selection);
            }, 0);
        } else {
            this.setState(prevState => ({
                editorHtml: html 
            }));
        }
    }
    
    componentDidMount() {
        if (typeof this.quillRef.getEditor !== 'function') return;
        this.editor = this.quillRef.getEditor();
    }
    
    render() {
        return (
            <div>
                <ReactQuill
                    className="quill-pre"
                    ref={(el) => { this.quillRef = el; }} 
                    onChange={this.handleChange} 
                    modules={this.modules}
                />
            </div>
        );
    }
}

ReactDOM.render( <
  RichEditor / > ,
  document.getElementById("quill")
);
.quill-pre p {
  white-space: pre;
}
<div id="quill"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/prop-types/prop-types.js"></script>
<script src="https://unpkg.com/react-quill@latest/dist/react-quill.js"></script>
<link href="//cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">

Obviously the rich text editor is not perfect at this point but fits for your requirements which was removing the error message what you had earlier. If you try typing the test text value #wassup then it won't throw any error message.

Additionally I have added white-space: pre; styling for the p tag in the component which resolves the white spacing issue after typing any hashtag.

Hope this helps!

Upvotes: 3

Related Questions