Как обернуть с помощью HTML-тегов трансграничный диапазон выбора DOM? - PullRequest
15 голосов
/ 13 ноября 2009

Прямо сейчас я собираю текстовые выделения пользователей через s = window.getSelection() и range = s.getRangeAt(0) ( в браузере означает ). Всякий раз, когда делается выбор в пределах a <p>, я могу легко позвонить range.surroundContents(document.createElement("em")), чтобы выделить выделенный текст , обернутый с тегом <em>.

В этом примере, однако,

<p>This is the Foo paragraph.</p>
<p>This is the Bar paragraph.</p>
<p>This is the Baz paragraph.</p>

когда пользователь делает выбор текста от Foo до Baz, я не могу вызвать range.surroundContents: Firefox завершается неудачно с The boundary-points of a range does not meet specific requirements." code: "1, потому что выбор не является допустимым HTML.

В этом случае я бы хотел как-то получить следующее состояние в DOM:

<p>This is the <em>Foo paragraph.</em></p>
<p><em>This is the Bar paragraph.</em></p>
<p><em>This is the Baz</em> paragraph.</p>

Есть идеи?


К вашему сведению: я пытался использовать Range API, но я не вижу простого способа достижения этого результата. С

var r = document.createRange();
r.setStart(range.startContainer, range.startOffset);
r.setEnd(range.endContainer, range.endOffset+40);
selection.addRange(r);

Я могу со временем взломать что-либо, переместив смещения, но только для контейнеров 'start' и 'end'! (т.е. в этом случае параграф Bar, как мне обернуть его?)

Ответы [ 3 ]

9 голосов
/ 28 мая 2012

вы пробовали следующий подход (который на самом деле является описанием в спецификации W3C того, что должен делать surroundContents):

var wrappingNode = document.createElement("div");
wrappingNode.appendChild(range.extractContents());
range.insertNode(wrappingNode);
3 голосов
/ 15 ноября 2013

В настоящее время я работаю над встроенным редактором и написал функцию, которая может правильно обернуть диапазон межэлементных элементов любым элементом, как это делает execCommand.

function surroundSelection(elementType) {
    function getAllDescendants (node, callback) {

        for (var i = 0; i < node.childNodes.length; i++) {
            var child = node.childNodes[i];
            getAllDescendants(child, callback);
            callback(child);
        }

    }

    function glueSplitElements (firstEl, secondEl){

        var done = false,
            result = [];

        if(firstEl === undefined || secondEl === undefined){
            return false;
        }

        if(firstEl.nodeName === secondEl.nodeName){
            result.push([firstEl, secondEl]);

            while(!done){
                firstEl = firstEl.childNodes[firstEl.childNodes.length - 1];
                secondEl = secondEl.childNodes[0];

                if(firstEl === undefined || secondEl === undefined){
                    break;
                }

                if(firstEl.nodeName !== secondEl.nodeName){
                    done = true;
                } else {
                    result.push([firstEl, secondEl]);
                }
            }
        }

        for(var i = result.length - 1; i >= 0; i--){
            var elements = result[i];
            while(elements[1].childNodes.length > 0){
                elements[0].appendChild(elements[1].childNodes[0]);
            }
            elements[1].parentNode.removeChild(elements[1]);
        }

    }

    // abort in case the given elemenType doesn't exist.
    try {
        document.createElement(elementType);
    } catch (e){
        return false;
    }

    var selection = getSelection();

    if(selection.rangeCount > 0){
        var range = selection.getRangeAt(0),
            rangeContents = range.extractContents(),
            nodesInRange  = rangeContents.childNodes,
            nodesToWrap   = [];

        for(var i = 0; i < nodesInRange.length; i++){
            if(nodesInRange[i].nodeName.toLowerCase() === "#text"){
                nodesToWrap.push(nodesInRange[i]);
            } else {
                getAllDescendants(nodesInRange[i], function(child){
                    if(child.nodeName.toLowerCase() === "#text"){
                        nodesToWrap.push(child);
                    }
                });
            }
        };


        for(var i = 0; i < nodesToWrap.length; i++){
            var child = nodesToWrap[i],
                wrap = document.createElement(elementType);

            if(child.nodeValue.replace(/(\s|\n|\t)/g, "").length !== 0){
                child.parentNode.insertBefore(wrap, child);
                wrap.appendChild(child);
            } else {
                wrap = null;
            }
        }

        var firstChild = rangeContents.childNodes[0];
        var lastChild = rangeContents.childNodes[rangeContents.childNodes.length - 1];

        range.insertNode(rangeContents);

        glueSplitElements(firstChild.previousSibling, firstChild);
        glueSplitElements(lastChild, lastChild.nextSibling);

        rangeContents = null;
    }
};

Вот JSFiddle с некоторым сложным HTML в качестве демонстрации: http://jsfiddle.net/mjf9K/1/. Обратите внимание, что я взял это прямо из своего приложения. Я использую несколько помощников, чтобы правильно восстановить диапазон до исходного выделения и т. Д. Они не включены.

1 голос
/ 13 ноября 2009

То есть, когда вы добавляете атрибут contentEditable = true к родительскому элементу этих абзацев, выбираете любой текст, даже между абзацами, а затем делаете вызов

document.execCommand('italic', false, null);

и, наконец, при желании установите атрибут contentEditable обратно в false.

Кстати, это работает и в IE, за исключением того, что для входа в редактируемый режим я думаю, что он называется designMode или что-то еще, Google для него.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...