mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-01-17 21:49:20 +01: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:
parent
0cf94c73c9
commit
a09c3c2fdf
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
@ -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>`.
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
@ -56,7 +56,7 @@ A change handler that will be called with the `change` that applied the change.
|
||||
|
||||
`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`
|
||||
|
||||
|
@ -24,7 +24,6 @@ In addition to the [core plugin hooks](../slate/plugins.md), when using `slate-r
|
||||
renderEditor: Function,
|
||||
renderMark: Function,
|
||||
renderNode: Function,
|
||||
renderPlaceholder: 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.
|
||||
|
||||
### `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`
|
||||
|
||||
`Function shouldNodeComponentUpdate(previousProps: Object, props: Object, editor: Editor, next: Function) => Boolean|Void`
|
||||
|
9
packages/slate-react-placeholder/Changelog.md
Normal file
9
packages/slate-react-placeholder/Changelog.md
Normal 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:
|
1
packages/slate-react-placeholder/Readme.md
Normal file
1
packages/slate-react-placeholder/Readme.md
Normal 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.
|
38
packages/slate-react-placeholder/package.json
Normal file
38
packages/slate-react-placeholder/package.json
Normal 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"
|
||||
]
|
||||
}
|
100
packages/slate-react-placeholder/src/index.js
Normal file
100
packages/slate-react-placeholder/src/index.js
Normal 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
|
0
packages/slate-react-placeholder/test/index.js
Normal file
0
packages/slate-react-placeholder/test/index.js
Normal 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
|
||||
|
||||
###### BREAKING
|
||||
|
@ -26,6 +26,7 @@
|
||||
"slate-hotkeys": "^0.2.7",
|
||||
"slate-plain-serializer": "^0.6.21",
|
||||
"slate-prop-types": "^0.5.12",
|
||||
"slate-react-placeholder": "^0.0.0",
|
||||
"tiny-invariant": "^1.0.1",
|
||||
"tiny-warning": "^0.0.3"
|
||||
},
|
||||
|
@ -63,6 +63,7 @@ class Editor extends React.Component {
|
||||
autoCorrect: true,
|
||||
onChange: () => {},
|
||||
options: {},
|
||||
placeholder: '',
|
||||
plugins: [],
|
||||
readOnly: false,
|
||||
schema: {},
|
||||
@ -132,8 +133,8 @@ class Editor extends React.Component {
|
||||
const props = { ...this.props, editor: this }
|
||||
|
||||
// Re-resolve the controller if needed based on memoized props.
|
||||
const { commands, plugins, queries, schema } = props
|
||||
this.resolveController(plugins, schema, commands, queries)
|
||||
const { commands, placeholder, plugins, queries, schema } = props
|
||||
this.resolveController(plugins, schema, commands, queries, placeholder)
|
||||
|
||||
// Set the current props on the controller.
|
||||
const { options, readOnly, value } = props
|
||||
@ -154,35 +155,38 @@ class Editor extends React.Component {
|
||||
* @param {Object} schema
|
||||
* @param {Object} commands
|
||||
* @param {Object} queries
|
||||
* @param {String} placeholder
|
||||
* @return {Editor}
|
||||
*/
|
||||
|
||||
resolveController = memoizeOne((plugins = [], schema, commands, queries) => {
|
||||
// If we've resolved a few times already, and it's exactly in line with
|
||||
// the updates, then warn the user that they may be doing something wrong.
|
||||
warning(
|
||||
this.tmp.resolves < 5 || this.tmp.resolves !== this.tmp.updates,
|
||||
'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.'
|
||||
)
|
||||
resolveController = memoizeOne(
|
||||
(plugins = [], schema, commands, queries, placeholder) => {
|
||||
// If we've resolved a few times already, and it's exactly in line with
|
||||
// the updates, then warn the user that they may be doing something wrong.
|
||||
warning(
|
||||
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++
|
||||
const react = ReactPlugin(this.props)
|
||||
this.tmp.resolves++
|
||||
const react = ReactPlugin(this.props)
|
||||
|
||||
const onChange = change => {
|
||||
if (this.tmp.mounted) {
|
||||
this.props.onChange(change)
|
||||
} else {
|
||||
this.tmp.change = change
|
||||
const onChange = change => {
|
||||
if (this.tmp.mounted) {
|
||||
this.props.onChange(change)
|
||||
} else {
|
||||
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
|
||||
|
@ -86,7 +86,11 @@ class Leaf extends React.Component {
|
||||
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 leaf = this.renderText()
|
||||
const attributes = {
|
||||
'data-slate-leaf': true,
|
||||
'data-slate-mark': true,
|
||||
}
|
||||
|
||||
return marks.reduce((children, mark) => {
|
||||
@ -165,8 +169,8 @@ class Leaf extends React.Component {
|
||||
const isLastLeaf = index === leaves.size - 1
|
||||
if (isLastText && isLastLeaf && lastChar === '\n') return `${text}\n`
|
||||
|
||||
// Otherwise, just return the text.
|
||||
return text
|
||||
// Otherwise, just return the content.
|
||||
return <span data-slate-content>{text}</span>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,8 +134,7 @@ class Node extends React.Component {
|
||||
const indexes = node.getSelectionIndexes(selection, isSelected)
|
||||
const decs = decorations.concat(node.getDecorations(editor))
|
||||
const childrenDecorations = getChildrenDecorations(node, decs)
|
||||
|
||||
let children = []
|
||||
const children = []
|
||||
|
||||
node.nodes.forEach((child, i) => {
|
||||
const isChildSelected = !!indexes && indexes.start <= i && i < indexes.end
|
||||
@ -166,16 +165,6 @@ class Node extends React.Component {
|
||||
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', {
|
||||
...props,
|
||||
attributes,
|
||||
|
@ -725,7 +725,7 @@ function AfterPlugin(options = {}) {
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default AfterPlugin
|
||||
|
@ -116,14 +116,6 @@ function BeforePlugin() {
|
||||
window.requestAnimationFrame(() => {
|
||||
if (compositionCount > n) return
|
||||
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 })
|
||||
@ -155,14 +147,6 @@ function BeforePlugin() {
|
||||
isComposing = true
|
||||
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 { selection } = value
|
||||
|
||||
@ -502,7 +486,7 @@ function BeforePlugin() {
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default BeforePlugin
|
||||
|
@ -18,7 +18,7 @@ function DOMPlugin(options = {}) {
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default DOMPlugin
|
||||
|
84
packages/slate-react/src/plugins/react.js
vendored
84
packages/slate-react/src/plugins/react.js
vendored
@ -1,5 +1,5 @@
|
||||
import PlaceholderPlugin from 'slate-react-placeholder'
|
||||
import React from 'react'
|
||||
import { Text } from 'slate'
|
||||
|
||||
import DOMPlugin from './dom'
|
||||
import Content from '../components/content'
|
||||
@ -19,7 +19,6 @@ const PROPS = [
|
||||
'renderEditor',
|
||||
'renderMark',
|
||||
'renderNode',
|
||||
'renderPlaceholder',
|
||||
'schema',
|
||||
]
|
||||
|
||||
@ -31,14 +30,28 @@ const PROPS = [
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Object}
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const ret = []
|
||||
const editorPlugin = PROPS.reduce((memo, prop) => {
|
||||
if (prop in options) memo[prop] = options[prop]
|
||||
return memo
|
||||
}, {})
|
||||
|
||||
const domPlugin = DOMPlugin({
|
||||
plugins: [editorPlugin, ...plugins],
|
||||
ret.push(
|
||||
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 [domPlugin, defaultsPlugin]
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default ReactPlugin
|
||||
|
@ -11,24 +11,25 @@ import findDOMNode from './find-dom-node'
|
||||
function findDOMPoint(point, win = window) {
|
||||
const el = findDOMNode(point.key, win)
|
||||
let start = 0
|
||||
let n
|
||||
|
||||
// COMPAT: In IE, this method's arguments are not optional, so we have to
|
||||
// pass in all four even though the last two are defaults. (2017/10/25)
|
||||
const iterator = win.document.createNodeIterator(
|
||||
el,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
() => NodeFilter.FILTER_ACCEPT,
|
||||
false
|
||||
// For each leaf, we need to isolate its content, which means filtering to its
|
||||
// direct text and zero-width spans. (We have to filter out any other siblings
|
||||
// that may have been rendered alongside them.)
|
||||
const texts = Array.from(
|
||||
el.querySelectorAll('[data-slate-content], [data-slate-zero-width]')
|
||||
)
|
||||
|
||||
while ((n = iterator.nextNode())) {
|
||||
const { length } = n.textContent
|
||||
for (const text of texts) {
|
||||
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
|
||||
|
||||
if (point.offset <= end) {
|
||||
const o = point.offset - start
|
||||
return { node: n, offset: o >= 0 ? o : 0 }
|
||||
const offset = Math.max(0, point.offset - start)
|
||||
return { node, offset }
|
||||
}
|
||||
|
||||
start = end
|
||||
|
@ -62,7 +62,7 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -70,7 +70,7 @@ export const output = `
|
||||
<div data-slate-void="true">
|
||||
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -81,7 +81,7 @@ export const output = `
|
||||
</div>
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -89,7 +89,7 @@ export const output = `
|
||||
<div data-slate-void="true">
|
||||
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -58,7 +58,7 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -66,7 +66,7 @@ export const output = `
|
||||
<div data-slate-void="true">
|
||||
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -77,7 +77,7 @@ export const output = `
|
||||
</div>
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -85,7 +85,7 @@ export const output = `
|
||||
<div data-slate-void="true">
|
||||
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -39,21 +39,27 @@ export const output = `
|
||||
<pre>
|
||||
<code>
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</code>
|
||||
</pre>
|
||||
<pre>
|
||||
<code>
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</code>
|
||||
</pre>
|
||||
<pre>
|
||||
<code>
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</code>
|
||||
</pre>
|
||||
|
@ -58,7 +58,7 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -66,7 +66,7 @@ export const output = `
|
||||
<div data-slate-void="true">
|
||||
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -77,7 +77,7 @@ export const output = `
|
||||
</div>
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -85,7 +85,7 @@ export const output = `
|
||||
<div data-slate-void="true">
|
||||
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -45,7 +45,7 @@ export const output = `
|
||||
<div data-slate-void="true">
|
||||
<div data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -35,7 +35,9 @@ export const output = `
|
||||
<pre>
|
||||
<code>
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</code>
|
||||
</pre>
|
||||
|
@ -52,9 +52,17 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>o</span>
|
||||
<span><strong data-slate-leaf="true">n</strong></span>
|
||||
<span>e</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">o</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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -44,37 +44,43 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
<a href="https://google.com">
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
<a href="https://google.com">
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
<a href="https://google.com">
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -45,14 +45,14 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
<span data-slate-void="true" contenteditable="false">
|
||||
<span data-slate-spacer="true" style="height:0;color:transparent;outline:none;position:absolute">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -62,7 +62,7 @@ export const output = `
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -40,17 +40,19 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
<a href="https://google.com">
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -34,9 +34,17 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>one</span>
|
||||
<span><strong data-slate-leaf="true">two</strong></span>
|
||||
<span>three</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">one</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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -20,17 +20,19 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
<span style="position:relative">
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -16,7 +16,9 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>word</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">word</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -20,12 +20,16 @@ export const output = `
|
||||
<div style="position:relative">
|
||||
<div dir="rtl" style="position:relative">
|
||||
<span>
|
||||
<span>مرحبا بالعالم</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">مرحبا بالعالم</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div dir="rtl" style="position:relative">
|
||||
<span>
|
||||
<span>שלום עולם</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">שלום עולם</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -44,7 +44,7 @@ export const output = `
|
||||
<div data-slate-editor="true">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="z"></span>
|
||||
</span>
|
||||
</span>
|
||||
@ -54,7 +54,7 @@ export const output = `
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-zero-width="n"><br /></span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -18,17 +18,23 @@ export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>Hello, world!</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">Hello, world!</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div dir="rtl" style="position:relative">
|
||||
<span>
|
||||
<span>مرحبا بالعالم</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">مرحبا بالعالم</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div dir="rtl" style="position:relative">
|
||||
<span>
|
||||
<span>שלום עולם</span>
|
||||
<span data-slate-leaf="true">
|
||||
<span data-slate-content="true">שלום עולם</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -448,7 +448,7 @@ class ElementInterface {
|
||||
'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)
|
||||
return decorations
|
||||
}
|
||||
|
@ -108,6 +108,10 @@ class Text extends Record(DEFAULTS) {
|
||||
throw new Error('leaves must be either Array or Immutable.List')
|
||||
}
|
||||
|
||||
if (leaves.size === 0) {
|
||||
leaves = leaves.push(Leaf.create())
|
||||
}
|
||||
|
||||
const node = new Text({
|
||||
leaves: Leaf.createLeaves(leaves),
|
||||
key,
|
||||
@ -216,16 +220,33 @@ class Text extends Record(DEFAULTS) {
|
||||
/**
|
||||
* Derive the leaves for a list of `decorations`.
|
||||
*
|
||||
* @param {Array|Void} decorations (optional)
|
||||
* @param {List} decorations (optional)
|
||||
* @return {List<Leaf>}
|
||||
*/
|
||||
|
||||
getLeaves(decorations = []) {
|
||||
getLeaves(decorations) {
|
||||
let { leaves } = this
|
||||
if (leaves.size === 0) return List.of(Leaf.create({}))
|
||||
if (!decorations || decorations.length === 0) return leaves
|
||||
if (this.text.length === 0) return leaves
|
||||
const { key } = this
|
||||
|
||||
// PERF: We can exit early without decorations.
|
||||
if (!decorations || decorations.size === 0) return leaves
|
||||
|
||||
// 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 => {
|
||||
const { start, end, mark } = dec
|
||||
@ -234,12 +255,12 @@ class Text extends Record(DEFAULTS) {
|
||||
|
||||
if (hasStart && hasEnd) {
|
||||
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 (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 [middle, after] = Leaf.splitLeaves(bundle, length)
|
||||
leaves = before.concat(middle.map(x => x.addMark(mark)), after)
|
||||
@ -614,19 +635,23 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
setLeaves(leaves) {
|
||||
const result = Leaf.createLeaves(leaves)
|
||||
leaves = Leaf.createLeaves(leaves)
|
||||
|
||||
if (result.size === 1) {
|
||||
const first = result.first()
|
||||
if (leaves.size === 1) {
|
||||
const first = leaves.first()
|
||||
|
||||
if (!first.marks || first.marks.size === 0) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import slateHyperscript from '../../packages/slate-hyperscript/package.json'
|
||||
import slatePlainSerializer from '../../packages/slate-plain-serializer/package.json'
|
||||
import slatePropTypes from '../../packages/slate-prop-types/package.json'
|
||||
import slateReact from '../../packages/slate-react/package.json'
|
||||
import slateReactPlaceholder from '../../packages/slate-react-placeholder/package.json'
|
||||
|
||||
const configurations = [
|
||||
...factory(slate),
|
||||
@ -19,6 +20,7 @@ const configurations = [
|
||||
...factory(slatePlainSerializer),
|
||||
...factory(slatePropTypes),
|
||||
...factory(slateReact),
|
||||
...factory(slateReactPlaceholder),
|
||||
]
|
||||
|
||||
export default configurations
|
||||
|
Loading…
x
Reference in New Issue
Block a user