Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jquery/Javascript - Syntax highlighting as user types in contentEditable region

I'm developing a contentEditable region on my website, where users will be able to type messages to each other.

<div contentEditable="true" class="smartText">User types here...</div>

The thing is, we will have smart text inside, meaning that if a user type @usersame inside this div, the @username should be highlighted in blue if the username exist and green if he doesn't exist. And of course all of this should happen as the user types...

I have no idea where to start, right now I have this:

$("body").on("keyup",".smartText",function(){
      var $this = $(this),
          value = $this.html(),
          regex = /[^>]#\S+[^ ]/gim;
      value = value.replace(regex,"<span style='color:red'>$&</span>");
      $this.html(value);
});

But the text keeps jumping (as well as the caret position) and doesn't feel like the right direction. I guess it's a little similar to JSFiddle which colors code as it finds it. I basically want the same thing as Twitter has.

Here is a JSFiddle to play around with: http://jsfiddle.net/denislexic/bhu9N/4/

Thanks in advance for your help.

like image 551
denislexic Avatar asked Oct 28 '12 07:10

denislexic


2 Answers

I liked this problem and I worked very hard to solve. I believe I have finally succeeded (with a little assistance).

= UPDATED =

Piece of Code:

[...]

// formatText
formatText: function (el) {
    var savedSel = helper.saveSelection(el);
    el.innerHTML = el.innerHTML.replace(/<span[\s\S]*?>([\s\S]*?)<\/span>/g,"$1");
    el.innerHTML = el.innerHTML.replace(/(@[^\s<\.]+)/g, helper.highlight);

    // Restore the original selection
    helper.restoreSelection(el, savedSel);
}

[...]

// point
keyup: function(e){                    

    // format if key is valid
    if(helper.keyIsAvailable(e)){
        helper.formatText($this[0]);
    }

    // delete blank html elements
    if(helper.keyIsDelete && $this.text()=="") {
        $this.html("");
    }
}

Screenshot:

sCREENSHOT 2

JSFiddle here: http://jsfiddle.net/hayatbiralem/9Z3Rg/11/

Needed External Resources:

  • http://dl.dropboxusercontent.com/u/14243582/jscalc/js/rangy-core.js
  • http://dl.dropboxusercontent.com/u/14243582/jscalc/js/rangy-selectionsaverestore.js

Helper Question (thanks): replace innerHTML in contenteditable div

Regex Test Tool (thanks): http://www.pagecolumn.com/tool/regtest.htm

like image 133
hayatbiralem Avatar answered Oct 02 '22 08:10

hayatbiralem


Keep in mind that the HTML markup typed by the user could be quite surprising, e.g: <span>@use</span><span>rname</span>, which still looks like @username to the user.

To avoid the crazy caret behavior (and some other nasty side effects) inside a contentEditable element, you should use W3C DOM API and walk the DOM tree each time there is a change in HTML (you can sniff the change by polling body.innerHTML upon a timer).

I've recently answered a similar question for CKEditor and described the algorithm of how to build a text-to-node map of the DOM, for finding a text match. The CKEditor DOM API is quite similar to the W3C one, you can adapt the same algorithm.

Once the match has been found, you should use DOM Range API to manipulate the content of the DOM nodes. E.g., to wrap a run of plain text with a styled <SPAN>:

var range = document.createRange();
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
var span = document.createElement("span");
span.style.backgroundColor = "blue"
range.surroundContents(span);

Overall, this task is quite non-trivial and certainly isn't something you can fit into a single page of JavaScript code, to be answered here.

like image 23
noseratio Avatar answered Oct 02 '22 09:10

noseratio