Ответ 1
Я модифицировал вашу демоверсию, чтобы сериализовать позицию как пару контейнера/смещения, а не просто позицию. Контейнер сериализуется как простой массив индексов в коллекцию childNodes
каждого node, начиная с ссылки node (которая в этом случае является элементом contenteditable
, конечно).
Мне не совсем понятно, что вы намерены использовать для этого, но поскольку он отражает модель выбора, он, надеюсь, даст вам гораздо меньше боли.
const $el = $('ce'),
$startContainer = $('start-container'),
$startOffset = $('start-offset'),
$endContainer = $('end-container'),
$endOffset = $('end-offset');
function pathFromNode(node, reference) {
function traverse(node, acc) {
if (node === reference) {
return acc;
} else {
const parent = node.parentNode;
const index = [...parent.childNodes].indexOf(node);
return traverse(parent, [index, ...acc]);
}
}
return traverse(node, []);
}
function nodeFromPath(path, reference) {
if (path.length === 0) {
return reference;
} else {
const [index, ...rest] = path;
const next = reference.childNodes[index];
return nodeFromPath(rest, next);
}
}
function getCaret(el) {
const range = document.getSelection().getRangeAt(0);
return {
start: {
container: pathFromNode(range.startContainer, el),
offset: range.startOffset
},
end: {
container: pathFromNode(range.endContainer, el),
offset: range.endOffset
}
};
}
function setCaret(el, start, end) {
const range = document.createRange();
range.setStart(nodeFromPath(start.container, el), start.offset);
range.setEnd(nodeFromPath(end.container, el), end.offset);
sel = document.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function update() {
const pos = getCaret($el);
$startContainer.value = JSON.stringify(pos.start.container);
$startOffset.value = pos.start.offset;
$endContainer.value = JSON.stringify(pos.end.container);
$endOffset.value = pos.end.offset;
}
$el.addEventListener('keyup', update);
$el.addEventListener('click', update);
$('set').addEventListener('click', () => {
const start = {
container: JSON.parse($startContainer.value),
offset: $startOffset.value
};
const end = {
container: JSON.parse($endContainer.value),
offset: $endOffset.value
};
setCaret($el, start, end);
});
function $(sel) {
return document.getElementById(sel);
}
input {
width: 40px;
}
[contenteditable] {
white-space: pre;
}
(updates on click & keyup)<br/>
<label>Start: <input id="start-container" type="text"/><input id="start-offset" type="number"/></label><br/>
<label>End: <input id="end-container" type="text"/><input id="end-offset" type="number"/></label><br/>
<button id="set">Set</button>
<p></p>
<!-- inline BR behave differently from <br> on their own separate line
<div id="ce" contenteditable>012345<br><br><br>9012345</div>
-->
<!-- get/set caret needs to work with these examples as well
* <br> at beginning
<div id="ce" contenteditable><br>12345<br><br><br>9012345</div>
* <br> wrapped in a <div>
-->
<div id="ce" contenteditable><div><br></div>12345<div><br></div><div><br></div><div><br></div>9012345</div>