mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-17 20:51:20 +02:00
Merge branch 'master' of github.com:ianstormtaylor/slate
This commit is contained in:
@@ -19,6 +19,7 @@ These tools are helpful when developing with Slate:
|
|||||||
|
|
||||||
These products are built with Slate, and can give you an idea of what's possible:
|
These products are built with Slate, and can give you an idea of what's possible:
|
||||||
|
|
||||||
|
* [Cake](https://www.cake.co/)
|
||||||
* [GitBook](https://www.gitbook.com/)
|
* [GitBook](https://www.gitbook.com/)
|
||||||
* [Grafana](https://grafana.com/)
|
* [Grafana](https://grafana.com/)
|
||||||
* [Guru](https://www.getguru.com/)
|
* [Guru](https://www.getguru.com/)
|
||||||
@@ -35,3 +36,4 @@ These pre-packaged editors are built on top of Slate, and can be helpful to see
|
|||||||
* [Nossas Editor](http://slate-editor.bonde.org/) is a drop-in WYSIWYG editor.
|
* [Nossas Editor](http://slate-editor.bonde.org/) is a drop-in WYSIWYG editor.
|
||||||
* [ORY Editor](https://editor.ory.am/) is a self-contained, inline WYSIWYG editor library.
|
* [ORY Editor](https://editor.ory.am/) is a self-contained, inline WYSIWYG editor library.
|
||||||
* [Outline Editor](https://github.com/outline/rich-markdown-editor) is the editor that powers the [Outline](https://www.getoutline.com/) wiki.
|
* [Outline Editor](https://github.com/outline/rich-markdown-editor) is the editor that powers the [Outline](https://www.getoutline.com/) wiki.
|
||||||
|
* [Chatterslate](https://github.com/chatterbugapp/chatterslate) helps teach language grammar and more at [Chatterbug](https://chatterbug.com)
|
||||||
|
@@ -14,6 +14,7 @@ import Embeds from './embeds'
|
|||||||
import Emojis from './emojis'
|
import Emojis from './emojis'
|
||||||
import ForcedLayout from './forced-layout'
|
import ForcedLayout from './forced-layout'
|
||||||
import History from './history'
|
import History from './history'
|
||||||
|
import Versions from './versions'
|
||||||
import HoveringMenu from './hovering-menu'
|
import HoveringMenu from './hovering-menu'
|
||||||
import HugeDocument from './huge-document'
|
import HugeDocument from './huge-document'
|
||||||
import Images from './images'
|
import Images from './images'
|
||||||
@@ -59,6 +60,7 @@ const EXAMPLES = [
|
|||||||
['Forced Layout', ForcedLayout, '/forced-layout'],
|
['Forced Layout', ForcedLayout, '/forced-layout'],
|
||||||
['Huge Document', HugeDocument, '/huge-document'],
|
['Huge Document', HugeDocument, '/huge-document'],
|
||||||
['History', History, '/history'],
|
['History', History, '/history'],
|
||||||
|
['Versions', Versions, '/versions'],
|
||||||
['Input Tester', InputTester, '/input-tester'],
|
['Input Tester', InputTester, '/input-tester'],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
282
examples/versions/index.js
Normal file
282
examples/versions/index.js
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
import { Value, Operation } from 'slate'
|
||||||
|
import { Editor } from 'slate-react'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import styled from 'react-emotion'
|
||||||
|
|
||||||
|
import { Stack } from 'immutable'
|
||||||
|
|
||||||
|
import { Button, Icon, Toolbar } from '../components'
|
||||||
|
|
||||||
|
const initialVersionState = [
|
||||||
|
{
|
||||||
|
name: 'version 1',
|
||||||
|
isRoot: true,
|
||||||
|
value: {
|
||||||
|
document: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
text: 'This example shows versions.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'version 2',
|
||||||
|
changes: [
|
||||||
|
[
|
||||||
|
Operation.create({
|
||||||
|
object: 'operation',
|
||||||
|
path: [0, 0],
|
||||||
|
position: 28,
|
||||||
|
properties: {
|
||||||
|
data: {},
|
||||||
|
type: undefined,
|
||||||
|
},
|
||||||
|
target: null,
|
||||||
|
type: 'split_node',
|
||||||
|
}),
|
||||||
|
Operation.create({
|
||||||
|
object: 'operation',
|
||||||
|
path: [0],
|
||||||
|
position: 1,
|
||||||
|
properties: {
|
||||||
|
data: {},
|
||||||
|
type: 'paragraph',
|
||||||
|
},
|
||||||
|
target: 28,
|
||||||
|
type: 'split_node',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Operation.create({
|
||||||
|
marks: [],
|
||||||
|
object: 'operation',
|
||||||
|
offset: 0,
|
||||||
|
path: [1, 0],
|
||||||
|
text: 'Try adding a new version by clicking the + icon.',
|
||||||
|
type: 'insert_text',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const VersionList = styled('ul')``
|
||||||
|
|
||||||
|
export const VersionListItem = styled('li')`
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${props => (props.active ? 'red' : 'black')};
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Version = ({ active, onClick, name }) => (
|
||||||
|
<VersionListItem active={active} onClick={onClick}>
|
||||||
|
{name}
|
||||||
|
</VersionListItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The versions example.
|
||||||
|
*
|
||||||
|
* @type {Component}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Versions extends React.Component {
|
||||||
|
/**
|
||||||
|
* Deserialize the initial editor value.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
state = {
|
||||||
|
value: Value.fromJSON(initialVersionState[0].value),
|
||||||
|
versions: initialVersionState,
|
||||||
|
activeVersionIndex: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setVersion(initialVersionState.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the history stack
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
resetHistory = () => {
|
||||||
|
let { value } = this.state
|
||||||
|
const change = value.change()
|
||||||
|
|
||||||
|
const history = value.history
|
||||||
|
.set('undos', new Stack())
|
||||||
|
.set('redos', new Stack())
|
||||||
|
value = value.set('history', history)
|
||||||
|
change.value = value
|
||||||
|
|
||||||
|
this.onChange(change)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a version as the active version
|
||||||
|
*
|
||||||
|
* @param {Number} index
|
||||||
|
*/
|
||||||
|
|
||||||
|
setVersion = index => {
|
||||||
|
const { value, versions, activeVersionIndex } = this.state
|
||||||
|
|
||||||
|
if (index === activeVersionIndex) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resetHistory()
|
||||||
|
|
||||||
|
const change = value.change()
|
||||||
|
const version = versions[index]
|
||||||
|
|
||||||
|
// the root just has a value so set it explicitly.
|
||||||
|
if (version.isRoot) {
|
||||||
|
this.setState({
|
||||||
|
activeVersionIndex: index,
|
||||||
|
value: Value.fromJSON(version.value),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const isForward = index > activeVersionIndex
|
||||||
|
|
||||||
|
let operationsToApply
|
||||||
|
|
||||||
|
if (isForward) {
|
||||||
|
operationsToApply = versions
|
||||||
|
.slice(activeVersionIndex + 1, index + 1)
|
||||||
|
.map(v => v.changes.flat())
|
||||||
|
.flat()
|
||||||
|
} else {
|
||||||
|
operationsToApply = versions
|
||||||
|
.slice(index + 1, activeVersionIndex + 1)
|
||||||
|
.map(v => v.changes.flat())
|
||||||
|
.flat()
|
||||||
|
.reverse()
|
||||||
|
.map(op => op.invert())
|
||||||
|
}
|
||||||
|
|
||||||
|
change.withoutNormalizing(() => {
|
||||||
|
change.withoutSaving(() => {
|
||||||
|
change.applyOperations(operationsToApply)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.onChange(change)
|
||||||
|
this.setState({ activeVersionIndex: index })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a version below the active version
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
addVersion = () => {
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const versionName = window.prompt('How do you want to call this version?')
|
||||||
|
|
||||||
|
if (!versionName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value, versions, activeVersionIndex } = this.state
|
||||||
|
const { history } = value
|
||||||
|
|
||||||
|
const newVersion = {
|
||||||
|
name: versionName,
|
||||||
|
value: this.state.value.toJSON(),
|
||||||
|
changes: history.undos
|
||||||
|
.toArray()
|
||||||
|
.reverse()
|
||||||
|
.map(list => list.toArray()),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
versions: [...versions, newVersion],
|
||||||
|
activeVersionIndex: activeVersionIndex + 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.resetHistory()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we are at the last version
|
||||||
|
*
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
atTail = () => {
|
||||||
|
const { versions, activeVersionIndex } = this.state
|
||||||
|
|
||||||
|
return versions.length - 1 === activeVersionIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the editor.
|
||||||
|
*
|
||||||
|
* @return {Component} component
|
||||||
|
*/
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { value, versions, activeVersionIndex } = this.state
|
||||||
|
const { history } = value
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<VersionList>
|
||||||
|
{versions.map((version, index) => (
|
||||||
|
<Version
|
||||||
|
key={index}
|
||||||
|
name={version.name}
|
||||||
|
active={index === activeVersionIndex}
|
||||||
|
onClick={() => this.setVersion(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</VersionList>
|
||||||
|
<Toolbar>
|
||||||
|
<Button active={history.undos.size} onMouseDown={this.addVersion}>
|
||||||
|
<Icon>add</Icon>
|
||||||
|
</Button>
|
||||||
|
<span>Undos: {history.undos.size}</span>
|
||||||
|
<span>Redos: {history.redos.size}</span>
|
||||||
|
</Toolbar>
|
||||||
|
<Editor
|
||||||
|
readOnly={!this.atTail()}
|
||||||
|
placeholder="Enter some text..."
|
||||||
|
value={this.state.value}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On change.
|
||||||
|
*
|
||||||
|
* @param {Change} change
|
||||||
|
*/
|
||||||
|
|
||||||
|
onChange = ({ value }) => {
|
||||||
|
this.setState({ value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Versions
|
@@ -284,8 +284,10 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allowEdit = el.isContentEditable || el.closest('[data-slate-void]')
|
||||||
return (
|
return (
|
||||||
el.isContentEditable &&
|
allowEdit &&
|
||||||
(el === element || el.closest('[data-slate-editor]') === element)
|
(el === element || el.closest('[data-slate-editor]') === element)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -436,8 +436,10 @@ class ElementInterface {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
getDecorations(stack) {
|
getDecorations(stack) {
|
||||||
const decorations = stack.find('decorateNode', this)
|
const allDecorations = stack
|
||||||
const list = Decoration.createList(decorations || [])
|
.map('decorateNode', this)
|
||||||
|
.map(decorations => Decoration.createList(decorations))
|
||||||
|
const list = List(allDecorations).flatten(true)
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user