Jquery/Javascript - Syntax highlighting as user ty

2019-02-06 03:54发布

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.

5条回答
狗以群分
2楼-- · 2019-02-06 04:26

The method you are using seems very browser intensive and may cause some issues if someone types very quickly and it's running multiple requests before the 'String' can be verified through ajax. You might be better off if you use a library such as http://aehlke.github.io/tag-it/ - You can depict a function to change font color, etc, the same way it recommends a tag.

If i get time, i will make fiddle demo.

查看更多
放荡不羁爱自由
3楼-- · 2019-02-06 04:27

As Ezos pointed out in his answer, I would not recommend trying to do anything intensive (such as making Ajax requests to check if a username exists or not) each time the user releases a key. You might have a bad time. With that said, I would recommend waiting a set amount of time after the user has stopped typing to run through what they've typed and highlight words, for example:

var textarea = $(".smartText");
var highlightWords = function highlightWords() {
    var original = textarea.text();
    var replaced = original.replace(/@[a-zA-Z0-9]+/g, function (username) {
        // Magic
        return "<span class='exists'>" + username + "</span>";
    });
    textarea.html(replaced);
};
var timer;

textarea.keyup(function (e) {
    clearTimeout(timer);
    if ($(this).text()) {
        timer = setTimeout(highlightWords, 1000);
    }
});

Link to a fiddle: http://jsfiddle.net/neJLW/

I think the code above should get you started in the right direction. Like you said, the cursor will still jump around so you'll have to save it and reset it in its old position each time you edit the contents of the div. Also, you'll want to adjust the timeout according to how long you expect determining if a username exists to take. You'll need to replace // Magic with your username check and adjust the return value accordingly.

As an aside, you'll want to keep in mind the accessibility issues with wrapping certain things in spans (see this GitHub issue for Lettering.js for an example).

Edit: Also note that this is not a robust solution (it doesn't react to copy paste for example). YMMV.

查看更多
手持菜刀,她持情操
4楼-- · 2019-02-06 04:33

This seems to be somewhat a solution to your problem.

DEMO here: http://jsfiddle.net/bhu9N/5/

$(document).ready(function() {
    $("body").on("keyup", ".editable", function(e) {
        var $this = $(this);
        if(e.keyCode==32) {//space
            var words = $this.text().split(' ');
            var lastword = $.trim(words[words.length-1]);
            var reg = /^@\S+/;
            if(reg.test(lastword)) {
                //make an AJAX call for checking this word for existence
                //suppose data is returned and data==1 means green
                var data = 1;
                if(data==1) {
                    var orgtext = $this.html();
                    orgtext = orgtext.replace(lastword, '<span class="green">'+lastword+'</span>');
                    $this.html(orgtext);
                }
            }   
        }
    });

});​

Once the text is highlighted, the cursor goes to the starting of the div. So this still needs to be fixed. I will be updating the solution if I am able to find it. Meanwhile, just play around with what I have provided now and see if it helps.

查看更多
等我变得足够好
5楼-- · 2019-02-06 04:36

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:

Helper Question (thanks): replace innerHTML in contenteditable div

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

查看更多
成全新的幸福
6楼-- · 2019-02-06 04:42

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.

查看更多
登录 后发表回答