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:
commit
7b4eca5e16
@ -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)
|
||||
|
@ -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
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
|
||||
}
|
||||
|
||||
const allowEdit = el.isContentEditable || el.closest('[data-slate-void]')
|
||||
return (
|
||||
el.isContentEditable &&
|
||||
allowEdit &&
|
||||
(el === element || el.closest('[data-slate-editor]') === element)
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user