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:
@@ -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'],
|
||||
]
|
||||
|
||||
/**
|
||||
|
@@ -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
|
||||
|
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