12

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>'));
});
Cœur
  • 37,241
  • 25
  • 195
  • 267
slyv
  • 828
  • 1
  • 10
  • 18
  • Is there a reason you have to use a `div` with `contenteditable` instead of a `textarea` or something similar? – royhowie Jul 19 '14 at 21:47
  • @Luxelin: I'd like to render html within the div, and as far as I am aware, html can't be rendered inside a textarea. – slyv Jul 19 '14 at 21:49
  • Even if you were to use a textarea I'm sure the caret would still move around. – Lee Taylor Jul 19 '14 at 21:53
  • 2
    [This question](http://stackoverflow.com/questions/1181700/set-cursor-position-on-contenteditable-div?rq=1) may be of use for you, regarding setting the caret position. – royhowie Jul 19 '14 at 21:54
  • I've answered a very similar question recently: http://stackoverflow.com/a/24687874/96100 – Tim Down Jul 20 '14 at 16:33

1 Answers1

6

$('#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);
        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="writer" contenteditable style="width:300px;height:100px;border:1px solid #ccc;">/input some words here!/</div>
Community
  • 1
  • 1
yao lu
  • 81
  • 4