allow getting the DocumentOffset for any node+offset, not just focusNode

we need this to get both offsets of the selection boundaries

getSelectionOffsetAndText offers the extra flexibility,
getCaretOffsetAndText keeps the old api for focusNode/focusOffset

Also did some renaming here now that it's not just for the caret anymore
This commit is contained in:
Bruno Windels 2019-09-03 15:58:50 +02:00
parent 648ae37ff4
commit eb87301855

View file

@ -16,6 +16,7 @@ limitations under the License.
*/ */
import {CARET_NODE_CHAR, isCaretNode} from "./render"; import {CARET_NODE_CHAR, isCaretNode} from "./render";
import DocumentOffset from "./offset";
export function walkDOMDepthFirst(rootNode, enterNodeCallback, leaveNodeCallback) { export function walkDOMDepthFirst(rootNode, enterNodeCallback, leaveNodeCallback) {
let node = rootNode.firstChild; let node = rootNode.firstChild;
@ -40,26 +41,30 @@ export function walkDOMDepthFirst(rootNode, enterNodeCallback, leaveNodeCallback
} }
export function getCaretOffsetAndText(editor, sel) { export function getCaretOffsetAndText(editor, sel) {
let {focusNode, focusOffset} = sel; const {offset, text} = getSelectionOffsetAndText(editor, sel.focusNode, sel.focusOffset);
// sometimes focusNode is an element, and then focusOffset means return {caret: offset, text};
}
export function getSelectionOffsetAndText(editor, selectionNode, selectionOffset) {
// sometimes selectionNode is an element, and then selectionOffset means
// the index of a child element ... - 1 🤷 // the index of a child element ... - 1 🤷
if (focusNode.nodeType === Node.ELEMENT_NODE && focusOffset !== 0) { if (selectionNode.nodeType === Node.ELEMENT_NODE && selectionOffset !== 0) {
focusNode = focusNode.childNodes[focusOffset - 1]; selectionNode = selectionNode.childNodes[selectionOffset - 1];
focusOffset = focusNode.textContent.length; selectionOffset = selectionNode.textContent.length;
} }
const {text, focusNodeOffset} = getTextAndFocusNodeOffset(editor, focusNode, focusOffset); const {text, offsetToNode} = getTextAndOffsetToNode(editor, selectionNode);
const caret = getCaret(focusNode, focusNodeOffset, focusOffset); const offset = getCaret(selectionNode, offsetToNode, selectionOffset);
return {caret, text}; return {offset, text};
} }
// gets the caret position details, ignoring and adjusting to // gets the caret position details, ignoring and adjusting to
// the ZWS if you're typing in a caret node // the ZWS if you're typing in a caret node
function getCaret(focusNode, focusNodeOffset, focusOffset) { function getCaret(node, offsetToNode, offsetWithinNode) {
let atNodeEnd = focusOffset === focusNode.textContent.length; let atNodeEnd = offsetWithinNode === node.textContent.length;
if (focusNode.nodeType === Node.TEXT_NODE && isCaretNode(focusNode.parentElement)) { if (node.nodeType === Node.TEXT_NODE && isCaretNode(node.parentElement)) {
const zwsIdx = focusNode.nodeValue.indexOf(CARET_NODE_CHAR); const zwsIdx = node.nodeValue.indexOf(CARET_NODE_CHAR);
if (zwsIdx !== -1 && zwsIdx < focusOffset) { if (zwsIdx !== -1 && zwsIdx < offsetWithinNode) {
focusOffset -= 1; offsetWithinNode -= 1;
} }
// if typing in a caret node, you're either typing before or after the ZWS. // if typing in a caret node, you're either typing before or after the ZWS.
// In both cases, you should be considered at node end because the ZWS is // In both cases, you should be considered at node end because the ZWS is
@ -67,20 +72,20 @@ function getCaret(focusNode, focusNodeOffset, focusOffset) {
// that caret node will be removed. // that caret node will be removed.
atNodeEnd = true; atNodeEnd = true;
} }
return {offset: focusNodeOffset + focusOffset, atNodeEnd}; return new DocumentOffset(offsetToNode + offsetWithinNode, atNodeEnd);
} }
// gets the text of the editor as a string, // gets the text of the editor as a string,
// and the offset in characters where the focusNode starts in that string // and the offset in characters where the selectionNode starts in that string
// all ZWS from caret nodes are filtered out // all ZWS from caret nodes are filtered out
function getTextAndFocusNodeOffset(editor, focusNode, focusOffset) { function getTextAndOffsetToNode(editor, selectionNode) {
let focusNodeOffset = 0; let offsetToNode = 0;
let foundCaret = false; let foundCaret = false;
let text = ""; let text = "";
function enterNodeCallback(node) { function enterNodeCallback(node) {
if (!foundCaret) { if (!foundCaret) {
if (node === focusNode) { if (node === selectionNode) {
foundCaret = true; foundCaret = true;
} }
} }
@ -90,12 +95,12 @@ function getTextAndFocusNodeOffset(editor, focusNode, focusOffset) {
// are not the last element in the DIV. // are not the last element in the DIV.
if (node.tagName === "BR" && node.nextSibling) { if (node.tagName === "BR" && node.nextSibling) {
text += "\n"; text += "\n";
focusNodeOffset += 1; offsetToNode += 1;
} }
const nodeText = node.nodeType === Node.TEXT_NODE && getTextNodeValue(node); const nodeText = node.nodeType === Node.TEXT_NODE && getTextNodeValue(node);
if (nodeText) { if (nodeText) {
if (!foundCaret) { if (!foundCaret) {
focusNodeOffset += nodeText.length; offsetToNode += nodeText.length;
} }
text += nodeText; text += nodeText;
} }
@ -110,14 +115,14 @@ function getTextAndFocusNodeOffset(editor, focusNode, focusOffset) {
if (node.tagName === "DIV" && node.nextSibling && node.nextSibling.tagName === "DIV") { if (node.tagName === "DIV" && node.nextSibling && node.nextSibling.tagName === "DIV") {
text += "\n"; text += "\n";
if (!foundCaret) { if (!foundCaret) {
focusNodeOffset += 1; offsetToNode += 1;
} }
} }
} }
walkDOMDepthFirst(editor, enterNodeCallback, leaveNodeCallback); walkDOMDepthFirst(editor, enterNodeCallback, leaveNodeCallback);
return {text, focusNodeOffset}; return {text, offsetToNode};
} }
// get text value of text node, ignoring ZWS if it's a caret node // get text value of text node, ignoring ZWS if it's a caret node