use caret nodes in caret positioning code, to move caret out of pills

pull/21833/head
Bruno Windels 2019-06-19 17:36:17 +02:00
parent 607fc328ed
commit a229641985
1 changed files with 86 additions and 32 deletions

View File

@ -15,50 +15,104 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {needsCaretNodeBefore, needsCaretNodeAfter} from "./render";
export function setCaretPosition(editor, model, caretPosition) {
const sel = document.getSelection();
sel.removeAllRanges();
const range = document.createRange();
const {offset, lineIndex, nodeIndex} = getLineAndNodePosition(model, caretPosition);
const lineNode = editor.childNodes[lineIndex];
let focusNode;
// empty line with just a <br>
if (nodeIndex === -1) {
focusNode = lineNode;
} else {
focusNode = lineNode.childNodes[nodeIndex];
// make sure we have a text node
if (focusNode.nodeType === Node.ELEMENT_NODE && focusNode.firstChild) {
focusNode = focusNode.firstChild;
}
}
range.setStart(focusNode, offset);
range.collapse(true);
sel.addRange(range);
}
function getLineAndNodePosition(model, caretPosition) {
const {parts} = model;
const {index} = caretPosition;
const partIndex = caretPosition.index;
const lineResult = findNodeInLineForPart(parts, partIndex);
const {lineIndex} = lineResult;
let {nodeIndex} = lineResult;
let {offset} = caretPosition;
// we're at an empty line between a newline part
// and another newline part or end/start of parts.
// set offset to 0 so it gets set to the <br> inside the line container
if (nodeIndex === -1) {
offset = 0;
} else {
// move caret out of uneditable part (into caret node, or empty line br) if needed
({nodeIndex, offset} = moveOutOfUneditablePart(parts, partIndex, nodeIndex, offset));
}
return {lineIndex, nodeIndex, offset};
}
function findNodeInLineForPart(parts, partIndex) {
let lineIndex = 0;
let nodeIndex = -1;
for (let i = 0; i <= index; ++i) {
let prevPart = null;
// go through to parts up till (and including) the index
// to find newline parts
for (let i = 0; i <= partIndex; ++i) {
const part = parts[i];
if (part && part.type === "newline") {
if (i < index) {
lineIndex += 1;
nodeIndex = -1;
} else {
// if index points at a newline part,
// put the caret at the end of the previous part
// so it stays on the same line
const prevPart = parts[i - 1];
offset = prevPart ? prevPart.text.length : 0;
if (part.type === "newline") {
lineIndex += 1;
nodeIndex = -1;
prevPart = null;
} else {
nodeIndex += 1;
if (needsCaretNodeBefore(part, prevPart)) {
nodeIndex += 1;
}
// only jump over caret node if we're not at our destination node already,
// as we'll assume in moveOutOfUneditablePart that nodeIndex
// refers to the node corresponding to the part,
// and not an adjacent caret node
if (i < partIndex) {
const nextPart = parts[i + 1];
const isLastOfLine = !nextPart || nextPart.type === "newline";
if (needsCaretNodeAfter(part, isLastOfLine)) {
nodeIndex += 1;
}
}
prevPart = part;
}
}
return {lineIndex, nodeIndex};
}
function moveOutOfUneditablePart(parts, partIndex, nodeIndex, offset) {
// move caret before or after uneditable part
const part = parts[partIndex];
if (part && !part.canEdit) {
if (offset === 0) {
nodeIndex -= 1;
const prevPart = parts[partIndex - 1];
// if the previous node is a caret node, it's empty
// so the offset can stay at 0
// only when it's not, we need to set the offset
// at the end of the node
if (!needsCaretNodeBefore(part, prevPart)) {
offset = prevPart.text.length;
}
} else {
nodeIndex += 1;
offset = 0;
}
}
let focusNode;
const lineNode = editor.childNodes[lineIndex];
if (lineNode) {
focusNode = lineNode.childNodes[nodeIndex];
if (!focusNode) {
focusNode = lineNode;
} else if (focusNode.nodeType === Node.ELEMENT_NODE) {
focusNode = focusNode.childNodes[0];
}
}
// node not found, set caret at end
if (!focusNode) {
range.selectNodeContents(editor);
range.collapse(false);
} else {
// make sure we have a text node
range.setStart(focusNode, offset);
range.collapse(true);
}
sel.addRange(range);
return {nodeIndex, offset};
}