mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-30 02:19:52 +02:00
fix firefox support
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
|
||||
import { Editor, Mark, Raw } from '../..'
|
||||
import { Editor, Mark, Raw, Utils } from '../..'
|
||||
import React from 'react'
|
||||
import initialState from './state.json'
|
||||
import keycode from 'keycode'
|
||||
|
||||
/**
|
||||
* Node renderers.
|
||||
@@ -55,42 +56,6 @@ class RichText extends React.Component {
|
||||
state: Raw.deserialize(initialState)
|
||||
};
|
||||
|
||||
hasMark = (type) => {
|
||||
const { state } = this.state
|
||||
return state.marks.some(mark => mark.type == type)
|
||||
}
|
||||
|
||||
hasBlock = (type) => {
|
||||
const { state } = this.state
|
||||
return state.blocks.some(node => node.type == type)
|
||||
}
|
||||
|
||||
onClickMark = (e, type) => {
|
||||
e.preventDefault()
|
||||
const isActive = this.hasMark(type)
|
||||
let { state } = this.state
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
[isActive ? 'unmark' : 'mark'](type)
|
||||
.apply()
|
||||
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onClickBlock = (e, type) => {
|
||||
e.preventDefault()
|
||||
const isActive = this.hasBlock(type)
|
||||
let { state } = this.state
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.setBlock(isActive ? 'paragraph' : type)
|
||||
.apply()
|
||||
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<div>
|
||||
@@ -146,6 +111,7 @@ class RichText extends React.Component {
|
||||
renderNode={this.renderNode}
|
||||
renderMark={this.renderMark}
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -159,6 +125,16 @@ class RichText extends React.Component {
|
||||
return MARKS[mark.type]
|
||||
}
|
||||
|
||||
hasMark = (type) => {
|
||||
const { state } = this.state
|
||||
return state.marks.some(mark => mark.type == type)
|
||||
}
|
||||
|
||||
hasBlock = (type) => {
|
||||
const { state } = this.state
|
||||
return state.blocks.some(node => node.type == type)
|
||||
}
|
||||
|
||||
onChange = (state) => {
|
||||
console.groupCollapsed('Change!')
|
||||
console.log('Document:', state.document.toJS())
|
||||
@@ -168,6 +144,63 @@ class RichText extends React.Component {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown = (e, state) => {
|
||||
if (!Utils.Key.isCommand(e)) return
|
||||
const key = keycode(e.which)
|
||||
let mark
|
||||
|
||||
switch (key) {
|
||||
case 'b':
|
||||
mark = 'bold'
|
||||
break
|
||||
case 'i':
|
||||
mark = 'italic'
|
||||
break
|
||||
case 'u':
|
||||
mark = 'underlined'
|
||||
break
|
||||
case '`':
|
||||
mark = 'code'
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
[this.hasMark(mark) ? 'unmark' : 'mark'](mark)
|
||||
.apply()
|
||||
|
||||
e.preventDefault()
|
||||
return state
|
||||
}
|
||||
|
||||
onClickMark = (e, type) => {
|
||||
e.preventDefault()
|
||||
const isActive = this.hasMark(type)
|
||||
let { state } = this.state
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
[isActive ? 'unmark' : 'mark'](type)
|
||||
.apply()
|
||||
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onClickBlock = (e, type) => {
|
||||
e.preventDefault()
|
||||
const isActive = this.hasBlock(type)
|
||||
let { state } = this.state
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.setBlock(isActive ? 'paragraph' : type)
|
||||
.apply()
|
||||
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -65,7 +65,7 @@
|
||||
}
|
||||
]
|
||||
},{
|
||||
"text": ", or add a semanticlly rendered block quote in the middle of the page, like this:"
|
||||
"text": ", or add a semantically rendered block quote in the middle of the page, like this:"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -1,11 +1,18 @@
|
||||
|
||||
import Key from '../utils/key'
|
||||
import OffsetKey from '../utils/offset-key'
|
||||
import Raw from '../serializers/raw'
|
||||
import React from 'react'
|
||||
import Text from './text'
|
||||
import Void from './void'
|
||||
import keycode from 'keycode'
|
||||
import { Raw } from '..'
|
||||
import { isCommand, isWindowsCommand } from '../utils/event'
|
||||
import { IS_FIREFOX } from '../utils/environment'
|
||||
|
||||
/**
|
||||
* Noop.
|
||||
*/
|
||||
|
||||
function noop() {}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
@@ -63,20 +70,10 @@ class Content extends React.Component {
|
||||
* @param {Object} props
|
||||
*/
|
||||
|
||||
componentWillMount = () => {
|
||||
this.tmp.isRendering = true
|
||||
}
|
||||
|
||||
componentWillUpdate = (props, state) => {
|
||||
this.tmp.isRendering = true
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
setTimeout(() => {
|
||||
this.tmp.isRendering = false
|
||||
})
|
||||
}
|
||||
|
||||
componentDidUpdate = (props, state) => {
|
||||
setTimeout(() => {
|
||||
this.tmp.isRendering = false
|
||||
@@ -214,10 +211,10 @@ class Content extends React.Component {
|
||||
(key == 'enter') ||
|
||||
(key == 'backspace') ||
|
||||
(key == 'delete') ||
|
||||
(key == 'b' && isCommand(e)) ||
|
||||
(key == 'i' && isCommand(e)) ||
|
||||
(key == 'y' && isWindowsCommand(e)) ||
|
||||
(key == 'z' && isCommand(e))
|
||||
(key == 'b' && Key.isCommand(e)) ||
|
||||
(key == 'i' && Key.isCommand(e)) ||
|
||||
(key == 'y' && Key.isWindowsCommand(e)) ||
|
||||
(key == 'z' && Key.isCommand(e))
|
||||
) {
|
||||
e.preventDefault()
|
||||
}
|
||||
@@ -234,7 +231,7 @@ class Content extends React.Component {
|
||||
onPaste = (e) => {
|
||||
e.preventDefault()
|
||||
const data = e.clipboardData
|
||||
const { types } = data
|
||||
const types = Array.from(data.types)
|
||||
const paste = {}
|
||||
|
||||
// Handle files.
|
||||
@@ -308,13 +305,13 @@ class Content extends React.Component {
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.focus()
|
||||
.moveTo({
|
||||
anchorKey: anchor.key,
|
||||
anchorOffset: anchor.offset,
|
||||
focusKey: focus.key,
|
||||
focusOffset: focus.offset
|
||||
})
|
||||
.focus()
|
||||
.apply({ isNative: true })
|
||||
|
||||
this.onChange(state)
|
||||
@@ -350,6 +347,7 @@ class Content extends React.Component {
|
||||
onKeyDown={this.onKeyDown}
|
||||
onPaste={this.onPaste}
|
||||
onSelect={this.onSelect}
|
||||
onKeyUp={noop}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
@@ -27,17 +27,22 @@ class Leaf extends React.Component {
|
||||
* Should component update?
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} state
|
||||
* @return {Boolean} shouldUpdate
|
||||
*/
|
||||
|
||||
shouldComponentUpdate(props, state) {
|
||||
return (
|
||||
shouldComponentUpdate(props) {
|
||||
const { start, end, node, state } = props
|
||||
const { selection } = state
|
||||
|
||||
const should = (
|
||||
selection.hasEdgeBetween(node, start, end) ||
|
||||
props.start != this.props.start ||
|
||||
props.end != this.props.end ||
|
||||
props.text != this.props.text ||
|
||||
props.marks != this.props.marks
|
||||
)
|
||||
|
||||
return should
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -55,19 +60,19 @@ class Leaf extends React.Component {
|
||||
// If the selection is not focused we have nothing to do.
|
||||
if (!selection.isFocused) return
|
||||
|
||||
const { anchorKey, anchorOffset, focusKey, focusOffset } = selection
|
||||
const { anchorOffset, focusOffset } = selection
|
||||
const { node, start, end } = this.props
|
||||
const { key } = node
|
||||
|
||||
// If neither matches, the selection doesn't start or end here, so exit.
|
||||
const hasStart = key == anchorKey && start <= anchorOffset && anchorOffset <= end
|
||||
const hasEnd = key == focusKey && start <= focusOffset && focusOffset <= end
|
||||
const hasStart = selection.hasStartBetween(node, start, end)
|
||||
const hasEnd = selection.hasEndBetween(node, start, end)
|
||||
if (!hasStart && !hasEnd) return
|
||||
|
||||
// We have a selection to render, so prepare a few things...
|
||||
const native = window.getSelection()
|
||||
const el = ReactDOM.findDOMNode(this).firstChild
|
||||
|
||||
|
||||
// If both the start and end are here, set the selection all at once.
|
||||
if (hasStart && hasEnd) {
|
||||
native.removeAllRanges()
|
||||
|
@@ -1,6 +1,5 @@
|
||||
|
||||
import Leaf from './leaf'
|
||||
import OffsetKey from '../utils/offset-key'
|
||||
import React from 'react'
|
||||
import groupByMarks from '../utils/group-by-marks'
|
||||
import { List } from 'immutable'
|
||||
@@ -32,6 +31,7 @@ class Text extends React.Component {
|
||||
|
||||
shouldComponentUpdate(props, state) {
|
||||
return (
|
||||
props.state.selection.hasEdgeIn(props.node) ||
|
||||
props.node.decorations != this.props.node.decorations ||
|
||||
props.node.characters != this.props.node.characters
|
||||
)
|
||||
@@ -67,7 +67,7 @@ class Text extends React.Component {
|
||||
const offset = previous.size
|
||||
? previous.map(r => r.text).join('').length
|
||||
: 0
|
||||
return this.renderLeaf(range, offset)
|
||||
return this.renderLeaf(range, i, offset)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,25 +75,21 @@ class Text extends React.Component {
|
||||
* Render a single leaf node given a `range` and `offset`.
|
||||
*
|
||||
* @param {Object} range
|
||||
* @param {Number} index
|
||||
* @param {Number} offset
|
||||
* @return {Element} leaf
|
||||
*/
|
||||
|
||||
renderLeaf(range, offset) {
|
||||
renderLeaf(range, index, offset) {
|
||||
const { node, renderMark, state } = this.props
|
||||
const text = range.text
|
||||
const marks = range.marks
|
||||
const start = offset
|
||||
const end = offset + text.length
|
||||
const offsetKey = OffsetKey.stringify({
|
||||
key: node.key,
|
||||
start,
|
||||
end
|
||||
})
|
||||
|
||||
return (
|
||||
<Leaf
|
||||
key={offsetKey}
|
||||
key={`${node.key}-${index}`}
|
||||
state={state}
|
||||
node={node}
|
||||
start={start}
|
||||
|
68
lib/index.js
68
lib/index.js
@@ -3,25 +3,69 @@
|
||||
* Components.
|
||||
*/
|
||||
|
||||
export { default as Editor } from './components/editor'
|
||||
import Editor from './components/editor'
|
||||
|
||||
/**
|
||||
* Models.
|
||||
*/
|
||||
|
||||
export { default as Block } from './models/block'
|
||||
export { default as Character } from './models/character'
|
||||
export { default as Data } from './models/data'
|
||||
export { default as Document } from './models/document'
|
||||
export { default as Inline } from './models/inline'
|
||||
export { default as Mark } from './models/mark'
|
||||
export { default as Selection } from './models/selection'
|
||||
export { default as State } from './models/state'
|
||||
export { default as Text } from './models/text'
|
||||
import Block from './models/block'
|
||||
import Character from './models/character'
|
||||
import Data from './models/data'
|
||||
import Document from './models/document'
|
||||
import Inline from './models/inline'
|
||||
import Mark from './models/mark'
|
||||
import Selection from './models/selection'
|
||||
import State from './models/state'
|
||||
import Text from './models/text'
|
||||
|
||||
/**
|
||||
* Serializers.
|
||||
*/
|
||||
|
||||
export { default as Html } from './serializers/html'
|
||||
export { default as Raw } from './serializers/raw'
|
||||
import Html from './serializers/html'
|
||||
import Raw from './serializers/raw'
|
||||
|
||||
/**
|
||||
* Utils.
|
||||
*/
|
||||
|
||||
import Key from './utils/key'
|
||||
|
||||
const Utils = { Key }
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export {
|
||||
Block,
|
||||
Character,
|
||||
Data,
|
||||
Document,
|
||||
Editor,
|
||||
Html,
|
||||
Inline,
|
||||
Mark,
|
||||
Raw,
|
||||
Selection,
|
||||
State,
|
||||
Text,
|
||||
Utils
|
||||
}
|
||||
|
||||
export default {
|
||||
Block,
|
||||
Character,
|
||||
Data,
|
||||
Document,
|
||||
Editor,
|
||||
Html,
|
||||
Inline,
|
||||
Mark,
|
||||
Raw,
|
||||
Selection,
|
||||
State,
|
||||
Text,
|
||||
Utils
|
||||
}
|
||||
|
@@ -2,7 +2,26 @@
|
||||
import { Record } from 'immutable'
|
||||
|
||||
/**
|
||||
* Record.
|
||||
* Start-and-end convenience methods to auto-generate.
|
||||
*/
|
||||
|
||||
const START_END_METHODS = [
|
||||
'moveTo%'
|
||||
]
|
||||
|
||||
/**
|
||||
* Start-end-and-edge convenience methods to auto-generate.
|
||||
*/
|
||||
|
||||
const EDGE_METHODS = [
|
||||
'has%AtStartOf',
|
||||
'has%AtEndOf',
|
||||
'has%Between',
|
||||
'has%In'
|
||||
]
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
@@ -115,6 +134,114 @@ class Selection extends new Record(DEFAULTS) {
|
||||
: this.focusOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether anchor point of the selection is at the start of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasAnchorAtStartOf(node) {
|
||||
const first = node.kind == 'text' ? node : node.getTextNodes().first()
|
||||
return this.anchorKey == first.key && this.anchorOffset == 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether anchor point of the selection is at the end of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasAnchorAtEndOf(node) {
|
||||
const last = node.kind == 'text' ? node : node.getTextNodes().last()
|
||||
return this.anchorKey == last.key && this.anchorOffset == last.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the anchor edge of a selection is in a `node` and at an
|
||||
* offset between `start` and `end`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {Number} start
|
||||
* @param {Number} end
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasAnchorBetween(node, start, end) {
|
||||
return (
|
||||
this.hasAnchorIn(node) &&
|
||||
start <= this.anchorOffset &&
|
||||
this.anchorOffset <= end
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the anchor edge of a selection is in a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasAnchorIn(node) {
|
||||
const nodes = node.kind == 'text' ? [node] : node.getTextNodes()
|
||||
return nodes.some(n => n.key == this.anchorKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether focus point of the selection is at the end of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasFocusAtEndOf(node) {
|
||||
const last = node.kind == 'text' ? node : node.getTextNodes().last()
|
||||
return this.focusKey == last.key && this.focusOffset == last.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether focus point of the selection is at the start of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasFocusAtStartOf(node) {
|
||||
const first = node.kind == 'text' ? node : node.getTextNodes().first()
|
||||
return this.focusKey == first.key && this.focusOffset == 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the focus edge of a selection is in a `node` and at an
|
||||
* offset between `start` and `end`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {Number} start
|
||||
* @param {Number} end
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasFocusBetween(node, start, end) {
|
||||
return (
|
||||
this.hasFocusIn(node) &&
|
||||
start <= this.focusOffset &&
|
||||
this.focusOffset <= end
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the focus edge of a selection is in a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasFocusIn(node) {
|
||||
const nodes = node.kind == 'text' ? [node] : node.getTextNodes()
|
||||
return nodes.some(n => n.key == this.focusKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the selection is at the start of a `node`.
|
||||
*
|
||||
@@ -141,38 +268,6 @@ class Selection extends new Record(DEFAULTS) {
|
||||
return this.isCollapsed && endKey == last.key && endOffset == last.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the selection has an edge at the start of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean} hasEdgeAtStart
|
||||
*/
|
||||
|
||||
hasEdgeAtStartOf(node) {
|
||||
const { startKey, startOffset, endKey, endOffset } = this
|
||||
const first = node.kind == 'text' ? node : node.getTextNodes().first()
|
||||
return (
|
||||
(startKey == first.key && startOffset == 0) ||
|
||||
(endKey == first.key && endOffset == 0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the selection has an edge at the end of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean} hasEdgeAtEnd
|
||||
*/
|
||||
|
||||
hasEdgeAtEndOf(node) {
|
||||
const { startKey, startOffset, endKey, endOffset } = this
|
||||
const last = node.kind == 'text' ? node : node.getTextNodes().last()
|
||||
return (
|
||||
(startKey == last.key && startOffset == last.length) ||
|
||||
(endKey == last.key && endOffset == last.length)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the selection, relative to a `node`, ensuring that the anchor
|
||||
* and focus nodes of the selection always refer to leaf text nodes.
|
||||
@@ -221,36 +316,6 @@ class Selection extends new Record(DEFAULTS) {
|
||||
: anchorIndex > focusIndex
|
||||
}
|
||||
|
||||
// If the selection is expanded and has an edge on a void block, move it.
|
||||
// if (isExpanded) {
|
||||
// let anchorBlock = node.getClosestBlock(anchorNode)
|
||||
// let focusBlock = node.getClosestBlock(focusNode)
|
||||
|
||||
// if (anchorBlock.isVoid) {
|
||||
// while (anchorBanchorBlock.isVoid) {
|
||||
// anchorBlock = isBackward
|
||||
// ? node.getPreviousBlock(anchorBlock)
|
||||
// : node.getNextBlock(anchorBlock)
|
||||
// }
|
||||
|
||||
// anchorNode = isBackward
|
||||
// ? anchorBlock.getTextNodes().last()
|
||||
// : anchorBlock.getTextNodes().first()
|
||||
// anchorOffset = isBackward
|
||||
// ? anchorNode.length
|
||||
// : 0
|
||||
// }
|
||||
|
||||
// else if (focusBlock.isVoid) {
|
||||
// focusNode = isBackward
|
||||
// ? node.getNextBlock(focusBlock).getTextNodes().first()
|
||||
// : node.getPreviousBlock(focusBlock).getTextNodes().last()
|
||||
// focusOffset = isBackward
|
||||
// ? 0
|
||||
// : focusNode.length
|
||||
// }
|
||||
// }
|
||||
|
||||
// Merge in any updated properties.
|
||||
return selection.merge({
|
||||
anchorKey: anchorNode.key,
|
||||
@@ -285,17 +350,6 @@ class Selection extends new Record(DEFAULTS) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the selection to a specific anchor and focus point.
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @return {Selection} selection
|
||||
*/
|
||||
|
||||
moveTo(properties) {
|
||||
return this.merge(properties)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the focus point to the anchor point.
|
||||
*
|
||||
@@ -324,46 +378,6 @@ class Selection extends new Record(DEFAULTS) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the end point to the start point.
|
||||
*
|
||||
* @return {Selection} selection
|
||||
*/
|
||||
|
||||
moveToStart() {
|
||||
return this.isBackward
|
||||
? this.merge({
|
||||
anchorKey: this.focusKey,
|
||||
anchorOffset: this.focusOffset,
|
||||
isBackward: false
|
||||
})
|
||||
: this.merge({
|
||||
focusKey: this.anchorKey,
|
||||
focusOffset: this.anchorOffset,
|
||||
isBackward: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the start point to the end point.
|
||||
*
|
||||
* @return {Selection} selection
|
||||
*/
|
||||
|
||||
moveToEnd() {
|
||||
return this.isBackward
|
||||
? this.merge({
|
||||
focusKey: this.anchorKey,
|
||||
focusOffset: this.anchorOffset,
|
||||
isBackward: false
|
||||
})
|
||||
: this.merge({
|
||||
anchorKey: this.focusKey,
|
||||
anchorOffset: this.focusOffset,
|
||||
isBackward: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the start of a `node`.
|
||||
*
|
||||
@@ -502,6 +516,41 @@ class Selection extends new Record(DEFAULTS) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add start, end and edge convenience methods.
|
||||
*/
|
||||
|
||||
START_END_METHODS.concat(EDGE_METHODS).forEach((pattern) => {
|
||||
const [ p, s ] = pattern.split('%')
|
||||
const anchor = `${p}Anchor${s}`
|
||||
const edge = `${p}Edge${s}`
|
||||
const end = `${p}End${s}`
|
||||
const focus = `${p}Focus${s}`
|
||||
const start = `${p}Start${s}`
|
||||
|
||||
Selection.prototype[start] = function (...args) {
|
||||
return this.isBackward
|
||||
? this[focus](...args)
|
||||
: this[anchor](...args)
|
||||
}
|
||||
|
||||
Selection.prototype[end] = function (...args) {
|
||||
return this.isBackward
|
||||
? this[anchor](...args)
|
||||
: this[focus](...args)
|
||||
}
|
||||
|
||||
if (!EDGE_METHODS.includes(pattern)) return
|
||||
|
||||
Selection.prototype[edge] = function (...args) {
|
||||
return this[anchor](...args) || this[focus](...args)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Add edge methods.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
@@ -519,6 +519,31 @@ class State extends new Record(DEFAULTS) {
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the selection to a specific anchor and focus point.
|
||||
*
|
||||
* @param {Object} properties
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
moveTo(properties) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
|
||||
// Pass in properties, and force `isBackward` to be re-resolved.
|
||||
selection = selection.merge({
|
||||
anchorKey: properties.anchorKey,
|
||||
anchorOffset: properties.anchorOffset,
|
||||
focusKey: properties.focusKey,
|
||||
focusOffset: properties.focusOffset,
|
||||
isBackward: null
|
||||
})
|
||||
|
||||
selection = selection.normalize(document)
|
||||
state = state.merge({ selection })
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the selection to the start of the previous block.
|
||||
*
|
||||
|
@@ -57,7 +57,6 @@ const SELECTION_TRANSFORMS = [
|
||||
'focus',
|
||||
'moveBackward',
|
||||
'moveForward',
|
||||
'moveTo',
|
||||
'moveToAnchor',
|
||||
'moveToEnd',
|
||||
'moveToEndOf',
|
||||
@@ -78,6 +77,7 @@ const STATE_TRANSFORMS = [
|
||||
'insertFragment',
|
||||
'insertText',
|
||||
'mark',
|
||||
'moveTo',
|
||||
'moveToStartOfPreviousBlock',
|
||||
'moveToEndOfPreviousBlock',
|
||||
'moveToStartOfNextBlock',
|
||||
|
@@ -1,7 +1,7 @@
|
||||
|
||||
import Key from '../utils/key'
|
||||
import React from 'react'
|
||||
import keycode from 'keycode'
|
||||
import { isCommand, isCtrl, isWindowsCommand, isWord } from '../utils/event'
|
||||
import { IS_WINDOWS, IS_MAC } from '../utils/environment'
|
||||
|
||||
/**
|
||||
@@ -93,13 +93,13 @@ export default {
|
||||
}
|
||||
|
||||
case 'backspace': {
|
||||
return isWord(e)
|
||||
return Key.isWord(e)
|
||||
? transform.backspaceWord().apply()
|
||||
: transform.deleteBackward().apply()
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
return isWord(e)
|
||||
return Key.isWord(e)
|
||||
? transform.deleteWord().apply()
|
||||
: transform.deleteForward().apply()
|
||||
}
|
||||
@@ -137,13 +137,13 @@ export default {
|
||||
}
|
||||
|
||||
case 'y': {
|
||||
if (!isWindowsCommand(e)) return
|
||||
if (!Key.isWindowsCommand(e)) return
|
||||
return transform.redo()
|
||||
}
|
||||
|
||||
case 'z': {
|
||||
if (!isCommand(e)) return
|
||||
return IS_MAC && e.shiftKey
|
||||
if (!Key.isCommand(e)) return
|
||||
return IS_MAC && Key.isShift(e)
|
||||
? transform.redo()
|
||||
: transform.undo()
|
||||
}
|
||||
|
@@ -16,3 +16,16 @@ export const IS_MAC = process.browser && new Parser().getOS().name == 'Mac OS'
|
||||
export const IS_SAFARI = process.browser && browser.name == 'safari'
|
||||
export const IS_UBUNTU = process.browser && new Parser().getOS().name == 'Ubuntu'
|
||||
export const IS_WINDOWS = process.browser && new Parser().getOS().name.includes('Windows')
|
||||
|
||||
export default {
|
||||
IS_ANDROID,
|
||||
IS_CHROME,
|
||||
IS_EDGE,
|
||||
IS_FIREFOX,
|
||||
IS_IE,
|
||||
IS_IOS,
|
||||
IS_MAC,
|
||||
IS_SAFARI,
|
||||
IS_UBUNTU,
|
||||
IS_WINDOWS
|
||||
}
|
||||
|
@@ -2,16 +2,27 @@
|
||||
import { IS_MAC, IS_WINDOWS } from './environment'
|
||||
|
||||
/**
|
||||
* Does an `e` have the word-level modifier?
|
||||
* Does an `e` have the alt modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isWord(e) {
|
||||
function isAlt(e) {
|
||||
return e.altKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the command modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isCommand(e) {
|
||||
return IS_MAC
|
||||
? e.altKey
|
||||
: e.ctrlKey
|
||||
? e.metaKey && !e.altKey
|
||||
: e.ctrlKey && !e.altKey
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,10 +32,21 @@ export function isWord(e) {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isCtrl(e) {
|
||||
function isCtrl(e) {
|
||||
return e.ctrlKey && !e.altKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the Mac command modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isMacCommand(e) {
|
||||
return IS_MAC && isCommand(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the option modifier?
|
||||
*
|
||||
@@ -32,7 +54,7 @@ export function isCtrl(e) {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isOption(e) {
|
||||
function isOption(e) {
|
||||
return IS_MAC && e.altKey
|
||||
}
|
||||
|
||||
@@ -43,34 +65,10 @@ export function isOption(e) {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isShift(e) {
|
||||
function isShift(e) {
|
||||
return e.shiftKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the command modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isCommand(e) {
|
||||
return IS_MAC
|
||||
? e.metaKey && !e.altKey
|
||||
: e.ctrlKey && !e.altKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the Mac command modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isMacCommand(e) {
|
||||
return IS_MAC && isCommand(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the Windows command modifier?
|
||||
*
|
||||
@@ -78,6 +76,34 @@ export function isMacCommand(e) {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isWindowsCommand(e) {
|
||||
function isWindowsCommand(e) {
|
||||
return IS_WINDOWS && isCommand(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the word-level modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isWord(e) {
|
||||
return IS_MAC
|
||||
? e.altKey
|
||||
: e.ctrlKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default {
|
||||
isAlt,
|
||||
isCommand,
|
||||
isCtrl,
|
||||
isMacCommand,
|
||||
isOption,
|
||||
isShift,
|
||||
isWindowsCommand,
|
||||
isWord
|
||||
}
|
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"repository": "git://github.com/ianstormtaylor/slate.git",
|
||||
"main": "./dist/index.js",
|
||||
"main": "./lib/index.js",
|
||||
"scripts": {
|
||||
"prepublish": "make dist",
|
||||
"test": "make check"
|
||||
@@ -32,6 +32,7 @@
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^13.0.1",
|
||||
"component-type": "^1.2.1",
|
||||
"draft-js": "^0.7.0",
|
||||
"eslint": "^3.0.1",
|
||||
"eslint-plugin-import": "^1.10.2",
|
||||
"eslint-plugin-react": "^5.2.2",
|
||||
|
Reference in New Issue
Block a user