mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-20 22:21:20 +02:00
add syncing-operations example
This commit is contained in:
@@ -21,6 +21,7 @@ import RTL from './rtl'
|
|||||||
import ReadOnly from './read-only'
|
import ReadOnly from './read-only'
|
||||||
import RichText from './rich-text'
|
import RichText from './rich-text'
|
||||||
import SearchHighlighting from './search-highlighting'
|
import SearchHighlighting from './search-highlighting'
|
||||||
|
import SyncingOperations from './syncing-operations'
|
||||||
import Tables from './tables'
|
import Tables from './tables'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,11 +45,12 @@ const EXAMPLES = [
|
|||||||
['Tables', Tables, '/tables'],
|
['Tables', Tables, '/tables'],
|
||||||
['Paste HTML', PasteHtml, '/paste-html'],
|
['Paste HTML', PasteHtml, '/paste-html'],
|
||||||
['Search Highlighting', SearchHighlighting, '/search-highlighting'],
|
['Search Highlighting', SearchHighlighting, '/search-highlighting'],
|
||||||
|
['Syncing Operations', SyncingOperations, '/syncing-operations'],
|
||||||
['Read-only', ReadOnly, '/read-only'],
|
['Read-only', ReadOnly, '/read-only'],
|
||||||
['RTL', RTL, '/rtl'],
|
['RTL', RTL, '/rtl'],
|
||||||
['Plugins', Plugins, '/plugins'],
|
['Plugins', Plugins, '/plugins'],
|
||||||
['Forced Layout', ForcedLayout, '/forced-layout'],
|
['Forced Layout', ForcedLayout, '/forced-layout'],
|
||||||
['Huge', HugeDocument, '/huge-document'],
|
['Huge Document', HugeDocument, '/huge-document'],
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -51,7 +51,7 @@ const schema = {
|
|||||||
* @type {Component}
|
* @type {Component}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class RichText extends React.Component {
|
class RichTextExample extends React.Component {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize the initial editor state.
|
* Deserialize the initial editor state.
|
||||||
@@ -305,4 +305,4 @@ class RichText extends React.Component {
|
|||||||
* Export.
|
* Export.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default RichText
|
export default RichTextExample
|
||||||
|
8
examples/syncing-operations/Readme.md
Normal file
8
examples/syncing-operations/Readme.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
# Rich Text Example
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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!
|
300
examples/syncing-operations/index.js
Normal file
300
examples/syncing-operations/index.js
Normal 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
|
78
examples/syncing-operations/state.json
Normal file
78
examples/syncing-operations/state.json
Normal 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."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user