javascript contentEditable - 包装交叉标记选择 [英] javascript contentEditable - wrap cross-tag selections

查看:105
本文介绍了javascript contentEditable - 包装交叉标记选择的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 contentEditable 并遇到此问题:我有以下js代码段

I'm experimenting a bit with contentEditable and encountered this issue: I have the following js snippet

var range = document.getSelection().getRangeAt(0);
var newNode = document.createElement("span");
newNode.className = "customStyle";
range.surroundContents(newNode);

和这个HTML片段:

<ul>
    <li>the <b>only entry</b> of the list</li>
</ul>
<p>Some text here in paragraph</p>

js代码允许用< span>包装当前选择 tag。

The js code allows to wrap the current selection with a <span> tag.

当选择包含整个HTML标签时(例如选择'唯一的条目'),它完美地工作但当然不是当选择只包括其中一个结尾时(例如,从'entry'选择'Some',两者都包括在内)。

It works perfectly when the selection includes whole HTML tags (e.g. selecting 'the only entry of') but not, of course, when the selection includes only one of their endings (e.g. selecting from 'entry' to 'Some', both included).

虽然我知道这个问题不是微不足道的,我正在寻找有关最佳方法的建议。在此先感谢!

Though I'm aware this problem is not trivial, I'm looking for suggestions about the best approach. Thanks in advance!

推荐答案

如果您只对包装文本部分感兴趣,那么基本方法是:

The basic approach if you're only interested in wrapping the text parts is:


  • 获取选择范围

  • 对于每个范围边界,如果它位于文本节点的中间,则需要在边界处将文本节点拆分为两个并更新范围的边界,以便范围保持不变(来自Rangy的示例代码

  • 获取范围内的所有文本节点(示例代码

  • 围绕< span> 元素

  • 重新选择范围

  • Get the selection range
  • For each range boundary, if it lies in the middle of a text node, you need to split the text node in two at the boundary and update the range's boundary so that the range stays in place (example code from Rangy)
  • Get all the text nodes within the range (example code)
  • Surround each text node in a <span> element
  • Re-select the range

这是我的课程申请人模块 Rangy图书馆。

This is the approach taken by the class applier module of my Rangy library.

我创建了一个示例,主要使用改编自Rangy的代码:

I've created an example, mostly using code adapted from Rangy:

function getNextNode(node) {
    var next = node.firstChild;
    if (next) {
        return next;
    }
    while (node) {
        if ( (next = node.nextSibling) ) {
            return next;
        }
        node = node.parentNode;
    }
}

function getNodesInRange(range) {
    var start = range.startContainer;
    var end = range.endContainer;
    var commonAncestor = range.commonAncestorContainer;
    var nodes = [];
    var node;

    // Walk parent nodes from start to common ancestor
    for (node = start.parentNode; node; node = node.parentNode) {
        nodes.push(node);
        if (node == commonAncestor) {
            break;
        }
    }
    nodes.reverse();

    // Walk children and siblings from start until end is found
    for (node = start; node; node = getNextNode(node)) {
        nodes.push(node);
        if (node == end) {
            break;
        }
    }

    return nodes;
}

function getNodeIndex(node) {
    var i = 0;
    while ( (node = node.previousSibling) ) {
        ++i;
    }
    return i;
}

function insertAfter(node, precedingNode) {
    var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
    if (nextNode) {
        parent.insertBefore(node, nextNode);
    } else {
        parent.appendChild(node);
    }
    return node;
}

// Note that we cannot use splitText() because it is bugridden in IE 9.
function splitDataNode(node, index) {
    var newNode = node.cloneNode(false);
    newNode.deleteData(0, index);
    node.deleteData(index, node.length - index);
    insertAfter(newNode, node);
    return newNode;
}

function isCharacterDataNode(node) {
    var t = node.nodeType;
    return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
}

function splitRangeBoundaries(range) {
    var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
    var startEndSame = (sc === ec);

    // Split the end boundary if necessary
    if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
        splitDataNode(ec, eo);
    }

    // Split the start boundary if necessary
    if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
        sc = splitDataNode(sc, so);
        if (startEndSame) {
            eo -= so;
            ec = sc;
        } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
            ++eo;
        }
        so = 0;
    }
    range.setStart(sc, so);
    range.setEnd(ec, eo);
}

function getTextNodesInRange(range) {
    var textNodes = [];
    var nodes = getNodesInRange(range);
    for (var i = 0, node, el; node = nodes[i++]; ) {
        if (node.nodeType == 3) {
            textNodes.push(node);
        }
    }
    return textNodes;
}

function surroundRangeContents(range, templateElement) {
    splitRangeBoundaries(range);
    var textNodes = getTextNodesInRange(range);
    if (textNodes.length == 0) {
        return;
    }
    for (var i = 0, node, el; node = textNodes[i++]; ) {
        if (node.nodeType == 3) {
            el = templateElement.cloneNode(false);
            node.parentNode.insertBefore(el, node);
            el.appendChild(node);
        }
    }
    range.setStart(textNodes[0], 0);
    var lastTextNode = textNodes[textNodes.length - 1];
    range.setEnd(lastTextNode, lastTextNode.length);
}

document.onmouseup = function() {
    if (window.getSelection) {
        var templateElement = document.createElement("span");
        templateElement.className = "highlight";
        var sel = window.getSelection();
        var ranges = [];
        var range;
        for (var i = 0, len = sel.rangeCount; i < len; ++i) {
            ranges.push( sel.getRangeAt(i) );
        }
        sel.removeAllRanges();

        // Surround ranges in reverse document order to prevent surrounding subsequent ranges messing with already-surrounded ones
        i = ranges.length;
        while (i--) {
            range = ranges[i];
            surroundRangeContents(range, templateElement);
            sel.addRange(range);
        }
    }
};

.highlight {
  font-weight: bold;
  color: red;
}

Select some of this text and it will be highlighted:

<ul>
    <li>the <b>only entry</b> of the list</li>
</ul>
<p>Some text here in paragraph</p>

<ul>
    <li>the <b>only entry</b> of the list</li>
</ul>
<p>Some text here in paragraph</p>

<ul>
    <li>the <b>only entry</b> of the list</li>
</ul>
<p>Some text here in paragraph</p>

这篇关于javascript contentEditable - 包装交叉标记选择的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆