1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-01-18 05:59:13 +01:00

fix fragment pasting

This commit is contained in:
Ian Storm Taylor 2016-06-27 14:08:30 -07:00
parent 0caa49fb10
commit f6c1e8de28
8 changed files with 148 additions and 35 deletions

View File

@ -14,7 +14,7 @@ import Data from './data'
import Inline from './inline'
import Node from './node'
import Text from './text'
import uid from 'uid'
import uid from '../utils/uid'
import Immutable, { Map, List, Record } from 'immutable'
/**

View File

@ -14,7 +14,7 @@ import Block from './block'
import Data from './data'
import Node from './node'
import Text from './text'
import uid from 'uid'
import uid from '../utils/uid'
import { List, Map, Record } from 'immutable'
/**

View File

@ -8,6 +8,7 @@ import Mark from './mark'
import Selection from './selection'
import Transforms from './transforms'
import Text from './text'
import uid from '../utils/uid'
import { List, Map, OrderedSet, Set } from 'immutable'
/**
@ -88,6 +89,23 @@ const Node = {
}, Block.createList())
},
/**
* Get the closest block nodes for each text node in the node.
*
* @return {List} nodes
*/
getBlocks() {
return this
.getTextNodes()
.reduce((blocks, text) => {
const block = this.getClosestBlock(text)
return blocks.includes(block)
? blocks
: blocks.push(block)
}, Block.createList())
},
/**
* Get the closest block nodes for each text node in a `range`.
*
@ -340,6 +358,8 @@ const Node = {
: node.getHighestChild(endKey)
nodes = node.getChildrenBetweenIncluding(startNode, endNode)
// Return a new document fragment.
return Document.create({ nodes })
},
@ -700,7 +720,7 @@ const Node = {
},
/**
* Insert child `nodes` after child by `key`.
* Insert child `nodes` before child by `key`.
*
* @param {String or Node} key
* @param {List} nodes
@ -710,7 +730,7 @@ const Node = {
insertChildrenBefore(key, nodes) {
key = normalizeKey(key)
const child = this.getChild(key)
const index = this.nodex.indexOf(child)
const index = this.nodes.indexOf(child)
nodes = this.nodes
.slice(0, index)
@ -736,6 +756,22 @@ const Node = {
return range.isAtStartOf(start) || range.isAtEndOf(start)
},
/**
* Map all children nodes, updating them in their parents.
*
* @param {Function} iterator
* @return {Node} node
*/
mapDescendants(iterator) {
const nodes = this.nodes.map((node, i, nodes) => {
if (node.kind != 'text') node = node.mapDescendants(iterator)
return iterator(node, i, nodes)
})
return this.merge({ nodes })
},
/**
* Normalize the node by joining any two adjacent text child nodes.
*
@ -746,13 +782,21 @@ const Node = {
let node = this
const texts = node.getTextNodes()
// If there are no text nodes, add one.
if (!texts.size) {
// If this node has no children, add a text node.
if (node.nodes.size == 0) {
const text = Text.create()
const nodes = node.nodes.push(text)
return node.merge({ nodes })
}
// Map this node's descendants, ensuring there are no duplicate keys.
const keys = []
node = node.mapDescendants((node) => {
if (keys.includes(node.key)) node = node.set('key', uid())
keys.push(node.key)
return node
})
// See if there are any adjacent text nodes.
let firstAdjacent = node.findDescendant((child) => {
if (child.kind != 'text') return
@ -773,13 +817,7 @@ const Node = {
// Then remove the second node.
parent = parent.removeDescendant(second)
// If the parent isn't this node, it needs to be updated.
if (parent != node) {
node = node.updateDescendant(parent)
} else {
node = parent
}
node = node.updateDescendant(parent)
// Recurse by normalizing again.
return node.normalize()

View File

@ -2,6 +2,7 @@
import Document from './document'
import Selection from './selection'
import Transform from './transform'
import uid from '../utils/uid'
import { Record, Stack } from 'immutable'
/**
@ -411,21 +412,63 @@ class State extends Record(DEFAULTS) {
insertFragment(fragment) {
let state = this
let { document, selection } = state
let after = selection
// If there's nothing in the fragment, do nothing.
if (!fragment.length) return state
// Lookup some nodes for determining the selection next.
const texts = fragment.getTextNodes()
const lastText = texts.last()
const lastInline = fragment.getClosestInline(lastText)
const startText = document.getDescendant(selection.startKey)
const startBlock = document.getClosestBlock(startText)
const startInline = document.getClosestInline(startText)
const nextText = document.getNextText(startText)
const nextBlock = nextText ? document.getClosestBlock(nextText) : null
const nextNextText = nextText ? document.getNextText(nextText) : null
// Insert the fragment.
document = document.insertFragmentAtRange(selection, fragment)
// Determine what the selection should be after inserting.
const texts = fragment.getTextNodes()
const first = texts.first()
const last = texts.last()
selection = first == last
? selection.moveForward(fragment.length)
: selection.moveToEndOf(last)
if (texts.size == 1) {
after = selection
.moveToStart()
.moveForward(fragment.length)
}
else if (!nextText) {
const text = document.getTextNodes().last()
after = selection
.moveToStartOf(text)
.moveForward(lastText.length)
}
else if (lastInline || startInline) {
const text = document.getPreviousText(nextText)
after = selection.moveToEndOf(text)
}
else if (nextBlock != startBlock) {
const text = document.getPreviousText(nextText)
after = selection
.moveToStartOf(text)
.moveForward(lastText.length)
}
else {
const text = nextNextText
? document.getPreviousText(nextNextText)
: document.getPreviousText(document.getTextNodes().last())
after = selection
.moveToStartOf(text)
.moveForward(lastText.length)
}
// Update the document and selection.
selection = after
state = state.merge({ document, selection })
return state
}

View File

@ -1,7 +1,7 @@
import Character from './character'
import Mark from './mark'
import uid from 'uid'
import uid from '../utils/uid'
import { List, Record } from 'immutable'
/**

View File

@ -7,6 +7,7 @@ import Inline from './inline'
import Mark from './mark'
import Selection from './selection'
import Text from './text'
import uid from '../utils/uid'
import { List, Map, Set } from 'immutable'
/**
@ -180,20 +181,25 @@ const Transforms = {
node = node.splitInlineAtRange(range)
}
// Make sure each node in the fragment has a new key.
fragment = fragment.mapDescendants(node => node.set('key', uid()))
// Insert the contents of the first block into the block at the cursor.
const { startKey, endKey } = range
let block = node.getClosestBlock(startKey)
let start = node.getDescendant(startKey)
if (!range.isAtEndOf(start)) start = node.getPreviousText(start)
const startChild = block.getHighestChild(start)
const nextChild = block.getNextSibling(startChild)
const startChild = start ? block.getHighestChild(start) : null
const nextChild = startChild
? block.getNextSibling(startChild)
: node.getClosestBlock(node.getTextNodes().first())
const blocks = fragment.getDeepestBlocks()
const firstBlock = blocks.first()
let lastBlock = blocks.last()
block = block.insertChildrenAfter(startChild, firstBlock.nodes)
block = block.insertChildrenBefore(nextChild, firstBlock.nodes)
node = node.updateDescendant(block)
// If there are no other siblings, that's it.
@ -205,11 +211,14 @@ const Transforms = {
// Then, add the inlines after the cursor from the current block to the
// start of the last block in the fragment.
lastBlock = lastBlock.concatChildren(block.getChildrenAfterIncluding(nextChild))
fragment = fragment.updateDescendant(lastBlock)
if (nextChild) {
lastBlock = lastBlock.concatChildren(block.getChildrenAfterIncluding(nextChild))
fragment = fragment.updateDescendant(lastBlock)
block = block.removeChildrenAfterIncluding(nextChild)
node = node.updateDescendant(block)
}
block = block.removeChildrenAfterIncluding(nextChild)
node = node.updateDescendant(block)
// Finally, add the fragment's children after the block.
node = node.insertChildrenAfter(block, fragment.nodes)

View File

@ -151,17 +151,22 @@ export default {
*/
onPaste(e, paste, state, editor) {
const { fragment } = editor
// Don't handle files in core.
if (paste.type == 'files') return
// If pasting html and the text matches the current fragment, use that.
if (paste.type == 'html' && paste.text == fragment.text) {
return state
.transform()
.insertFragment(fragment)
.apply()
if (paste.type == 'html') {
const { fragment } = editor
const text = fragment
.getBlocks()
.map(block => block.text)
.join('\n')
if (paste.text == text) {
return state
.transform()
.insertFragment(fragment)
.apply()
}
}
// Otherwise, just insert the plain text splitting at new lines.

18
lib/utils/uid.js Normal file
View File

@ -0,0 +1,18 @@
import generate from 'uid'
/**
* Create a unique identifier.
*
* @return {String} uid
*/
function uid() {
return generate(4)
}
/**
* Export.
*/
export default uid