diff --git a/docs/walkthroughs/using-plugins.md b/docs/walkthroughs/using-plugins.md
index 67fc7881e..5b582a5fa 100644
--- a/docs/walkthroughs/using-plugins.md
+++ b/docs/walkthroughs/using-plugins.md
@@ -91,10 +91,6 @@ Boom! Now we're getting somewhere. That code is reusable for any type of mark.
Now that we have our plugin, let's remove the hard-coded logic from our app, and replace it with our brand new `MarkHotkey` plugin instead, passing in the same options that will keep our **bold** functionality intact:
```js
-function BoldMark(props) {
- return {props.children}
-}
-
// Initialize our bold-mark-adding plugin.
const boldPlugin = MarkHotkey({
type: 'bold',
diff --git a/packages/slate-html-serializer/src/index.js b/packages/slate-html-serializer/src/index.js
index 99b5fe7e1..3794354d4 100644
--- a/packages/slate-html-serializer/src/index.js
+++ b/packages/slate-html-serializer/src/index.js
@@ -27,7 +27,7 @@ const String = new Record({
const TEXT_RULE = {
deserialize(el) {
- if (el.tagName.toLowerCase() == 'br') {
+ if (el.tagName && el.tagName.toLowerCase() === 'br') {
return {
kind: 'text',
ranges: [{ text: '\n' }],
@@ -45,7 +45,7 @@ const TEXT_RULE = {
},
serialize(obj, children) {
- if (obj.kind == 'string') {
+ if (obj.kind === 'string') {
return children
.split('\n')
.reduce((array, text, i) => {
@@ -66,7 +66,7 @@ const TEXT_RULE = {
*/
function defaultParseHtml(html) {
- if (typeof DOMParser == 'undefined') {
+ if (typeof DOMParser === 'undefined') {
throw new Error('The native `DOMParser` global which the `Html` serializer uses by default is not present in this environment. You must supply the `options.parseHtml` function instead.')
}
@@ -341,7 +341,7 @@ class Html {
*/
serializeNode = (node) => {
- if (node.kind == 'text') {
+ if (node.kind === 'text') {
const ranges = node.getRanges()
return ranges.map(this.serializeRange)
}
@@ -405,7 +405,7 @@ class Html {
*/
cruftNewline = (element) => {
- return !(element.nodeName == '#text' && element.value == '\n')
+ return !(element.nodeName === '#text' && element.value == '\n')
}
}
diff --git a/packages/slate-react/src/components/content.js b/packages/slate-react/src/components/content.js
index e3c3ae168..a172068cb 100644
--- a/packages/slate-react/src/components/content.js
+++ b/packages/slate-react/src/components/content.js
@@ -15,6 +15,7 @@ import findClosestNode from '../utils/find-closest-node'
import getCaretPosition from '../utils/get-caret-position'
import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
import getPoint from '../utils/get-point'
+import getDropPoint from '../utils/get-drop-point'
import getTransferData from '../utils/get-transfer-data'
import setTransferData from '../utils/set-transfer-data'
import scrollToSelection from '../utils/scroll-to-selection'
@@ -369,7 +370,7 @@ class Content extends React.Component {
*/
onDragEnd = (event) => {
- if (!this.isInEditor(event.target)) return
+ event.stopPropagation()
this.tmp.isDragging = false
this.tmp.isInternalDrag = null
@@ -384,7 +385,8 @@ class Content extends React.Component {
*/
onDragOver = (event) => {
- if (!this.isInEditor(event.target)) return
+ event.stopPropagation()
+
if (this.tmp.isDragging) return
this.tmp.isDragging = true
this.tmp.isInternalDrag = false
@@ -425,33 +427,21 @@ class Content extends React.Component {
*/
onDrop = (event) => {
+ event.stopPropagation()
event.preventDefault()
if (this.props.readOnly) return
- if (!this.isInEditor(event.target)) return
- const window = getWindow(event.target)
- const { state, editor } = this.props
+ const { editor, state } = this.props
const { nativeEvent } = event
- const { dataTransfer, x, y } = nativeEvent
+ const { dataTransfer } = nativeEvent
const data = getTransferData(dataTransfer)
+ const point = getDropPoint(event, state, editor)
- // Resolve the point where the drop occured.
- let range
-
- // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
- if (window.document.caretRangeFromPoint) {
- range = window.document.caretRangeFromPoint(x, y)
- } else {
- range = window.document.createRange()
- range.setStart(nativeEvent.rangeParent, nativeEvent.rangeOffset)
- }
-
- const { startContainer, startOffset } = range
- const point = getPoint(startContainer, startOffset, state, editor)
if (!point) return
- const target = Selection.create({
+ // Add drop-specific information to the data.
+ data.target = Selection.create({
anchorKey: point.key,
anchorOffset: point.offset,
focusKey: point.key,
@@ -459,9 +449,6 @@ class Content extends React.Component {
isFocused: true
})
- // Add drop-specific information to the data.
- data.target = target
-
// COMPAT: Edge throws "Permission denied" errors when
// accessing `dropEffect` or `effectAllowed` (2017/7/12)
try {
diff --git a/packages/slate-react/src/components/void.js b/packages/slate-react/src/components/void.js
index dc6e79fd4..11facdef7 100644
--- a/packages/slate-react/src/components/void.js
+++ b/packages/slate-react/src/components/void.js
@@ -41,17 +41,6 @@ class Void extends React.Component {
state: SlateTypes.state.isRequired,
}
- /**
- * State
- *
- * @type {Object}
- */
-
- state = {
- dragCounter: 0,
- editable: false,
- }
-
/**
* Debug.
*
@@ -90,44 +79,9 @@ class Void extends React.Component {
})
}
- /**
- * Increment counter, and temporarily switch node to editable to allow drop events
- * Counter required as onDragLeave fires when hovering over child elements
- *
- * @param {Event} event
- */
+ onDragOver = event => event.preventDefault()
- onDragEnter = () => {
- this.setState((prevState) => {
- const dragCounter = prevState.dragCounter + 1
- return { dragCounter, editable: undefined }
- })
- }
-
- /**
- * Decrement counter, and if counter 0, then no longer dragging over node
- * and thus switch back to non-editable
- *
- * @param {Event} event
- */
-
- onDragLeave = () => {
- this.setState((prevState) => {
- const dragCounter = prevState.dragCounter - 1
- const editable = dragCounter === 0 ? false : undefined
- return { dragCounter, editable }
- })
- }
-
- /**
- * If dropped item onto node, then reset state
- *
- * @param {Event} event
- */
-
- onDrop = () => {
- this.setState({ dragCounter: 0, editable: false })
- }
+ onDragEnter = event => event.preventDefault()
/**
* Render.
@@ -145,13 +99,13 @@ class Void extends React.Component {
return (
{this.renderSpacer()}
-
+
{children}
diff --git a/packages/slate-react/src/plugins/core.js b/packages/slate-react/src/plugins/core.js
index 1c58b3ce2..48b5397ca 100644
--- a/packages/slate-react/src/plugins/core.js
+++ b/packages/slate-react/src/plugins/core.js
@@ -163,7 +163,7 @@ function Plugin(options = {}) {
const window = getWindow(e.target)
const native = window.getSelection()
const { state } = change
- const { endBlock, endInline } = state
+ const { startKey, endKey, startText, endBlock, endInline } = state
const isVoidBlock = endBlock && endBlock.isVoid
const isVoidInline = endInline && endInline.isVoid
const isVoid = isVoidBlock || isVoidInline
@@ -187,6 +187,25 @@ function Plugin(options = {}) {
attach = contents.childNodes[contents.childNodes.length - 1].firstChild
}
+ // COMPAT: in Safari and Chrome when selecting a single marked word,
+ // marks are not preserved when copying.
+ // If the attatched is not void, and the startKey and endKey is the same,
+ // check if there is marks involved. If so, set the range start just before the
+ // startText node
+ if ((IS_CHROME || IS_SAFARI) && !isVoid && startKey === endKey) {
+ const hasMarks = startText.characters
+ .slice(state.selection.anchorOffset, state.selection.focusOffset)
+ .filter(char => char.marks.size !== 0)
+ .size !== 0
+ if (hasMarks) {
+ const r = range.cloneRange()
+ const node = findDOMNode(startText)
+ r.setStartBefore(node)
+ contents = r.cloneContents()
+ attach = contents.childNodes[contents.childNodes.length - 1].firstChild
+ }
+ }
+
// Remove any zero-width space spans from the cloned DOM so that they don't
// show up elsewhere when pasted.
const zws = [].slice.call(contents.querySelectorAll('[data-slate-zero-width]'))
diff --git a/packages/slate-react/src/utils/get-drop-point.js b/packages/slate-react/src/utils/get-drop-point.js
new file mode 100644
index 000000000..96671d927
--- /dev/null
+++ b/packages/slate-react/src/utils/get-drop-point.js
@@ -0,0 +1,89 @@
+
+import getWindow from 'get-window'
+
+import findClosestNode from './find-closest-node'
+import getPoint from './get-point'
+
+/**
+ * Get the target point for a drop event.
+ *
+ * @param {Event} event
+ * @param {State} state
+ * @param {Editor} editor
+ * @return {Object}
+ */
+
+function getDropPoint(event, state, editor) {
+ const { document } = state
+ const { nativeEvent, target } = event
+ const { x, y } = nativeEvent
+
+ // Resolve the caret position where the drop occured.
+ const window = getWindow(target)
+ let n, o
+
+ // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
+ if (window.document.caretRangeFromPoint) {
+ const range = window.document.caretRangeFromPoint(x, y)
+ n = range.startContainer
+ o = range.startOffset
+ } else {
+ const position = window.document.caretPositionFromPoint(x, y)
+ n = position.offsetNode
+ o = position.offset
+ }
+
+ const nodeEl = findClosestNode(n, '[data-key]')
+ const nodeKey = nodeEl.getAttribute('data-key')
+ const node = document.key === nodeKey ? document : document.getDescendant(nodeKey)
+
+ // If the drop DOM target is inside an inline void node use last position of
+ // the previous sibling text node or first position of the next sibling text
+ // node as Slate target.
+ if (node.isVoid && node.kind === 'inline') {
+ const rect = nodeEl.getBoundingClientRect()
+ const previous = x - rect.left < rect.left + rect.width - x
+ const text = previous ?
+ document.getPreviousSibling(nodeKey) :
+ document.getNextSibling(nodeKey)
+ const key = text.key
+ const offset = previous ? text.characters.size : 0
+
+ return { key, offset }
+ }
+
+ // If the drop DOM target is inside a block void node use last position of
+ // the previous sibling block node or first position of the next sibling block
+ // node as Slate target.
+ if (node.isVoid) {
+ const rect = nodeEl.getBoundingClientRect()
+ const previous = y - rect.top < rect.top + rect.height - y
+
+ if (previous) {
+ const block = document.getPreviousBlock(nodeKey)
+ const text = block.getLastText()
+ const { key } = text
+ const offset = text.characters.size
+ return { key, offset }
+ }
+
+ const block = document.getNextBlock(nodeKey)
+ const text = block.getLastText()
+ const { key } = text
+ const offset = 0
+
+ return { key, offset }
+ }
+
+ const point = getPoint(n, o, state, editor)
+
+ return point
+}
+
+/**
+ * Export.
+ *
+ * @type {Function}
+ */
+
+export default getDropPoint
diff --git a/packages/slate/src/models/node.js b/packages/slate/src/models/node.js
index 85052813a..03af04e61 100644
--- a/packages/slate/src/models/node.js
+++ b/packages/slate/src/models/node.js
@@ -1448,7 +1448,7 @@ class Node {
}
// PERF: exit early if both start and end have been found.
- return start != null && end != null
+ return start == null || end == null
})
if (isSelected && start == null) start = 0