mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-17 20:51:20 +02:00
refactor placeholder to use schema (#1253)
* refactor placeholder to use schema * update placeholder, remove old export * add maxWidth to prevent overflow * update docs
This commit is contained in:
51
packages/slate-react/src/components/default-node.js
Normal file
51
packages/slate-react/src/components/default-node.js
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
|
||||
/**
|
||||
* Default node.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
class DefaultNode extends React.Component {
|
||||
|
||||
/**
|
||||
* Prop types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
attributes: Types.object.isRequired,
|
||||
editor: Types.object.isRequired,
|
||||
isSelected: Types.bool.isRequired,
|
||||
node: SlateTypes.node.isRequired,
|
||||
parent: SlateTypes.node.isRequired,
|
||||
readOnly: Types.bool.isRequired,
|
||||
state: SlateTypes.state.isRequired,
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
render() {
|
||||
const { attributes, children, node } = this.props
|
||||
const Tag = node.kind == 'block' ? 'div' : 'span'
|
||||
const style = { position: 'relative' }
|
||||
return <Tag {...attributes} style={style}>{children}</Tag>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
export default DefaultNode
|
64
packages/slate-react/src/components/default-placeholder.js
Normal file
64
packages/slate-react/src/components/default-placeholder.js
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
|
||||
/**
|
||||
* Default placeholder.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
class DefaultPlaceholder extends React.Component {
|
||||
|
||||
/**
|
||||
* Property types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
editor: Types.object.isRequired,
|
||||
isSelected: Types.bool.isRequired,
|
||||
node: SlateTypes.node.isRequired,
|
||||
parent: SlateTypes.node.isRequired,
|
||||
readOnly: Types.bool.isRequired,
|
||||
state: SlateTypes.state.isRequired,
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
render() {
|
||||
const { editor, state } = this.props
|
||||
if (state.document.nodes.size > 1) return null
|
||||
if (!editor.props.placeholder) return null
|
||||
|
||||
const style = {
|
||||
pointerEvents: 'none',
|
||||
display: 'inline-block',
|
||||
width: '0',
|
||||
maxWidth: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: '0.333',
|
||||
}
|
||||
|
||||
return (
|
||||
<span contentEditable={false} style={style}>
|
||||
{editor.props.placeholder}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
export default DefaultPlaceholder
|
@@ -54,6 +54,7 @@ class Node extends React.Component {
|
||||
const { node, schema } = props
|
||||
this.state = {}
|
||||
this.state.Component = node.getComponent(schema)
|
||||
this.state.Placeholder = node.getPlaceholder(schema)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +79,8 @@ class Node extends React.Component {
|
||||
componentWillReceiveProps = (props) => {
|
||||
if (props.node == this.props.node) return
|
||||
const Component = props.node.getComponent(props.schema)
|
||||
this.setState({ Component })
|
||||
const Placeholder = props.node.getPlaceholder(props.schema)
|
||||
this.setState({ Component, Placeholder })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +156,7 @@ class Node extends React.Component {
|
||||
this.debug('render', { props })
|
||||
|
||||
const { editor, isSelected, node, parent, readOnly, state } = props
|
||||
const { Component } = this.state
|
||||
const { Component, Placeholder } = this.state
|
||||
const { selection } = state
|
||||
const indexes = node.getSelectionIndexes(selection, isSelected)
|
||||
const children = node.nodes.toArray().map((child, i) => {
|
||||
@@ -173,17 +175,19 @@ class Node extends React.Component {
|
||||
if (direction == 'rtl') attributes.dir = 'rtl'
|
||||
}
|
||||
|
||||
const p = {
|
||||
editor,
|
||||
isSelected,
|
||||
key: node.key,
|
||||
node,
|
||||
parent,
|
||||
readOnly,
|
||||
state
|
||||
}
|
||||
|
||||
const element = (
|
||||
<Component
|
||||
attributes={attributes}
|
||||
editor={editor}
|
||||
isSelected={isSelected}
|
||||
key={node.key}
|
||||
node={node}
|
||||
parent={parent}
|
||||
readOnly={readOnly}
|
||||
state={state}
|
||||
>
|
||||
<Component {...p} attributes={attributes}>
|
||||
{Placeholder && <Placeholder {...p} />}
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
|
@@ -1,125 +0,0 @@
|
||||
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
|
||||
/**
|
||||
* Placeholder.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
class Placeholder extends React.Component {
|
||||
|
||||
/**
|
||||
* Property types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
children: Types.any.isRequired,
|
||||
className: Types.string,
|
||||
firstOnly: Types.bool,
|
||||
node: SlateTypes.node.isRequired,
|
||||
parent: SlateTypes.node,
|
||||
state: SlateTypes.state.isRequired,
|
||||
style: Types.object,
|
||||
}
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
static defaultProps = {
|
||||
firstOnly: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the placeholder update?
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} state
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
shouldComponentUpdate = (props, state) => {
|
||||
return (
|
||||
props.children != this.props.children ||
|
||||
props.className != this.props.className ||
|
||||
props.firstOnly != this.props.firstOnly ||
|
||||
props.parent != this.props.parent ||
|
||||
props.node != this.props.node ||
|
||||
props.style != this.props.style
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the placeholder visible?
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
isVisible = () => {
|
||||
const { firstOnly, node, parent } = this.props
|
||||
if (node.text) return false
|
||||
|
||||
if (firstOnly) {
|
||||
if (parent.nodes.size > 1) return false
|
||||
if (parent.nodes.first() === node) return true
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
* If the placeholder is a string, and no `className` or `style` has been
|
||||
* passed, give it a default style of lowered opacity.
|
||||
*
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
render() {
|
||||
const isVisible = this.isVisible()
|
||||
if (!isVisible) return null
|
||||
|
||||
const { children, className } = this.props
|
||||
let { style } = this.props
|
||||
|
||||
if (typeof children === 'string' && style == null && className == null) {
|
||||
style = { opacity: '0.333' }
|
||||
} else if (style == null) {
|
||||
style = {}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '0px',
|
||||
bottom: '0px',
|
||||
left: '0px',
|
||||
pointerEvents: 'none',
|
||||
...style
|
||||
}
|
||||
|
||||
return (
|
||||
<span contentEditable={false} className={className} style={styles}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
export default Placeholder
|
@@ -1,6 +1,5 @@
|
||||
|
||||
import Editor from './components/editor'
|
||||
import Placeholder from './components/placeholder'
|
||||
import findDOMNode from './utils/find-dom-node'
|
||||
import findDOMRange from './utils/find-dom-range'
|
||||
import findNode from './utils/find-node'
|
||||
@@ -17,7 +16,6 @@ import setEventTransfer from './utils/set-event-transfer'
|
||||
|
||||
export {
|
||||
Editor,
|
||||
Placeholder,
|
||||
findDOMNode,
|
||||
findDOMRange,
|
||||
findNode,
|
||||
@@ -29,7 +27,6 @@ export {
|
||||
|
||||
export default {
|
||||
Editor,
|
||||
Placeholder,
|
||||
findDOMNode,
|
||||
findDOMRange,
|
||||
findNode,
|
||||
|
@@ -4,12 +4,13 @@ import Debug from 'debug'
|
||||
import Plain from 'slate-plain-serializer'
|
||||
import React from 'react'
|
||||
import getWindow from 'get-window'
|
||||
import { Block, Inline, coreSchema } from 'slate'
|
||||
import { Block, Inline, Text, coreSchema } from 'slate'
|
||||
|
||||
import EVENT_HANDLERS from '../constants/event-handlers'
|
||||
import HOTKEYS from '../constants/hotkeys'
|
||||
import Content from '../components/content'
|
||||
import Placeholder from '../components/placeholder'
|
||||
import DefaultNode from '../components/default-node'
|
||||
import DefaultPlaceholder from '../components/default-placeholder'
|
||||
import findDOMNode from '../utils/find-dom-node'
|
||||
import findNode from '../utils/find-node'
|
||||
import findPoint from '../utils/find-point'
|
||||
@@ -31,19 +32,10 @@ const debug = Debug('slate:core:after')
|
||||
* The after plugin.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @property {Element} placeholder
|
||||
* @property {String} placeholderClassName
|
||||
* @property {Object} placeholderStyle
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function AfterPlugin(options = {}) {
|
||||
const {
|
||||
placeholder,
|
||||
placeholderClassName,
|
||||
placeholderStyle,
|
||||
} = options
|
||||
|
||||
let isDraggingInternally = null
|
||||
|
||||
/**
|
||||
@@ -726,55 +718,6 @@ function AfterPlugin(options = {}) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A default schema rule to render block nodes.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const BLOCK_RENDER_RULE = {
|
||||
match: (node) => {
|
||||
return node.kind == 'block'
|
||||
},
|
||||
render: (props) => {
|
||||
return (
|
||||
<div {...props.attributes} style={{ position: 'relative' }}>
|
||||
{props.children}
|
||||
{placeholder
|
||||
? <Placeholder
|
||||
className={placeholderClassName}
|
||||
node={props.node}
|
||||
parent={props.state.document}
|
||||
state={props.state}
|
||||
style={placeholderStyle}
|
||||
>
|
||||
{placeholder}
|
||||
</Placeholder>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A default schema rule to render inline nodes.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const INLINE_RENDER_RULE = {
|
||||
match: (node) => {
|
||||
return node.kind == 'inline'
|
||||
},
|
||||
render: (props) => {
|
||||
return (
|
||||
<span {...props.attributes} style={{ position: 'relative' }}>
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default rendering rules to the schema.
|
||||
*
|
||||
@@ -783,8 +726,14 @@ function AfterPlugin(options = {}) {
|
||||
|
||||
const schema = {
|
||||
rules: [
|
||||
BLOCK_RENDER_RULE,
|
||||
INLINE_RENDER_RULE
|
||||
{
|
||||
match: obj => obj.kind == 'block' || obj.kind == 'inline',
|
||||
render: DefaultNode,
|
||||
},
|
||||
{
|
||||
match: obj => obj.kind == 'block' && Text.isTextList(obj.nodes) && obj.text == '',
|
||||
placeholder: DefaultPlaceholder,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user