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:
parent
0caa49fb10
commit
f6c1e8de28
@ -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'
|
||||
|
||||
/**
|
||||
|
@ -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'
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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'
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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
18
lib/utils/uid.js
Normal 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
|
Loading…
x
Reference in New Issue
Block a user