mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-30 10:29:48 +02:00
add plugins example, fix soft break handling
This commit is contained in:
@@ -14,6 +14,7 @@ import Images from './images'
|
|||||||
import Links from './links'
|
import Links from './links'
|
||||||
import PasteHtml from './paste-html'
|
import PasteHtml from './paste-html'
|
||||||
import PlainText from './plain-text'
|
import PlainText from './plain-text'
|
||||||
|
import Plugins from './plugins'
|
||||||
import ReadOnly from './read-only'
|
import ReadOnly from './read-only'
|
||||||
import RichText from './rich-text'
|
import RichText from './rich-text'
|
||||||
import Tables from './tables'
|
import Tables from './tables'
|
||||||
@@ -69,7 +70,8 @@ class App extends React.Component {
|
|||||||
{this.renderTab('Tables', 'tables')}
|
{this.renderTab('Tables', 'tables')}
|
||||||
{this.renderTab('Code Highlighting', 'code-highlighting')}
|
{this.renderTab('Code Highlighting', 'code-highlighting')}
|
||||||
{this.renderTab('Paste HTML', 'paste-html')}
|
{this.renderTab('Paste HTML', 'paste-html')}
|
||||||
{this.renderTab('Read Only', 'read-only')}
|
{this.renderTab('Read-only', 'read-only')}
|
||||||
|
{this.renderTab('Plugins', 'plugins')}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -120,6 +122,7 @@ const router = (
|
|||||||
<Route path="links" component={Links} />
|
<Route path="links" component={Links} />
|
||||||
<Route path="paste-html" component={PasteHtml} />
|
<Route path="paste-html" component={PasteHtml} />
|
||||||
<Route path="plain-text" component={PlainText} />
|
<Route path="plain-text" component={PlainText} />
|
||||||
|
<Route path="plugins" component={Plugins} />
|
||||||
<Route path="read-only" component={ReadOnly} />
|
<Route path="read-only" component={ReadOnly} />
|
||||||
<Route path="rich-text" component={RichText} />
|
<Route path="rich-text" component={RichText} />
|
||||||
<Route path="tables" component={Tables} />
|
<Route path="tables" component={Tables} />
|
||||||
|
8
examples/plugins/Readme.md
Normal file
8
examples/plugins/Readme.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
# Plain Text Example
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This is the most basic Slate example. It's basically a glorified `<textarea>`. But it gives you a sense for the absolute basics of Slate.
|
||||||
|
|
||||||
|
Check out the [Examples readme](..) to see how to run it!
|
77
examples/plugins/index.js
Normal file
77
examples/plugins/index.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
|
||||||
|
import { Editor, Plain } from '../..'
|
||||||
|
import React from 'react'
|
||||||
|
import AutoReplaceText from 'slate-auto-replace-text'
|
||||||
|
import CollapseOnEscape from 'slate-collapse-on-escape'
|
||||||
|
import SoftBreak from 'slate-soft-break'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugins.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
AutoReplaceText('(c)', '©'),
|
||||||
|
AutoReplaceText('(r)', '®'),
|
||||||
|
AutoReplaceText('(tm)', '™'),
|
||||||
|
CollapseOnEscape(),
|
||||||
|
SoftBreak()
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The plugins example.
|
||||||
|
*
|
||||||
|
* @type {Component}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Plugins extends React.Component {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize the initial editor state.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
state = {
|
||||||
|
state: Plain.deserialize(`This example shows how you can extend Slate with plugins! It uses three fairly simple plugins, but you can use any plugins you want, or write your own!
|
||||||
|
|
||||||
|
The first is an "auto replacer". Try typing "(c)" and you'll see it turn into a copyright symbol automatically!
|
||||||
|
|
||||||
|
The second is a simple plugin to collapse the selection whenever the escape key is pressed. Try selecting some text and pressing escape.
|
||||||
|
|
||||||
|
And the third is another simple plugin that inserts a "soft" break when enter is pressed instead of creating a new block. Try pressing enter!`)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On change.
|
||||||
|
*
|
||||||
|
* @param {State} state
|
||||||
|
*/
|
||||||
|
|
||||||
|
onChange = (state) => {
|
||||||
|
this.setState({ state })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the editor.
|
||||||
|
*
|
||||||
|
* @return {Component} component
|
||||||
|
*/
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
placeholder={'Enter some text...'}
|
||||||
|
plugins={plugins}
|
||||||
|
state={this.state.state}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default Plugins
|
@@ -422,13 +422,19 @@ class Content extends React.Component {
|
|||||||
const { selection } = state
|
const { selection } = state
|
||||||
const native = window.getSelection()
|
const native = window.getSelection()
|
||||||
const { anchorNode, anchorOffset, focusOffset } = native
|
const { anchorNode, anchorOffset, focusOffset } = native
|
||||||
const { textContent } = anchorNode
|
let { textContent } = anchorNode
|
||||||
const offsetKey = OffsetKey.findKey(anchorNode)
|
const offsetKey = OffsetKey.findKey(anchorNode)
|
||||||
const { key, index } = OffsetKey.parse(offsetKey)
|
const { key, index } = OffsetKey.parse(offsetKey)
|
||||||
const { start, end } = OffsetKey.findBounds(key, index, state)
|
const { start, end } = OffsetKey.findBounds(key, index, state)
|
||||||
const range = OffsetKey.findRange(anchorNode, state)
|
const range = OffsetKey.findRange(anchorNode, state)
|
||||||
const { text, marks } = range
|
const { text, marks } = range
|
||||||
|
|
||||||
|
// COMPAT: If the DOM text ends in a new line, we will have added one to
|
||||||
|
// account for browsers collapsing a single one, so remove it.
|
||||||
|
if (textContent.charAt(textContent.length - 1) == '\n') {
|
||||||
|
textContent = textContent.slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
// If the text is no different, abort.
|
// If the text is no different, abort.
|
||||||
if (textContent == text) return
|
if (textContent == text) return
|
||||||
|
|
||||||
|
@@ -133,22 +133,12 @@ class Leaf extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { node, index, text, marks, renderMark } = this.props
|
const { node, index } = this.props
|
||||||
const offsetKey = OffsetKey.stringify({
|
const offsetKey = OffsetKey.stringify({
|
||||||
key: node.key,
|
key: node.key,
|
||||||
index
|
index
|
||||||
})
|
})
|
||||||
|
|
||||||
const style = marks.reduce((memo, mark) => {
|
|
||||||
const styles = renderMark(mark, marks)
|
|
||||||
|
|
||||||
for (const key in styles) {
|
|
||||||
memo[key] = styles[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
return memo
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
// Increment the renders key, which forces a re-render whenever this
|
// Increment the renders key, which forces a re-render whenever this
|
||||||
// component is told it should update. This is required because "native"
|
// component is told it should update. This is required because "native"
|
||||||
// renders where we don't update the leaves cause React's internal state to
|
// renders where we don't update the leaves cause React's internal state to
|
||||||
@@ -159,13 +149,35 @@ class Leaf extends React.Component {
|
|||||||
<span
|
<span
|
||||||
key={this.tmp.renders}
|
key={this.tmp.renders}
|
||||||
data-offset-key={offsetKey}
|
data-offset-key={offsetKey}
|
||||||
style={style}
|
style={this.renderStyle()}
|
||||||
>
|
>
|
||||||
{text || <br />}
|
{this.renderText()}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderText() {
|
||||||
|
const { text } = this.props
|
||||||
|
if (!text) return <br />
|
||||||
|
if (text.charAt(text.length - 1) == '\n') return `${text}\n`
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStyle() {
|
||||||
|
const { marks, renderMark } = this.props
|
||||||
|
const style = marks.reduce((memo, mark) => {
|
||||||
|
const styles = renderMark(mark, marks)
|
||||||
|
|
||||||
|
for (const key in styles) {
|
||||||
|
memo[key] = styles[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
return memo
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -49,6 +49,9 @@
|
|||||||
"react-router": "^2.5.1",
|
"react-router": "^2.5.1",
|
||||||
"read-metadata": "^1.0.0",
|
"read-metadata": "^1.0.0",
|
||||||
"selection-position": "^1.0.0",
|
"selection-position": "^1.0.0",
|
||||||
|
"slate-auto-replace-text": "^0.2.0",
|
||||||
|
"slate-collapse-on-escape": "^0.1.0",
|
||||||
|
"slate-soft-break": "^0.1.0",
|
||||||
"source-map-support": "^0.4.0",
|
"source-map-support": "^0.4.0",
|
||||||
"to-camel-case": "^1.0.0",
|
"to-camel-case": "^1.0.0",
|
||||||
"to-title-case": "^1.0.0",
|
"to-title-case": "^1.0.0",
|
||||||
|
Reference in New Issue
Block a user