Replace specific word in contenteditable

前端 未结 3 1691
我寻月下人不归
我寻月下人不归 2021-02-08 07:52

I have a contenteditable div

I need to get the last word from caret position and on certai

相关标签:
3条回答
  • 2021-02-08 08:27
     words = ['oele', 'geel', 'politie', 'foo bar'];
    
    function markWords() {
        var html = div.html().replace(/<\/?strong>/gi, ''),
            text = html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' '),
            exp;
        $.each(words, function(i, word) {
            exp = new RegExp('\\b(' + word + ')\\b', 'gi');
            html = html.replace(exp, function(m) {
    console.log('WORD MATCH:', m);
                return '<strong>' + m + '</strong>';
            });
        });
        //html = html.replace('&nbsp;', ' ').replace(/\s+/g, ' ');
    console.log('HTML:', html);
    console.log('----');
        div.html(html);
    }
    

    Call this function on setinterval

    Fiddle

    0 讨论(0)
  • 2021-02-08 08:28

    Tobías' solution works well for single-line contenteditable div. But if you add multiple lines, it doesn't work anymore.

    Here is a general solution that works for both single-line or multiline contenteditable div.

    0 讨论(0)
  • 2021-02-08 08:48

    To work with Ranges we need to keep in mind that we are working with Nodes, not only the text that is rendered. The structure you want to manipulate is:

    <div id="divTest" contenteditable="true"> <-- Element Node
        "some text" <-- TextNode
    </div>
    

    But it also could be:

    <div id="divTest" contenteditable="true"> <-- Element Node
        "some text" <-- TextNode
        "more text" <-- TextNode
        "" <-- TextNode
    </div>
    

    To solve your problem is simplier to handle only one TextNode, I propose to use the normalize() function to join all of them into a single one.

    Then you only need to set the Range to the word's bounds before deleteContents(). Once deleted, you can insert a new TextNode with the substitution using insertNode().

    var wordStart = range.toString().lastIndexOf(lastWord);
    var wordEnd = wordStart + lastWord.length;
    
    /* containerEl.firstChild refers to the div's TextNode */                   
    range.setStart(containerEl.firstChild, wordStart);
    range.setEnd(containerEl.firstChild, wordEnd);
    range.deleteContents();
    range.insertNode(document.createTextNode(resultValue));
    

    For this to work, you need that the text is in a single TextNode. But after ìnsertNode the div will contain multiple text nodes. To fix this simply call normalize() to join all TextNode elements.

    containerEl.normalize();
    

    Edit:

    As Basj points out, the original solution fails for multiline. That's because when hitting ENTER the structure changes from:

    <div id="divTest" contenteditable="true"> <-- Element Node
        "some text" <-- TextNode
    </div>
    

    to something like:

    <div id="divTest" contenteditable="true"> <-- Element Node
        <div>"some text"</div>
        <div>"more text"</div>
    </div>
    

    I've updated this answer, but it's also worth to read Basj's answer at this question: Replace word before cursor, when multiple lines in contenteditable

    JSFiddle demo or runnable code snippet:

    document.getElementById('divTest').onkeyup = function (e) {
        if (e.keyCode == 32) {
            getWordPrecedingCaret(this);
        }
    };
    
    function getWordPrecedingCaret(containerEl) {
        var preceding = "",
            sel,
            range,
            precedingRange;
        if (window.getSelection) {
            sel = window.getSelection();
            if (sel.rangeCount > 0) {
                range = sel.getRangeAt(0).cloneRange();
                range.collapse(true);
                range.setStart(containerEl, 0);
                preceding = range.toString();
            }
        } else if ((sel = document.selection) && sel.type != "Control") {
            range = sel.createRange();
            precedingRange = range.duplicate();
            precedingRange.moveToElementText(containerEl);
            precedingRange.setEndPoint("EndToStart", range);
            preceding = precedingRange.text;
        }
    
        var words = range.toString().trim().split(' '),
            lastWord = words[words.length - 1];
            
        if (lastWord) {
            var resultValue = 'some'; // this value is coming from some other function
            if (resultValue == lastWord) {
                console.log('do nothing: ' + lastWord);
                // do nothing
            } else {
                console.log('replace word ' + lastWord);
                
                /* Find word start and end */
                var wordStart = range.endContainer.data.lastIndexOf(lastWord);
                var wordEnd = wordStart + lastWord.length;
                console.log("pos: (" + wordStart + ", " + wordEnd + ")");
                               
                range.setStart(range.endContainer, wordStart);
                range.setEnd(range.endContainer, wordEnd);
                range.deleteContents();
                range.insertNode(document.createTextNode(resultValue));
                // delete That specific word and replace if with resultValue
    
                /* Merge multiple text nodes */            
                containerEl.normalize();
            }
            return lastWord;
        }
    }
    <div id="divTest" contenteditable="true">Write words here and hit SPACE BAR</div>

    0 讨论(0)
提交回复
热议问题