1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-04-21 22:02:05 +02:00

fix normalization of selection from node and offset, closes #440

This commit is contained in:
Ian Storm Taylor 2016-12-01 11:33:59 -08:00
parent e37d1b20f3
commit 2a355afa1e
2 changed files with 98 additions and 30 deletions

View File

@ -0,0 +1,81 @@
/**
* From a DOM selection's `node` and `offset`, normalize so that it always
* refers to a text node.
*
* @param {Element} node
* @param {Number} offset
* @return {Object}
*/
function normalizeNodeAndOffset(node, offset) {
// If it's an element node, its offset refers to the index of its children
// including comment nodes, so convert it to a text equivalent.
if (node.nodeType == 1) {
const isLast = offset == node.childNodes.length
const direction = isLast ? 'backward' : 'forward'
const index = isLast ? offset - 1 : offset
node = getNonComment(node, index, direction)
// If the node is not a text node, traverse until we have one.
while (node.nodeType != 3) {
const i = isLast ? node.childNodes.length - 1 : 0
node = getNonComment(node, i, direction)
}
// Determine the new offset inside the text node.
offset = isLast ? node.textContent.length : 0
}
// Return the node and offset.
return { node, offset }
}
/**
* Get the nearest non-comment to `index` in a `parent`, preferring `direction`.
*
* @param {Element} parent
* @param {Number} index
* @param {String} direction ('forward' or 'backward')
* @return {Element|Null}
*/
function getNonComment(parent, index, direction) {
const { childNodes } = parent
let child = childNodes[index]
let i = index
let triedForward = false
let triedBackward = false
while (child.nodeType == 8) {
if (triedForward && triedBackward) break
if (i >= childNodes.length) {
triedForward = true
i = index - 1
direction = 'backward'
continue
}
if (i < 0) {
triedBackward = true
i = index + 1
direction = 'forward'
continue
}
child = childNodes[i]
if (direction == 'forward') i++
if (direction == 'backward') i--
}
return child || null
}
/**
* Export.
*
* @type {Function}
*/
export default normalizeNodeAndOffset

View File

@ -1,4 +1,6 @@
import normalizeNodeAndOffset from './normalize-node-and-offset'
/**
* Offset key parser regex.
*
@ -46,44 +48,30 @@ function findBounds(index, ranges) {
}
/**
* From a `element`, find the closest parent's offset key.
* From a DOM node, find the closest parent's offset key.
*
* @param {Element} element
* @param {Number} offset
* @param {Element} rawNode
* @param {Number} rawOffset
* @return {Object}
*/
function findKey(element, offset) {
if (element.nodeType == 3) element = element.parentNode
function findKey(rawNode, rawOffset) {
let { node, offset } = normalizeNodeAndOffset(rawNode, rawOffset)
const parent = element.closest(SELECTOR)
const children = element.querySelectorAll(SELECTOR)
// Find the closest parent with an offset key attribute.
const closest = node.parentNode.closest(SELECTOR)
let offsetKey
// Get the key from a parent if one exists.
if (parent) {
offsetKey = parent.getAttribute(ATTRIBUTE)
// Get the key from the closest matching node if one exists.
if (closest) {
offsetKey = closest.getAttribute(ATTRIBUTE)
}
// COMPAT: In Firefox, and potentially other browsers, when performing a
// "select all" action, a parent element is selected instead of the text. In
// this case, we need to select the proper inner text nodes. (2016/07/26)
else if (children.length) {
let child = children[0]
if (offset != 0) {
child = children[children.length - 1]
offset = child.textContent.length
}
offsetKey = child.getAttribute(ATTRIBUTE)
}
// Otherwise, for void node scenarios, a cousin element will be selected, and
// Otherwise, for void node scenarios, a cousin node will be selected, and
// we need to select the first text node cousin we can find.
else {
while (element = element.parentNode) {
const cousin = element.querySelector(SELECTOR)
while (node = node.parentNode) {
const cousin = node.querySelector(SELECTOR)
if (!cousin) continue
offsetKey = cousin.getAttribute(ATTRIBUTE)
offset = cousin.textContent.length
@ -93,12 +81,11 @@ function findKey(element, offset) {
// If we still didn't find an offset key, error. This is a bug.
if (!offsetKey) {
throw new Error(`Unable to find offset key for ${element} with offset "${offset}".`)
throw new Error(`Unable to find offset key for ${node} with offset "${offset}".`)
}
// Parse the offset key.
// Return the parsed the offset key.
const parsed = parse(offsetKey)
return {
key: parsed.key,
index: parsed.index,