Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modify text in a contenteditable div without resetting the caret (cursor) position

I am trying to replace any instances of /any thing in here/ with <b>/any thing in here/</b> on the fly, as changes are made in a contenteditable div.

My current implementation works, but at every keypress the caret is moved to the beginning of div making the implementation unusable. Is there some way to keep the caret position while replacing the div's contents?

$('.writer').on('keyup', function(e) {
     $(this).html($(this).html().replace(/\/(.*)\//g, '<b>\/$1\/<\/b>'));
});
like image 286
slyv Avatar asked Jul 19 '14 21:07

slyv


1 Answers

try it demo

    $('#writer').on('keyup', function(e) {
        var range = window.getSelection().getRangeAt(0);
        var end_node = range.endContainer;
        var end = range.endOffset;
        if(end_node != this){
            var text_nodes = get_text_nodes_in(this);
            for (var i = 0; i < text_nodes.length; ++i) {
                if(text_nodes[i] == end_node){
                    break;
                }
                end += text_nodes[i].length;
            }
        }
        var html = $(this).html();
        if(/\&nbsp;$/.test(html) && $(this).text().length == end){
            end = end - 1;
            set_range(end,end,this);
            return;
        }
        var filter = html.replace(/(<b>)?\/([^<\/]*)(<\/b>)?/g, '\/$2');
        console.log(filter);
        filter = filter.replace(/(<b>)?([^<\/]*)\/(<\/b>)?/g, '$2\/');
        console.log(filter);
        filter = filter.replace(/(<b>)?\/([^<\/]*)\/(<\/b>)?/g, '<b>\/$2\/<\/b>');
        console.log(filter);
        if(!/\&nbsp;$/.test($(this).html())){
            filter += '&nbsp;';
        }
        $(this).html(filter);
        set_range(end,end,this);

    });

    $('#writer').on('mouseup', function(e) {
        if(!/\&nbsp;$/.test($(this).html())){
            return;
        }
        var range = window.getSelection().getRangeAt(0);
        var end = range.endOffset;
        var end_node = range.endContainer;
        if(end_node != this){
            var text_nodes = get_text_nodes_in(this);
            for (var i = 0; i < text_nodes.length; ++i) {
                if(text_nodes[i] == end_node){
                    break;
                }
                end += text_nodes[i].length;
            }
        }
        if($(this).text().length == end){
            end = end - 1;
            set_range(end,end,this);
        }
    });

    function get_text_nodes_in(node) {
        var text_nodes = [];
        if (node.nodeType === 3) {
            text_nodes.push(node);
        } else {
            var children = node.childNodes;
            for (var i = 0, len = children.length; i < len; ++i) {
                var text_node
                text_nodes.push.apply(text_nodes, get_text_nodes_in(children[i]));
            }
        }
        return text_nodes;
    }

    function set_range(start, end, element) {
        var range = document.createRange();
        range.selectNodeContents(element);
        var text_nodes = get_text_nodes_in(element);
        var foundStart = false;
        var char_count = 0, end_char_count;

        for (var i = 0, text_node; text_node = text_nodes[i++]; ) {
            end_char_count = char_count + text_node.length;
            if (!foundStart && start >= char_count && (start < end_char_count || (start === end_char_count && i < text_nodes.length))) {
                range.setStart(text_node, start - char_count);
                foundStart = true;
            }
            if (foundStart && end <= end_char_count) {
                range.setEnd(text_node, end - char_count);
                break;
            }
            char_count = end_char_count;
        }

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    }
like image 83
yao lu Avatar answered Nov 14 '22 22:11

yao lu