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

Merge branch 'master' of github.com:ianstormtaylor/slate

This commit is contained in:
Ian Storm Taylor 2018-09-26 13:12:42 -07:00
commit 7b4eca5e16
5 changed files with 293 additions and 3 deletions

View File

@ -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:
* [Cake](https://www.cake.co/)
* [GitBook](https://www.gitbook.com/)
* [Grafana](https://grafana.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.
* [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.
* [Chatterslate](https://github.com/chatterbugapp/chatterslate) helps teach language grammar and more at [Chatterbug](https://chatterbug.com)

View File

@ -14,6 +14,7 @@ import Embeds from './embeds'
import Emojis from './emojis'
import ForcedLayout from './forced-layout'
import History from './history'
import Versions from './versions'
import HoveringMenu from './hovering-menu'
import HugeDocument from './huge-document'
import Images from './images'
@ -59,6 +60,7 @@ const EXAMPLES = [
['Forced Layout', ForcedLayout, '/forced-layout'],
['Huge Document', HugeDocument, '/huge-document'],
['History', History, '/history'],
['Versions', Versions, '/versions'],
['Input Tester', InputTester, '/input-tester'],
]

282
examples/versions/index.js Normal file
View 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

View File

@ -284,8 +284,10 @@ class Content extends React.Component {
throw err
}
const allowEdit = el.isContentEditable || el.closest('[data-slate-void]')
return (
el.isContentEditable &&
allowEdit &&
(el === element || el.closest('[data-slate-editor]') === element)
)
}

View File

@ -436,8 +436,10 @@ class ElementInterface {
*/
getDecorations(stack) {
const decorations = stack.find('decorateNode', this)
const list = Decoration.createList(decorations || [])
const allDecorations = stack
.map('decorateNode', this)
.map(decorations => Decoration.createList(decorations))
const list = List(allDecorations).flatten(true)
return list
}