【问题标题】:jQuery: Convert text URL to link as typingjQuery:将文本 URL 转换为链接作为输入
【发布时间】:2013-01-16 04:05:20
【问题描述】:

我正在取得进展,但不太确定如何使其正常工作...

我有一个内容可编辑的 div,其功能类似于 textarea。

我还有一个正则表达式来识别正在输入的 URL 并自动链接它们。但是,当用户输入时,我无法让这项工作“实时”运行。

这是我目前所拥有的jsFiddle。我遇到的另一个问题是输入链接后光标跳转到 div 的开头(因为我要替换 div 的.html()?)

是否有创造性的解决方案可以在 div 中的单个文本字符串上使用 .replace() 而无需替换 div 的全部内容?

【问题讨论】:

    标签: jquery regex replace contenteditable


    【解决方案1】:

    首先,IE 会自动为您执行此操作。

    对于其他浏览器,我建议在用户一段时间不活动后进行更换。这是我的一个答案,说明了如何进行替换:

    https://stackoverflow.com/a/4045531/96100

    这是一个类似的,带有(错误的)链接正则表达式和讨论:

    https://stackoverflow.com/a/4026684/96100

    为了保存和恢复选择,我建议使用基于字符偏移的方法。以下代码通常存在缺点,但对于在更改格式但保持文本不变的同时保存和恢复选择的特殊情况,这是理想的。这是一个例子:

    https://stackoverflow.com/a/13950376/96100

    最后,这里有一些关于如何等待用户不活动的讨论和示例的答案:

    把它们放在一起:

    var saveSelection, restoreSelection;
    
    if (window.getSelection && document.createRange) {
        saveSelection = function(containerEl) {
            var range = window.getSelection().getRangeAt(0);
            var preSelectionRange = range.cloneRange();
            preSelectionRange.selectNodeContents(containerEl);
            preSelectionRange.setEnd(range.startContainer, range.startOffset);
            var start = preSelectionRange.toString().length;
    
            return {
                start: start,
                end: start + range.toString().length
            }
        };
    
        restoreSelection = function(containerEl, savedSel) {
            var charIndex = 0, range = document.createRange();
            range.setStart(containerEl, 0);
            range.collapse(true);
            var nodeStack = [containerEl], node, foundStart = false, stop = false;
    
            while (!stop && (node = nodeStack.pop())) {
                if (node.nodeType == 3) {
                    var nextCharIndex = charIndex + node.length;
                    if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
                        range.setStart(node, savedSel.start - charIndex);
                        foundStart = true;
                    }
                    if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
                        range.setEnd(node, savedSel.end - charIndex);
                        stop = true;
                    }
                    charIndex = nextCharIndex;
                } else {
                    var i = node.childNodes.length;
                    while (i--) {
                        nodeStack.push(node.childNodes[i]);
                    }
                }
            }
    
            var sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        }
    } else if (document.selection) {
        saveSelection = function(containerEl) {
            var selectedTextRange = document.selection.createRange();
            var preSelectionTextRange = document.body.createTextRange();
            preSelectionTextRange.moveToElementText(containerEl);
            preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
            var start = preSelectionTextRange.text.length;
    
            return {
                start: start,
                end: start + selectedTextRange.text.length
            }
        };
    
        restoreSelection = function(containerEl, savedSel) {
            var textRange = document.body.createTextRange();
            textRange.moveToElementText(containerEl);
            textRange.collapse(true);
            textRange.moveEnd("character", savedSel.end);
            textRange.moveStart("character", savedSel.start);
            textRange.select();
        };
    }
    
    function createLink(matchedTextNode) {
        var el = document.createElement("a");
        el.href = matchedTextNode.data;
        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, createLink, 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);
        }
    }
    
    var textbox = document.getElementById("textbox");
    var urlRegex = /http(s?):\/\/($|[^\s]+)/;
    
    function updateLinks() {
        var savedSelection = saveSelection(textbox);
        surroundInElement(textbox, urlRegex, createLink, shouldLinkifyContents);
        restoreSelection(textbox, savedSelection);
    }
    
    var $textbox = $(textbox);
    
    $(document).ready(function () {
        $textbox.focus();
    
        var keyTimer = null, keyDelay = 1000;
    
        $textbox.keyup(function() {
            if (keyTimer) {
                window.clearTimeout(keyTimer);
            }
            keyTimer = window.setTimeout(function() {
                updateLinks();
                keyTimer = null;
            }, keyDelay);
        });
    });
    body { font:.8rem/1.5 sans-serif;  margin:2rem; }
    #textbox {
        border:thin solid gray;
        padding:1rem;
        height:10rem;
        margin:1rem 0;
        color:black;
        font-size:1rem;
    }
    a { color:blue; background:lightblue; }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    Start typing a message with a link i.e. <code>http://example.com</code>...
    <div id="textbox" contenteditable></div>

    【讨论】:

    • 哇。杰出的!非常感谢。
    • hay @Tim Down 我尝试使用上面的代码 iam 在 firebug IndexSizeError: Index or size is negative or greater than the allowed amount at var range = window.getSelection().getRangeAt(0); 中出现错误,请您帮忙
    • @dhee:这意味着没有选择。哪个浏览器?哪个例子?
    • 我在此尝试了代码 sn-p - 但是当我在框中点击“输入”时......事情有点疯狂:) 我需要一个可以处理输入键的文本区域.在最新的 chrome 中尝试一下。
    • 在“Enter”上框变得疯狂的原因是因为这些行在多个 div 中分开。只需将 CSS 选择器从 div 更改为 #textbox 应该会有所帮助。此外,添加 'word-break: break-word' 有助于处理长单词。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-22
    • 2020-08-26
    • 1970-01-01
    • 1970-01-01
    • 2013-03-30
    • 2015-04-22
    • 2018-01-30
    相关资源
    最近更新 更多