mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-12 18:24:03 +02:00
Characters to leaves (#1816)
* Preparing tests * splitNode replacement * Remove slow splice; replace mergeNode * normalize Leaves * Partially remove characters * Partially remove characters * styling * Fix bugs; almost all characters are replaced * Fixes almost existing tests; preparing adding tests for text * Remove un-necessary check * Empty leaf * fix characters in getMarks * Faster fromJSON * Some corner cases for empty text * Fix naive bug * Supporting empty text with marks * Supporting empty text with marks in hyperscript * changes tests for marks in empty text * Support splitNode marks with empty text * Add tests for splitNode->insert * Faster removeText * Add warning ; remove getMarksAtIndex cache * Remove characters in getInsertMarkAtRange * Adding tests * Change names of tests * Update marks test * Add a test confirm for invalid offsets: * Add test for get active marks between offsets * Fix document * Add testing for insert-text * Better remove text * More sensible marks in empty text * Allow marks of empty text after deleting partially marked text * Add test for removing on partially marked text * chnage test structure * Add test for removeText * Add test for removeText * Avoid conflict between empty marked text and cursor * Simple style fixes * Simple style fixes * Line break fixes * Line break fixes * Annotate the createLeaves * Line breaks in test * Line breaks fix * add add-marks test * add merge test * Fix version update * Remove empty_leaf optimization; optimize of that will be other PRs * Clean up getMarksAtPosition * Fix get-insert-marks-at-range * clean up get-marks-at-position * Fix spaces
This commit is contained in:
committed by
Ian Storm Taylor
parent
c500becf81
commit
cb3a9a5528
@@ -273,7 +273,7 @@ function createChildren(children, options = {}) {
|
||||
const firstNodeOrText = children.find(c => typeof c !== 'string')
|
||||
const firstText = Text.isText(firstNodeOrText) ? firstNodeOrText : null
|
||||
const key = options.key ? options.key : firstText ? firstText.key : undefined
|
||||
let node = Text.create({ key })
|
||||
let node = Text.create({ key, leaves: [{ text: '', marks: options.marks }] })
|
||||
|
||||
// Create a helper to update the current node while preserving any stored
|
||||
// anchor or focus information.
|
||||
@@ -290,10 +290,18 @@ function createChildren(children, options = {}) {
|
||||
// If the child is a non-text node, push the current node and the new child
|
||||
// onto the array, then creating a new node for future selection tracking.
|
||||
if (Node.isNode(child) && !Text.isText(child)) {
|
||||
if (node.text.length || node.__anchor != null || node.__focus != null)
|
||||
if (
|
||||
node.text.length ||
|
||||
node.__anchor != null ||
|
||||
node.__focus != null ||
|
||||
node.getMarksAtIndex(0).size
|
||||
) {
|
||||
array.push(node)
|
||||
}
|
||||
array.push(child)
|
||||
node = isLast ? null : Text.create()
|
||||
node = isLast
|
||||
? null
|
||||
: Text.create({ leaves: [{ text: '', marks: options.marks }] })
|
||||
length = 0
|
||||
}
|
||||
|
||||
|
42
packages/slate-hyperscript/test/default/empty-marked-text.js
Normal file
42
packages/slate-hyperscript/test/default/empty-marked-text.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
<mark type="bold" />
|
||||
</block>
|
||||
</document>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
object: 'document',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: '',
|
||||
marks: [
|
||||
{
|
||||
type: 'bold',
|
||||
object: 'mark',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
@@ -13,7 +13,7 @@ import Mark from './mark'
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
marks: new Set(),
|
||||
marks: Set(),
|
||||
text: '',
|
||||
}
|
||||
|
||||
@@ -49,6 +49,115 @@ class Leaf extends Record(DEFAULTS) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a valid List of `Leaf` from `leaves`
|
||||
*
|
||||
* @param {List<Leaf>} leaves
|
||||
* @return {List<Leaf>}
|
||||
*/
|
||||
|
||||
static createLeaves(leaves) {
|
||||
if (leaves.size <= 1) return leaves
|
||||
|
||||
let invalid = false
|
||||
|
||||
// TODO: we can make this faster with [List] and then flatten
|
||||
const result = List().withMutations(cache => {
|
||||
// Search from the leaves left end to find invalid node;
|
||||
leaves.findLast((leaf, index) => {
|
||||
const firstLeaf = cache.first()
|
||||
|
||||
// If the first leaf of cache exist, check whether the first leaf is connectable with the current leaf
|
||||
if (firstLeaf) {
|
||||
// If marks equals, then the two leaves can be connected
|
||||
if (firstLeaf.marks.equals(leaf.marks)) {
|
||||
invalid = true
|
||||
cache.set(0, firstLeaf.set('text', `${leaf.text}${firstLeaf.text}`))
|
||||
return
|
||||
}
|
||||
|
||||
// If the cached leaf is empty, drop the empty leaf with the upcoming leaf
|
||||
if (firstLeaf.text === '') {
|
||||
invalid = true
|
||||
cache.set(0, leaf)
|
||||
return
|
||||
}
|
||||
|
||||
// If the current leaf is empty, drop the leaf
|
||||
if (leaf.text === '') {
|
||||
invalid = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cache.unshift(leaf)
|
||||
})
|
||||
})
|
||||
|
||||
if (!invalid) return leaves
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a list of leaves to two lists; if the leaves are valid leaves, the returned leaves are also valid
|
||||
* Corner Cases:
|
||||
* 1. if offset is smaller than 0, then return [List(), leaves]
|
||||
* 2. if offset is bigger than the text length, then return [leaves, List()]
|
||||
*
|
||||
* @param {List<Leaf> leaves
|
||||
* @return {Array<List<Leaf>>}
|
||||
*/
|
||||
|
||||
static splitLeaves(leaves, offset) {
|
||||
if (offset < 0) return [List(), leaves]
|
||||
|
||||
if (leaves.size === 0) {
|
||||
return [List(), List()]
|
||||
}
|
||||
|
||||
let endOffset = 0
|
||||
let index = -1
|
||||
let left, right
|
||||
|
||||
leaves.find(leaf => {
|
||||
index++
|
||||
const startOffset = endOffset
|
||||
const { text } = leaf
|
||||
endOffset += text.length
|
||||
|
||||
if (endOffset < offset) return false
|
||||
if (startOffset > offset) return false
|
||||
|
||||
const length = offset - startOffset
|
||||
left = leaf.set('text', text.slice(0, length))
|
||||
right = leaf.set('text', text.slice(length))
|
||||
return true
|
||||
})
|
||||
|
||||
if (!left) return [leaves, List()]
|
||||
|
||||
if (left.text === '') {
|
||||
if (index === 0) {
|
||||
return [List.of(left), leaves]
|
||||
}
|
||||
|
||||
return [leaves.take(index), leaves.skip(index)]
|
||||
}
|
||||
|
||||
if (right.text === '') {
|
||||
if (index === leaves.size - 1) {
|
||||
return [leaves, List.of(right)]
|
||||
}
|
||||
|
||||
return [leaves.take(index + 1), leaves.skip(index + 1)]
|
||||
}
|
||||
|
||||
return [
|
||||
leaves.take(index).push(left),
|
||||
leaves.skip(index + 1).unshift(right),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Leaf` list from `attrs`.
|
||||
*
|
||||
@@ -79,7 +188,7 @@ class Leaf extends Record(DEFAULTS) {
|
||||
|
||||
const leaf = new Leaf({
|
||||
text,
|
||||
marks: new Set(marks.map(Mark.fromJSON)),
|
||||
marks: Set(marks.map(Mark.fromJSON)),
|
||||
})
|
||||
|
||||
return leaf
|
||||
@@ -136,6 +245,10 @@ class Leaf extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
getCharacters() {
|
||||
logger.deprecate(
|
||||
'slate@0.34.0',
|
||||
'The `characters` property of Slate objects is deprecated'
|
||||
)
|
||||
const { marks } = this
|
||||
const characters = Character.createList(
|
||||
this.text.split('').map(char => {
|
||||
@@ -149,6 +262,48 @@ class Leaf extends Record(DEFAULTS) {
|
||||
return characters
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a `mark` at leaf, replace with newMark
|
||||
*
|
||||
* @param {Mark} mark
|
||||
* @param {Mark} newMark
|
||||
* @returns {Leaf}
|
||||
*/
|
||||
|
||||
updateMark(mark, newMark) {
|
||||
const { marks } = this
|
||||
if (newMark.equals(mark)) return this
|
||||
if (!marks.has(mark)) return this
|
||||
const newMarks = marks.withMutations(collection => {
|
||||
collection.remove(mark).add(newMark)
|
||||
})
|
||||
return this.set('marks', newMarks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a `set` of marks at `index` and `length`.
|
||||
*
|
||||
* @param {Set<Mark>} set
|
||||
* @returns {Text}
|
||||
*/
|
||||
|
||||
addMarks(set) {
|
||||
const { marks } = this
|
||||
return this.set('marks', marks.union(set))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a `mark` at `index` and `length`.
|
||||
*
|
||||
* @param {Mark} mark
|
||||
* @returns {Text}
|
||||
*/
|
||||
|
||||
removeMark(mark) {
|
||||
const { marks } = this
|
||||
return this.set('marks', marks.remove(mark))
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JSON representation of the leaf.
|
||||
*
|
||||
|
@@ -63,7 +63,7 @@ class Mark extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
if (elements == null) {
|
||||
return new Set()
|
||||
return Set()
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
|
@@ -1036,11 +1036,9 @@ class Node {
|
||||
return this.getMarksAtPosition(range.startKey, range.startOffset)
|
||||
}
|
||||
|
||||
const text = this.getDescendant(range.startKey)
|
||||
const char = text.characters.get(range.startOffset)
|
||||
if (!char) return Set()
|
||||
|
||||
return char.marks
|
||||
const { startKey, startOffset } = range
|
||||
const text = this.getDescendant(startKey)
|
||||
return text.getMarksAtIndex(startOffset + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1164,26 +1162,28 @@ class Node {
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {number} offset
|
||||
* @return {OrderedSet}
|
||||
* @return {Set}
|
||||
*/
|
||||
|
||||
getMarksAtPosition(key, offset) {
|
||||
if (offset == 0) {
|
||||
const previous = this.getPreviousText(key)
|
||||
if (!previous || previous.text.length == 0) return OrderedSet()
|
||||
if (this.getClosestBlock(key) !== this.getClosestBlock(previous.key)) {
|
||||
return OrderedSet()
|
||||
}
|
||||
const text = this.getDescendant(key)
|
||||
const currentMarks = text.getMarksAtIndex(offset)
|
||||
if (offset !== 0) return currentMarks
|
||||
const closestBlock = this.getClosestBlock(key)
|
||||
|
||||
const char = previous.characters.last()
|
||||
if (!char) return OrderedSet()
|
||||
return new OrderedSet(char.marks)
|
||||
if (closestBlock.text === '') {
|
||||
// insert mark for empty block; the empty block are often created by split node or add marks in a range including empty blocks
|
||||
return currentMarks
|
||||
}
|
||||
|
||||
const text = this.getDescendant(key)
|
||||
const char = text.characters.get(offset - 1)
|
||||
if (!char) return OrderedSet()
|
||||
return new OrderedSet(char.marks)
|
||||
const previous = this.getPreviousText(key)
|
||||
if (!previous) return Set()
|
||||
|
||||
if (closestBlock.hasDescendant(previous.key)) {
|
||||
return previous.getMarksAtIndex(previous.text.length)
|
||||
}
|
||||
|
||||
return currentMarks
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1857,10 +1857,9 @@ class Node {
|
||||
)
|
||||
}
|
||||
|
||||
// If the nodes are text nodes, concatenate their characters together.
|
||||
// If the nodes are text nodes, concatenate their leaves together
|
||||
if (one.object == 'text') {
|
||||
const characters = one.characters.concat(two.characters)
|
||||
one = one.set('characters', characters)
|
||||
one = one.mergeText(two)
|
||||
} else {
|
||||
// Otherwise, concatenate their child nodes together.
|
||||
const nodes = one.nodes.concat(two.nodes)
|
||||
@@ -1942,7 +1941,7 @@ class Node {
|
||||
throw new Error(`Could not find a descendant node with key "${key}".`)
|
||||
|
||||
const index = parent.nodes.findIndex(n => n.key === key)
|
||||
const nodes = parent.nodes.splice(index, 1)
|
||||
const nodes = parent.nodes.delete(index)
|
||||
|
||||
parent = parent.set('nodes', nodes)
|
||||
node = node.updateNode(parent)
|
||||
@@ -1957,7 +1956,7 @@ class Node {
|
||||
*/
|
||||
|
||||
removeNode(index) {
|
||||
const nodes = this.nodes.splice(index, 1)
|
||||
const nodes = this.nodes.delete(index)
|
||||
return this.set('nodes', nodes)
|
||||
}
|
||||
|
||||
@@ -1978,10 +1977,7 @@ class Node {
|
||||
// If the child is a text node, the `position` refers to the text offset at
|
||||
// which to split it.
|
||||
if (child.object == 'text') {
|
||||
const befores = child.characters.take(position)
|
||||
const afters = child.characters.skip(position)
|
||||
one = child.set('characters', befores)
|
||||
two = child.set('characters', afters).regenerateKey()
|
||||
;[one, two] = child.splitText(position)
|
||||
} else {
|
||||
// Otherwise, if the child is not a text node, the `position` refers to the
|
||||
// index at which to split its children.
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import logger from 'slate-dev-logger'
|
||||
import { List, OrderedSet, Record, Set, is } from 'immutable'
|
||||
import { List, OrderedSet, Record, Set } from 'immutable'
|
||||
|
||||
import Character from './character'
|
||||
import Mark from './mark'
|
||||
import Leaf from './leaf'
|
||||
import MODEL_TYPES, { isType } from '../constants/model-types'
|
||||
import generateKey from '../utils/generate-key'
|
||||
@@ -16,7 +14,7 @@ import memoize from '../utils/memoize'
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
characters: new List(),
|
||||
leaves: List(),
|
||||
key: undefined,
|
||||
}
|
||||
|
||||
@@ -87,14 +85,19 @@ class Text extends Record(DEFAULTS) {
|
||||
return object
|
||||
}
|
||||
|
||||
const { leaves = [], key = generateKey() } = object
|
||||
const { key = generateKey() } = object
|
||||
let { leaves = List() } = object
|
||||
|
||||
const characters = leaves
|
||||
.map(Leaf.fromJSON)
|
||||
.reduce((l, r) => l.concat(r.getCharacters()), new List())
|
||||
if (Array.isArray(leaves)) {
|
||||
leaves = List(leaves.map(x => Leaf.create(x)))
|
||||
} else if (List.isList(leaves)) {
|
||||
leaves = leaves.map(x => Leaf.create(x))
|
||||
} else {
|
||||
throw new Error('leaves must be either Array or Immutable.List')
|
||||
}
|
||||
|
||||
const node = new Text({
|
||||
characters,
|
||||
leaves: Leaf.createLeaves(leaves),
|
||||
key,
|
||||
})
|
||||
|
||||
@@ -162,7 +165,61 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
get text() {
|
||||
return this.characters.reduce((string, char) => string + char.text, '')
|
||||
return this.getString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the concatenated text of the node, cached for text getter
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
getString() {
|
||||
return this.leaves.reduce((string, leaf) => string + leaf.text, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the concatenated characters of the node;
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
get characters() {
|
||||
return this.leaves.flatMap(x => x.getCharacters())
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the 'first' leaf at offset; By 'first' the alorighthm prefers `endOffset === offset` than `startOffset === offset`
|
||||
* Corner Cases:
|
||||
* 1. if offset is negative, return the first leaf;
|
||||
* 2. if offset is larger than text length, the leaf is null, startOffset, endOffset and index is of the last leaf
|
||||
*
|
||||
* @param {number}
|
||||
* @returns {Object}
|
||||
* @property {number} startOffset
|
||||
* @property {number} endOffset
|
||||
* @property {number} index
|
||||
* @property {Leaf} leaf
|
||||
*/
|
||||
|
||||
searchLeafAtOffset(offset) {
|
||||
let endOffset = 0
|
||||
let startOffset = 0
|
||||
let index = -1
|
||||
|
||||
const leaf = this.leaves.find(l => {
|
||||
index++
|
||||
startOffset = endOffset
|
||||
endOffset = startOffset + l.text.length
|
||||
return endOffset >= offset
|
||||
})
|
||||
|
||||
return {
|
||||
leaf,
|
||||
endOffset,
|
||||
index,
|
||||
startOffset,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,12 +232,14 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
addMark(index, length, mark) {
|
||||
const marks = new Set([mark])
|
||||
const marks = Set.of(mark)
|
||||
return this.addMarks(index, length, marks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a `set` of marks at `index` and `length`.
|
||||
* Corner Cases:
|
||||
* 1. If empty text, and if length === 0 and index === 0
|
||||
*
|
||||
* @param {Number} index
|
||||
* @param {Number} length
|
||||
@@ -189,42 +248,30 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
addMarks(index, length, set) {
|
||||
const characters = this.characters.map((char, i) => {
|
||||
if (i < index) return char
|
||||
if (i >= index + length) return char
|
||||
let { marks } = char
|
||||
marks = marks.union(set)
|
||||
char = char.set('marks', marks)
|
||||
return char
|
||||
})
|
||||
if (this.text === '' && length === 0 && index === 0) {
|
||||
const { leaves } = this
|
||||
const first = leaves.first()
|
||||
|
||||
return this.set('characters', characters)
|
||||
}
|
||||
if (!first) {
|
||||
return this.set(
|
||||
'leaves',
|
||||
List.of(Leaf.fromJSON({ text: '', marks: set }))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a set of decorated characters with `decorations`.
|
||||
*
|
||||
* @param {List<Decoration>} decorations
|
||||
* @return {List<Character>}
|
||||
*/
|
||||
const newFirst = first.addMarks(set)
|
||||
if (newFirst === first) return this
|
||||
return this.set('leaves', List.of(newFirst))
|
||||
}
|
||||
|
||||
getDecoratedCharacters(decorations) {
|
||||
let node = this
|
||||
const { key, characters } = node
|
||||
if (this.text === '') return this
|
||||
if (length === 0) return this
|
||||
if (index >= this.text.length) return this
|
||||
|
||||
// PERF: Exit early if there are no characters to be decorated.
|
||||
if (characters.size == 0) return characters
|
||||
|
||||
decorations.forEach(range => {
|
||||
const { startKey, endKey, startOffset, endOffset, marks } = range
|
||||
const hasStart = startKey == key
|
||||
const hasEnd = endKey == key
|
||||
const index = hasStart ? startOffset : 0
|
||||
const length = hasEnd ? endOffset - index : characters.size
|
||||
node = node.addMarks(index, length, marks)
|
||||
})
|
||||
|
||||
return node.characters
|
||||
const [before, bundle] = Leaf.splitLeaves(this.leaves, index)
|
||||
const [middle, after] = Leaf.splitLeaves(bundle, length)
|
||||
const leaves = before.concat(middle.map(x => x.addMarks(set)), after)
|
||||
return this.setLeaves(leaves)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,82 +286,85 @@ class Text extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the leaves for a list of `characters`.
|
||||
* Derive the leaves for a list of `decorations`.
|
||||
*
|
||||
* @param {Array|Void} decorations (optional)
|
||||
* @return {List<Leaf>}
|
||||
*/
|
||||
|
||||
getLeaves(decorations = []) {
|
||||
const characters = this.getDecoratedCharacters(decorations)
|
||||
let leaves = []
|
||||
let { leaves } = this
|
||||
if (leaves.size === 0) return List.of(Leaf.create({}))
|
||||
if (!decorations || decorations.length === 0) return leaves
|
||||
if (this.text.length === 0) return leaves
|
||||
const { key } = this
|
||||
|
||||
// PERF: cache previous values for faster lookup.
|
||||
let prevChar
|
||||
let prevLeaf
|
||||
decorations.forEach(range => {
|
||||
const { startKey, endKey, startOffset, endOffset, marks } = range
|
||||
const hasStart = startKey == key
|
||||
const hasEnd = endKey == key
|
||||
|
||||
// If there are no characters, return one empty range.
|
||||
if (characters.size == 0) {
|
||||
leaves.push({})
|
||||
} else {
|
||||
// Otherwise, loop the characters and build the leaves...
|
||||
characters.forEach((char, i) => {
|
||||
const { marks, text } = char
|
||||
if (hasStart && hasEnd) {
|
||||
const index = hasStart ? startOffset : 0
|
||||
const length = hasEnd ? endOffset - index : this.text.length - index
|
||||
|
||||
// The first one can always just be created.
|
||||
if (i == 0) {
|
||||
prevChar = char
|
||||
prevLeaf = { text, marks }
|
||||
leaves.push(prevLeaf)
|
||||
if (length < 1) return
|
||||
if (index >= this.text.length) return
|
||||
|
||||
if (index !== 0 || length < this.text.length) {
|
||||
const [before, bundle] = Leaf.splitLeaves(this.leaves, index)
|
||||
const [middle, after] = Leaf.splitLeaves(bundle, length)
|
||||
leaves = before.concat(middle.map(x => x.addMarks(marks)), after)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, compare the current and previous marks.
|
||||
const prevMarks = prevChar.marks
|
||||
const isSame = is(marks, prevMarks)
|
||||
leaves = leaves.map(x => x.addMarks(marks))
|
||||
})
|
||||
|
||||
// If the marks are the same, add the text to the previous range.
|
||||
if (isSame) {
|
||||
prevChar = char
|
||||
prevLeaf.text += text
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, create a new range.
|
||||
prevChar = char
|
||||
prevLeaf = { text, marks }
|
||||
leaves.push(prevLeaf)
|
||||
}, [])
|
||||
}
|
||||
|
||||
// PERF: convert the leaves to immutable objects after iterating.
|
||||
leaves = new List(leaves.map(object => new Leaf(object)))
|
||||
|
||||
// Return the leaves.
|
||||
return leaves
|
||||
if (leaves === this.leaves) return leaves
|
||||
return Leaf.createLeaves(leaves)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the active marks on between two offsets
|
||||
* Corner Cases:
|
||||
* 1. if startOffset is equal or bigger than endOffset, then return Set();
|
||||
* 2. If no text is selected between start and end, then return Set()
|
||||
*
|
||||
* @return {Set<Mark>}
|
||||
*/
|
||||
|
||||
getActiveMarksBetweenOffsets(startOffset, endOffset) {
|
||||
if (startOffset === 0 && endOffset === this.characters.size) {
|
||||
if (startOffset <= 0 && endOffset >= this.text.length) {
|
||||
return this.getActiveMarks()
|
||||
}
|
||||
const startCharacter = this.characters.get(startOffset)
|
||||
if (!startCharacter) return Set()
|
||||
const result = startCharacter.marks
|
||||
if (result.size === 0) return result
|
||||
return result.withMutations(x => {
|
||||
for (let index = startOffset + 1; index < endOffset; index++) {
|
||||
const c = this.characters.get(index)
|
||||
x.intersect(c.marks)
|
||||
if (x.size === 0) return
|
||||
|
||||
if (startOffset >= endOffset) return Set()
|
||||
// For empty text in a paragraph, use getActiveMarks;
|
||||
if (this.text === '') return this.getActiveMarks()
|
||||
|
||||
let result = null
|
||||
let leafEnd = 0
|
||||
|
||||
this.leaves.forEach(leaf => {
|
||||
const leafStart = leafEnd
|
||||
leafEnd = leafStart + leaf.text.length
|
||||
|
||||
if (leafEnd <= startOffset) return
|
||||
if (leafStart >= endOffset) return false
|
||||
|
||||
if (!result) {
|
||||
result = leaf.marks
|
||||
return
|
||||
}
|
||||
|
||||
result = result.intersect(leaf.marks)
|
||||
if (result && result.size === 0) return false
|
||||
return false
|
||||
})
|
||||
|
||||
return result || Set()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,12 +374,13 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
getActiveMarks() {
|
||||
if (this.characters.size === 0) return Set()
|
||||
const result = this.characters.first().marks
|
||||
if (this.leaves.size === 0) return Set()
|
||||
|
||||
const result = this.leaves.first().marks
|
||||
if (result.size === 0) return result
|
||||
|
||||
return result.withMutations(x => {
|
||||
this.characters.forEach(c => {
|
||||
this.leaves.forEach(c => {
|
||||
x.intersect(c.marks)
|
||||
if (x.size === 0) return false
|
||||
})
|
||||
@@ -338,20 +389,41 @@ class Text extends Record(DEFAULTS) {
|
||||
|
||||
/**
|
||||
* Get all of the marks on between two offsets
|
||||
* Corner Cases:
|
||||
* 1. if startOffset is equal or bigger than endOffset, then return Set();
|
||||
* 2. If no text is selected between start and end, then return Set()
|
||||
*
|
||||
* @return {OrderedSet<Mark>}
|
||||
*/
|
||||
|
||||
getMarksBetweenOffsets(startOffset, endOffset) {
|
||||
if (startOffset === 0 && endOffset === this.characters.size) {
|
||||
if (startOffset <= 0 && endOffset >= this.text.length) {
|
||||
return this.getMarks()
|
||||
}
|
||||
return new OrderedSet().withMutations(result => {
|
||||
for (let index = startOffset; index < endOffset; index++) {
|
||||
const c = this.characters.get(index)
|
||||
result.union(c.marks)
|
||||
|
||||
if (startOffset >= endOffset) return Set()
|
||||
// For empty text in a paragraph, use getActiveMarks;
|
||||
if (this.text === '') return this.getActiveMarks()
|
||||
|
||||
let result = null
|
||||
let leafEnd = 0
|
||||
|
||||
this.leaves.forEach(leaf => {
|
||||
const leafStart = leafEnd
|
||||
leafEnd = leafStart + leaf.text.length
|
||||
|
||||
if (leafEnd <= startOffset) return
|
||||
if (leafStart >= endOffset) return false
|
||||
|
||||
if (!result) {
|
||||
result = leaf.marks
|
||||
return
|
||||
}
|
||||
|
||||
result = result.union(leaf.marks)
|
||||
})
|
||||
|
||||
return result || Set()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,34 +444,35 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
getMarksAsArray() {
|
||||
if (this.characters.size === 0) return []
|
||||
const first = this.characters.first().marks
|
||||
let previousMark = first
|
||||
if (this.leaves.size === 0) return []
|
||||
const first = this.leaves.first().marks
|
||||
if (this.leaves.size === 1) return first.toArray()
|
||||
|
||||
const result = []
|
||||
this.characters.forEach(c => {
|
||||
// If the character marks is the same with the
|
||||
// previous characters, we do not need to
|
||||
// add the marks twice
|
||||
if (c.marks === previousMark) return true
|
||||
previousMark = c.marks
|
||||
result.push(previousMark.toArray())
|
||||
|
||||
this.leaves.forEach(leaf => {
|
||||
result.push(leaf.marks.toArray())
|
||||
})
|
||||
|
||||
return Array.prototype.concat.apply(first.toArray(), result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marks on the text at `index`.
|
||||
* Corner Cases:
|
||||
* 1. if no text is before the index, and index !== 0, then return Set()
|
||||
* 2. (for insert after split node or mark at range) if index === 0, and text === '', then return the leaf.marks
|
||||
* 3. if index === 0, text !== '', return Set()
|
||||
*
|
||||
*
|
||||
* @param {Number} index
|
||||
* @return {Set<Mark>}
|
||||
*/
|
||||
|
||||
getMarksAtIndex(index) {
|
||||
if (index == 0) return Mark.createSet()
|
||||
const { characters } = this
|
||||
const char = characters.get(index - 1)
|
||||
if (!char) return Mark.createSet()
|
||||
return char.marks
|
||||
const { leaf } = this.searchLeafAtOffset(index)
|
||||
if (!leaf) return Set()
|
||||
return leaf.marks
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -427,24 +500,42 @@ class Text extends Record(DEFAULTS) {
|
||||
/**
|
||||
* Insert `text` at `index`.
|
||||
*
|
||||
* @param {Numbder} index
|
||||
* @param {Numbder} offset
|
||||
* @param {String} text
|
||||
* @param {String} marks (optional)
|
||||
* @param {Set} marks (optional)
|
||||
* @return {Text}
|
||||
*/
|
||||
|
||||
insertText(index, text, marks) {
|
||||
let { characters } = this
|
||||
const chars = Character.createList(
|
||||
text.split('').map(char => ({ text: char, marks }))
|
||||
insertText(offset, text, marks) {
|
||||
if (this.text === '') {
|
||||
return this.set('leaves', List.of(Leaf.create({ text, marks })))
|
||||
}
|
||||
|
||||
if (text.length === 0) return this
|
||||
if (!marks) marks = Set()
|
||||
|
||||
const { startOffset, leaf, index } = this.searchLeafAtOffset(offset)
|
||||
const delta = offset - startOffset
|
||||
const beforeText = leaf.text.slice(0, delta)
|
||||
const afterText = leaf.text.slice(delta)
|
||||
const { leaves } = this
|
||||
|
||||
if (leaf.marks.equals(marks)) {
|
||||
return this.set(
|
||||
'leaves',
|
||||
leaves.set(index, leaf.set('text', beforeText + text + afterText))
|
||||
)
|
||||
}
|
||||
|
||||
const nextLeaves = leaves.splice(
|
||||
index,
|
||||
1,
|
||||
leaf.set('text', beforeText),
|
||||
Leaf.create({ text, marks }),
|
||||
leaf.set('text', afterText)
|
||||
)
|
||||
|
||||
characters = characters
|
||||
.slice(0, index)
|
||||
.concat(chars)
|
||||
.concat(characters.slice(index))
|
||||
|
||||
return this.set('characters', characters)
|
||||
return this.setLeaves(nextLeaves)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,32 +559,74 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
removeMark(index, length, mark) {
|
||||
const characters = this.characters.map((char, i) => {
|
||||
if (i < index) return char
|
||||
if (i >= index + length) return char
|
||||
let { marks } = char
|
||||
marks = marks.remove(mark)
|
||||
char = char.set('marks', marks)
|
||||
return char
|
||||
})
|
||||
if (this.text === '' && index === 0 && length === 0) {
|
||||
const first = this.leaves.first()
|
||||
if (!first) return this
|
||||
const newFirst = first.removeMark(mark)
|
||||
if (newFirst === first) return this
|
||||
return this.set('leaves', List.of(newFirst))
|
||||
}
|
||||
|
||||
return this.set('characters', characters)
|
||||
if (length <= 0) return this
|
||||
if (index >= this.text.length) return this
|
||||
const [before, bundle] = Leaf.splitLeaves(this.leaves, index)
|
||||
const [middle, after] = Leaf.splitLeaves(bundle, length)
|
||||
const leaves = before.concat(middle.map(x => x.removeMark(mark)), after)
|
||||
return this.setLeaves(leaves)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove text from the text node at `index` for `length`.
|
||||
* Remove text from the text node at `start` for `length`.
|
||||
*
|
||||
* @param {Number} index
|
||||
* @param {Number} start
|
||||
* @param {Number} length
|
||||
* @return {Text}
|
||||
*/
|
||||
|
||||
removeText(index, length) {
|
||||
let { characters } = this
|
||||
const start = index
|
||||
const end = index + length
|
||||
characters = characters.filterNot((char, i) => start <= i && i < end)
|
||||
return this.set('characters', characters)
|
||||
removeText(start, length) {
|
||||
if (length <= 0) return this
|
||||
if (start >= this.text.length) return this
|
||||
|
||||
// PERF: For simple backspace, we can operate directly on the leaf
|
||||
if (length === 1) {
|
||||
const { leaf, index, startOffset } = this.searchLeafAtOffset(start)
|
||||
const offset = start - startOffset
|
||||
|
||||
if (leaf) {
|
||||
if (leaf.text.length === 1) {
|
||||
const leaves = this.leaves.remove(index)
|
||||
return this.setLeaves(leaves)
|
||||
}
|
||||
|
||||
const beforeText = leaf.text.slice(0, offset)
|
||||
const afterText = leaf.text.slice(offset + length)
|
||||
const text = beforeText + afterText
|
||||
|
||||
if (text.length > 0) {
|
||||
return this.set(
|
||||
'leaves',
|
||||
this.leaves.set(index, leaf.set('text', text))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [before, bundle] = Leaf.splitLeaves(this.leaves, start)
|
||||
const after = Leaf.splitLeaves(bundle, length)[1]
|
||||
const leaves = Leaf.createLeaves(before.concat(after))
|
||||
|
||||
if (leaves.size === 1) {
|
||||
const first = leaves.first()
|
||||
|
||||
if (first.text === '') {
|
||||
return this.set(
|
||||
'leaves',
|
||||
List.of(first.set('marks', this.getActiveMarks()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return this.set('leaves', leaves)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -539,18 +672,51 @@ class Text extends Record(DEFAULTS) {
|
||||
updateMark(index, length, mark, properties) {
|
||||
const newMark = mark.merge(properties)
|
||||
|
||||
const characters = this.characters.map((char, i) => {
|
||||
if (i < index) return char
|
||||
if (i >= index + length) return char
|
||||
let { marks } = char
|
||||
if (!marks.has(mark)) return char
|
||||
marks = marks.remove(mark)
|
||||
marks = marks.add(newMark)
|
||||
char = char.set('marks', marks)
|
||||
return char
|
||||
})
|
||||
if (this.text === '' && length === 0 && index === 0) {
|
||||
const { leaves } = this
|
||||
const first = leaves.first()
|
||||
if (!first) return this
|
||||
const newFirst = first.updateMark(mark, newMark)
|
||||
if (newFirst === first) return this
|
||||
return this.set('leaves', List.of(newFirst))
|
||||
}
|
||||
|
||||
return this.set('characters', characters)
|
||||
if (length <= 0) return this
|
||||
if (index >= this.text.length) return this
|
||||
|
||||
const [before, bundle] = Leaf.splitLeaves(this.leaves, index)
|
||||
const [middle, after] = Leaf.splitLeaves(bundle, length)
|
||||
|
||||
const leaves = before.concat(
|
||||
middle.map(x => x.updateMark(mark, newMark)),
|
||||
after
|
||||
)
|
||||
|
||||
return this.setLeaves(leaves)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split this text and return two different texts
|
||||
* @param {Number} position
|
||||
* @returns {Array<Text>}
|
||||
*/
|
||||
|
||||
splitText(offset) {
|
||||
const splitted = Leaf.splitLeaves(this.leaves, offset)
|
||||
const one = this.set('leaves', splitted[0])
|
||||
const two = this.set('leaves', splitted[1]).regenerateKey()
|
||||
return [one, two]
|
||||
}
|
||||
|
||||
/**
|
||||
* merge this text and another text at the end
|
||||
* @param {Text} text
|
||||
* @returns {Text}
|
||||
*/
|
||||
|
||||
mergeText(text) {
|
||||
const leaves = this.leaves.concat(text.leaves)
|
||||
return this.setLeaves(leaves)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,7 +732,7 @@ class Text extends Record(DEFAULTS) {
|
||||
|
||||
/**
|
||||
* Get the first invalid descendant
|
||||
* PREF: Do not cache this method; because it can cause cycle reference
|
||||
* PERF: Do not cache this method; because it can cause cycle reference
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @returns {Text|Null}
|
||||
@@ -575,6 +741,28 @@ class Text extends Record(DEFAULTS) {
|
||||
getFirstInvalidDescendant(schema) {
|
||||
return this.validate(schema) ? this : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Set leaves with normalized `leaves`
|
||||
*
|
||||
* @param {Schema} schema
|
||||
* @returns {Text|Null}
|
||||
*/
|
||||
|
||||
setLeaves(leaves) {
|
||||
const result = Leaf.createLeaves(leaves)
|
||||
|
||||
if (result.size === 1) {
|
||||
const first = result.first()
|
||||
if (!first.marks || first.marks.size === 0) {
|
||||
if (first.text === '') {
|
||||
return this.set('leaves', List())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.set('leaves', Leaf.createLeaves(leaves))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -588,14 +776,12 @@ Text.prototype[MODEL_TYPES.TEXT] = true
|
||||
*/
|
||||
|
||||
memoize(Text.prototype, [
|
||||
'getDecoratedCharacters',
|
||||
'getDecorations',
|
||||
'getLeaves',
|
||||
'getActiveMarks',
|
||||
'getMarks',
|
||||
'getMarksAsArray',
|
||||
'getMarksAtIndex',
|
||||
'validate',
|
||||
'getString',
|
||||
])
|
||||
|
||||
/**
|
||||
|
@@ -31,8 +31,10 @@ export const output = (
|
||||
wo<anchor />
|
||||
<b>rd</b>
|
||||
</link>
|
||||
<b />
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<b />
|
||||
<link>
|
||||
<b>an</b>
|
||||
<focus />other
|
||||
|
@@ -33,8 +33,14 @@ export const output = (
|
||||
<b>rd</b>
|
||||
</i>
|
||||
</link>
|
||||
<i>
|
||||
<b />
|
||||
</i>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<i>
|
||||
<b />
|
||||
</i>
|
||||
<link>
|
||||
<i>
|
||||
<b>an</b>
|
||||
|
@@ -0,0 +1,41 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default function(change) {
|
||||
change.insertText('is ')
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<b>
|
||||
<i>Cat</i>
|
||||
</b>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<b>
|
||||
<cursor />Cute
|
||||
</b>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<b>
|
||||
<i>Cat</i>
|
||||
</b>
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<b>
|
||||
is <cursor />Cute
|
||||
</b>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,31 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default function(change) {
|
||||
change.insertText('Cat')
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<b>
|
||||
<cursor />
|
||||
</b>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<b>
|
||||
Cat<cursor />
|
||||
</b>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -31,8 +31,10 @@ export const output = (
|
||||
wo<anchor />
|
||||
<b>rd</b>
|
||||
</link>
|
||||
<mark type="bold" />
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<mark type="bold" />
|
||||
<link>
|
||||
<b>an</b>
|
||||
<focus />other
|
||||
|
@@ -50,4 +50,6 @@ describe('models', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
require('./text/')
|
||||
})
|
||||
|
@@ -0,0 +1,19 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>Cat is</b>very <b>very Cute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.removeText(6, 9)
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
<b>Cat is Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,20 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>Cat is</b>very <i>very Cute</i>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.removeText(6, 9)
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
<b>Cat is</b>
|
||||
<i> Cute</i>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,15 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>Cat</b> is <i>Cute</i>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.removeText(0, t.text.length)
|
||||
}
|
||||
|
||||
export const output = <text />[0]
|
@@ -0,0 +1,11 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = <b>Cat is Cute</b>[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.removeText(0, t.text.length)
|
||||
}
|
||||
|
||||
export const output = <b />[0]
|
@@ -0,0 +1,15 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<b>
|
||||
Cat is <i>Cute</i>
|
||||
</b>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.removeText(0, t.text.length)
|
||||
}
|
||||
|
||||
export const output = <b />[0]
|
@@ -0,0 +1,19 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>Catt</b> is <i>Cute</i>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.removeText(3, 1)
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
<b>Cat</b> is <i>Cute</i>
|
||||
</text>
|
||||
)[0]
|
43
packages/slate/test/models/text/index.js
Normal file
43
packages/slate/test/models/text/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import assert from 'assert'
|
||||
import fs from 'fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
describe('texts', () => {
|
||||
const dir = resolve(__dirname)
|
||||
|
||||
const categories = fs
|
||||
.readdirSync(dir)
|
||||
.filter(c => c[0] != '.' && c != 'index.js')
|
||||
|
||||
for (const category of categories) {
|
||||
describe(category, () => {
|
||||
const categoryDir = resolve(dir, category)
|
||||
const methods = fs
|
||||
.readdirSync(categoryDir)
|
||||
.filter(c => c[0] != '.' && !c.includes('.js'))
|
||||
|
||||
for (const method of methods) {
|
||||
describe(method, () => {
|
||||
const testDir = resolve(categoryDir, method)
|
||||
const tests = fs
|
||||
.readdirSync(testDir)
|
||||
.filter(t => t[0] != '.' && t.includes('.js'))
|
||||
|
||||
for (const test of tests) {
|
||||
const module = require(resolve(testDir, test))
|
||||
const { input, output, skip } = module
|
||||
const fn = module.default
|
||||
const t = skip ? it.skip : it
|
||||
|
||||
t(test.replace('.js', ''), () => {
|
||||
const actual = fn(input)
|
||||
const opts = { preserveData: true }
|
||||
const expected = output.toJSON(opts)
|
||||
assert.deepEqual(actual.toJSON(opts), expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
@@ -0,0 +1,20 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<i>Cat</i>
|
||||
<b> Cute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(3, ' is')
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
<i>Cat</i> is<b> Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,19 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
Cat<b> Cute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(3, ' is')
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
Cat is<b> Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,19 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
Cat<b> is</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(6, ' Cute')
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
Cat<b> is</b> Cute
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,17 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = <b>CatCute</b>[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(3, ' is ', Set.of(Mark.create('bold')))
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<b>
|
||||
<b>Cat is Cute</b>
|
||||
</b>
|
||||
)[0]
|
@@ -0,0 +1,17 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = <text>CatCute</text>[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(3, ' is ', Set.of(Mark.create('bold')))
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
Cat<b> is </b>Cute
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,19 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>CatCute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(3, ' is ')
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
<b>Cat</b> is <b>Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,11 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = <text>CatCute</text>[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(3, ' is ')
|
||||
}
|
||||
|
||||
export const output = <text>Cat is Cute</text>[0]
|
@@ -0,0 +1,17 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { List } from 'immutable'
|
||||
import { Mark } from '../../../../..'
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = <i />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(0, 'Cat is Cute', List.of(Mark.create({ type: 'bold' })))
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
<b>Cat is Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,11 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = <text />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(1, 'Cat is Cute')
|
||||
}
|
||||
|
||||
export const output = <text>Cat is Cute</text>[0]
|
@@ -0,0 +1,11 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = <text />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.insertText(0, 'Cat is Cute')
|
||||
}
|
||||
|
||||
export const output = <text>Cat is Cute</text>[0]
|
@@ -0,0 +1,20 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
Cat<b> is Cute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.addMark(3, 4, Mark.create('bold'))
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
Cat<b> is Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,21 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
Cat is
|
||||
<b> Cute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.addMark(3, 3, Mark.create('bold'))
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
Cat<b> is Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,25 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
Cat i
|
||||
<b>s Cute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.addMark(3, 4, Mark.create('italic'))
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
Cat<i> i</i>
|
||||
<i>
|
||||
<b>s </b>
|
||||
</i>
|
||||
<b>Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,20 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>
|
||||
Cat
|
||||
<i> is</i> Cute
|
||||
</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getActiveMarksBetweenOffsets(0, 6)
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('bold'))
|
@@ -0,0 +1,17 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>Cat</b>
|
||||
<i> is</i> Cute
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getActiveMarksBetweenOffsets(0, 6)
|
||||
}
|
||||
|
||||
export const output = Set()
|
@@ -0,0 +1,13 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = <b />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getActiveMarksBetweenOffsets(0, 0)
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('bold'))
|
@@ -0,0 +1,20 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Set } from 'immutable'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>
|
||||
<i>Cat is </i>
|
||||
<i>Cute</i>
|
||||
</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getActiveMarks()
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('italic'), Mark.create('bold'))
|
@@ -0,0 +1,20 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Set } from 'immutable'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>
|
||||
<i>Cat is </i>
|
||||
Cute
|
||||
</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getActiveMarks()
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('bold'))
|
@@ -0,0 +1,18 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Set } from 'immutable'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<i>Cat</i>
|
||||
is
|
||||
<i>Cute</i>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getActiveMarks()
|
||||
}
|
||||
|
||||
export const output = Set()
|
@@ -0,0 +1,13 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = <b />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarksAtIndex(0)
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('bold'))
|
@@ -0,0 +1,18 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>Cat</b>
|
||||
<i> is</i> Cute
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarksBetweenOffsets(0, 6)
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('bold'), Mark.create('italic'))
|
@@ -0,0 +1,30 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b x={1}>Cat</b>
|
||||
<i x={1}> is</i>
|
||||
<b x={2}>Cat</b>
|
||||
<i x={2}> is</i>
|
||||
<b x={3}>Cat</b>
|
||||
<i> is</i>
|
||||
<b>Cat</b>
|
||||
<i> is</i>
|
||||
<b>Cat</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarksBetweenOffsets(0, 12)
|
||||
}
|
||||
|
||||
export const output = Set.of(
|
||||
Mark.create({ type: 'bold', data: { x: 1 } }),
|
||||
Mark.create({ type: 'italic', data: { x: 1 } }),
|
||||
Mark.create({ type: 'bold', data: { x: 2 } }),
|
||||
Mark.create({ type: 'italic', data: { x: 2 } })
|
||||
)
|
@@ -0,0 +1,13 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = <b />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarksBetweenOffsets(0, 0)
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('bold'))
|
@@ -0,0 +1,19 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Set } from 'immutable'
|
||||
import { Mark } from '../../../../../'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<i>Cat </i>
|
||||
is
|
||||
<b> Cute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarks()
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('italic'), Mark.create('bold'))
|
@@ -0,0 +1,13 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../../'
|
||||
|
||||
export const input = <b />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarks()
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('bold'))
|
12
packages/slate/test/models/text/marks/get-marks/null-text.js
Normal file
12
packages/slate/test/models/text/marks/get-marks/null-text.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Set } from 'immutable'
|
||||
|
||||
export const input = <text />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarks()
|
||||
}
|
||||
|
||||
export const output = Set()
|
@@ -0,0 +1,19 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Set } from 'immutable'
|
||||
import h from '../../../../helpers/h'
|
||||
import { Mark } from '../../../../..'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<i>Cat</i>
|
||||
is
|
||||
<i>Cute</i>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarks()
|
||||
}
|
||||
|
||||
export const output = Set.of(Mark.create('italic'))
|
@@ -0,0 +1,12 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
import { Set } from 'immutable'
|
||||
|
||||
export const input = <text> Cat is Cute </text>[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.getMarks()
|
||||
}
|
||||
|
||||
export const output = Set()
|
@@ -0,0 +1,20 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Mark } from '../../../../..'
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>Cat is Cute</b>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.removeMark(0, 3, Mark.create('bold'))
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
Cat<b> is Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,22 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Mark } from '../../../../..'
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<b>Cat</b>
|
||||
<i> is Cute</i>
|
||||
</text>
|
||||
)[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.updateMark(0, 6, Mark.create('bold'), { data: { x: 1 } })
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
<b x={1}>Cat</b>
|
||||
<i> is Cute</i>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,17 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Mark } from '../../../../..'
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = <b>Cat is Cute</b>[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.updateMark(0, 3, Mark.create('bold'), { data: { x: 1 } })
|
||||
}
|
||||
|
||||
export const output = (
|
||||
<text>
|
||||
<b x={1}>Cat</b>
|
||||
<b> is Cute</b>
|
||||
</text>
|
||||
)[0]
|
@@ -0,0 +1,12 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Mark } from '../../../../..'
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = <b />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.updateMark(0, 1, Mark.create('bold'), { data: { x: 1 } })
|
||||
}
|
||||
|
||||
export const output = input
|
@@ -0,0 +1,12 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { Mark } from '../../../../..'
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = <b />[0]
|
||||
|
||||
export default function(t) {
|
||||
return t.updateMark(0, 0, Mark.create('bold'), { data: { x: 1 } })
|
||||
}
|
||||
|
||||
export const output = <b x={1} />[0]
|
@@ -0,0 +1,11 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = [<i>Some</i>[0], <text />[0]]
|
||||
|
||||
export default function(texts) {
|
||||
return texts[0].mergeText(texts[1])
|
||||
}
|
||||
|
||||
export const output = <i>Some</i>[0]
|
@@ -0,0 +1,11 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = [<b />[0], <text />[0]]
|
||||
|
||||
export default function(texts) {
|
||||
return texts[0].mergeText(texts[1])
|
||||
}
|
||||
|
||||
export const output = <b />[0]
|
@@ -0,0 +1,11 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../../helpers/h'
|
||||
|
||||
export const input = [<b />[0], <text>Some</text>[0]]
|
||||
|
||||
export default function(texts) {
|
||||
return texts[0].mergeText(texts[1])
|
||||
}
|
||||
|
||||
export const output = <text>Some</text>[0]
|
Reference in New Issue
Block a user