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

add syncing-operations example

This commit is contained in:
Ian Storm Taylor
2017-10-13 16:24:00 -07:00
parent 70a008c178
commit a1d2223e36
5 changed files with 391 additions and 3 deletions

View File

@@ -21,6 +21,7 @@ import RTL from './rtl'
import ReadOnly from './read-only'
import RichText from './rich-text'
import SearchHighlighting from './search-highlighting'
import SyncingOperations from './syncing-operations'
import Tables from './tables'
/**
@@ -44,11 +45,12 @@ const EXAMPLES = [
['Tables', Tables, '/tables'],
['Paste HTML', PasteHtml, '/paste-html'],
['Search Highlighting', SearchHighlighting, '/search-highlighting'],
['Syncing Operations', SyncingOperations, '/syncing-operations'],
['Read-only', ReadOnly, '/read-only'],
['RTL', RTL, '/rtl'],
['Plugins', Plugins, '/plugins'],
['Forced Layout', ForcedLayout, '/forced-layout'],
['Huge', HugeDocument, '/huge-document'],
['Huge Document', HugeDocument, '/huge-document'],
]
/**

View File

@@ -51,7 +51,7 @@ const schema = {
* @type {Component}
*/
class RichText extends React.Component {
class RichTextExample extends React.Component {
/**
* Deserialize the initial editor state.
@@ -305,4 +305,4 @@ class RichText extends React.Component {
* Export.
*/
export default RichText
export default RichTextExample

View File

@@ -0,0 +1,8 @@
# Rich Text Example
![](../../docs/images/rich-text-example.png)
This example shows you can add a very different concepts together: key commands, toolbars, and custom formatting, to get the functionality you'd expect from a rich text editor. Of course this is just the beginning, you can layer in whatever other behaviors you want!
Check out the [Examples readme](..) to see how to run it!

View File

@@ -0,0 +1,300 @@
import { Editor } from 'slate-react'
import { State } from 'slate'
import React from 'react'
import initialState from './state.json'
/**
* Define a schema.
*
* @type {Object}
*/
const schema = {
marks: {
bold: {
fontWeight: 'bold'
},
code: {
fontFamily: 'monospace',
backgroundColor: '#eee',
padding: '3px',
borderRadius: '4px'
},
italic: {
fontStyle: 'italic'
},
underlined: {
textDecoration: 'underline'
}
}
}
/**
* A simple editor component to demo syncing with.
*
* @type {Component}
*/
class SyncingEditor extends React.Component {
/**
* Deserialize the initial editor state.
*
* @type {Object}
*/
state = {
state: State.fromJSON(initialState),
}
/**
* When new `operations` are received from one of the other editors that is in
* sync with this one, apply them in a new change.
*
* @param {Array} operations
*/
applyOperations = (operations) => {
const { state } = this.state
const change = state.change().applyOperations(operations)
this.onChange(change, { remote: true })
}
/**
* Check if the current selection has a mark with `type` in it.
*
* @param {String} type
* @return {Boolean}
*/
hasMark = (type) => {
const { state } = this.state
return state.activeMarks.some(mark => mark.type == type)
}
/**
* On change, save the new `state`. And if it's a local change, call the
* passed-in `onChange` handler.
*
* @param {Change} change
* @param {Object} options
*/
onChange = (change, options = {}) => {
this.setState({ state: change.state })
if (!options.remote) {
this.props.onChange(change)
}
}
/**
* On key down, if it's a formatting command toggle a mark.
*
* @param {Event} e
* @param {Object} data
* @param {Change} change
* @return {Change}
*/
onKeyDown = (e, data, change) => {
if (!data.isMod) return
let mark
switch (data.key) {
case 'b':
mark = 'bold'
break
case 'i':
mark = 'italic'
break
case 'u':
mark = 'underlined'
break
case '`':
mark = 'code'
break
default:
return
}
e.preventDefault()
change.toggleMark(mark)
return true
}
/**
* When a mark button is clicked, toggle the current mark.
*
* @param {Event} e
* @param {String} type
*/
onClickMark = (e, type) => {
e.preventDefault()
const { state } = this.state
const change = state.change().toggleMark(type)
this.onChange(change)
}
/**
* Render.
*
* @return {Element}
*/
render() {
return (
<div>
{this.renderToolbar()}
{this.renderEditor()}
</div>
)
}
/**
* Render the toolbar.
*
* @return {Element}
*/
renderToolbar = () => {
return (
<div className="menu toolbar-menu">
{this.renderButton('bold', 'format_bold')}
{this.renderButton('italic', 'format_italic')}
{this.renderButton('underlined', 'format_underlined')}
{this.renderButton('code', 'code')}
</div>
)
}
/**
* Render a mark-toggling toolbar button.
*
* @param {String} type
* @param {String} icon
* @return {Element}
*/
renderButton = (type, icon) => {
const isActive = this.hasMark(type)
const onMouseDown = e => this.onClickMark(e, type)
return (
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
<span className="material-icons">{icon}</span>
</span>
)
}
/**
* Render the editor.
*
* @return {Element}
*/
renderEditor = () => {
return (
<div className="editor">
<Editor
state={this.state.state}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
schema={schema}
placeholder={'Enter some rich text...'}
spellCheck
/>
</div>
)
}
}
/**
* The syncing operations example.
*
* @type {Component}
*/
class SyncingOperationsExample extends React.Component {
/**
* Save a reference to editor `one`.
*
* @param {SyncingEditor} one
*/
oneRef = (one) => {
this.one = one
}
/**
* Save a reference to editor `two`.
*
* @param {SyncingEditor} two
*/
twoRef = (two) => {
this.two = two
}
/**
* When editor one changes, send document-alterting operations to edtior two.
*
* @param {Array} operations
*/
onOneChange = (change) => {
const ops = change.operations.filter(o => o.type != 'set_selection' && o.type != 'set_state')
this.two.applyOperations(ops)
}
/**
* When editor two changes, send document-alterting operations to edtior one.
*
* @param {Array} operations
*/
onTwoChange = (change) => {
const ops = change.operations.filter(o => o.type != 'set_selection' && o.type != 'set_state')
this.one.applyOperations(ops)
}
/**
* Render both editors.
*
* @return {Element}
*/
render() {
return (
<div>
<SyncingEditor
ref={this.oneRef}
onChange={this.onOneChange}
/>
<div
style={{
height: '20px',
backgroundColor: '#eee',
margin: '20px -20px',
}}
/>
<SyncingEditor
ref={this.twoRef}
onChange={this.onTwoChange}
/>
</div>
)
}
}
/**
* Export.
*/
export default SyncingOperationsExample

View File

@@ -0,0 +1,78 @@
{
"document": {
"nodes": [
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "These two editors are kept "
},
{
"text": "in sync",
"marks": [
{
"type": "bold"
}
]
},
{
"text": " with one another as you type!"
}
]
}
]
},
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "They achieve this by sending any document-altering operations to each other whenever a change occurs, and then applying them locally with "
},
{
"text": "change.applyOperations()",
"marks": [
{
"type": "code"
}
]
},
{
"text": "."
}
]
}
]
},
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "Note: ",
"marks": [
{
"type": "italic"
}
]
},
{
"text": "this example doesn't showcase operational transforms or network communication, which are required for realtime editing with multiple people at once."
}
]
}
]
}
]
}
}