Tag-like autocompletion and caret/cursor movement in contenteditable elements

前端 未结 3 1645
旧时难觅i
旧时难觅i 2020-12-03 06:06

I\'m working on a jQuery plugin that will allow you to do @username style tags, like Facebook does in their status update input box.

My problem is, that

3条回答
  •  一向
    一向 (楼主)
    2020-12-03 06:26

    I got interested in this, so I've written the starting point for a full solution. The following uses my Rangy library with its selection save/restore module to save and restore the selection and normalize cross browser issues. It surrounds all matching text (@whatever in this case) with a link element and positions the selection where it had been previously. This is triggered after there has been no keyboard activity for one second. It should be quite reusable.

    function createLink(matchedTextNode) {
        var el = document.createElement("a");
        el.style.backgroundColor = "yellow";
        el.style.padding = "2px";
        el.contentEditable = false;
        var matchedName = matchedTextNode.data.slice(1); // Remove the leading @
        el.href = "http://www.example.com/?name=" + matchedName;
        matchedTextNode.data = matchedName;
        el.appendChild(matchedTextNode);
        return el;
    }
    
    function shouldLinkifyContents(el) {
        return el.tagName != "A";
    }
    
    function surroundInElement(el, regex, surrounderCreateFunc, shouldSurroundFunc) {
        var child = el.lastChild;
        while (child) {
            if (child.nodeType == 1 && shouldSurroundFunc(el)) {
                surroundInElement(child, regex, surrounderCreateFunc, shouldSurroundFunc);
            } else if (child.nodeType == 3) {
                surroundMatchingText(child, regex, surrounderCreateFunc);
            }
            child = child.previousSibling;
        }
    }
    
    function surroundMatchingText(textNode, regex, surrounderCreateFunc) {
        var parent = textNode.parentNode;
        var result, surroundingNode, matchedTextNode, matchLength, matchedText;
        while ( textNode && (result = regex.exec(textNode.data)) ) {
            matchedTextNode = textNode.splitText(result.index);
            matchedText = result[0];
            matchLength = matchedText.length;
            textNode = (matchedTextNode.length > matchLength) ?
                matchedTextNode.splitText(matchLength) : null;
            surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true));
            parent.insertBefore(surroundingNode, matchedTextNode);
            parent.removeChild(matchedTextNode);
        }
    }
    
    function updateLinks() {
        var el = document.getElementById("editable");
        var savedSelection = rangy.saveSelection();
        surroundInElement(el, /@\w+/, createLink, shouldLinkifyContents);
        rangy.restoreSelection(savedSelection);
    }
    
    var keyTimer = null, keyDelay = 1000;
    
    function keyUpLinkifyHandler() {
        if (keyTimer) {
            window.clearTimeout(keyTimer);
        }
        keyTimer = window.setTimeout(function() {
            updateLinks();
            keyTimer = null;
        }, keyDelay);
    }
    

    HTML:

    Some editable content for @someone or other

提交回复
热议问题