mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-01-18 05:59:13 +01:00
handle split
This commit is contained in:
parent
ec7da72562
commit
e0ca80384e
@ -13,7 +13,7 @@ const state = {
|
||||
kind: 'node',
|
||||
type: 'code',
|
||||
data: {},
|
||||
children: [
|
||||
nodes: [
|
||||
{
|
||||
type: 'text',
|
||||
ranges: [
|
||||
@ -29,7 +29,7 @@ const state = {
|
||||
kind: 'node',
|
||||
type: 'paragraph',
|
||||
data: {},
|
||||
children: [
|
||||
nodes: [
|
||||
{
|
||||
type: 'text',
|
||||
ranges: [
|
||||
@ -114,8 +114,7 @@ class App extends React.Component {
|
||||
renderMark={renderMark}
|
||||
state={this.state.state}
|
||||
onChange={(state) => {
|
||||
console.log('Selection:', state.selection.toJS())
|
||||
console.log('Text:', state.nodes.last().children.first().characters.map(c => c.text).toJS())
|
||||
console.log('State:', state.toJS())
|
||||
this.setState({ state })
|
||||
}}
|
||||
/>
|
||||
|
@ -153,9 +153,9 @@ class Content extends React.Component {
|
||||
}
|
||||
|
||||
const Component = renderNode(node)
|
||||
const children = node.children
|
||||
const children = node.nodes
|
||||
.toArray()
|
||||
.map(child => this.renderNode(child))
|
||||
.map(node => this.renderNode(node))
|
||||
|
||||
return (
|
||||
<Component
|
||||
|
@ -8,9 +8,9 @@ import { Map, OrderedMap, Record } from 'immutable'
|
||||
*/
|
||||
|
||||
const NodeRecord = new Record({
|
||||
children: new OrderedMap(),
|
||||
data: new Map(),
|
||||
key: null,
|
||||
nodes: new OrderedMap(),
|
||||
type: null
|
||||
})
|
||||
|
||||
@ -29,9 +29,9 @@ class Node extends NodeRecord {
|
||||
|
||||
static create(object) {
|
||||
return new Node({
|
||||
children: Node.createMap(object.children),
|
||||
data: new Map(object.data),
|
||||
data: new Map(object.data || {}),
|
||||
key: uid(4),
|
||||
nodes: Node.createMap(object.nodes || []),
|
||||
type: object.type
|
||||
})
|
||||
}
|
||||
@ -54,56 +54,57 @@ class Node extends NodeRecord {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for a child node by `key`.
|
||||
* Get the length of the concatenated text of the node.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
* @return {Number} length
|
||||
*/
|
||||
|
||||
setNode(key, node) {
|
||||
if (this.children.get(key)) {
|
||||
const children = this.children.set(key, node)
|
||||
return this.set('children', children)
|
||||
}
|
||||
|
||||
const children = this.children.map((child) => {
|
||||
return child instanceof Node
|
||||
? child.setNode(key, node)
|
||||
: child
|
||||
})
|
||||
|
||||
return this.set('children', children)
|
||||
get length() {
|
||||
return this.text.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find children nodes by `iterator`.
|
||||
* Get the concatenated text `string` of all child nodes.
|
||||
*
|
||||
* @return {String} text
|
||||
*/
|
||||
|
||||
get text() {
|
||||
return this
|
||||
.filterNodes(node => node.type == 'text')
|
||||
.map(node => node.characters)
|
||||
.flatten()
|
||||
.map(character => character.text)
|
||||
.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find nodes nodes by `iterator`.
|
||||
*
|
||||
* @param {Function} iterator
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
findNode(iterator) {
|
||||
const shallow = this.children.find(iterator)
|
||||
const shallow = this.nodes.find(iterator)
|
||||
if (shallow != null) return shallow
|
||||
|
||||
const deep = this.children
|
||||
return this.nodes
|
||||
.map(node => node instanceof Node ? node.findNode(iterator) : null)
|
||||
.filter(node => node)
|
||||
.first()
|
||||
return deep
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively filter children nodes with `iterator`.
|
||||
* Recursively filter nodes nodes with `iterator`.
|
||||
*
|
||||
* @param {Function} iterator
|
||||
* @return {OrderedMap} matches
|
||||
*/
|
||||
|
||||
filterNodes(iterator) {
|
||||
const shallow = this.children.filter(iterator)
|
||||
const deep = this.children
|
||||
const shallow = this.nodes.filter(iterator)
|
||||
const deep = this.nodes
|
||||
.map(node => node instanceof Node ? node.filterNodes(iterator) : null)
|
||||
.filter(node => node)
|
||||
.reduce((all, map) => {
|
||||
@ -113,6 +114,115 @@ class Node extends NodeRecord {
|
||||
return deep
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a child node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getNode(key) {
|
||||
return this.findNode(node => node.key == key) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child node after the one by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getNodeAfter(key) {
|
||||
const shallow = this.nodes
|
||||
.skipUntil(node => node.key == key)
|
||||
.rest()
|
||||
.first()
|
||||
|
||||
if (shallow != null) return shallow
|
||||
|
||||
return this.nodes
|
||||
.map(node => node instanceof Node ? node.getNodeAfter(key) : null)
|
||||
.filter(node => node)
|
||||
.first()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child text node at `offset`.
|
||||
*
|
||||
* @param {String} offset
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getNodeAtOffset(offset) {
|
||||
let match = null
|
||||
let i
|
||||
|
||||
this.nodes.forEach((node) => {
|
||||
if (!node.length > offset + i) return
|
||||
match = node.type == 'text'
|
||||
? node
|
||||
: node.getNodeAtOffset(offset - i)
|
||||
i += node.length
|
||||
})
|
||||
|
||||
return match
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent of a child node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getParentOfNode(key) {
|
||||
if (this.nodes.get(key)) return this
|
||||
let node = null
|
||||
|
||||
this.nodes.forEach((child) => {
|
||||
if (!(child instanceof Node)) return
|
||||
const match = child.getParentOfNode(key)
|
||||
if (match) node = match
|
||||
})
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a new `node` onto the map of nodes.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
pushNode(node) {
|
||||
let nodes = this.nodes.set(node.key, node)
|
||||
return this.merge({ nodes })
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for a child node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
setNode(key, node) {
|
||||
if (this.nodes.get(key)) {
|
||||
const nodes = this.nodes.set(key, node)
|
||||
return this.set('nodes', nodes)
|
||||
}
|
||||
|
||||
const nodes = this.nodes.map((child) => {
|
||||
return child instanceof Node
|
||||
? child.setNode(key, node)
|
||||
: child
|
||||
})
|
||||
|
||||
return this.merge({ nodes })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
import Selection from './selection'
|
||||
import Node from './node'
|
||||
import Text from './text'
|
||||
import toCamel from 'to-camel-case'
|
||||
import { OrderedMap, Record } from 'immutable'
|
||||
|
||||
@ -43,26 +44,89 @@ class State extends StateRecord {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set a new value for a child node by `key`.
|
||||
* Get the concatenated text of all nodes.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
* @return {String} text
|
||||
*/
|
||||
|
||||
setNode(key, node) {
|
||||
if (this.nodes.get(key)) {
|
||||
const nodes = this.nodes.set(key, node)
|
||||
return this.merge({ nodes })
|
||||
}
|
||||
get text() {
|
||||
return this.nodes
|
||||
.map(node => node.text)
|
||||
.join('')
|
||||
}
|
||||
|
||||
const nodes = this.nodes.map((child) => {
|
||||
return child instanceof Node
|
||||
? child.setNode(key, node)
|
||||
: child
|
||||
/**
|
||||
* Get a node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getNode(key) {
|
||||
return this.findNode(node => node.key == key) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child node after the one by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getNodeAfter(key) {
|
||||
const shallow = this.nodes
|
||||
.skipUntil(node => node.key == key)
|
||||
.rest()
|
||||
.first()
|
||||
|
||||
if (shallow != null) return shallow
|
||||
|
||||
return this.nodes
|
||||
.map(node => node instanceof Node ? node.getNodeAfter(key) : null)
|
||||
.filter(node => node)
|
||||
.first()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child text node at `offset`.
|
||||
*
|
||||
* @param {String} offset
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getNodeAtOffset(offset) {
|
||||
let node = null
|
||||
let i
|
||||
|
||||
this.nodes.forEach((child) => {
|
||||
const match = child.text.length > offset + i
|
||||
if (!match) return
|
||||
node = match.type == 'text'
|
||||
? match
|
||||
: match.getNodeAtOffset(offset - i)
|
||||
})
|
||||
|
||||
return this.merge({ nodes })
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent of a child node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Node or Null}
|
||||
*/
|
||||
|
||||
getParentOfNode(key) {
|
||||
if (this.nodes.get(key)) return this
|
||||
let node = null
|
||||
|
||||
this.nodes.forEach((child) => {
|
||||
if (!(child instanceof Node)) return
|
||||
const match = child.getParentOfNode(key)
|
||||
if (match) node = match
|
||||
})
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,6 +166,41 @@ class State extends StateRecord {
|
||||
return deep
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a new `node` onto the map of nodes.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
pushNode(node) {
|
||||
let notes = this.notes.set(node.key, node)
|
||||
return this.merge({ notes })
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for a child node by `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
setNode(key, node) {
|
||||
if (this.nodes.get(key)) {
|
||||
const nodes = this.nodes.set(key, node)
|
||||
return this.merge({ nodes })
|
||||
}
|
||||
|
||||
const nodes = this.nodes.map((child) => {
|
||||
return child instanceof Node
|
||||
? child.setNode(key, node)
|
||||
: child
|
||||
})
|
||||
|
||||
return this.merge({ nodes })
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* TRANSFORMS.
|
||||
@ -115,11 +214,12 @@ class State extends StateRecord {
|
||||
/**
|
||||
* Backspace a single character.
|
||||
*
|
||||
* @param {Selection} selection (optional)
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
backspace(selection = this.selection) {
|
||||
backspace() {
|
||||
const { selection } = this
|
||||
|
||||
// when not collapsed, remove the entire selection
|
||||
if (!selection.isCollapsed) {
|
||||
return this
|
||||
@ -131,12 +231,11 @@ class State extends StateRecord {
|
||||
if (selection.isAtStartOf(this)) return this
|
||||
|
||||
// otherwise, remove one character behind of the cursor
|
||||
let { startKey, endOffset } = selection
|
||||
let { nodes } = this
|
||||
let node = this.findNode(node => node.key == startKey)
|
||||
let startOffset = endOffset - 1
|
||||
const { startKey, endOffset } = selection
|
||||
const node = this.getNode(startKey)
|
||||
const startOffset = endOffset - 1
|
||||
return this
|
||||
.removeText(node, startOffset, endOffset)
|
||||
.removeCharacters(node, startOffset, endOffset)
|
||||
.moveTo({
|
||||
anchorOffset: startOffset,
|
||||
focusOffset: startOffset
|
||||
@ -158,8 +257,7 @@ class State extends StateRecord {
|
||||
focusOffset: anchorOffset
|
||||
})
|
||||
|
||||
let state = this.merge({ selection })
|
||||
return state
|
||||
return this.merge({ selection })
|
||||
}
|
||||
|
||||
/**
|
||||
@ -170,25 +268,25 @@ class State extends StateRecord {
|
||||
|
||||
collapseForward() {
|
||||
let { selection } = this
|
||||
let { focusKey, focusOffset } = selection
|
||||
const { focusKey, focusOffset } = selection
|
||||
|
||||
selection = selection.merge({
|
||||
anchorKey: focusKey,
|
||||
anchorOffset: focusOffset
|
||||
})
|
||||
|
||||
let state = this.merge({ selection })
|
||||
return state
|
||||
return this.merge({ selection })
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single character.
|
||||
*
|
||||
* @param {Selection} selection (optional)
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
delete(selection = this.selection) {
|
||||
delete() {
|
||||
const { selection } = this
|
||||
|
||||
// when not collapsed, remove the entire selection
|
||||
if (!selection.isCollapsed) {
|
||||
return this
|
||||
@ -200,11 +298,10 @@ class State extends StateRecord {
|
||||
if (selection.isAtEndOf(this)) return this
|
||||
|
||||
// otherwise, remove one character ahead of the cursor
|
||||
let { startKey, startOffset } = selection
|
||||
let { nodes } = this
|
||||
let node = this.findNode(node => node.key == startKey)
|
||||
let endOffset = startOffset + 1
|
||||
return this.removeText(node, startOffset, endOffset)
|
||||
const { startKey, startOffset } = selection
|
||||
const node = this.getNode(startKey)
|
||||
const endOffset = startOffset + 1
|
||||
return this.removeCharacters(node, startOffset, endOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,62 +316,151 @@ class State extends StateRecord {
|
||||
*/
|
||||
|
||||
moveTo(properties) {
|
||||
let selection = this.selection.merge(properties)
|
||||
let state = this.merge({ selection })
|
||||
return state
|
||||
const selection = this.selection.merge(properties)
|
||||
return this.merge({ selection })
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize all nodes, ensuring that no two text nodes are adjacent.
|
||||
*
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
normalize() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the existing selection's content.
|
||||
*
|
||||
* @param {Selection} selection (optional)
|
||||
* @param {Selection} selection
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
removeSelection(selection = this.selection) {
|
||||
removeSelection(selection) {
|
||||
// if already collapsed, there's nothing to remove
|
||||
if (selection.isCollapsed) return this
|
||||
|
||||
// if the start and end nodes are the same, just remove the matching text
|
||||
let { nodes } = this
|
||||
let { startKey, startOffset, endKey, endOffset } = selection
|
||||
let startNode = nodes.get(startKey)
|
||||
let endNode = nodes.get(endKey)
|
||||
if (startNode == endNode) return this.removeText(startNode, startOffset, endOffset)
|
||||
const { startKey, startOffset, endKey, endOffset } = selection
|
||||
if (startKey == endKey) return this.removeCharacters(startKey, startOffset, endOffset)
|
||||
|
||||
// otherwise, remove all of the other nodes between them...
|
||||
nodes = nodes
|
||||
const nodes = this.nodes
|
||||
.takeUntil(node => node.key == startKey)
|
||||
.take(1)
|
||||
.skipUntil(node => node.key == endKey)
|
||||
.take(Infinity)
|
||||
|
||||
// ...and remove the text from the first and last nodes
|
||||
let state = this.merge({ nodes })
|
||||
state = state.removeText(startNode, startOffset, startNode.text.length)
|
||||
state = state.removeText(endNode, 0, endOffset)
|
||||
return state
|
||||
const startNode = this.getNode(startKey)
|
||||
return this
|
||||
.merge({ nodes })
|
||||
.removeCharacters(startKey, startOffset, startNode.text.length)
|
||||
.removeCharacters(endKey, 0, endOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the text from a `node`.
|
||||
* Remove characters from a node by `key` between offsets.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {String} key
|
||||
* @param {Number} startOffset
|
||||
* @param {Number} endOffset
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
removeText(node, startOffset, endOffset) {
|
||||
let { nodes } = this
|
||||
removeCharacters(key, startOffset, endOffset) {
|
||||
let node = this.getNode(key)
|
||||
let { characters } = node
|
||||
|
||||
characters = characters.filterNot((char, i) => {
|
||||
characters = node.characters.filterNot((char, i) => {
|
||||
return startOffset <= i && i < endOffset
|
||||
})
|
||||
|
||||
node = node.merge({ characters })
|
||||
let state = this.setNode(node.key, node)
|
||||
return this.setNode(key, node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split at a `selection`.
|
||||
*
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
split() {
|
||||
let { selection } = this
|
||||
let state = this.splitSelection(selection)
|
||||
let { anchorKey } = state.selection
|
||||
let parent = state.getParentOfNode(anchorKey)
|
||||
let node = state.getNodeAfter(parent.key)
|
||||
let text = node.nodes.first()
|
||||
return state.moveTo({
|
||||
anchorKey: text.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: text.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the nodes at a `selection`.
|
||||
*
|
||||
* @param {Selection} selection
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
splitSelection(selection) {
|
||||
let state = this
|
||||
|
||||
// if there's an existing selection, remove it first
|
||||
if (!selection.isCollapsed) {
|
||||
state = state.removeSelection(selection)
|
||||
selection = selection.merge({
|
||||
focusKey: selection.anchorKey,
|
||||
focusOffset: selection.anchorOffset
|
||||
})
|
||||
}
|
||||
|
||||
// then split the node at the selection
|
||||
const { startKey, startOffset } = selection
|
||||
const text = state.getNode(startKey)
|
||||
const parent = state.getParentOfNode(text.key)
|
||||
|
||||
// split the characters
|
||||
const { characters , length } = text
|
||||
const firstCharacters = characters.take(startOffset)
|
||||
const secondCharacters = characters.takeLast(length - startOffset)
|
||||
|
||||
// Create a new first node with only the first set of characters.
|
||||
const firstText = text.set('characters', firstCharacters)
|
||||
const firstNode = parent.setNode(firstText.key, firstText)
|
||||
|
||||
// Create a brand new second node with the second set of characters.
|
||||
let secondText = Text.create({})
|
||||
secondText = secondText.set('characters', secondCharacters)
|
||||
|
||||
let secondNode = Node.create({
|
||||
type: firstNode.type,
|
||||
data: firstNode.data
|
||||
})
|
||||
secondNode = secondNode.pushNode(secondText)
|
||||
|
||||
// Replace the old parent node in the grandparent with the two new ones.
|
||||
let grandparent = state.getParentOfNode(parent.key)
|
||||
const befores = grandparent.nodes.takeUntil(node => node.key == parent.key)
|
||||
const afters = grandparent.nodes.skipUntil(node => node.key == parent.key).rest()
|
||||
const nodes = befores
|
||||
.set(firstNode.key, firstNode)
|
||||
.set(secondNode.key, secondNode)
|
||||
.concat(afters)
|
||||
|
||||
if (grandparent == state) {
|
||||
state = state.merge({ nodes })
|
||||
} else {
|
||||
grandparent = grandparent.merge({ nodes })
|
||||
state = state.setNode(grandparent.key, grandparent)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { List, Record } from 'immutable'
|
||||
*/
|
||||
|
||||
const TextRecord = new Record({
|
||||
characters: new List,
|
||||
characters: new List(),
|
||||
key: null
|
||||
})
|
||||
|
||||
@ -26,13 +26,35 @@ class Text extends TextRecord {
|
||||
*/
|
||||
|
||||
static create(attrs) {
|
||||
const characters = convertRangesToCharacters(attrs.ranges)
|
||||
const characters = convertRangesToCharacters(attrs.ranges || [])
|
||||
return new Text({
|
||||
key: uid(4),
|
||||
characters
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the concatenated text of the node.
|
||||
*
|
||||
* @return {Number} length
|
||||
*/
|
||||
|
||||
get length() {
|
||||
return this.text.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the concatenated text of the node.
|
||||
*
|
||||
* @return {String} text
|
||||
*/
|
||||
|
||||
get text() {
|
||||
return this.characters
|
||||
.map(char => char.text)
|
||||
.join('')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +67,7 @@ const CORE_PLUGIN = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the the word-level modifier?
|
||||
* Does an `e` have the word-level modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
@ -80,7 +80,7 @@ function isWord(e) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the the control modifier?
|
||||
* Does an `e` have the control modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
@ -91,7 +91,7 @@ function isCtrl(e) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the the option modifier?
|
||||
* Does an `e` have the option modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
@ -113,7 +113,7 @@ function isShift(e) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an `e` have the the command modifier?
|
||||
* Does an `e` have the command modifier?
|
||||
*
|
||||
* @param {Event} e
|
||||
* @return {Boolean}
|
||||
|
Loading…
x
Reference in New Issue
Block a user