Как обернуть текст внутри нескольких узлов с помощью HTML-тега - PullRequest
0 голосов
/ 09 сентября 2018

Я работаю с contenteditable и пытаюсь создать простой редактор. К сожалению, я не могу использовать document.execCommand() и должен сам это реализовать. Что я пытаюсь сделать здесь, так это то, что если пользователь нажимает кнопку «полужирный», я хочу сделать текст жирным. Код, который я написал ниже, работает, но он работает только тогда, когда выбор находится в одном узле, а не в нескольких.

document.getElementById("bold").onclick = function() {
  var selection = document.getSelection(),
      range = selection.getRangeAt(0).cloneRange();
  range.surroundContents(document.createElement("b"));
  selection.removeAllRanges();
  selection.addRange(range);
}
<div contenteditable="true" id="div">This is the editor. If you embolden only **this**, it will work. But if you try to embolden **this <i>and this**</i>, it will not work because they are in different nodes</div>
<button id="bold">Bold</button>

Мой вопрос таков: есть ли решение, в котором я могу нажать жирным шрифтом, и это может ободрить текст, даже если они находятся в разных узлах? Если так, как я могу это сделать? Я ищу что-то простое и элегантное, но если оно должно быть сложным, я был бы признателен за некоторое объяснение кода. Большое спасибо.

Ответы [ 2 ]

0 голосов
/ 09 сентября 2018

function makeItBold() {
	const x = document.querySelectorAll(".selected");
	for (let i = 0; i < x.length; i++) {
    	x[i].style.fontWeight = "bold"
	}
}

function makeItNormal() {
	const x = document.querySelectorAll(".selected");
	for (let i = 0; i < x.length; i++) {
    	x[i].style.fontWeight = "normal"
        x[i].style.fontStyle = "normal"
	}
}

function makeItItalic() {
	const x = document.querySelectorAll(".selected");
	for (let i = 0; i < x.length; i++) {
    	x[i].style.fontStyle = "italic"
	}
}
<!DOCTYPE html>
<html>
<body>

<h4>A simple demonstration:</h4>

<button onclick="makeItBold()">Bold</button>
<button onclick="makeItNormal()">Normal</button>
<button onclick="makeItItalic()">Italic</button>

<p class="selected">Test me out</p>

</body>
</html>
0 голосов
/ 09 сентября 2018

Это не просто и не элегантно, но работает как положено, без дополнительной разметки и является лучшим, что я могу придумать.

По сути, вы должны пересечь дерево dom, пересекающееся с диапазоном выбора, и собрать поддиапазоны, состоящие только из текстового узла во время обхода.

Просмотрите Документацию по диапазону для получения информации о startContainer и endContainer. Проще говоря, они одинаковы при выборе в одном текстовом узле, и в противном случае они дают вам начальную и конечную точку вашего обхода.

Как только вы соберете эти диапазоны, вы можете обернуть их в тег по своему вкусу.

Это работает довольно хорошо, но, к сожалению, я не смог сохранить первоначальный выбор после полужирного шрифта (пробовал все с selection.setRange(..) безуспешно):

document.getElementById("bold").onclick = function() {
    var selection = document.getSelection(),
    range = selection.getRangeAt(0).cloneRange();
    // start and end are always text nodes
    var start = range.startContainer;
    var end = range.endContainer;
    var ranges = [];

    // if start === end then it's fine we have selected a portion of a text node
	while (start !== end) {

        var startText = start.nodeValue;
        var currentRange = range.cloneRange();
        // pin the range at the end of this text node
        currentRange.setEnd(start, startText.length);
        // keep the range for later
        ranges.push(currentRange);

        var sibling = start;
        do {
            if (sibling.hasChildNodes()) {
                // if it has children then it's not a text node, go deeper
                sibling = sibling.firstChild;
            } else if (sibling.nextSibling) {
                // it has a sibling, go for it
                sibling = sibling.nextSibling;
            } else {
                // we're into a corner, we have to go up one level and go for next sibling
                while (!sibling.nextSibling && sibling.parentNode) {
                    sibling = sibling.parentNode;
                }
                if (sibling) {
                    sibling = sibling.nextSibling;
                }
            }
        } while (sibling !== null && sibling.nodeValue === null);
        if (!sibling) {
            // out of nodes!
            break;
        }
        // move range start to the identified next text node (sibling)
        range.setStart(sibling, 0);
        start = range.startContainer;
	}
    // surround all collected range by the b tag
    for (var i = 0; i < ranges.length; i++) {
        var currentRange = ranges[i];
  	    currentRange.surroundContents(document.createElement("b"));
    }
    // surround the remaining range by a b tag
    range.surroundContents(document.createElement("b"));

    // unselect everything because I can't presere the original selection
    selection.removeAllRanges();

}
<div contenteditable="true" id="div">This is the editor. If you embolden only **this**, it will work. If you try <font color="red">to embolden **this <i>and this**</i>, it will work <font color="green">because</font> we are traversing the</font> nodes<table rules="all">
<tr><td>it</td><td>will<td>even</td><td>work</td></tr>
<tr><td>in</td><td>more</td><td>complicated</td><td><i>markup</i></td></tr>
</table></div>
<button id="bold">Bold</button>
...