1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-07-31 04:20:26 +02:00

Improve and refactor examples (#1930)

This just refactors the examples to make the styled defined inline with each example, to make it easier to follow for folks. And in the process fixes a few issues that people brought up.

Fixes https://github.com/ianstormtaylor/slate/issues/1920
Fixes https://github.com/ianstormtaylor/slate/issues/1925
This commit is contained in:
Ian Storm Taylor
2018-07-01 15:13:29 -06:00
committed by GitHub
parent 1923888af1
commit 257b28aa84
28 changed files with 1459 additions and 1468 deletions

View File

@@ -119,7 +119,6 @@
"radix": "error",
"react/jsx-boolean-value": ["error", "never"],
"react/jsx-key": "error",
"react/jsx-no-bind": "error",
"react/jsx-no-duplicate-props": "error",
"react/jsx-no-target-blank": "error",
"react/jsx-no-undef": "error",

View File

@@ -1,5 +1,12 @@
import React from 'react'
import { HashRouter, NavLink, Route, Redirect, Switch } from 'react-router-dom'
import styled from 'react-emotion'
import {
HashRouter,
Link as RouterLink,
Route,
Redirect,
Switch,
} from 'react-router-dom'
import CheckLists from './check-lists'
import CodeHighlighting from './code-highlighting'
@@ -53,6 +60,83 @@ const EXAMPLES = [
['History', History, '/history'],
]
/**
* Some styled components.
*
* @type {Component}
*/
const Nav = styled('div')`
padding: 10px 15px;
color: #aaa;
background: #000;
`
const Title = styled('span')`
margin-right: 0.5em;
`
const LinkList = styled('div')`
float: right;
`
const Link = styled('a')`
margin-left: 1em;
color: #aaa;
text-decoration: none;
&:hover {
color: #fff;
text-decoration: underline;
}
`
const TabList = styled('div')`
padding: 15px 15px;
background-color: #222;
text-align: center;
margin-bottom: 30px;
& > * + * {
margin-left: 0.5em;
}
`
const Tab = styled(RouterLink)`
display: inline-block;
margin-bottom: 0.2em;
padding: 0.2em 0.5em;
border-radius: 0.2em;
text-decoration: none;
color: ${props => (props.active ? 'white' : '#777')};
background: ${props => (props.active ? '#333' : 'transparent')};
&:hover {
background: #333;
}
`
const Wrapper = styled('div')`
max-width: 42em;
margin: 0 auto 20px;
padding: 20px;
`
const Example = styled(Wrapper)`
background: #fff;
`
const Warning = styled(Wrapper)`
background: #fffae0;
& > pre {
background: #fbf1bd;
white-space: pre;
overflow-x: scroll;
margin-bottom: 0;
}
`
/**
* App.
*
@@ -60,76 +144,81 @@ const EXAMPLES = [
*/
export default class App extends React.Component {
/**
* Initial state.
*
* @type {Object}
*/
state = {
error: null,
info: null,
}
/**
* Catch the `error` and `info`.
*
* @param {Error} error
* @param {Object} info
*/
componentDidCatch(error, info) {
this.setState({ error, info })
}
/**
* Render the example app.
*
* @return {Element}
*/
render() {
return (
<HashRouter>
<div className="app">
<div className="nav">
<span className="nav-title">Slate Examples</span>
<div className="nav-links">
<a
className="nav-link"
href="https://github.com/ianstormtaylor/slate"
>
GitHub
</a>
<a className="nav-link" href="https://docs.slatejs.org/">
Docs
</a>
</div>
</div>
<div className="tabs">
<div>
<Nav>
<Title>Slate Examples</Title>
<LinkList>
<Link href="https://github.com/ianstormtaylor/slate">GitHub</Link>
<Link href="https://docs.slatejs.org/">Docs</Link>
</LinkList>
</Nav>
<TabList>
{EXAMPLES.map(([name, Component, path]) => (
<NavLink
key={path}
to={path}
className="tab"
activeClassName="active"
>
{name}
</NavLink>
<Route key={path} exact path={path}>
{({ match }) => (
<Tab to={path} active={match && match.isExact}>
{name}
</Tab>
)}
</Route>
))}
</div>
{this.state.error ? this.renderError() : this.renderExample()}
</TabList>
{this.state.error ? (
<Warning>
<p>
An error was thrown by one of the example's React components!
</p>
<pre>
<code>
{this.state.error.stack}
{'\n'}
{this.state.info.componentStack}
</code>
</pre>
</Warning>
) : (
<Example>
<Switch>
{EXAMPLES.map(([name, Component, path]) => (
<Route key={path} path={path} component={Component} />
))}
<Redirect from="/" to="/rich-text" />
</Switch>
</Example>
)}
</div>
</HashRouter>
)
}
renderExample() {
return (
<div className="example">
<Switch>
{EXAMPLES.map(([name, Component, path]) => (
<Route key={path} path={path} component={Component} />
))}
<Redirect from="/" to="/rich-text" />
</Switch>
</div>
)
}
renderError() {
return (
<div className="error">
<p>An error was thrown by one of the example's React components!</p>
<pre className="info">
<code>
{this.state.error.stack}
{'\n'}
{this.state.info.componentStack}
</code>
</pre>
</div>
)
}
}

View File

@@ -3,6 +3,37 @@ import { Value } from 'slate'
import React from 'react'
import initialValue from './value.json'
import styled from 'react-emotion'
/**
* Create a few styling components.
*
* @type {Component}
*/
const ItemWrapper = styled('div')`
display: flex;
flex-direction: row;
align-items: center;
& + & {
margin-top: 0;
}
`
const CheckboxWrapper = styled('span')`
margin-right: 0.75em;
`
const ContentWrapper = styled('span')`
flex: 1;
opacity: ${props => (props.checked ? 0.666 : 1)};
text-decoration: ${props => (props.checked ? 'none' : 'line-through')};
&:focus {
outline: none;
}
`
/**
* Check list item.
@@ -34,18 +65,18 @@ class CheckListItem extends React.Component {
const { attributes, children, node, readOnly } = this.props
const checked = node.data.get('checked')
return (
<div
className={`check-list-item ${checked ? 'checked' : ''}`}
contentEditable={false}
{...attributes}
>
<span>
<ItemWrapper {...attributes}>
<CheckboxWrapper contentEditable={false}>
<input type="checkbox" checked={checked} onChange={this.onChange} />
</span>
<span contentEditable={!readOnly} suppressContentEditableWarning>
</CheckboxWrapper>
<ContentWrapper
checked={checked}
contentEditable={!readOnly}
suppressContentEditableWarning
>
{children}
</span>
</div>
</ContentWrapper>
</ItemWrapper>
)
}
}
@@ -67,6 +98,39 @@ class CheckLists extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* Render.
*
* @return {Element}
*/
render() {
return (
<Editor
spellCheck
placeholder="Get to work..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
switch (props.node.type) {
case 'check-list-item':
return <CheckListItem {...props} />
}
}
/**
* On change, save the new value.
*
@@ -109,43 +173,6 @@ class CheckLists extends React.Component {
return true
}
}
/**
* Render.
*
* @return {Element}
*/
render() {
return (
<div>
<div className="editor">
<Editor
spellCheck
placeholder="Get to work..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
</div>
</div>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
switch (props.node.type) {
case 'check-list-item':
return <CheckListItem {...props} />
}
}
}
/**

View File

@@ -45,6 +45,23 @@ function CodeBlockLine(props) {
return <div {...props.attributes}>{props.children}</div>
}
/**
* A helper function to return the content of a Prism `token`.
*
* @param {Object} token
* @return {String}
*/
function getContent(token) {
if (typeof token == 'string') {
return token
} else if (typeof token.content == 'string') {
return token.content
} else {
return token.content.map(getContent).join('')
}
}
/**
* The code highlighting example.
*
@@ -62,53 +79,23 @@ class CodeHighlighting extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* On change, save the new value.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* On key down inside code blocks, insert soft new lines.
*
* @param {Event} event
* @param {Change} change
* @return {Change}
*/
onKeyDown = (event, change) => {
const { value } = change
const { startBlock } = value
if (event.key != 'Enter') return
if (startBlock.type != 'code') return
if (value.isExpanded) change.delete()
change.insertText('\n')
return true
}
/**
* Render.
*
* @return {Component}
*/
render = () => {
render() {
return (
<div className="editor">
<Editor
placeholder="Write some code..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
renderMark={this.renderMark}
decorateNode={this.decorateNode}
/>
</div>
<Editor
placeholder="Write some code..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
renderMark={this.renderMark}
decorateNode={this.decorateNode}
/>
)
}
@@ -166,14 +153,32 @@ class CodeHighlighting extends React.Component {
}
}
tokenToContent = token => {
if (typeof token == 'string') {
return token
} else if (typeof token.content == 'string') {
return token.content
} else {
return token.content.map(this.tokenToContent).join('')
}
/**
* On change, save the new value.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* On key down inside code blocks, insert soft new lines.
*
* @param {Event} event
* @param {Change} change
* @return {Change}
*/
onKeyDown = (event, change) => {
const { value } = change
const { startBlock } = value
if (event.key != 'Enter') return
if (startBlock.type != 'code') return
if (value.isExpanded) change.delete()
change.insertText('\n')
return true
}
/**
@@ -202,7 +207,7 @@ class CodeHighlighting extends React.Component {
startText = endText
startOffset = endOffset
const content = this.tokenToContent(token)
const content = getContent(token)
const newlines = content.split('\n').length - 1
const length = content.length - newlines
const end = start + length

35
examples/components.js Normal file
View File

@@ -0,0 +1,35 @@
import React from 'react'
import styled from 'react-emotion'
export const Button = styled('span')`
cursor: pointer;
color: ${props =>
props.reversed
? props.active ? 'white' : '#aaa'
: props.active ? 'black' : '#ccc'};
`
export const Icon = styled(({ className, ...rest }) => {
return <span className={`material-icons ${className}`} {...rest} />
})`
font-size: 18px;
vertical-align: text-bottom;
`
export const Menu = styled('div')`
& > * {
display: inline-block;
}
& > * + * {
margin-left: 15px;
}
`
export const Toolbar = styled(Menu)`
position: relative;
padding: 1px 18px 17px;
margin: 0 -20px;
border-bottom: 2px solid #eee;
margin-bottom: 20px;
`

View File

@@ -22,16 +22,6 @@ class Embeds extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* Render the app.
*
@@ -40,14 +30,12 @@ class Embeds extends React.Component {
render() {
return (
<div className="editor">
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
renderNode={this.renderNode}
/>
</div>
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
renderNode={this.renderNode}
/>
)
}
@@ -64,6 +52,16 @@ class Embeds extends React.Component {
return <Video {...props} />
}
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
}
/**

View File

@@ -3,6 +3,18 @@ import { Value } from 'slate'
import React from 'react'
import initialValue from './value.json'
import styled from 'react-emotion'
import { Button, Icon, Toolbar } from '../components'
/**
* A styled emoji inline component.
*
* @type {Component}
*/
const Emoji = styled('span')`
outline: ${props => (props.selected ? '2px solid blue' : 'none')};
`
/**
* Emojis.
@@ -27,7 +39,6 @@ const EMOJIS = [
'👻',
'🍔',
'🍑',
'🍆',
'🔑',
]
@@ -56,6 +67,62 @@ class Emojis extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* Render the app.
*
* @return {Element} element
*/
render() {
return (
<div>
<Toolbar>
{EMOJIS.map((emoji, i) => (
<Button key={i} onMouseDown={e => this.onClickEmoji(e, emoji)}>
<Icon>{emoji}</Icon>
</Button>
))}
</Toolbar>
<Editor
placeholder="Write some 😍👋🎉..."
value={this.state.value}
onChange={this.onChange}
renderNode={this.renderNode}
/>
</div>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node, isSelected } = props
switch (node.type) {
case 'paragraph': {
return <p {...attributes}>{children}</p>
}
case 'emoji': {
const code = node.data.get('code')
return (
<Emoji
{...props.attributes}
selected={isSelected}
contentEditable={false}
onDrop={noop}
>
{code}
</Emoji>
)
}
}
}
/**
* On change.
*
@@ -88,93 +155,6 @@ class Emojis extends React.Component {
this.onChange(change)
}
/**
* Render the app.
*
* @return {Element} element
*/
render = () => {
return (
<div>
{this.renderToolbar()}
{this.renderEditor()}
</div>
)
}
/**
* Render the toolbar.
*
* @return {Element} element
*/
renderToolbar = () => {
return (
<div className="menu toolbar-menu">
{EMOJIS.map((emoji, i) => {
const onMouseDown = e => this.onClickEmoji(e, emoji)
return (
// eslint-disable-next-line react/jsx-no-bind
<span key={i} className="button" onMouseDown={onMouseDown}>
<span className="material-icons">{emoji}</span>
</span>
)
})}
</div>
)
}
/**
* Render the editor.
*
* @return {Element} element
*/
renderEditor = () => {
return (
<div className="editor">
<Editor
placeholder="Write some 😍👋🎉..."
value={this.state.value}
onChange={this.onChange}
renderNode={this.renderNode}
/>
</div>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node, isSelected } = props
switch (node.type) {
case 'paragraph': {
return <p {...attributes}>{children}</p>
}
case 'emoji': {
const { data } = node
const code = data.get('code')
return (
<span
className={`emoji ${isSelected ? 'selected' : ''}`}
{...props.attributes}
contentEditable={false}
onDrop={noop}
>
{code}
</span>
)
}
}
}
}
/**

View File

@@ -51,16 +51,6 @@ class ForcedLayout extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* Render the editor.
*
@@ -69,15 +59,13 @@ class ForcedLayout extends React.Component {
render() {
return (
<div className="editor">
<Editor
placeholder="Enter a title..."
value={this.state.value}
schema={schema}
onChange={this.onChange}
renderNode={this.renderNode}
/>
</div>
<Editor
placeholder="Enter a title..."
value={this.state.value}
schema={schema}
onChange={this.onChange}
renderNode={this.renderNode}
/>
)
}
@@ -98,6 +86,16 @@ class ForcedLayout extends React.Component {
return <p {...attributes}>{children}</p>
}
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
}
/**

View File

@@ -3,18 +3,7 @@ import { Editor } from 'slate-react'
import React from 'react'
import initialValue from './value.json'
/**
* Toolbar button component.
*
* @type {Function}
*/
const ToolbarButton = props => (
<span className="button" onMouseDown={props.onMouseDown}>
<span className="material-icons">{props.icon}</span>
</span>
)
import { Button, Icon, Toolbar } from '../components'
/**
* The history example.
@@ -33,6 +22,36 @@ class History extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
const { value } = this.state
const { history } = value
return (
<div>
<Toolbar>
<Button onMouseDown={this.onClickUndo}>
<Icon>undo</Icon>
</Button>
<Button onMouseDown={this.onClickRedo}>
<Icon>redo</Icon>
</Button>
<span>Undos: {history.undos.size}</span>
<span>Redos: {history.redos.size}</span>
</Toolbar>
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
/>
</div>
)
}
/**
* On change.
*
@@ -66,57 +85,6 @@ class History extends React.Component {
const change = value.change().undo()
this.onChange(change)
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<div className="editor">
{this.renderToolbar()}
{this.renderEditor()}
</div>
)
}
/**
* Render the toolbar.
*
* @return {Element}
*/
renderToolbar = () => {
const { value } = this.state
return (
<div className="menu toolbar-menu">
<ToolbarButton icon="undo" onMouseDown={this.onClickUndo} />
<ToolbarButton icon="redo" onMouseDown={this.onClickRedo} />
<span className="button">Undos: {value.history.undos.size}</span>
<span className="button">Redos: {value.history.redos.size}</span>
</div>
)
}
/**
* Render the Slate editor.
*
* @return {Element}
*/
renderEditor = () => {
return (
<div className="editor">
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
/>
</div>
)
}
}
/**

View File

@@ -4,24 +4,76 @@ import { Value } from 'slate'
import React from 'react'
import ReactDOM from 'react-dom'
import initialValue from './value.json'
import styled from 'react-emotion'
import { Button, Icon, Menu } from '../components'
/**
* The menu.
* Give the menu some styles.
*
* @type {Component}
*/
class Menu extends React.Component {
const StyledMenu = styled(Menu)`
padding: 8px 7px 6px;
position: absolute;
z-index: 1;
top: -10000px;
left: -10000px;
margin-top: -6px;
opacity: 0;
background-color: #222;
border-radius: 4px;
transition: opacity 0.75s;
`
/**
* The hovering menu.
*
* @type {Component}
*/
class HoverMenu extends React.Component {
/**
* Check if the current selection has a mark with `type` in it.
* Render.
*
* @param {String} type
* @return {Boolean}
* @return {Element}
*/
hasMark(type) {
render() {
const { className, innerRef } = this.props
const root = window.document.getElementById('root')
return ReactDOM.createPortal(
<StyledMenu className={className} innerRef={innerRef}>
{this.renderMarkButton('bold', 'format_bold')}
{this.renderMarkButton('italic', 'format_italic')}
{this.renderMarkButton('underlined', 'format_underlined')}
{this.renderMarkButton('code', 'code')}
</StyledMenu>,
root
)
}
/**
* Render a mark-toggling toolbar button.
*
* @param {String} type
* @param {String} icon
* @return {Element}
*/
renderMarkButton(type, icon) {
const { value } = this.props
return value.activeMarks.some(mark => mark.type == type)
const isActive = value.activeMarks.some(mark => mark.type == type)
return (
<Button
reversed
active={isActive}
onMouseDown={event => this.onClickMark(event, type)}
>
<Icon>{icon}</Icon>
</Button>
)
}
/**
@@ -37,46 +89,6 @@ class Menu extends React.Component {
const change = value.change().toggleMark(type)
onChange(change)
}
/**
* Render a mark-toggling toolbar button.
*
* @param {String} type
* @param {String} icon
* @return {Element}
*/
renderMarkButton(type, icon) {
const isActive = this.hasMark(type)
const onMouseDown = event => this.onClickMark(event, type)
return (
// eslint-disable-next-line react/jsx-no-bind
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
<span className="material-icons">{icon}</span>
</span>
)
}
/**
* Render.
*
* @return {Element}
*/
render() {
const root = window.document.getElementById('root')
return ReactDOM.createPortal(
<div className="menu hover-menu" ref={this.props.menuRef}>
{this.renderMarkButton('bold', 'format_bold')}
{this.renderMarkButton('italic', 'format_italic')}
{this.renderMarkButton('underlined', 'format_underlined')}
{this.renderMarkButton('code', 'code')}
</div>,
root
)
}
}
/**
@@ -134,26 +146,6 @@ class HoveringMenu extends React.Component {
rect.width / 2}px`
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* Save the `menu` ref.
*
* @param {Menu} menu
*/
menuRef = menu => {
this.menu = menu
}
/**
* Render.
*
@@ -163,19 +155,17 @@ class HoveringMenu extends React.Component {
render() {
return (
<div>
<Menu
menuRef={this.menuRef}
<HoverMenu
innerRef={menu => (this.menu = menu)}
value={this.state.value}
onChange={this.onChange}
/>
<div className="editor">
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
renderMark={this.renderMark}
/>
</div>
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
renderMark={this.renderMark}
/>
</div>
)
}
@@ -201,6 +191,16 @@ class HoveringMenu extends React.Component {
return <u {...attributes}>{children}</u>
}
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
}
/**

View File

@@ -48,22 +48,7 @@ class HugeDocument extends React.Component {
* @type {Object}
*/
constructor() {
super()
console.time('deserializeHugeDocument')
this.state = { value: Value.fromJSON(json, { normalize: false }) }
console.timeEnd('deserializeHugeDocument')
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
state = { value: Value.fromJSON(json, { normalize: false }) }
/**
* Render the editor.
@@ -73,17 +58,14 @@ class HugeDocument extends React.Component {
render() {
return (
<div className="editor">
<Editor
placeholder="Enter some text..."
spellCheck={false}
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
renderMark={this.renderMark}
/>
</div>
<Editor
placeholder="Enter some text..."
spellCheck={false}
value={this.state.value}
onChange={this.onChange}
renderNode={this.renderNode}
renderMark={this.renderMark}
/>
)
}
@@ -124,6 +106,16 @@ class HugeDocument extends React.Component {
return <u {...attributes}>{children}</u>
}
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
}
/**

View File

@@ -6,6 +6,21 @@ import React from 'react'
import initialValue from './value.json'
import imageExtensions from 'image-extensions'
import isUrl from 'is-url'
import styled from 'react-emotion'
import { Button, Icon, Toolbar } from '../components'
/**
* A styled image block component.
*
* @type {Component}
*/
const Image = styled('img')`
display: block;
max-width: 100%;
max-height: 20em;
box-shadow: ${props => (props.selected ? '0 0 0 2px blue;' : 'none')};
`
/*
* A function to determine whether a URL has an image extension.
@@ -84,37 +99,11 @@ class Images extends React.Component {
render() {
return (
<div>
{this.renderToolbar()}
{this.renderEditor()}
</div>
)
}
/**
* Render the toolbar.
*
* @return {Element} element
*/
renderToolbar = () => {
return (
<div className="menu toolbar-menu">
<span className="button" onMouseDown={this.onClickImage}>
<span className="material-icons">image</span>
</span>
</div>
)
}
/**
* Render the editor.
*
* @return {Element} element
*/
renderEditor = () => {
return (
<div className="editor">
<Toolbar>
<Button onMouseDown={this.onClickImage}>
<Icon>image</Icon>
</Button>
</Toolbar>
<Editor
placeholder="Enter some text..."
value={this.state.value}
@@ -141,11 +130,7 @@ class Images extends React.Component {
switch (node.type) {
case 'image': {
const src = node.data.get('src')
const className = isSelected ? 'active' : null
const style = { display: 'block' }
return (
<img src={src} className={className} style={style} {...attributes} />
)
return <Image src={src} selected={isSelected} {...attributes} />
}
}
}

View File

@@ -31,10 +31,6 @@ img {
max-height: 20em;
}
img.active {
box-shadow: 0 0 0 2px blue;
}
blockquote {
border-left: 2px solid #ddd;
margin-left: 0;
@@ -74,199 +70,6 @@ input:focus {
border-color: blue;
}
/**
* Icons.
*/
.material-icons {
font-size: 18px;
vertical-align: text-bottom;
}
/**
* App.
*/
.nav {
padding: 10px 15px;
color: #aaa;
background: #000;
}
.nav-title {
margin-right: 0.5em;
}
.nav-links {
float: right;
}
.nav-link {
margin-left: 1em;
color: #aaa;
text-decoration: none;
}
.nav-link:hover {
color: #fff;
text-decoration: underline;
}
.tabs {
padding: 15px 15px;
background-color: #222;
text-align: center;
margin-bottom: 30px;
}
.tab {
color: #777;
display: inline-block;
text-decoration: none;
padding: 0.2em 0.5em;
border-radius: 0.2em;
margin-bottom: 0.2em;
}
.tab:hover {
background: #333;
}
.tab + .tab {
margin-left: 0.5em;
}
.tab.active {
color: white;
background: #333;
}
/**
* Example.
*/
.example,
.error {
max-width: 42em;
margin: 0 auto 20px;
padding: 20px;
}
.example {
background: #fff;
}
.error {
background: #fffae0;
}
.error .info {
background: #fbf1bd;
white-space: pre;
overflow-x: scroll;
margin-bottom: 0;
}
.editor > * > * + * {
[data-slate-editor] > * + * {
margin-top: 1em;
}
.menu > * {
display: inline-block;
}
.menu > * + * {
margin-left: 15px;
}
.button {
color: #ccc;
cursor: pointer;
}
.button[data-active='true'] {
color: black;
}
.toolbar-menu {
position: relative;
padding: 1px 18px 17px;
margin: 0 -20px;
border-bottom: 2px solid #eee;
margin-bottom: 20px;
}
.toolbar-menu .search {
position: relative;
}
.toolbar-menu .search-icon {
position: absolute;
top: 0.5em;
left: 0.5em;
color: #ccc;
}
.toolbar-menu .search-box {
padding-left: 2em;
width: 100%;
}
.hover-menu {
padding: 8px 7px 6px;
position: absolute;
z-index: 1;
top: -10000px;
left: -10000px;
margin-top: -6px;
opacity: 0;
background-color: #222;
border-radius: 4px;
transition: opacity 0.75s;
}
.hover-menu .button {
color: #aaa;
}
.hover-menu .button[data-active='true'] {
color: #fff;
}
.emoji.selected {
outline: 2px solid blue;
}
.check-list-item + .check-list-item {
margin-top: 0;
}
.check-list-item {
display: flex;
flex-direction: row;
align-items: center;
}
.check-list-item.checked {
opacity: 0.666;
text-decoration: line-through;
}
.check-list-item > span:first-child {
margin-right: 0.75em;
}
.check-list-item > span:last-child {
flex: 1;
}
.check-list-item > span:last-child:focus {
outline: none;
}
.word-counter {
margin-top: 10px;
padding: 12px;
background-color: #ebebeb;
display: inline-block;
}

View File

@@ -5,7 +5,7 @@ import App from './app'
import './index.css'
/**
* Mount the router.
* Render the app.
*/
const root = window.document.createElement('div')
@@ -23,7 +23,10 @@ const render = Component => {
render(App)
// Webpack Hot Module Replacement API
/**
* Re-render for hot module replacement in development.
*/
if (module.hot) {
module.hot.accept('./app', () => render(App))
}

View File

@@ -4,6 +4,7 @@ import { Value } from 'slate'
import React from 'react'
import initialValue from './value.json'
import isUrl from 'is-url'
import { Button, Icon, Toolbar } from '../components'
/**
* A change helper to standardize wrapping links.
@@ -59,6 +60,54 @@ class Links extends React.Component {
return value.inlines.some(inline => inline.type == 'link')
}
/**
* Render the app.
*
* @return {Element} element
*/
render() {
return (
<div>
<Toolbar>
<Button active={this.hasLinks()} onMouseDown={this.onClickLink}>
<Icon>link</Icon>
</Button>
</Toolbar>
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
onPaste={this.onPaste}
renderNode={this.renderNode}
/>
</div>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node } = props
switch (node.type) {
case 'link': {
const { data } = node
const href = data.get('href')
return (
<a {...attributes} href={href}>
{children}
</a>
)
}
}
}
/**
* On change.
*
@@ -122,85 +171,6 @@ class Links extends React.Component {
change.call(wrapLink, text)
return true
}
/**
* Render the app.
*
* @return {Element} element
*/
render() {
return (
<div>
{this.renderToolbar()}
{this.renderEditor()}
</div>
)
}
/**
* Render the toolbar.
*
* @return {Element} element
*/
renderToolbar = () => {
const hasLinks = this.hasLinks()
return (
<div className="menu toolbar-menu">
<span
className="button"
onMouseDown={this.onClickLink}
data-active={hasLinks}
>
<span className="material-icons">link</span>
</span>
</div>
)
}
/**
* Render the editor.
*
* @return {Element} element
*/
renderEditor = () => {
return (
<div className="editor">
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
onPaste={this.onPaste}
renderNode={this.renderNode}
/>
</div>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node } = props
switch (node.type) {
case 'link': {
const { data } = node
const href = data.get('href')
return (
<a {...attributes} href={href}>
{children}
</a>
)
}
}
}
}
/**

View File

@@ -30,16 +30,6 @@ class MarkdownPreview extends React.Component {
),
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
*
* Render the example.
@@ -49,15 +39,13 @@ class MarkdownPreview extends React.Component {
render() {
return (
<div className="editor">
<Editor
placeholder="Write some markdown..."
value={this.state.value}
onChange={this.onChange}
renderMark={this.renderMark}
decorateNode={this.decorateNode}
/>
</div>
<Editor
placeholder="Write some markdown..."
value={this.state.value}
onChange={this.onChange}
renderMark={this.renderMark}
decorateNode={this.decorateNode}
/>
)
}
@@ -133,6 +121,16 @@ class MarkdownPreview extends React.Component {
}
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* Define a decorator for markdown styles.
*

View File

@@ -62,15 +62,13 @@ class MarkdownShortcuts extends React.Component {
render() {
return (
<div className="editor">
<Editor
placeholder="Write some markdown..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
</div>
<Editor
placeholder="Write some markdown..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
)
}

View File

@@ -4,6 +4,7 @@ import { Value } from 'slate'
import React from 'react'
import initialValue from './value.json'
import styled from 'react-emotion'
/**
* Tags to blocks.
@@ -40,6 +41,19 @@ const MARK_TAGS = {
code: 'code',
}
/**
* A styled image block component.
*
* @type {Component}
*/
const Image = styled('img')`
display: block;
max-width: 100%;
max-height: 20em;
box-shadow: ${props => (props.selected ? '0 0 0 2px blue;' : 'none')};
`
/**
* Serializer rules.
*
@@ -149,31 +163,6 @@ class PasteHtml extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* On change, save the new value.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* On paste, deserialize the HTML and then insert the fragment.
*
* @param {Event} event
* @param {Change} change
*/
onPaste = (event, change) => {
const transfer = getEventTransfer(event)
if (transfer.type != 'html') return
const { document } = serializer.deserialize(transfer.html)
change.insertFragment(document)
return true
}
/**
* Render.
*
@@ -182,16 +171,14 @@ class PasteHtml extends React.Component {
render() {
return (
<div className="editor">
<Editor
placeholder="Paste in some HTML..."
value={this.state.value}
onPaste={this.onPaste}
onChange={this.onChange}
renderNode={this.renderNode}
renderMark={this.renderMark}
/>
</div>
<Editor
placeholder="Paste in some HTML..."
value={this.state.value}
onPaste={this.onPaste}
onChange={this.onChange}
renderNode={this.renderNode}
renderMark={this.renderMark}
/>
)
}
@@ -243,11 +230,7 @@ class PasteHtml extends React.Component {
}
case 'image': {
const src = node.data.get('src')
const className = isSelected ? 'active' : null
const style = { display: 'block' }
return (
<img src={src} className={className} style={style} {...attributes} />
)
return <Image src={src} selected={isSelected} {...attributes} />
}
}
}
@@ -273,6 +256,31 @@ class PasteHtml extends React.Component {
return <u {...attributes}>{children}</u>
}
}
/**
* On change, save the new value.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* On paste, deserialize the HTML and then insert the fragment.
*
* @param {Event} event
* @param {Change} change
*/
onPaste = (event, change) => {
const transfer = getEventTransfer(event)
if (transfer.type != 'html') return
const { document } = serializer.deserialize(transfer.html)
change.insertFragment(document)
return true
}
}
/**

View File

@@ -22,6 +22,22 @@ class PlainText extends React.Component {
),
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<Editor
placeholder="Enter some plain text..."
value={this.state.value}
onChange={this.onChange}
/>
)
}
/**
* On change.
*
@@ -31,24 +47,6 @@ class PlainText extends React.Component {
onChange = ({ value }) => {
this.setState({ value })
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<div className="editor">
<Editor
placeholder="Enter some plain text..."
value={this.state.value}
onChange={this.onChange}
/>
</div>
)
}
}
/**

View File

@@ -4,6 +4,20 @@ import { Editor } from 'slate-react'
import React from 'react'
import CollapseOnEscape from 'slate-collapse-on-escape'
import SoftBreak from 'slate-soft-break'
import styled from 'react-emotion'
/**
* A styled word counter component.
*
* @type {Component}
*/
const WordCounter = styled('span')`
margin-top: 10px;
padding: 12px;
background-color: #ebebeb;
display: inline-block;
`
/**
* A simple word count plugin.
@@ -18,9 +32,9 @@ function WordCount(options) {
return (
<div>
<div>{props.children}</div>
<span className="word-counter">
<WordCounter>
Word Count: {props.value.document.text.split(' ').length}
</span>
</WordCounter>
</div>
)
},
@@ -53,6 +67,23 @@ The second is another simple plugin that inserts a "soft" break when enter is pr
The third is an example of using the plugin.render property to create a higher-order-component.`),
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<Editor
placeholder="Enter some text..."
plugins={plugins}
value={this.state.value}
onChange={this.onChange}
/>
)
}
/**
* On change.
*
@@ -62,25 +93,6 @@ The third is an example of using the plugin.render property to create a higher-o
onChange = ({ value }) => {
this.setState({ value })
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<div className="editor">
<Editor
placeholder="Enter some text..."
plugins={plugins}
value={this.state.value}
onChange={this.onChange}
/>
</div>
)
}
}
/**

View File

@@ -22,6 +22,23 @@ class ReadOnly extends React.Component {
),
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<Editor
readOnly
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
/>
)
}
/**
* On change.
*
@@ -31,25 +48,6 @@ class ReadOnly extends React.Component {
onChange = ({ value }) => {
this.setState({ value })
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<div className="editor">
<Editor
readOnly
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
/>
</div>
)
}
}
/**

View File

@@ -4,6 +4,7 @@ import { Value } from 'slate'
import React from 'react'
import initialValue from './value.json'
import { isKeyHotkey } from 'is-hotkey'
import { Button, Icon, Toolbar } from '../components'
/**
* Define the default node type.
@@ -65,6 +66,136 @@ class RichTextExample extends React.Component {
return value.blocks.some(node => node.type == type)
}
/**
* Render.
*
* @return {Element}
*/
render() {
return (
<div>
<Toolbar>
{this.renderMarkButton('bold', 'format_bold')}
{this.renderMarkButton('italic', 'format_italic')}
{this.renderMarkButton('underlined', 'format_underlined')}
{this.renderMarkButton('code', 'code')}
{this.renderBlockButton('heading-one', 'looks_one')}
{this.renderBlockButton('heading-two', 'looks_two')}
{this.renderBlockButton('block-quote', 'format_quote')}
{this.renderBlockButton('numbered-list', 'format_list_numbered')}
{this.renderBlockButton('bulleted-list', 'format_list_bulleted')}
</Toolbar>
<Editor
spellCheck
autoFocus
placeholder="Enter some rich text..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
renderMark={this.renderMark}
/>
</div>
)
}
/**
* Render a mark-toggling toolbar button.
*
* @param {String} type
* @param {String} icon
* @return {Element}
*/
renderMarkButton = (type, icon) => {
const isActive = this.hasMark(type)
return (
<Button
active={isActive}
onMouseDown={event => this.onClickMark(event, type)}
>
<Icon>{icon}</Icon>
</Button>
)
}
/**
* Render a block-toggling toolbar button.
*
* @param {String} type
* @param {String} icon
* @return {Element}
*/
renderBlockButton = (type, icon) => {
let isActive = this.hasBlock(type)
if (['numbered-list', 'bulleted-list'].includes(type)) {
const { value } = this.state
const parent = value.document.getParent(value.blocks.first().key)
isActive = this.hasBlock('list-item') && parent && parent.type === type
}
return (
<Button
active={isActive}
onMouseDown={event => this.onClickBlock(event, type)}
>
<Icon>{icon}</Icon>
</Button>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node } = props
switch (node.type) {
case 'block-quote':
return <blockquote {...attributes}>{children}</blockquote>
case 'bulleted-list':
return <ul {...attributes}>{children}</ul>
case 'heading-one':
return <h1 {...attributes}>{children}</h1>
case 'heading-two':
return <h2 {...attributes}>{children}</h2>
case 'list-item':
return <li {...attributes}>{children}</li>
case 'numbered-list':
return <ol {...attributes}>{children}</ol>
}
}
/**
* Render a Slate mark.
*
* @param {Object} props
* @return {Element}
*/
renderMark = props => {
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong {...attributes}>{children}</strong>
case 'code':
return <code {...attributes}>{children}</code>
case 'italic':
return <em {...attributes}>{children}</em>
case 'underlined':
return <u {...attributes}>{children}</u>
}
}
/**
* On change, save the new `value`.
*
@@ -168,161 +299,6 @@ class RichTextExample extends React.Component {
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.renderMarkButton('bold', 'format_bold')}
{this.renderMarkButton('italic', 'format_italic')}
{this.renderMarkButton('underlined', 'format_underlined')}
{this.renderMarkButton('code', 'code')}
{this.renderBlockButton('heading-one', 'looks_one')}
{this.renderBlockButton('heading-two', 'looks_two')}
{this.renderBlockButton('block-quote', 'format_quote')}
{this.renderBlockButton('numbered-list', 'format_list_numbered')}
{this.renderBlockButton('bulleted-list', 'format_list_bulleted')}
</div>
)
}
/**
* Render a mark-toggling toolbar button.
*
* @param {String} type
* @param {String} icon
* @return {Element}
*/
renderMarkButton = (type, icon) => {
const isActive = this.hasMark(type)
const onMouseDown = event => this.onClickMark(event, type)
return (
// eslint-disable-next-line react/jsx-no-bind
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
<span className="material-icons">{icon}</span>
</span>
)
}
/**
* Render a block-toggling toolbar button.
*
* @param {String} type
* @param {String} icon
* @return {Element}
*/
renderBlockButton = (type, icon) => {
let isActive = this.hasBlock(type)
if (['numbered-list', 'bulleted-list'].includes(type)) {
const { value } = this.state
const parent = value.document.getParent(value.blocks.first().key)
isActive = this.hasBlock('list-item') && parent && parent.type === type
}
const onMouseDown = event => this.onClickBlock(event, type)
return (
// eslint-disable-next-line react/jsx-no-bind
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
<span className="material-icons">{icon}</span>
</span>
)
}
/**
* Render the Slate editor.
*
* @return {Element}
*/
renderEditor = () => {
return (
<div className="editor">
<Editor
placeholder="Enter some rich text..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
renderMark={this.renderMark}
spellCheck
autoFocus
/>
</div>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node } = props
switch (node.type) {
case 'block-quote':
return <blockquote {...attributes}>{children}</blockquote>
case 'bulleted-list':
return <ul {...attributes}>{children}</ul>
case 'heading-one':
return <h1 {...attributes}>{children}</h1>
case 'heading-two':
return <h2 {...attributes}>{children}</h2>
case 'list-item':
return <li {...attributes}>{children}</li>
case 'numbered-list':
return <ol {...attributes}>{children}</ol>
}
}
/**
* Render a Slate mark.
*
* @param {Object} props
* @return {Element}
*/
renderMark = props => {
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong {...attributes}>{children}</strong>
case 'code':
return <code {...attributes}>{children}</code>
case 'italic':
return <em {...attributes}>{children}</em>
case 'underlined':
return <u {...attributes}>{children}</u>
}
}
}
/**

View File

@@ -5,7 +5,7 @@ import React from 'react'
import initialValue from './value.json'
/**
* The plain text example.
* A right-to-left text example.
*
* @type {Component}
*/
@@ -21,6 +21,40 @@ class RTL extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<Editor
placeholder="Enter some plain text..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node } = props
switch (node.type) {
case 'block-quote':
return <blockquote {...attributes}>{children}</blockquote>
}
}
/**
* On change.
*
@@ -45,42 +79,6 @@ class RTL extends React.Component {
return true
}
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<div className="editor">
<Editor
placeholder="Enter some plain text..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
</div>
)
}
/**
* Render a Slate node.
*
* @param {Object} props
* @return {Element}
*/
renderNode = props => {
const { attributes, children, node } = props
switch (node.type) {
case 'block-quote':
return <blockquote {...attributes}>{children}</blockquote>
}
}
}
/**

View File

@@ -3,9 +3,33 @@ import { Value } from 'slate'
import React from 'react'
import initialValue from './value.json'
import styled from 'react-emotion'
import { Toolbar } from '../components'
/**
* The rich text example.
* Some styled components for the search box.
*
* @type {Component}
*/
const SearchWrapper = styled('div')`
position: relative;
`
const SearchIcon = styled('icon')`
position: absolute;
top: 0.5em;
left: 0.5em;
color: #ccc;
`
const SearchInput = styled('input')`
padding-left: 2em;
width: 100%;
`
/**
* The search highlighting example.
*
* @type {Component}
*/
@@ -21,6 +45,56 @@ class SearchHighlighting extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* Render.
*
* @return {Element}
*/
render() {
return (
<div>
<Toolbar>
<SearchWrapper>
<SearchIcon>search</SearchIcon>
<SearchInput
type="search"
placeholder="Search the text..."
onChange={this.onInputChange}
/>
</SearchWrapper>
</Toolbar>
<Editor
placeholder="Enter some rich text..."
value={this.state.value}
onChange={this.onChange}
renderMark={this.renderMark}
spellCheck
/>
</div>
)
}
/**
* Render a Slate mark.
*
* @param {Object} props
* @return {Element}
*/
renderMark = props => {
const { children, mark, attributes } = props
switch (mark.type) {
case 'highlight':
return (
<span {...attributes} style={{ backgroundColor: '#ffeeba' }}>
{children}
</span>
)
}
}
/**
* On change, save the new `value`.
*
@@ -64,94 +138,17 @@ class SearchHighlighting extends React.Component {
})
})
// setting the `save` option to false prevents this change from being added
// Setting the `save` option to false prevents this change from being added
// to the undo/redo stack and clearing the redo stack if the user has undone
// changes.
const change = value
.change()
.setOperationFlag('save', false)
.setValue({ decorations })
.setOperationFlag('save', true)
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">
<div className="search">
<span className="search-icon material-icons">search</span>
<input
className="search-box"
type="search"
placeholder="Search the text..."
onChange={this.onInputChange}
/>
</div>
</div>
)
}
/**
* Render the Slate editor.
*
* @return {Element}
*/
renderEditor = () => {
return (
<div className="editor">
<Editor
placeholder="Enter some rich text..."
value={this.state.value}
onChange={this.onChange}
renderMark={this.renderMark}
spellCheck
/>
</div>
)
}
/**
* Render a Slate mark.
*
* @param {Object} props
* @return {Element}
*/
renderMark = props => {
const { children, mark, attributes } = props
switch (mark.type) {
case 'highlight':
return (
<span {...attributes} style={{ backgroundColor: '#ffeeba' }}>
{children}
</span>
)
}
}
}
/**

View File

@@ -3,7 +3,21 @@ import { Value } from 'slate'
import React from 'react'
import initialValue from './value.json'
import styled from 'react-emotion'
import { isKeyHotkey } from 'is-hotkey'
import { Button, Icon, Toolbar } from '../components'
/**
* A spacer component.
*
* @type {Component}
*/
const Spacer = styled('div')`
height: 20px;
background-color: #eee;
margin: 20px -20px;
`
/**
* Hotkey matchers.
@@ -58,6 +72,74 @@ class SyncingEditor extends React.Component {
return value.activeMarks.some(mark => mark.type == type)
}
/**
* Render.
*
* @return {Element}
*/
render() {
return (
<div>
<Toolbar>
{this.renderMarkButton('bold', 'format_bold')}
{this.renderMarkButton('italic', 'format_italic')}
{this.renderMarkButton('underlined', 'format_underlined')}
{this.renderMarkButton('code', 'code')}
</Toolbar>
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderMark={this.renderMark}
spellCheck
/>
</div>
)
}
/**
* Render a mark-toggling toolbar button.
*
* @param {String} type
* @param {String} icon
* @return {Element}
*/
renderMarkButton = (type, icon) => {
return (
<Button
active={this.hasMark(type)}
onMouseDown={event => this.onClickMark(event, type)}
>
<Icon>{icon}</Icon>
</Button>
)
}
/**
* Render a Slate mark.
*
* @param {Object} props
* @return {Element}
*/
renderMark = props => {
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong {...attributes}>{children}</strong>
case 'code':
return <code {...attributes}>{children}</code>
case 'italic':
return <em {...attributes}>{children}</em>
case 'underlined':
return <u {...attributes}>{children}</u>
}
}
/**
* On change, save the new `value`. And if it's a local change, call the
* passed-in `onChange` handler.
@@ -115,101 +197,6 @@ class SyncingEditor extends React.Component {
const change = value.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 = event => this.onClickMark(event, type)
return (
// eslint-disable-next-line react/jsx-no-bind
<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
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderMark={this.renderMark}
spellCheck
/>
</div>
)
}
/**
* Render a Slate mark.
*
* @param {Object} props
* @return {Element}
*/
renderMark = props => {
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong {...attributes}>{children}</strong>
case 'code':
return <code {...attributes}>{children}</code>
case 'italic':
return <em {...attributes}>{children}</em>
case 'underlined':
return <u {...attributes}>{children}</u>
}
}
}
/**
@@ -220,23 +207,25 @@ class SyncingEditor extends React.Component {
class SyncingOperationsExample extends React.Component {
/**
* Save a reference to editor `one`.
* Render both editors.
*
* @param {SyncingEditor} one
* @return {Element}
*/
oneRef = one => {
this.one = one
}
/**
* Save a reference to editor `two`.
*
* @param {SyncingEditor} two
*/
twoRef = two => {
this.two = two
render() {
return (
<div>
<SyncingEditor
ref={one => (this.one = one)}
onChange={this.onOneChange}
/>
<Spacer />
<SyncingEditor
ref={two => (this.two = two)}
onChange={this.onTwoChange}
/>
</div>
)
}
/**
@@ -270,28 +259,6 @@ class SyncingOperationsExample extends React.Component {
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>
)
}
}
/**

View File

@@ -1,4 +1,5 @@
import { Editor } from 'slate-react'
import Plain from 'slate-plain-serializer'
import { Editor, getEventTransfer } from 'slate-react'
import { Value } from 'slate'
import React from 'react'
@@ -21,97 +22,6 @@ class Tables extends React.Component {
value: Value.fromJSON(initialValue),
}
/**
* On backspace, do nothing if at the start of a table cell.
*
* @param {Event} event
* @param {Change} change
*/
onBackspace = (event, change) => {
const { value } = change
if (value.startOffset != 0) return
event.preventDefault()
return true
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* On delete, do nothing if at the end of a table cell.
*
* @param {Event} event
* @param {Change} change
*/
onDelete = (event, change) => {
const { value } = change
if (value.endOffset != value.startText.text.length) return
event.preventDefault()
return true
}
/**
* On return, do nothing if inside a table cell.
*
* @param {Event} event
* @param {Change} change
*/
onEnter = (event, change) => {
event.preventDefault()
return true
}
/**
* On key down, check for our specific key shortcuts.
*
* @param {Event} event
* @param {Change} change
*/
onKeyDown = (event, change) => {
const { value } = change
const { document, selection } = value
const { startKey } = selection
const startNode = document.getDescendant(startKey)
if (selection.isAtStartOf(startNode)) {
const previous = document.getPreviousText(startNode.key)
const prevBlock = document.getClosestBlock(previous.key)
if (prevBlock.type == 'table-cell') {
if (['Backspace', 'Delete', 'Enter'].includes(event.key)) {
event.preventDefault()
return true
} else {
return
}
}
}
if (value.startBlock.type != 'table-cell') {
return
}
switch (event.key) {
case 'Backspace':
return this.onBackspace(event, change)
case 'Delete':
return this.onDelete(event, change)
case 'Enter':
return this.onEnter(event, change)
}
}
/**
* Render the example.
*
@@ -120,16 +30,16 @@ class Tables extends React.Component {
render() {
return (
<div className="editor">
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
renderMark={this.renderMark}
/>
</div>
<Editor
placeholder="Enter some text..."
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onDrop={this.onDropOrPaste}
onPaste={this.onDropOrPaste}
renderNode={this.renderNode}
renderMark={this.renderMark}
/>
)
}
@@ -172,6 +82,123 @@ class Tables extends React.Component {
return <strong {...attributes}>{children}</strong>
}
}
/**
* On backspace, do nothing if at the start of a table cell.
*
* @param {Event} event
* @param {Change} change
*/
onBackspace = (event, change) => {
const { value } = change
if (value.startOffset != 0) return
event.preventDefault()
return true
}
/**
* On change.
*
* @param {Change} change
*/
onChange = ({ value }) => {
this.setState({ value })
}
/**
* On delete, do nothing if at the end of a table cell.
*
* @param {Event} event
* @param {Change} change
*/
onDelete = (event, change) => {
const { value } = change
if (value.endOffset != value.startText.text.length) return
event.preventDefault()
return true
}
/**
* On paste or drop, only support plain text for this example.
*
* @param {Event} event
* @param {Change} change
*/
onDropOrPaste = (event, change) => {
const transfer = getEventTransfer(event)
const { value } = change
const { text = '' } = transfer
if (value.startBlock.type !== 'table-cell') {
return
}
if (!text) {
return
}
const lines = text.split('\n')
const { document } = Plain.deserialize(lines[0] || '')
change.insertFragment(document)
return false
}
/**
* On return, do nothing if inside a table cell.
*
* @param {Event} event
* @param {Change} change
*/
onEnter = (event, change) => {
event.preventDefault()
return true
}
/**
* On key down, check for our specific key shortcuts.
*
* @param {Event} event
* @param {Change} change
*/
onKeyDown = (event, change) => {
const { value } = change
const { document, selection } = value
const { startKey } = selection
const startNode = document.getDescendant(startKey)
if (selection.isAtStartOf(startNode)) {
const previous = document.getPreviousText(startNode.key)
const prevBlock = document.getClosestBlock(previous.key)
if (prevBlock.type === 'table-cell') {
if (['Backspace', 'Delete', 'Enter'].includes(event.key)) {
event.preventDefault()
return true
} else {
return
}
}
}
if (value.startBlock.type !== 'table-cell') {
return
}
switch (event.key) {
case 'Backspace':
return this.onBackspace(event, change)
case 'Delete':
return this.onDelete(event, change)
case 'Enter':
return this.onEnter(event, change)
}
}
}
/**

View File

@@ -19,6 +19,7 @@
"copy-webpack-plugin": "^4.4.1",
"cross-env": "^5.1.3",
"css-loader": "^0.28.9",
"emotion": "^9.2.4",
"eslint": "^4.19.1",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.8.0",
@@ -42,12 +43,12 @@
"npm-run-all": "^4.1.2",
"prettier": "^1.10.2",
"prismjs": "^1.5.1",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-emotion": "^9.2.4",
"react-hot-loader": "^3.1.3",
"react-portal": "^3.1.0",
"react-router": "^2.5.1",
"react-router-dom": "^4.1.1",
"react-portal": "^4.1.5",
"react-router-dom": "^4.3.1",
"read-metadata": "^1.0.0",
"rollup": "^0.55.1",
"rollup-plugin-alias": "^1.4.0",

307
yarn.lock
View File

@@ -24,6 +24,13 @@
dependencies:
"@babel/types" "7.0.0-beta.36"
"@babel/helper-module-imports@7.0.0-beta.40":
version "7.0.0-beta.40"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.40.tgz#251cbb6404599282e8f7356a5b32c9381bef5d2d"
dependencies:
"@babel/types" "7.0.0-beta.40"
lodash "^4.2.0"
"@babel/template@7.0.0-beta.36":
version "7.0.0-beta.36"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.36.tgz#02e903de5d68bd7899bce3c5b5447e59529abb00"
@@ -54,6 +61,60 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@babel/types@7.0.0-beta.40":
version "7.0.0-beta.40"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.40.tgz#25c3d7aae14126abe05fcb098c65a66b6d6b8c14"
dependencies:
esutils "^2.0.2"
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@emotion/babel-utils@^0.6.4":
version "0.6.4"
resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.4.tgz#2eac69eb31ae944fbe4a2a0e736a35db5f810866"
dependencies:
"@emotion/hash" "^0.6.3"
"@emotion/memoize" "^0.6.2"
"@emotion/serialize" "^0.8.2"
convert-source-map "^1.5.1"
find-root "^1.1.0"
source-map "^0.7.2"
"@emotion/hash@^0.6.2", "@emotion/hash@^0.6.3":
version "0.6.3"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.6.3.tgz#0e7a5604626fc6c6d4ac4061a2f5ac80d50262a4"
"@emotion/is-prop-valid@^0.6.1":
version "0.6.2"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.6.2.tgz#a76a16b174ff03f8e3a27faf6259bacd21a02adc"
dependencies:
"@emotion/memoize" "^0.6.2"
"@emotion/memoize@^0.6.1", "@emotion/memoize@^0.6.2":
version "0.6.2"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.2.tgz#138e00b332d519b4e307bded6159e5ba48aba3ae"
"@emotion/serialize@^0.8.2":
version "0.8.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.8.2.tgz#d3b2caddfc93107d63c79fc6bbc11e555e3b762e"
dependencies:
"@emotion/hash" "^0.6.3"
"@emotion/memoize" "^0.6.2"
"@emotion/unitless" "^0.6.3"
"@emotion/utils" "^0.7.1"
"@emotion/stylis@^0.6.10":
version "0.6.10"
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.6.10.tgz#7d321e639ebc8ba23ace5990c20e94dcebb8f3dd"
"@emotion/unitless@^0.6.2", "@emotion/unitless@^0.6.3":
version "0.6.3"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.6.3.tgz#65682e68a82701c70eefb38d7f941a2c0bfa90de"
"@emotion/utils@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.7.1.tgz#e44e596d03c9f16ba3b127ad333a8a072bcb5a0a"
"@types/node@^6.0.46":
version "6.0.83"
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.83.tgz#dd022db01ac2c01c1057775e88ccffce96d1d6fe"
@@ -628,12 +689,35 @@ babel-plugin-check-es2015-constants@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
babel-plugin-emotion@^9.2.4:
version "9.2.4"
resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-9.2.4.tgz#a4e54a8097f6ba06cbbc7a9063927afafe9fe73a"
dependencies:
"@babel/helper-module-imports" "7.0.0-beta.40"
"@emotion/babel-utils" "^0.6.4"
"@emotion/hash" "^0.6.2"
"@emotion/memoize" "^0.6.1"
"@emotion/stylis" "^0.6.10"
babel-plugin-macros "^2.0.0"
babel-plugin-syntax-jsx "^6.18.0"
convert-source-map "^1.5.0"
find-root "^1.1.0"
mkdirp "^0.5.1"
source-map "^0.5.7"
touch "^1.0.0"
babel-plugin-external-helpers@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-external-helpers/-/babel-plugin-external-helpers-6.22.0.tgz#2285f48b02bd5dede85175caf8c62e86adccefa1"
dependencies:
babel-runtime "^6.22.0"
babel-plugin-macros@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.2.2.tgz#049c93f4b934453688a6ec38bba529c55bf0fa1f"
dependencies:
cosmiconfig "^4.0.0"
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
@@ -678,7 +762,7 @@ babel-plugin-syntax-function-bind@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46"
babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
babel-plugin-syntax-jsx@^6.18.0, babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
@@ -2079,7 +2163,7 @@ conventional-recommended-bump@^1.0.1:
meow "^3.3.0"
object-assign "^4.0.1"
convert-source-map@^1.5.0:
convert-source-map@^1.5.0, convert-source-map@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
@@ -2135,6 +2219,15 @@ core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
cosmiconfig@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-4.0.0.tgz#760391549580bbd2df1e562bc177b13c290972dc"
dependencies:
is-directory "^0.3.1"
js-yaml "^3.9.0"
parse-json "^4.0.0"
require-from-string "^2.0.1"
create-ecdh@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
@@ -2142,6 +2235,24 @@ create-ecdh@^4.0.0:
bn.js "^4.1.0"
elliptic "^6.0.0"
create-emotion-styled@^9.2.3:
version "9.2.3"
resolved "https://registry.yarnpkg.com/create-emotion-styled/-/create-emotion-styled-9.2.3.tgz#17fb13b3ae4c165ea6e5a11356ab8b9ca1dad9c5"
dependencies:
"@emotion/is-prop-valid" "^0.6.1"
create-emotion@^9.2.4:
version "9.2.4"
resolved "https://registry.yarnpkg.com/create-emotion/-/create-emotion-9.2.4.tgz#0a4379f6bf0708c54fe26bfcd6b6bd3592e8cf23"
dependencies:
"@emotion/hash" "^0.6.2"
"@emotion/memoize" "^0.6.1"
"@emotion/stylis" "^0.6.10"
"@emotion/unitless" "^0.6.2"
csstype "^2.5.2"
stylis "^3.5.0"
stylis-rule-sheet "^0.0.10"
create-error-class@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
@@ -2313,6 +2424,10 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
dependencies:
cssom "0.3.x"
csstype@^2.5.2:
version "2.5.5"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.5.tgz#4125484a3d42189a863943f23b9e4b80fedfa106"
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@@ -2388,7 +2503,7 @@ dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
deep-equal@^1.0.0, deep-equal@^1.0.1:
deep-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@@ -2681,6 +2796,13 @@ emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
emotion@^9.2.4:
version "9.2.4"
resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.2.4.tgz#0139e7cc154b2845f4b9afaa996dd4de13bb90e3"
dependencies:
babel-plugin-emotion "^9.2.4"
create-emotion "^9.2.4"
encodeurl@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@@ -3305,6 +3427,10 @@ find-cache-dir@^1.0.0:
make-dir "^1.0.0"
pkg-dir "^2.0.0"
find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@@ -3871,23 +3997,14 @@ he@1.1.x:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
history@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/history/-/history-2.1.2.tgz#4aa2de897a0e4867e4539843be6ecdb2986bfdec"
dependencies:
deep-equal "^1.0.0"
invariant "^2.0.0"
query-string "^3.0.0"
warning "^2.0.0"
history@^4.5.1, history@^4.6.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/history/-/history-4.6.1.tgz#911cf8eb65728555a94f2b12780a0c531a14d2fd"
history@^4.7.2:
version "4.7.2"
resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
dependencies:
invariant "^2.2.1"
loose-envify "^1.2.0"
resolve-pathname "^2.0.0"
value-equal "^0.2.0"
resolve-pathname "^2.2.0"
value-equal "^0.4.0"
warning "^3.0.0"
hmac-drbg@^1.0.0:
@@ -3906,9 +4023,9 @@ hoek@4.x.x:
version "4.2.0"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
hoist-non-react-statics@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
hoist-non-react-statics@^2.5.0:
version "2.5.5"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
home-or-tmp@^2.0.0:
version "2.0.0"
@@ -4163,12 +4280,18 @@ interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
loose-envify "^1.0.0"
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
loose-envify "^1.0.0"
invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
@@ -4259,6 +4382,10 @@ is-descriptor@^1.0.0:
is-data-descriptor "^1.0.0"
kind-of "^6.0.2"
is-directory@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
is-dotfile@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d"
@@ -4537,7 +4664,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
js-yaml@^3.9.1:
js-yaml@^3.9.0, js-yaml@^3.9.1:
version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
dependencies:
@@ -5458,6 +5585,12 @@ nopt@^4.0.1:
abbrev "1"
osenv "^0.1.4"
nopt@~1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
dependencies:
abbrev "1"
normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5:
version "2.4.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
@@ -5824,7 +5957,7 @@ path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
path-to-regexp@^1.5.3:
path-to-regexp@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
dependencies:
@@ -6262,6 +6395,13 @@ prop-types@^15.6.0:
loose-envify "^1.3.1"
object-assign "^4.1.1"
prop-types@^15.6.1:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
proxy-addr@~2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
@@ -6362,12 +6502,6 @@ qs@~6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
query-string@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-3.0.3.tgz#ae2e14b4d05071d4e9b9eb4873c35b0dcd42e638"
dependencies:
strict-uri-encode "^1.0.0"
query-string@^4.1.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@@ -6446,15 +6580,22 @@ react-deep-force-update@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-2.1.1.tgz#8ea4263cd6455a050b37445b3f08fd839d86e909"
react-dom@^16.0.0:
version "16.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.0.0.tgz#9cc3079c3dcd70d4c6e01b84aab2a7e34c303f58"
react-dom@^16.4.1:
version "16.4.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.1.tgz#7f8b0223b3a5fbe205116c56deb85de32685dad6"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.0"
react-emotion@^9.2.4:
version "9.2.4"
resolved "https://registry.yarnpkg.com/react-emotion/-/react-emotion-9.2.4.tgz#98e00f70ce2ca4ee13923460123e763e492c013a"
dependencies:
babel-plugin-emotion "^9.2.4"
create-emotion-styled "^9.2.3"
react-hot-loader@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-3.1.3.tgz#6f92877326958c7cb0134b512474517869126082"
@@ -6475,46 +6616,44 @@ react-portal@^3.1.0:
dependencies:
prop-types "^15.5.8"
react-portal@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.1.5.tgz#6665d4d2a92d47d6f8b07a6529e26fc52d5cccde"
dependencies:
prop-types "^15.5.8"
react-proxy@^3.0.0-alpha.0:
version "3.0.0-alpha.1"
resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-3.0.0-alpha.1.tgz#4400426bcfa80caa6724c7755695315209fa4b07"
dependencies:
lodash "^4.6.1"
react-router-dom@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.1.1.tgz#3021ade1f2c160af97cf94e25594c5f294583025"
react-router-dom@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
dependencies:
history "^4.5.1"
history "^4.7.2"
invariant "^2.2.4"
loose-envify "^1.3.1"
prop-types "^15.5.4"
react-router "^4.1.1"
prop-types "^15.6.1"
react-router "^4.3.1"
warning "^4.0.1"
react-router@^2.5.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-2.8.1.tgz#73e9491f6ceb316d0f779829081863e378ee4ed7"
react-router@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e"
dependencies:
history "^2.1.2"
hoist-non-react-statics "^1.2.0"
invariant "^2.2.1"
loose-envify "^1.2.0"
warning "^3.0.0"
react-router@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.1.1.tgz#d448f3b7c1b429a6fbb03395099949c606b1fe95"
dependencies:
history "^4.6.0"
hoist-non-react-statics "^1.2.0"
invariant "^2.2.2"
history "^4.7.2"
hoist-non-react-statics "^2.5.0"
invariant "^2.2.4"
loose-envify "^1.3.1"
path-to-regexp "^1.5.3"
prop-types "^15.5.4"
warning "^3.0.0"
path-to-regexp "^1.7.0"
prop-types "^15.6.1"
warning "^4.0.1"
react@^16.0.0:
version "16.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.0.0.tgz#ce7df8f1941b036f02b2cca9dbd0cb1f0e855e2d"
react@^16.4.1:
version "16.4.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
@@ -6848,6 +6987,10 @@ require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
require-from-string@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
require-main-filename@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
@@ -6877,9 +7020,9 @@ resolve-from@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
resolve-pathname@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.1.0.tgz#e8358801b86b83b17560d4e3c382d7aef2100944"
resolve-pathname@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879"
resolve-url@^0.2.1:
version "0.2.1"
@@ -7345,7 +7488,7 @@ source-map@0.5.6, source-map@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
source-map@0.5.x, source-map@^0.5.3, source-map@~0.5.1, source-map@~0.5.6:
source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@@ -7359,6 +7502,10 @@ source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
source-map@^0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
sourcemapped-stacktrace@^1.1.6:
version "1.1.8"
resolved "https://registry.yarnpkg.com/sourcemapped-stacktrace/-/sourcemapped-stacktrace-1.1.8.tgz#6b7a3f1a6fb15f6d40e701e23ce404553480d688"
@@ -7618,6 +7765,14 @@ style-loader@^0.20.2:
loader-utils "^1.1.0"
schema-utils "^0.4.3"
stylis-rule-sheet@^0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430"
stylis@^3.5.0:
version "3.5.1"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.1.tgz#fd341d59f57f9aeb412bc14c9d8a8670b438e03b"
supports-color@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e"
@@ -7854,6 +8009,12 @@ toposort@^1.0.0:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.6.tgz#c31748e55d210effc00fdcdc7d6e68d7d7bb9cec"
touch@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/touch/-/touch-1.0.0.tgz#449cbe2dbae5a8c8038e30d71fa0ff464947c4de"
dependencies:
nopt "~1.0.10"
tough-cookie@>=2.3.3, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
@@ -8140,9 +8301,9 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
value-equal@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.2.1.tgz#c220a304361fce6994dbbedaa3c7e1a1b895871d"
value-equal@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
vary@~1.1.2:
version "1.1.2"
@@ -8168,18 +8329,18 @@ vm-browserify@0.0.4:
dependencies:
indexof "0.0.1"
warning@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/warning/-/warning-2.1.0.tgz#21220d9c63afc77a8c92111e011af705ce0c6901"
dependencies:
loose-envify "^1.0.0"
warning@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
dependencies:
loose-envify "^1.0.0"
warning@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.1.tgz#66ce376b7fbfe8a887c22bdf0e7349d73d397745"
dependencies:
loose-envify "^1.0.0"
watchpack@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac"