mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-09 08:46:35 +02:00
add placeholder
This commit is contained in:
9
Makefile
9
Makefile
@@ -34,7 +34,6 @@ dist: $(shell find ./lib) package.json
|
|||||||
--out-dir \
|
--out-dir \
|
||||||
./dist \
|
./dist \
|
||||||
./lib
|
./lib
|
||||||
@ touch ./dist
|
|
||||||
|
|
||||||
# Build the examples.
|
# Build the examples.
|
||||||
examples:
|
examples:
|
||||||
@@ -84,6 +83,14 @@ test-server:
|
|||||||
--fgrep "$(GREP)" \
|
--fgrep "$(GREP)" \
|
||||||
./test/server.js
|
./test/server.js
|
||||||
|
|
||||||
|
# Watch the source.
|
||||||
|
watch-dist: $(shell find ./lib) package.json
|
||||||
|
@ $(babel) \
|
||||||
|
--watch \
|
||||||
|
--out-dir \
|
||||||
|
./dist \
|
||||||
|
./lib
|
||||||
|
|
||||||
# Watch the examples.
|
# Watch the examples.
|
||||||
watch-examples:
|
watch-examples:
|
||||||
@ $(watchify) \
|
@ $(watchify) \
|
||||||
|
@@ -66,6 +66,7 @@ class PlainText extends React.Component {
|
|||||||
render = () => {
|
render = () => {
|
||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
|
placeholder={'Enter some plain text...'}
|
||||||
state={this.state.state}
|
state={this.state.state}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { Editor, Mark, Raw, Utils } from '../..'
|
import { Editor, Mark, Placeholder, Raw, Utils } from '../..'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import initialState from './state.json'
|
import initialState from './state.json'
|
||||||
import keycode from 'keycode'
|
import keycode from 'keycode'
|
||||||
@@ -11,13 +11,12 @@ import keycode from 'keycode'
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const NODES = {
|
const NODES = {
|
||||||
'block-quote': props => <blockquote>{props.children}</blockquote>,
|
'block-quote': props => <blockquote {...props.attributes}>{props.children}</blockquote>,
|
||||||
'bulleted-list': props => <ul>{props.chidlren}</ul>,
|
'bulleted-list': props => <ul {...props.attributes}>{props.chidlren}</ul>,
|
||||||
'heading-one': props => <h1>{props.children}</h1>,
|
'heading-one': props => <h1 {...props.attributes}>{props.children}</h1>,
|
||||||
'heading-two': props => <h2>{props.children}</h2>,
|
'heading-two': props => <h2 {...props.attributes}>{props.children}</h2>,
|
||||||
'list-item': props => <li>{props.chidlren}</li>,
|
'list-item': props => <li {...props.attributes}>{props.chidlren}</li>,
|
||||||
'numbered-list': props => <ol>{props.children}</ol>,
|
'numbered-list': props => <ol {...props.attributes}>{props.children}</ol>
|
||||||
'paragraph': props => <p>{props.children}</p>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,6 +106,7 @@ class RichText extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="editor">
|
<div className="editor">
|
||||||
<Editor
|
<Editor
|
||||||
|
placeholder={'Enter some rich text...'}
|
||||||
state={this.state.state}
|
state={this.state.state}
|
||||||
renderNode={this.renderNode}
|
renderNode={this.renderNode}
|
||||||
renderMark={this.renderMark}
|
renderMark={this.renderMark}
|
||||||
|
@@ -387,8 +387,13 @@ class Content extends React.Component {
|
|||||||
.map(child => this.renderNode(child))
|
.map(child => this.renderNode(child))
|
||||||
.toArray()
|
.toArray()
|
||||||
|
|
||||||
|
const attributes = {
|
||||||
|
'data-key': node.key
|
||||||
|
}
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<Component
|
<Component
|
||||||
|
attributes={attributes}
|
||||||
key={node.key}
|
key={node.key}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
node={node}
|
node={node}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
import Content from './content'
|
import Content from './content'
|
||||||
|
import CorePlugin from '../plugins/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import State from '../models/state'
|
import State from '../models/state'
|
||||||
import corePlugin from '../plugins/core'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor.
|
* Editor.
|
||||||
@@ -190,7 +190,6 @@ class Editor extends React.Component {
|
|||||||
if (!plugin.renderNode) continue
|
if (!plugin.renderNode) continue
|
||||||
const component = plugin.renderNode(node, this.state.state, this)
|
const component = plugin.renderNode(node, this.state.state, this)
|
||||||
if (component) return component
|
if (component) return component
|
||||||
throw new Error(`No renderer found for node with type "${node.type}".`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +205,6 @@ class Editor extends React.Component {
|
|||||||
if (!plugin.renderMark) continue
|
if (!plugin.renderMark) continue
|
||||||
const style = plugin.renderMark(mark, this.state.state, this)
|
const style = plugin.renderMark(mark, this.state.state, this)
|
||||||
if (style) return style
|
if (style) return style
|
||||||
throw new Error(`No renderer found for mark with type "${mark.type}".`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +224,7 @@ class Editor extends React.Component {
|
|||||||
|
|
||||||
resolvePlugins = (props) => {
|
resolvePlugins = (props) => {
|
||||||
const { onChange, plugins, ...editorPlugin } = props
|
const { onChange, plugins, ...editorPlugin } = props
|
||||||
|
const corePlugin = CorePlugin(props)
|
||||||
return [
|
return [
|
||||||
editorPlugin,
|
editorPlugin,
|
||||||
...plugins,
|
...plugins,
|
||||||
|
108
lib/components/placeholder.js
Normal file
108
lib/components/placeholder.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
|
||||||
|
import Portal from 'react-portal'
|
||||||
|
import React from 'react'
|
||||||
|
import findDOMNode from '../utils/find-dom-node'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Placeholder extends React.Component {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: React.PropTypes.any.isRequired,
|
||||||
|
className: React.PropTypes.string,
|
||||||
|
node: React.PropTypes.object.isRequired,
|
||||||
|
parent: React.PropTypes.object.isRequired,
|
||||||
|
state: React.PropTypes.object.isRequired,
|
||||||
|
style: React.PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
onlyFirstChild: false,
|
||||||
|
style: {
|
||||||
|
opacity: '0.333'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the component update?
|
||||||
|
*
|
||||||
|
* @param {Object} props
|
||||||
|
* @param {Object} state
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
shouldComponentUpdate = (props, state) => {
|
||||||
|
return (
|
||||||
|
props.children != this.props.children ||
|
||||||
|
props.className != this.props.className ||
|
||||||
|
props.parent != this.props.parent ||
|
||||||
|
props.node != this.props.node ||
|
||||||
|
props.style != this.props.style
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the placeholder visible?
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
isVisible = () => {
|
||||||
|
const { onlyFirstChild, node, parent } = this.props
|
||||||
|
if (node.text) return false
|
||||||
|
if (parent.nodes.size > 1) return false
|
||||||
|
|
||||||
|
const isFirst = parent.nodes.first() === node
|
||||||
|
if (isFirst) return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On open, update the placeholder element's position.
|
||||||
|
*
|
||||||
|
* @param {Element} portal
|
||||||
|
*/
|
||||||
|
|
||||||
|
onOpen = (portal) => {
|
||||||
|
const { node } = this.props
|
||||||
|
const el = portal.firstChild
|
||||||
|
const nodeEl = findDOMNode(node)
|
||||||
|
const rect = nodeEl.getBoundingClientRect()
|
||||||
|
el.style.pointerEvents = 'none'
|
||||||
|
el.style.position = 'absolute'
|
||||||
|
el.style.top = `${rect.top}px`
|
||||||
|
el.style.left = `${rect.left}px`
|
||||||
|
el.style.width = `${rect.width}px`
|
||||||
|
el.style.height = `${rect.height}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render.
|
||||||
|
*
|
||||||
|
* @return {Element} element
|
||||||
|
*/
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const { children, className, style } = this.props
|
||||||
|
const isOpen = this.isVisible()
|
||||||
|
return (
|
||||||
|
<Portal isOpened={isOpen} onOpen={this.onOpen}>
|
||||||
|
<span className={className} style={style}>{children}</span>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default Placeholder
|
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Editor from './components/editor'
|
import Editor from './components/editor'
|
||||||
|
import Placeholder from './components/placeholder'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Models.
|
* Models.
|
||||||
@@ -31,8 +32,12 @@ import Raw from './serializers/raw'
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Key from './utils/key'
|
import Key from './utils/key'
|
||||||
|
import findDOMNode from './utils/find-dom-node'
|
||||||
|
|
||||||
const Utils = { Key }
|
const Utils = {
|
||||||
|
Key,
|
||||||
|
findDOMNode
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export.
|
* Export.
|
||||||
@@ -47,6 +52,7 @@ export {
|
|||||||
Html,
|
Html,
|
||||||
Inline,
|
Inline,
|
||||||
Mark,
|
Mark,
|
||||||
|
Placeholder,
|
||||||
Raw,
|
Raw,
|
||||||
Selection,
|
Selection,
|
||||||
State,
|
State,
|
||||||
@@ -63,6 +69,7 @@ export default {
|
|||||||
Html,
|
Html,
|
||||||
Inline,
|
Inline,
|
||||||
Mark,
|
Mark,
|
||||||
|
Placeholder,
|
||||||
Raw,
|
Raw,
|
||||||
Selection,
|
Selection,
|
||||||
State,
|
State,
|
||||||
|
@@ -485,7 +485,7 @@ const Node = {
|
|||||||
if (range.isCollapsed && startOffset == 0) {
|
if (range.isCollapsed && startOffset == 0) {
|
||||||
const text = this.getDescendant(startKey)
|
const text = this.getDescendant(startKey)
|
||||||
const previous = this.getPreviousText(startKey)
|
const previous = this.getPreviousText(startKey)
|
||||||
if (!previous) return marks
|
if (!previous || !previous.length) return marks
|
||||||
const char = previous.characters.get(previous.length - 1)
|
const char = previous.characters.get(previous.length - 1)
|
||||||
return char.marks
|
return char.marks
|
||||||
}
|
}
|
||||||
|
@@ -1,204 +1,258 @@
|
|||||||
|
|
||||||
import Key from '../utils/key'
|
import Key from '../utils/key'
|
||||||
|
import Placeholder from '../components/placeholder'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import keycode from 'keycode'
|
import keycode from 'keycode'
|
||||||
import { IS_WINDOWS, IS_MAC } from '../utils/environment'
|
import { IS_WINDOWS, IS_MAC } from '../utils/environment'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default block renderer.
|
* The default plugin.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} options
|
||||||
* @return {Element} element
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function DEFAULT_BLOCK(props) {
|
function Plugin(options = {}) {
|
||||||
return <div>{props.children}</div>
|
const { placeholder } = options
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a default block renderer.
|
||||||
|
*
|
||||||
|
* @type {Component}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DEFAULT_BLOCK extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
attributes: React.PropTypes.object.isRequired,
|
||||||
|
children: React.PropTypes.any.isRequired,
|
||||||
|
node: React.PropTypes.object.isRequired,
|
||||||
|
state: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const { attributes, children } = this.props
|
||||||
|
return (
|
||||||
|
<div {...attributes}>
|
||||||
|
{this.renderPlaceholder()}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlaceholder = () => {
|
||||||
|
if (!placeholder) return null
|
||||||
|
const { node, state } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Placeholder parent={state.document} node={node} state={state}>
|
||||||
|
{placeholder}
|
||||||
|
</Placeholder>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a default inline renderer.
|
||||||
|
*
|
||||||
|
* @type {Component}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DEFAULT_INLINE extends React.Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
attributes: React.PropTypes.object.isRequired,
|
||||||
|
children: React.PropTypes.any.isRequired,
|
||||||
|
node: React.PropTypes.object.isRequired,
|
||||||
|
state: React.PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const { attributes, children } = this.props
|
||||||
|
return <span {...attributes}>{children}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a default mark renderer.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DEFAULT_MARK = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the plugin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The core `onBeforeInput` handler.
|
||||||
|
*
|
||||||
|
* If the current selection is expanded, we have to re-render.
|
||||||
|
*
|
||||||
|
* If the next state resolves a new list of decorations for any of its text
|
||||||
|
* nodes, we have to re-render.
|
||||||
|
*
|
||||||
|
* Otherwise, we can allow the default, native text insertion, avoiding a
|
||||||
|
* re-render for improved performance.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
* @param {State} state
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @return {State or Null} newState
|
||||||
|
*/
|
||||||
|
|
||||||
|
onBeforeInput(e, state, editor) {
|
||||||
|
const transform = state.transform().insertText(e.data)
|
||||||
|
const synthetic = transform.apply()
|
||||||
|
const resolved = editor.resolveState(synthetic)
|
||||||
|
|
||||||
|
const isSynthenic = (
|
||||||
|
state.isExpanded ||
|
||||||
|
!resolved.equals(synthetic)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isSynthenic) e.preventDefault()
|
||||||
|
|
||||||
|
return isSynthenic
|
||||||
|
? synthetic
|
||||||
|
: transform.apply({ isNative: true })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The core `onKeyDown` handler.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
* @param {State} state
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @return {State or Null} newState
|
||||||
|
*/
|
||||||
|
|
||||||
|
onKeyDown(e, state, editor) {
|
||||||
|
const key = keycode(e.which)
|
||||||
|
const transform = state.transform()
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'enter': {
|
||||||
|
return transform.splitBlock().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'backspace': {
|
||||||
|
return Key.isWord(e)
|
||||||
|
? transform.backspaceWord().apply()
|
||||||
|
: transform.deleteBackward().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete': {
|
||||||
|
return Key.isWord(e)
|
||||||
|
? transform.deleteWord().apply()
|
||||||
|
: transform.deleteForward().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'up': {
|
||||||
|
if (state.isExpanded) return
|
||||||
|
const first = state.blocks.first()
|
||||||
|
if (!first || !first.isVoid) return
|
||||||
|
e.preventDefault()
|
||||||
|
return transform.moveToEndOfPreviousBlock().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'down': {
|
||||||
|
if (state.isExpanded) return
|
||||||
|
const first = state.blocks.first()
|
||||||
|
if (!first || !first.isVoid) return
|
||||||
|
e.preventDefault()
|
||||||
|
return transform.moveToStartOfNextBlock().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'left': {
|
||||||
|
if (state.isExpanded) return
|
||||||
|
const node = state.blocks.first() || state.inlines.first()
|
||||||
|
if (!node || !node.isVoid) return
|
||||||
|
e.preventDefault()
|
||||||
|
return transform.moveToEndOfPreviousText().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'right': {
|
||||||
|
if (state.isExpanded) return
|
||||||
|
const node = state.blocks.first() || state.inlines.first()
|
||||||
|
if (!node || !node.isVoid) return
|
||||||
|
e.preventDefault()
|
||||||
|
return transform.moveToStartOfNextText().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'y': {
|
||||||
|
if (!Key.isWindowsCommand(e)) return
|
||||||
|
return transform.redo()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'z': {
|
||||||
|
if (!Key.isCommand(e)) return
|
||||||
|
return IS_MAC && Key.isShift(e)
|
||||||
|
? transform.redo()
|
||||||
|
: transform.undo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The core `onPaste` handler, which treats everything as plain text.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
* @param {Object} paste
|
||||||
|
* @param {State} state
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @return {State or Null} newState
|
||||||
|
*/
|
||||||
|
|
||||||
|
onPaste(e, paste, state, editor) {
|
||||||
|
if (paste.type == 'files') return
|
||||||
|
|
||||||
|
let transform = state.transform()
|
||||||
|
|
||||||
|
paste.text
|
||||||
|
.split('\n')
|
||||||
|
.forEach((line, i) => {
|
||||||
|
if (i > 0) transform = transform.splitBlock()
|
||||||
|
transform = transform.insertText(line)
|
||||||
|
})
|
||||||
|
|
||||||
|
return transform.apply()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The core `node` renderer, which uses plain `<div>` or `<span>` depending on
|
||||||
|
* what kind of node it is.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Component} component
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderNode(node) {
|
||||||
|
return node.kind == 'block'
|
||||||
|
? DEFAULT_BLOCK
|
||||||
|
: DEFAULT_INLINE
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The core `mark` renderer, with no styles.
|
||||||
|
*
|
||||||
|
* @param {Mark} mark
|
||||||
|
* @return {Object} style
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderMark(mark) {
|
||||||
|
return DEFAULT_MARK
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Default inline renderer.
|
|
||||||
*
|
|
||||||
* @param {Object} props
|
|
||||||
* @return {Element} element
|
|
||||||
*/
|
|
||||||
|
|
||||||
function DEFAULT_INLINE(props) {
|
|
||||||
return <span>{props.children}</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default mark renderer.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const DEFAULT_MARK = {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export.
|
* Export.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default {
|
export default Plugin
|
||||||
|
|
||||||
/**
|
|
||||||
* The core `onBeforeInput` handler.
|
|
||||||
*
|
|
||||||
* If the current selection is expanded, we have to re-render.
|
|
||||||
*
|
|
||||||
* If the next state resolves a new list of decorations for any of its text
|
|
||||||
* nodes, we have to re-render.
|
|
||||||
*
|
|
||||||
* Otherwise, we can allow the default, native text insertion, avoiding a
|
|
||||||
* re-render for improved performance.
|
|
||||||
*
|
|
||||||
* @param {Event} e
|
|
||||||
* @param {State} state
|
|
||||||
* @param {Editor} editor
|
|
||||||
* @return {State or Null} newState
|
|
||||||
*/
|
|
||||||
|
|
||||||
onBeforeInput(e, state, editor) {
|
|
||||||
const transform = state.transform().insertText(e.data)
|
|
||||||
const synthetic = transform.apply()
|
|
||||||
const resolved = editor.resolveState(synthetic)
|
|
||||||
|
|
||||||
const isSynthenic = (
|
|
||||||
state.isExpanded ||
|
|
||||||
!resolved.equals(synthetic)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isSynthenic) e.preventDefault()
|
|
||||||
|
|
||||||
return isSynthenic
|
|
||||||
? synthetic
|
|
||||||
: transform.apply({ isNative: true })
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The core `onKeyDown` handler.
|
|
||||||
*
|
|
||||||
* @param {Event} e
|
|
||||||
* @param {State} state
|
|
||||||
* @param {Editor} editor
|
|
||||||
* @return {State or Null} newState
|
|
||||||
*/
|
|
||||||
|
|
||||||
onKeyDown(e, state, editor) {
|
|
||||||
const key = keycode(e.which)
|
|
||||||
const transform = state.transform()
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case 'enter': {
|
|
||||||
return transform.splitBlock().apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'backspace': {
|
|
||||||
return Key.isWord(e)
|
|
||||||
? transform.backspaceWord().apply()
|
|
||||||
: transform.deleteBackward().apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'delete': {
|
|
||||||
return Key.isWord(e)
|
|
||||||
? transform.deleteWord().apply()
|
|
||||||
: transform.deleteForward().apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'up': {
|
|
||||||
if (state.isExpanded) return
|
|
||||||
const first = state.blocks.first()
|
|
||||||
if (!first || !first.isVoid) return
|
|
||||||
e.preventDefault()
|
|
||||||
return transform.moveToEndOfPreviousBlock().apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'down': {
|
|
||||||
if (state.isExpanded) return
|
|
||||||
const first = state.blocks.first()
|
|
||||||
if (!first || !first.isVoid) return
|
|
||||||
e.preventDefault()
|
|
||||||
return transform.moveToStartOfNextBlock().apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'left': {
|
|
||||||
if (state.isExpanded) return
|
|
||||||
const node = state.blocks.first() || state.inlines.first()
|
|
||||||
if (!node || !node.isVoid) return
|
|
||||||
e.preventDefault()
|
|
||||||
return transform.moveToEndOfPreviousText().apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'right': {
|
|
||||||
if (state.isExpanded) return
|
|
||||||
const node = state.blocks.first() || state.inlines.first()
|
|
||||||
if (!node || !node.isVoid) return
|
|
||||||
e.preventDefault()
|
|
||||||
return transform.moveToStartOfNextText().apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'y': {
|
|
||||||
if (!Key.isWindowsCommand(e)) return
|
|
||||||
return transform.redo()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'z': {
|
|
||||||
if (!Key.isCommand(e)) return
|
|
||||||
return IS_MAC && Key.isShift(e)
|
|
||||||
? transform.redo()
|
|
||||||
: transform.undo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The core `onPaste` handler, which treats everything as plain text.
|
|
||||||
*
|
|
||||||
* @param {Event} e
|
|
||||||
* @param {Object} paste
|
|
||||||
* @param {State} state
|
|
||||||
* @param {Editor} editor
|
|
||||||
* @return {State or Null} newState
|
|
||||||
*/
|
|
||||||
|
|
||||||
onPaste(e, paste, state, editor) {
|
|
||||||
if (paste.type == 'files') return
|
|
||||||
|
|
||||||
let transform = state.transform()
|
|
||||||
|
|
||||||
paste.text
|
|
||||||
.split('\n')
|
|
||||||
.forEach((line, i) => {
|
|
||||||
if (i > 0) transform = transform.splitBlock()
|
|
||||||
transform = transform.insertText(line)
|
|
||||||
})
|
|
||||||
|
|
||||||
return transform.apply()
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The core `node` renderer, which uses plain `<div>` or `<span>` depending on
|
|
||||||
* what kind of node it is.
|
|
||||||
*
|
|
||||||
* @param {Node} node
|
|
||||||
* @return {Component} component
|
|
||||||
*/
|
|
||||||
|
|
||||||
renderNode(node) {
|
|
||||||
return node.kind == 'block'
|
|
||||||
? DEFAULT_BLOCK
|
|
||||||
: DEFAULT_INLINE
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The core `mark` renderer, with no styles.
|
|
||||||
*
|
|
||||||
* @param {Mark} mark
|
|
||||||
* @return {Object} style
|
|
||||||
*/
|
|
||||||
|
|
||||||
renderMark(mark) {
|
|
||||||
return DEFAULT_MARK
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
17
lib/utils/find-dom-node.js
Normal file
17
lib/utils/find-dom-node.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Find the DOM node for a `node`.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Element} el
|
||||||
|
*/
|
||||||
|
|
||||||
|
function findDOMNode(node) {
|
||||||
|
return window.document.querySelector(`[data-key="${node.key}"]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default findDOMNode
|
Reference in New Issue
Block a user