mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-18 21:21:21 +02:00
add Node.createRange
for range resolution (#2025)
#### Is this adding or improving a _feature_ or fixing a _bug_? Improvement. #### What's the new behavior? Adds a `Node.createRange` method for more easily creating ranges that are normalized to the current document. As well as a `Node.resolveRange` lower-level method for just doing the normalization on an existing range. Later we can deprecate `Range.normalize` since it should be on the node instead. #### Have you checked that...? <!-- Please run through this checklist for your pull request: --> * [x] The new code matches the existing patterns and styles. * [x] The tests pass with `yarn test`. * [x] The linter passes with `yarn lint`. (Fix errors with `yarn prettier`.) * [x] The relevant examples still work. (Run examples with `yarn watch`.) #### Does this fix any issues or need any specific reviewers? Fixes: #2011
This commit is contained in:
@@ -344,10 +344,11 @@ function AfterPlugin() {
|
|||||||
// Determine what the selection should be after changing the text.
|
// Determine what the selection should be after changing the text.
|
||||||
const delta = textContent.length - text.length
|
const delta = textContent.length - text.length
|
||||||
const corrected = selection.collapseToEnd().move(delta)
|
const corrected = selection.collapseToEnd().move(delta)
|
||||||
const entire = selection
|
let entire = selection
|
||||||
.moveAnchorTo(point.key, start)
|
.moveAnchorTo(point.key, start)
|
||||||
.moveFocusTo(point.key, end)
|
.moveFocusTo(point.key, end)
|
||||||
.normalize(document)
|
|
||||||
|
entire = document.normalizeRange(entire)
|
||||||
|
|
||||||
// Change the current value to have the leaf's text replaced.
|
// Change the current value to have the leaf's text replaced.
|
||||||
change.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
|
change.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
|
||||||
@@ -583,7 +584,7 @@ function AfterPlugin() {
|
|||||||
if (next) range = range.moveFocusTo(next.key, 0)
|
if (next) range = range.moveFocusTo(next.key, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
range = range.normalize(document)
|
range = document.normalizeRange(range)
|
||||||
change.select(range)
|
change.select(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import getWindow from 'get-window'
|
import getWindow from 'get-window'
|
||||||
import isBackward from 'selection-is-backward'
|
import isBackward from 'selection-is-backward'
|
||||||
import { Range } from 'slate'
|
|
||||||
import { IS_IE, IS_EDGE } from 'slate-dev-environment'
|
import { IS_IE, IS_EDGE } from 'slate-dev-environment'
|
||||||
|
|
||||||
import findPoint from './find-point'
|
import findPoint from './find-point'
|
||||||
@@ -60,7 +59,8 @@ function findRange(native, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let range = Range.create({
|
const { document } = value
|
||||||
|
const range = document.createRange({
|
||||||
anchorKey: anchor.key,
|
anchorKey: anchor.key,
|
||||||
anchorOffset: anchor.offset,
|
anchorOffset: anchor.offset,
|
||||||
focusKey: focus.key,
|
focusKey: focus.key,
|
||||||
@@ -69,7 +69,6 @@ function findRange(native, value) {
|
|||||||
isFocused: true,
|
isFocused: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
range = range.normalize(value.document)
|
|
||||||
return range
|
return range
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import getWindow from 'get-window'
|
import getWindow from 'get-window'
|
||||||
|
|
||||||
import { Range } from 'slate'
|
|
||||||
import findNode from './find-node'
|
import findNode from './find-node'
|
||||||
import findRange from './find-range'
|
import findRange from './find-range'
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ function getEventRange(event, value) {
|
|||||||
: y - rect.top < rect.top + rect.height - y
|
: y - rect.top < rect.top + rect.height - y
|
||||||
|
|
||||||
const text = node.getFirstText()
|
const text = node.getFirstText()
|
||||||
const range = Range.create()
|
const range = document.createRange()
|
||||||
|
|
||||||
if (isPrevious) {
|
if (isPrevious) {
|
||||||
const previousText = document.getPreviousText(text.key)
|
const previousText = document.getPreviousText(text.key)
|
||||||
|
@@ -3,7 +3,6 @@ import Inline from '../models/inline'
|
|||||||
import Mark from '../models/mark'
|
import Mark from '../models/mark'
|
||||||
import Node from '../models/node'
|
import Node from '../models/node'
|
||||||
import PathUtils from '../utils/path-utils'
|
import PathUtils from '../utils/path-utils'
|
||||||
import Range from '../models/range'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes.
|
* Changes.
|
||||||
@@ -343,12 +342,12 @@ Changes.replaceTextByPath = (
|
|||||||
length = node.text.length - offset
|
length = node.text.length - offset
|
||||||
}
|
}
|
||||||
|
|
||||||
const range = Range.create({
|
const range = document.createRange({
|
||||||
anchorPath: path,
|
anchorPath: path,
|
||||||
focusPath: path,
|
focusPath: path,
|
||||||
anchorOffset: offset,
|
anchorOffset: offset,
|
||||||
focusOffset: offset + length,
|
focusOffset: offset + length,
|
||||||
}).normalize(document)
|
})
|
||||||
|
|
||||||
let activeMarks = document.getActiveMarksAtRange(range)
|
let activeMarks = document.getActiveMarksAtRange(range)
|
||||||
|
|
||||||
@@ -358,7 +357,8 @@ Changes.replaceTextByPath = (
|
|||||||
// Do not use mark at index when marks and activeMarks are both empty
|
// Do not use mark at index when marks and activeMarks are both empty
|
||||||
marks = activeMarks ? activeMarks : []
|
marks = activeMarks ? activeMarks : []
|
||||||
} else if (activeMarks) {
|
} else if (activeMarks) {
|
||||||
// Do not use `has` because we may want to reset marks like font-size with an updated data;
|
// Do not use `has` because we may want to reset marks like font-size with
|
||||||
|
// an updated data;
|
||||||
activeMarks = activeMarks.filter(
|
activeMarks = activeMarks.filter(
|
||||||
activeMark => !marks.find(m => activeMark.type === m.type)
|
activeMark => !marks.find(m => activeMark.type === m.type)
|
||||||
)
|
)
|
||||||
|
@@ -25,7 +25,7 @@ Changes.select = (change, properties, options = {}) => {
|
|||||||
const { value } = change
|
const { value } = change
|
||||||
const { document, selection } = value
|
const { document, selection } = value
|
||||||
const props = {}
|
const props = {}
|
||||||
const next = selection.merge(properties).normalize(document)
|
const next = document.createRange(selection.merge(properties))
|
||||||
|
|
||||||
// Re-compute the properties, to ensure that we get their normalized values.
|
// Re-compute the properties, to ensure that we get their normalized values.
|
||||||
properties = pick(next, Object.keys(properties))
|
properties = pick(next, Object.keys(properties))
|
||||||
@@ -325,7 +325,7 @@ PROXY_TRANSFORMS.forEach(method => {
|
|||||||
const { value } = change
|
const { value } = change
|
||||||
const { document, selection } = value
|
const { document, selection } = value
|
||||||
let next = selection[method](...args)
|
let next = selection[method](...args)
|
||||||
if (normalize) next = next.normalize(document)
|
if (normalize) next = document.createRange(next)
|
||||||
change.select(next)
|
change.select(next)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -1,13 +1,9 @@
|
|||||||
/**
|
|
||||||
* Dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import isPlainObject from 'is-plain-object'
|
import isPlainObject from 'is-plain-object'
|
||||||
import logger from 'slate-dev-logger'
|
import logger from 'slate-dev-logger'
|
||||||
import { List, Map, Record } from 'immutable'
|
import { List, Map, Record } from 'immutable'
|
||||||
|
|
||||||
import MODEL_TYPES, { isType } from '../constants/model-types'
|
|
||||||
import KeyUtils from '../utils/key-utils'
|
import KeyUtils from '../utils/key-utils'
|
||||||
|
import MODEL_TYPES, { isType } from '../constants/model-types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default properties.
|
* Default properties.
|
||||||
|
@@ -205,6 +205,19 @@ class Node {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new range with `properties` relative to the node.
|
||||||
|
*
|
||||||
|
* @param {Object|Range} properties
|
||||||
|
* @return {Range}
|
||||||
|
*/
|
||||||
|
|
||||||
|
createRange(properties) {
|
||||||
|
properties = Range.createProperties(properties)
|
||||||
|
const range = this.resolveRange(properties)
|
||||||
|
return range
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively filter all descendant nodes with `iterator`.
|
* Recursively filter all descendant nodes with `iterator`.
|
||||||
*
|
*
|
||||||
@@ -275,7 +288,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getActiveMarksAtRange(range) {
|
getActiveMarksAtRange(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
if (range.isUnset) return Set()
|
if (range.isUnset) return Set()
|
||||||
|
|
||||||
if (range.isCollapsed) {
|
if (range.isCollapsed) {
|
||||||
@@ -397,7 +410,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getBlocksAtRangeAsArray(range) {
|
getBlocksAtRangeAsArray(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
if (range.isUnset) return []
|
if (range.isUnset) return []
|
||||||
|
|
||||||
const { startKey, endKey } = range
|
const { startKey, endKey } = range
|
||||||
@@ -465,7 +478,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getCharactersAtRange(range) {
|
getCharactersAtRange(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
if (range.isUnset) return List()
|
if (range.isUnset) return List()
|
||||||
const { startKey, endKey, startOffset, endOffset } = range
|
const { startKey, endKey, startOffset, endOffset } = range
|
||||||
|
|
||||||
@@ -672,7 +685,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getFragmentAtRange(range) {
|
getFragmentAtRange(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
|
|
||||||
if (range.isUnset) {
|
if (range.isUnset) {
|
||||||
return Document.create()
|
return Document.create()
|
||||||
@@ -839,7 +852,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getInlinesAtRangeAsArray(range) {
|
getInlinesAtRangeAsArray(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
if (range.isUnset) return []
|
if (range.isUnset) return []
|
||||||
|
|
||||||
const array = this.getTextsAtRangeAsArray(range)
|
const array = this.getTextsAtRangeAsArray(range)
|
||||||
@@ -892,7 +905,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getInsertMarksAtRange(range) {
|
getInsertMarksAtRange(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
if (range.isUnset) return Set()
|
if (range.isUnset) return Set()
|
||||||
|
|
||||||
if (range.isCollapsed) {
|
if (range.isCollapsed) {
|
||||||
@@ -1178,7 +1191,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getOffsetAtRange(range) {
|
getOffsetAtRange(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
|
|
||||||
if (range.isUnset) {
|
if (range.isUnset) {
|
||||||
throw new Error('The range cannot be unset to calculcate its offset.')
|
throw new Error('The range cannot be unset to calculcate its offset.')
|
||||||
@@ -1213,7 +1226,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getOrderedMarksAtRange(range) {
|
getOrderedMarksAtRange(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
if (range.isUnset) return OrderedSet()
|
if (range.isUnset) return OrderedSet()
|
||||||
|
|
||||||
if (range.isCollapsed) {
|
if (range.isCollapsed) {
|
||||||
@@ -1537,7 +1550,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getTextsAtRange(range) {
|
getTextsAtRange(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
if (range.isUnset) return List()
|
if (range.isUnset) return List()
|
||||||
const { startKey, endKey } = range
|
const { startKey, endKey } = range
|
||||||
const list = new List(
|
const list = new List(
|
||||||
@@ -1555,7 +1568,7 @@ class Node {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getTextsAtRangeAsArray(range) {
|
getTextsAtRangeAsArray(range) {
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
if (range.isUnset) return []
|
if (range.isUnset) return []
|
||||||
const { startKey, endKey } = range
|
const { startKey, endKey } = range
|
||||||
const texts = this.getTextsBetweenPositionsAsArray(startKey, endKey)
|
const texts = this.getTextsBetweenPositionsAsArray(startKey, endKey)
|
||||||
@@ -1835,6 +1848,18 @@ class Node {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the node with a `schema`.
|
||||||
|
*
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @return {Function|Void}
|
||||||
|
*/
|
||||||
|
|
||||||
|
normalize(schema) {
|
||||||
|
const normalizer = schema.normalizeNode(this)
|
||||||
|
return normalizer
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to "refind" a node by a previous `path`, falling back to looking
|
* Attempt to "refind" a node by a previous `path`, falling back to looking
|
||||||
* it up by `key` again.
|
* it up by `key` again.
|
||||||
@@ -1977,6 +2002,20 @@ class Node {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a `range`, relative to the node, ensuring that the keys and
|
||||||
|
* offsets in the range exist and that they are synced with the paths.
|
||||||
|
*
|
||||||
|
* @param {Range|Object} range
|
||||||
|
* @return {Range}
|
||||||
|
*/
|
||||||
|
|
||||||
|
resolveRange(range) {
|
||||||
|
range = Range.create(range)
|
||||||
|
range = range.normalize(this)
|
||||||
|
return range
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set `properties` on a node.
|
* Set `properties` on a node.
|
||||||
*
|
*
|
||||||
@@ -2046,17 +2085,6 @@ class Node {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize the node with a `schema`.
|
|
||||||
*
|
|
||||||
* @param {Schema} schema
|
|
||||||
* @return {Function|Void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
normalize(schema) {
|
|
||||||
return schema.normalizeNode(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the node against a `schema`.
|
* Validate the node against a `schema`.
|
||||||
*
|
*
|
||||||
@@ -2138,7 +2166,7 @@ class Node {
|
|||||||
'The `Node.isInRange` method is deprecated. Use the new `PathUtils.compare` helper instead.'
|
'The `Node.isInRange` method is deprecated. Use the new `PathUtils.compare` helper instead.'
|
||||||
)
|
)
|
||||||
|
|
||||||
range = range.normalize(this)
|
range = this.resolveRange(range)
|
||||||
|
|
||||||
const node = this
|
const node = this
|
||||||
const { startKey, endKey, isCollapsed } = range
|
const { startKey, endKey, isCollapsed } = range
|
||||||
|
@@ -119,7 +119,7 @@ class Value extends Record(DEFAULTS) {
|
|||||||
if (text) selection = selection.collapseToStartOf(text)
|
if (text) selection = selection.collapseToStartOf(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
selection = selection.normalize(document)
|
selection = document.createRange(selection)
|
||||||
|
|
||||||
let value = new Value({
|
let value = new Value({
|
||||||
data,
|
data,
|
||||||
@@ -978,8 +978,8 @@ class Value extends Record(DEFAULTS) {
|
|||||||
setSelection(properties) {
|
setSelection(properties) {
|
||||||
let value = this
|
let value = this
|
||||||
let { document, selection } = value
|
let { document, selection } = value
|
||||||
selection = selection.merge(properties)
|
const next = selection.merge(properties)
|
||||||
selection = selection.normalize(document)
|
selection = document.createRange(next)
|
||||||
value = value.set('selection', selection)
|
value = value.set('selection', selection)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -1036,14 +1036,14 @@ class Value extends Record(DEFAULTS) {
|
|||||||
if (selection) {
|
if (selection) {
|
||||||
let next = selection.isSet ? iterator(selection) : selection
|
let next = selection.isSet ? iterator(selection) : selection
|
||||||
if (!next) next = selection.deselect()
|
if (!next) next = selection.deselect()
|
||||||
if (next !== selection) next = next.normalize(document)
|
if (next !== selection) next = document.createRange(next)
|
||||||
value = value.set('selection', next)
|
value = value.set('selection', next)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decorations) {
|
if (decorations) {
|
||||||
let next = decorations.map(decoration => {
|
let next = decorations.map(decoration => {
|
||||||
let n = decoration.isSet ? iterator(decoration) : decoration
|
let n = decoration.isSet ? iterator(decoration) : decoration
|
||||||
if (n && n !== decoration) n = n.normalize(document)
|
if (n && n !== decoration) n = document.createRange(n)
|
||||||
return n
|
return n
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user