1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-10 17:24:02 +02:00

Remove renderPlaceholder (#2370)

* add placeholder plugin in slate-react

* remove renderPlaceholder logic

* extract placeholder into a plugin

* remove other old placeholder-based logic

* update changelogs
This commit is contained in:
Ian Storm Taylor
2018-11-02 17:04:20 -07:00
committed by GitHub
parent 0cf94c73c9
commit a09c3c2fdf
38 changed files with 394 additions and 225 deletions

View File

@@ -2,7 +2,7 @@
One of the best parts of Slate is that it's built with React, so it fits right into your existing application. It doesn't re-invent its own view layer that you have to learn. It tries to keep everything as React-y as possible. One of the best parts of Slate is that it's built with React, so it fits right into your existing application. It doesn't re-invent its own view layer that you have to learn. It tries to keep everything as React-y as possible.
To that end, Slate gives you control over the rendering behavior of every node and mark in your document, any placeholders you want to render, and even the top-level editor itself. To that end, Slate gives you control over the rendering behavior of every node and mark in your document, and even the top-level editor itself.
You can define these behaviors by passing `props` into the editor, or you can define them in Slate plugins. You can define these behaviors by passing `props` into the editor, or you can define them in Slate plugins.
@@ -100,37 +100,6 @@ That way, if you happen to have a global stylesheet that defines `strong`, `em`,
> 🤖 Be aware though that marks aren't guaranteed to be "contiguous". Which means even though a **word** is bolded, it's not guaranteed to render as a single `<strong>` element. If some of its characters are also italic, it might be split up into multiple elements—one `<strong>wo</strong>` and one `<em><strong>rd</strong></em>`. > 🤖 Be aware though that marks aren't guaranteed to be "contiguous". Which means even though a **word** is bolded, it's not guaranteed to render as a single `<strong>` element. If some of its characters are also italic, it might be split up into multiple elements—one `<strong>wo</strong>` and one `<em><strong>rd</strong></em>`.
## Placeholders
By default Slate will render a placeholder for you which mimics the native DOM `placeholder` attribute of `<input>` and `<textarea>` elements—it's in the same typeface as the editor, and it's slightly translucent. And as soon as the document has any content, the placeholder disappears.
However sometimes you want to customize things. Or maybe you want to render placeholders inside specific blocks like inside an image caption. To do that, you can define your own `renderPlaceholder` function:
```js
function renderPlaceholder(props, editor, next) {
const { node } = props
if (node.object != 'block') return next()
if (node.type != 'caption') return next()
if (node.text != '') return next()
return (
<span
contentEditable={false}
style={{ display: 'inline-block', width: '0', whiteSpace: 'nowrap', opacity: '0.33' }}
>
{editor.props.placeholder}
</span>
)
}
<Editor
renderPlaceholder={renderPlaceholder}
...
/>
```
That will render a simple placeholder element inside all of your `caption` blocks until someone decides to write in a caption.
## The Editor Itself ## The Editor Itself
Not only can you control the rendering behavior of the components inside the editor, but you can also control the rendering of the editor itself. Not only can you control the rendering behavior of the components inside the editor, but you can also control the rendering of the editor itself.

View File

@@ -56,7 +56,7 @@ A change handler that will be called with the `change` that applied the change.
`String || Element` `String || Element`
A placeholder string (or React element) that will be rendered as the default block type's placeholder. A placeholder string (or React element) that will be rendered if the document only contains a single empty block.
### `plugins` ### `plugins`

View File

@@ -24,7 +24,6 @@ In addition to the [core plugin hooks](../slate/plugins.md), when using `slate-r
renderEditor: Function, renderEditor: Function,
renderMark: Function, renderMark: Function,
renderNode: Function, renderNode: Function,
renderPlaceholder: Function,
shouldNodeComponentUpdate: Function, shouldNodeComponentUpdate: Function,
} }
``` ```
@@ -153,21 +152,6 @@ Render a `Node` with `props`. The `props` object contains:
You must spread the `props.attributes` onto the top-level DOM node you use to render the node. You must spread the `props.attributes` onto the top-level DOM node you use to render the node.
### `renderPlaceholder`
`Function renderPlaceholder(props: Object, editor: Editor, next: Function) => ReactNode|Void`
Render the placeholder that is shown when the editor has no `value`. The `props` object contains:
```js
{
editor: Editor,
readOnly: Boolean,
}
```
The `placeholder` prop that was passed to the editor can be found at `editor.props.placeholder`.
### `shouldNodeComponentUpdate` ### `shouldNodeComponentUpdate`
`Function shouldNodeComponentUpdate(previousProps: Object, props: Object, editor: Editor, next: Function) => Boolean|Void` `Function shouldNodeComponentUpdate(previousProps: Object, props: Object, editor: Editor, next: Function) => Boolean|Void`

View File

@@ -0,0 +1,9 @@
# Changelog
This document maintains a list of changes to the `slate-react-placeholder` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes won't be accounted for since the library is moving quickly.
---
### `0.1.0` — November 2, 2017
:tada:

View File

@@ -0,0 +1 @@
This package contains a set of React prop types for Slate values that you can use in your own components and plugins.

View File

@@ -0,0 +1,38 @@
{
"name": "slate-react-placeholder",
"description": "A Slate plugin to render a placeholder with React.",
"version": "0.0.0",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "lib/slate-react-placeholder.js",
"module": "lib/slate-react-placeholder.es.js",
"umd": "dist/slate-react-placeholder.js",
"umdMin": "dist/slate-react-placeholder.min.js",
"files": [
"dist/",
"lib/"
],
"peerDependencies": {
"react": ">=0.14.0",
"slate": ">=0.32.0"
},
"devDependencies": {
"mocha": "^2.5.3",
"slate": "^0.43.4",
"tiny-invariant": "^1.0.1"
},
"scripts": {
"clean": "rm -rf ./dist ./lib ./node_modules"
},
"umdGlobals": {
"react": "React",
"slate": "Slate"
},
"keywords": [
"editor",
"placeholder",
"react",
"render",
"slate"
]
}

View File

@@ -0,0 +1,100 @@
import invariant from 'tiny-invariant'
import React from 'react'
/**
* A plugin that renders a React placeholder for a given Slate node.
*
* @param {Object} options
* @return {Object}
*/
function SlateReactPlaceholder(options = {}) {
const { placeholder, when } = options
invariant(
placeholder,
'You must pass `SlateReactPlaceholder` an `options.placeholder` string.'
)
invariant(
when,
'You must pass `SlateReactPlaceholder` an `options.when` query.'
)
/**
* Decorate a match node with a placeholder mark when it fits the query.
*
* @param {Node} node
* @param {Editor} editor
* @param {Function} next
* @return {Array}
*/
function decorateNode(node, editor, next) {
if (!editor.query(when, node)) {
return next()
}
const others = next()
const first = node.getFirstText()
const last = node.getLastText()
const decoration = {
anchor: { key: first.key, offset: 0 },
focus: { key: last.key, offset: last.text.length },
mark: { type: 'placeholder' },
}
return [...others, decoration]
}
/**
* Render an inline placeholder for the placeholder mark.
*
* @param {Object} props
* @param {Editor} editor
* @param {Function} next
* @return {Element}
*/
function renderMark(props, editor, next) {
const { children, mark } = props
if (mark.type === 'placeholder') {
const style = {
pointerEvents: 'none',
display: 'inline-block',
width: '0',
maxWidth: '100%',
whiteSpace: 'nowrap',
opacity: '0.333',
}
return (
<React.Fragment>
<span contentEditable={false} style={style}>
{placeholder}
</span>
{children}
</React.Fragment>
)
}
return next()
}
/**
* Return the plugin.
*
* @return {Object}
*/
return { decorateNode, renderMark }
}
/**
* Export.
*
* @type {Function}
*/
export default SlateReactPlaceholder

View File

@@ -4,6 +4,18 @@ This document maintains a list of changes to the `slate-react` package with each
--- ---
### `0.21.0` — November 2, 2018
###### NEW
**Introducing the `slate-react-placeholder` package.** This new package is what handles the default `placeholder=` prop logic for the editor, and it can be used yourself for situations where you want to render browser-like placeholders in custom nodes.
###### BREAKING
**The `renderPlacehodler` middleware has been removed.** Previously this was how you rendered custom placeholders in the editor, but that logic can now be implemented with `decorateNode` instead, in a way that causes less confusion and overlap in the API. The new `slate-react-placeholder` package does exactly that, adding a decoration to the editor when it is empty.
---
### `0.20.0` — October 27, 2018 ### `0.20.0` — October 27, 2018
###### BREAKING ###### BREAKING

View File

@@ -26,6 +26,7 @@
"slate-hotkeys": "^0.2.7", "slate-hotkeys": "^0.2.7",
"slate-plain-serializer": "^0.6.21", "slate-plain-serializer": "^0.6.21",
"slate-prop-types": "^0.5.12", "slate-prop-types": "^0.5.12",
"slate-react-placeholder": "^0.0.0",
"tiny-invariant": "^1.0.1", "tiny-invariant": "^1.0.1",
"tiny-warning": "^0.0.3" "tiny-warning": "^0.0.3"
}, },

View File

@@ -63,6 +63,7 @@ class Editor extends React.Component {
autoCorrect: true, autoCorrect: true,
onChange: () => {}, onChange: () => {},
options: {}, options: {},
placeholder: '',
plugins: [], plugins: [],
readOnly: false, readOnly: false,
schema: {}, schema: {},
@@ -132,8 +133,8 @@ class Editor extends React.Component {
const props = { ...this.props, editor: this } const props = { ...this.props, editor: this }
// Re-resolve the controller if needed based on memoized props. // Re-resolve the controller if needed based on memoized props.
const { commands, plugins, queries, schema } = props const { commands, placeholder, plugins, queries, schema } = props
this.resolveController(plugins, schema, commands, queries) this.resolveController(plugins, schema, commands, queries, placeholder)
// Set the current props on the controller. // Set the current props on the controller.
const { options, readOnly, value } = props const { options, readOnly, value } = props
@@ -154,35 +155,38 @@ class Editor extends React.Component {
* @param {Object} schema * @param {Object} schema
* @param {Object} commands * @param {Object} commands
* @param {Object} queries * @param {Object} queries
* @param {String} placeholder
* @return {Editor} * @return {Editor}
*/ */
resolveController = memoizeOne((plugins = [], schema, commands, queries) => { resolveController = memoizeOne(
// If we've resolved a few times already, and it's exactly in line with (plugins = [], schema, commands, queries, placeholder) => {
// the updates, then warn the user that they may be doing something wrong. // If we've resolved a few times already, and it's exactly in line with
warning( // the updates, then warn the user that they may be doing something wrong.
this.tmp.resolves < 5 || this.tmp.resolves !== this.tmp.updates, warning(
'A Slate <Editor> component is re-resolving the `plugins`, `schema`, `commands` or `queries` on each update, which leads to poor performance. This is often due to passing in a new references for these props with each render by declaring them inline in your render function. Do not do this! Declare them outside your render function, or memoize them instead.' this.tmp.resolves < 5 || this.tmp.resolves !== this.tmp.updates,
) 'A Slate <Editor> component is re-resolving the `plugins`, `schema`, `commands`, `queries` or `placeholder` prop on each update, which leads to poor performance. This is often due to passing in a new references for these props with each render by declaring them inline in your render function. Do not do this! Declare them outside your render function, or memoize them instead.'
)
this.tmp.resolves++ this.tmp.resolves++
const react = ReactPlugin(this.props) const react = ReactPlugin(this.props)
const onChange = change => { const onChange = change => {
if (this.tmp.mounted) { if (this.tmp.mounted) {
this.props.onChange(change) this.props.onChange(change)
} else { } else {
this.tmp.change = change this.tmp.change = change
}
} }
this.controller = new Controller(
{ plugins: [react], onChange },
{ controller: this, construct: false }
)
this.controller.run('onConstruct')
} }
)
this.controller = new Controller(
{ plugins: [react], onChange },
{ controller: this, construct: false }
)
this.controller.run('onConstruct')
})
/** /**
* Mimic the API of the `Editor` controller, so that this component instance * Mimic the API of the `Editor` controller, so that this component instance

View File

@@ -86,7 +86,11 @@ class Leaf extends React.Component {
index, index,
}) })
return <span data-offset-key={offsetKey}>{this.renderMarks()}</span> return (
<span data-slate-leaf data-offset-key={offsetKey}>
{this.renderMarks()}
</span>
)
} }
/** /**
@@ -99,7 +103,7 @@ class Leaf extends React.Component {
const { marks, node, offset, text, editor } = this.props const { marks, node, offset, text, editor } = this.props
const leaf = this.renderText() const leaf = this.renderText()
const attributes = { const attributes = {
'data-slate-leaf': true, 'data-slate-mark': true,
} }
return marks.reduce((children, mark) => { return marks.reduce((children, mark) => {
@@ -165,8 +169,8 @@ class Leaf extends React.Component {
const isLastLeaf = index === leaves.size - 1 const isLastLeaf = index === leaves.size - 1
if (isLastText && isLastLeaf && lastChar === '\n') return `${text}\n` if (isLastText && isLastLeaf && lastChar === '\n') return `${text}\n`
// Otherwise, just return the text. // Otherwise, just return the content.
return text return <span data-slate-content>{text}</span>
} }
} }

View File

@@ -134,8 +134,7 @@ class Node extends React.Component {
const indexes = node.getSelectionIndexes(selection, isSelected) const indexes = node.getSelectionIndexes(selection, isSelected)
const decs = decorations.concat(node.getDecorations(editor)) const decs = decorations.concat(node.getDecorations(editor))
const childrenDecorations = getChildrenDecorations(node, decs) const childrenDecorations = getChildrenDecorations(node, decs)
const children = []
let children = []
node.nodes.forEach((child, i) => { node.nodes.forEach((child, i) => {
const isChildSelected = !!indexes && indexes.start <= i && i < indexes.end const isChildSelected = !!indexes && indexes.start <= i && i < indexes.end
@@ -166,16 +165,6 @@ class Node extends React.Component {
readOnly, readOnly,
} }
let placeholder = editor.run('renderPlaceholder', props)
if (placeholder) {
placeholder = React.cloneElement(placeholder, {
key: `${node.key}-placeholder`,
})
children = [placeholder, ...children]
}
const element = editor.run('renderNode', { const element = editor.run('renderNode', {
...props, ...props,
attributes, attributes,

View File

@@ -725,7 +725,7 @@ function AfterPlugin(options = {}) {
/** /**
* Export. * Export.
* *
* @type {Object} * @type {Function}
*/ */
export default AfterPlugin export default AfterPlugin

View File

@@ -116,14 +116,6 @@ function BeforePlugin() {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
if (compositionCount > n) return if (compositionCount > n) return
isComposing = false isComposing = false
// HACK: we need to re-render the editor here so that it will update its
// placeholder in case one is currently rendered. This should be handled
// differently ideally, in a less invasive way?
// (apply force re-render if isComposing changes)
if (editor.state.isComposing) {
editor.setState({ isComposing: false })
}
}) })
debug('onCompositionEnd', { event }) debug('onCompositionEnd', { event })
@@ -155,14 +147,6 @@ function BeforePlugin() {
isComposing = true isComposing = true
compositionCount++ compositionCount++
// HACK: we need to re-render the editor here so that it will update its
// placeholder in case one is currently rendered. This should be handled
// differently ideally, in a less invasive way?
// (apply force re-render if isComposing changes)
if (!editor.state.isComposing) {
editor.setState({ isComposing: true })
}
const { value } = editor const { value } = editor
const { selection } = value const { selection } = value
@@ -502,7 +486,7 @@ function BeforePlugin() {
/** /**
* Export. * Export.
* *
* @type {Object} * @type {Function}
*/ */
export default BeforePlugin export default BeforePlugin

View File

@@ -18,7 +18,7 @@ function DOMPlugin(options = {}) {
/** /**
* Export. * Export.
* *
* @type {Object} * @type {Function}
*/ */
export default DOMPlugin export default DOMPlugin

View File

@@ -1,5 +1,5 @@
import PlaceholderPlugin from 'slate-react-placeholder'
import React from 'react' import React from 'react'
import { Text } from 'slate'
import DOMPlugin from './dom' import DOMPlugin from './dom'
import Content from '../components/content' import Content from '../components/content'
@@ -19,7 +19,6 @@ const PROPS = [
'renderEditor', 'renderEditor',
'renderMark', 'renderMark',
'renderNode', 'renderNode',
'renderPlaceholder',
'schema', 'schema',
] ]
@@ -31,14 +30,28 @@ const PROPS = [
*/ */
function ReactPlugin(options = {}) { function ReactPlugin(options = {}) {
const { plugins = [] } = options const { placeholder, plugins = [] } = options
/**
* Decorate node.
*
* @param {Object} node
* @param {Editor} editor
* @param {Function} next
* @return {Array}
*/
function decorateNode(node, editor, next) {
return []
}
/** /**
* Render editor. * Render editor.
* *
* @param {Object} props * @param {Object} props
* @param {Editor} editor
* @param {Function} next * @param {Function} next
* @return {Object} * @return {Element}
*/ */
function renderEditor(props, editor, next) { function renderEditor(props, editor, next) {
@@ -81,62 +94,49 @@ function ReactPlugin(options = {}) {
) )
} }
/**
* Render placeholder.
*
* @param {Object} props
* @param {Function} next
* @return {Element}
*/
function renderPlaceholder(props, editor, next) {
const { node } = props
if (!editor.props.placeholder) return null
if (editor.state.isComposing) return null
if (node.object != 'block') return null
if (!Text.isTextList(node.nodes)) return null
if (node.text != '') return null
if (editor.value.document.getBlocks().size > 1) 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>
)
}
/** /**
* Return the plugins. * Return the plugins.
* *
* @type {Array} * @type {Array}
*/ */
const ret = []
const editorPlugin = PROPS.reduce((memo, prop) => { const editorPlugin = PROPS.reduce((memo, prop) => {
if (prop in options) memo[prop] = options[prop] if (prop in options) memo[prop] = options[prop]
return memo return memo
}, {}) }, {})
const domPlugin = DOMPlugin({ ret.push(
plugins: [editorPlugin, ...plugins], DOMPlugin({
plugins: [editorPlugin, ...plugins],
})
)
if (placeholder) {
ret.push(
PlaceholderPlugin({
placeholder,
when: (editor, node) =>
node.object === 'document' &&
node.text === '' &&
node.nodes.size === 1,
})
)
}
ret.push({
decorateNode,
renderEditor,
renderNode,
}) })
const defaultsPlugin = { renderEditor, renderNode, renderPlaceholder } return ret
return [domPlugin, defaultsPlugin]
} }
/** /**
* Export. * Export.
* *
* @type {Object} * @type {Function}
*/ */
export default ReactPlugin export default ReactPlugin

View File

@@ -11,24 +11,25 @@ import findDOMNode from './find-dom-node'
function findDOMPoint(point, win = window) { function findDOMPoint(point, win = window) {
const el = findDOMNode(point.key, win) const el = findDOMNode(point.key, win)
let start = 0 let start = 0
let n
// COMPAT: In IE, this method's arguments are not optional, so we have to // For each leaf, we need to isolate its content, which means filtering to its
// pass in all four even though the last two are defaults. (2017/10/25) // direct text and zero-width spans. (We have to filter out any other siblings
const iterator = win.document.createNodeIterator( // that may have been rendered alongside them.)
el, const texts = Array.from(
NodeFilter.SHOW_TEXT, el.querySelectorAll('[data-slate-content], [data-slate-zero-width]')
() => NodeFilter.FILTER_ACCEPT,
false
) )
while ((n = iterator.nextNode())) { for (const text of texts) {
const { length } = n.textContent const node = text.childNodes[0]
// Account zero-width spaces which shouldn't really take up space, because
// we only render them when the text is empty.
const length = node.textContent === '\uFEFF' ? 0 : node.textContent.length
const end = start + length const end = start + length
if (point.offset <= end) { if (point.offset <= end) {
const o = point.offset - start const offset = Math.max(0, point.offset - start)
return { node: n, offset: o >= 0 ? o : 0 } return { node, offset }
} }
start = end start = end

View File

@@ -62,7 +62,7 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>
@@ -70,7 +70,7 @@ export const output = `
<div data-slate-void="true"> <div data-slate-void="true">
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute"> <div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
@@ -81,7 +81,7 @@ export const output = `
</div> </div>
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>
@@ -89,7 +89,7 @@ export const output = `
<div data-slate-void="true"> <div data-slate-void="true">
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute"> <div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>

View File

@@ -58,7 +58,7 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>
@@ -66,7 +66,7 @@ export const output = `
<div data-slate-void="true"> <div data-slate-void="true">
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute"> <div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
@@ -77,7 +77,7 @@ export const output = `
</div> </div>
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>
@@ -85,7 +85,7 @@ export const output = `
<div data-slate-void="true"> <div data-slate-void="true">
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute"> <div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>

View File

@@ -39,21 +39,27 @@ export const output = `
<pre> <pre>
<code> <code>
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</code> </code>
</pre> </pre>
<pre> <pre>
<code> <code>
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</code> </code>
</pre> </pre>
<pre> <pre>
<code> <code>
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</code> </code>
</pre> </pre>

View File

@@ -58,7 +58,7 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>
@@ -66,7 +66,7 @@ export const output = `
<div data-slate-void="true"> <div data-slate-void="true">
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute"> <div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
@@ -77,7 +77,7 @@ export const output = `
</div> </div>
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>
@@ -85,7 +85,7 @@ export const output = `
<div data-slate-void="true"> <div data-slate-void="true">
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute"> <div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>

View File

@@ -45,7 +45,7 @@ export const output = `
<div data-slate-void="true"> <div data-slate-void="true">
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute"> <div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>

View File

@@ -35,7 +35,9 @@ export const output = `
<pre> <pre>
<code> <code>
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</code> </code>
</pre> </pre>

View File

@@ -52,9 +52,17 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span>o</span> <span data-slate-leaf="true">
<span><strong data-slate-leaf="true">n</strong></span> <span data-slate-content="true">o</span>
<span>e</span> </span>
<span data-slate-leaf="true">
<strong data-slate-mark="true">
<span data-slate-content="true">n</span>
</strong>
</span>
<span data-slate-leaf="true">
<span data-slate-content="true">e</span>
</span>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -44,37 +44,43 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
<a href="https://google.com"> <a href="https://google.com">
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</a> </a>
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
<a href="https://google.com"> <a href="https://google.com">
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</a> </a>
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
<a href="https://google.com"> <a href="https://google.com">
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</a> </a>
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>

View File

@@ -45,14 +45,14 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
<span data-slate-void="true" contenteditable="false"> <span data-slate-void="true" contenteditable="false">
<span data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute"> <span data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
@@ -62,7 +62,7 @@ export const output = `
</span> </span>
</span> </span>
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>

View File

@@ -40,17 +40,19 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
<a href="https://google.com"> <a href="https://google.com">
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</a> </a>
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>

View File

@@ -34,9 +34,17 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span>one</span> <span data-slate-leaf="true">
<span><strong data-slate-leaf="true">two</strong></span> <span data-slate-content="true">one</span>
<span>three</span> </span>
<span data-slate-leaf="true">
<strong data-slate-mark="true">
<span data-slate-content="true">two</span>
</strong>
</span>
<span data-slate-leaf="true">
<span data-slate-content="true">three</span>
</span>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -20,17 +20,19 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
<span style="position:relative"> <span style="position:relative">
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</span> </span>
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>

View File

@@ -16,7 +16,9 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span>word</span> <span data-slate-leaf="true">
<span data-slate-content="true">word</span>
</span>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -18,7 +18,7 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>

View File

@@ -20,12 +20,16 @@ export const output = `
<div style="position:relative"> <div style="position:relative">
<div dir="rtl" style="position:relative"> <div dir="rtl" style="position:relative">
<span> <span>
<span>مرحبا بالعالم</span> <span data-slate-leaf="true">
<span data-slate-content="true">مرحبا بالعالم</span>
</span>
</span> </span>
</div> </div>
<div dir="rtl" style="position:relative"> <div dir="rtl" style="position:relative">
<span> <span>
<span>שלום עולם</span> <span data-slate-leaf="true">
<span data-slate-content="true">שלום עולם</span>
</span>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -44,7 +44,7 @@ export const output = `
<div data-slate-editor="true"> <div data-slate-editor="true">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="z">&#xFEFF;</span> <span data-slate-zero-width="z">&#xFEFF;</span>
</span> </span>
</span> </span>
@@ -54,7 +54,7 @@ export const output = `
</span> </span>
</span> </span>
<span> <span>
<span> <span data-slate-leaf="true">
<span data-slate-zero-width="n">&#xFEFF;<br /></span> <span data-slate-zero-width="n">&#xFEFF;<br /></span>
</span> </span>
</span> </span>

View File

@@ -18,17 +18,23 @@ export const output = `
<div data-slate-editor="true" contenteditable="true" role="textbox"> <div data-slate-editor="true" contenteditable="true" role="textbox">
<div style="position:relative"> <div style="position:relative">
<span> <span>
<span>Hello, world!</span> <span data-slate-leaf="true">
<span data-slate-content="true">Hello, world!</span>
</span>
</span> </span>
</div> </div>
<div dir="rtl" style="position:relative"> <div dir="rtl" style="position:relative">
<span> <span>
<span>مرحبا بالعالم</span> <span data-slate-leaf="true">
<span data-slate-content="true">مرحبا بالعالم</span>
</span>
</span> </span>
</div> </div>
<div dir="rtl" style="position:relative"> <div dir="rtl" style="position:relative">
<span> <span>
<span>שלום עולם</span> <span data-slate-leaf="true">
<span data-slate-content="true">שלום עולם</span>
</span>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -448,7 +448,7 @@ class ElementInterface {
'As of Slate 0.42.0, the `node.getDecorations` method takes an `editor` instead of a `value`.' 'As of Slate 0.42.0, the `node.getDecorations` method takes an `editor` instead of a `value`.'
) )
const array = editor.run('decorateNode', this) || [] const array = editor.run('decorateNode', this)
const decorations = Decoration.createList(array) const decorations = Decoration.createList(array)
return decorations return decorations
} }

View File

@@ -108,6 +108,10 @@ class Text extends Record(DEFAULTS) {
throw new Error('leaves must be either Array or Immutable.List') throw new Error('leaves must be either Array or Immutable.List')
} }
if (leaves.size === 0) {
leaves = leaves.push(Leaf.create())
}
const node = new Text({ const node = new Text({
leaves: Leaf.createLeaves(leaves), leaves: Leaf.createLeaves(leaves),
key, key,
@@ -216,16 +220,33 @@ class Text extends Record(DEFAULTS) {
/** /**
* Derive the leaves for a list of `decorations`. * Derive the leaves for a list of `decorations`.
* *
* @param {Array|Void} decorations (optional) * @param {List} decorations (optional)
* @return {List<Leaf>} * @return {List<Leaf>}
*/ */
getLeaves(decorations = []) { getLeaves(decorations) {
let { leaves } = this let { leaves } = this
if (leaves.size === 0) return List.of(Leaf.create({}))
if (!decorations || decorations.length === 0) return leaves // PERF: We can exit early without decorations.
if (this.text.length === 0) return leaves if (!decorations || decorations.size === 0) return leaves
const { key } = this
// HACK: We shouldn't need this, because text nodes should never be in a
// position of not having any leaves...
if (leaves.size === 0) {
const marks = decorations.map(d => d.mark)
const leaf = Leaf.create({ marks })
return List([leaf])
}
// HACK: this shouldn't be necessary, because the loop below should handle
// the `0` case without failures. It may already even, not sure.
if (this.text.length === 0) {
const marks = decorations.map(d => d.mark)
const leaf = Leaf.create({ marks })
return List([leaf])
}
const { key, text } = this
decorations.forEach(dec => { decorations.forEach(dec => {
const { start, end, mark } = dec const { start, end, mark } = dec
@@ -234,12 +255,12 @@ class Text extends Record(DEFAULTS) {
if (hasStart && hasEnd) { if (hasStart && hasEnd) {
const index = hasStart ? start.offset : 0 const index = hasStart ? start.offset : 0
const length = hasEnd ? end.offset - index : this.text.length - index const length = hasEnd ? end.offset - index : text.length - index
if (length < 1) return if (length < 1) return
if (index >= this.text.length) return if (index >= text.length) return
if (index !== 0 || length < this.text.length) { if (index !== 0 || length < text.length) {
const [before, bundle] = Leaf.splitLeaves(leaves, index) const [before, bundle] = Leaf.splitLeaves(leaves, index)
const [middle, after] = Leaf.splitLeaves(bundle, length) const [middle, after] = Leaf.splitLeaves(bundle, length)
leaves = before.concat(middle.map(x => x.addMark(mark)), after) leaves = before.concat(middle.map(x => x.addMark(mark)), after)
@@ -614,19 +635,23 @@ class Text extends Record(DEFAULTS) {
*/ */
setLeaves(leaves) { setLeaves(leaves) {
const result = Leaf.createLeaves(leaves) leaves = Leaf.createLeaves(leaves)
if (result.size === 1) { if (leaves.size === 1) {
const first = result.first() const first = leaves.first()
if (!first.marks || first.marks.size === 0) { if (!first.marks || first.marks.size === 0) {
if (first.text === '') { if (first.text === '') {
return this.set('leaves', List()) return this.set('leaves', List([Leaf.create()]))
} }
} }
} }
return this.set('leaves', Leaf.createLeaves(leaves)) if (leaves.size === 0) {
leaves = leaves.push(Leaf.create())
}
return this.set('leaves', leaves)
} }
} }

View File

@@ -8,6 +8,7 @@ import slateHyperscript from '../../packages/slate-hyperscript/package.json'
import slatePlainSerializer from '../../packages/slate-plain-serializer/package.json' import slatePlainSerializer from '../../packages/slate-plain-serializer/package.json'
import slatePropTypes from '../../packages/slate-prop-types/package.json' import slatePropTypes from '../../packages/slate-prop-types/package.json'
import slateReact from '../../packages/slate-react/package.json' import slateReact from '../../packages/slate-react/package.json'
import slateReactPlaceholder from '../../packages/slate-react-placeholder/package.json'
const configurations = [ const configurations = [
...factory(slate), ...factory(slate),
@@ -19,6 +20,7 @@ const configurations = [
...factory(slatePlainSerializer), ...factory(slatePlainSerializer),
...factory(slatePropTypes), ...factory(slatePropTypes),
...factory(slateReact), ...factory(slateReact),
...factory(slateReactPlaceholder),
] ]
export default configurations export default configurations