This commit is contained in:
Tom Moor
2017-12-02 23:14:27 -08:00
parent 5da809c4ab
commit 15e8e50601
29 changed files with 617 additions and 610 deletions

View File

@ -8,6 +8,9 @@
.*/node_modules/polished/.* .*/node_modules/polished/.*
.*/node_modules/react-side-effect/.* .*/node_modules/react-side-effect/.*
.*/node_modules/fbjs/.* .*/node_modules/fbjs/.*
.*/node_modules/slate-edit-code/.*
.*/node_modules/slate-edit-list/.*
.*/node_modules/slate-prism/.*
.*/node_modules/config-chain/.* .*/node_modules/config-chain/.*
*.test.js *.test.js

View File

@ -29,8 +29,8 @@ const Collaborators = ({ document }: Props) => {
<Avatars> <Avatars>
<StyledTooltip tooltip={tooltip} placement="bottom"> <StyledTooltip tooltip={tooltip} placement="bottom">
{collaborators.map(user => ( {collaborators.map(user => (
<AvatarWrapper> <AvatarWrapper key={user.id}>
<Avatar key={user.id} src={user.avatarUrl} /> <Avatar src={user.avatarUrl} />
</AvatarWrapper> </AvatarWrapper>
))} ))}
</StyledTooltip> </StyledTooltip>

View File

@ -2,9 +2,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { observable } from 'mobx'; import { observable } from 'mobx';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Editor, Plain } from 'slate'; import { Editor } from 'slate-react';
import type { state, props, change } from 'slate-prop-types';
import Plain from 'slate-plain-serializer';
import keydown from 'react-keydown'; import keydown from 'react-keydown';
import type { State, Editor as EditorType } from './types';
import getDataTransferFiles from 'utils/getDataTransferFiles'; import getDataTransferFiles from 'utils/getDataTransferFiles';
import Flex from 'shared/components/Flex'; import Flex from 'shared/components/Flex';
import ClickablePadding from './components/ClickablePadding'; import ClickablePadding from './components/ClickablePadding';
@ -13,18 +14,19 @@ import BlockInsert from './components/BlockInsert';
import Placeholder from './components/Placeholder'; import Placeholder from './components/Placeholder';
import Contents from './components/Contents'; import Contents from './components/Contents';
import Markdown from './serializer'; import Markdown from './serializer';
import createSchema from './schema';
import createPlugins from './plugins'; import createPlugins from './plugins';
import insertImage from './insertImage'; import insertImage from './insertImage';
import renderMark from './marks';
import createRenderNode from './nodes';
import styled from 'styled-components'; import styled from 'styled-components';
type Props = { type Props = {
text: string, text: string,
onChange: Function, onChange: change => *,
onSave: Function, onSave: (redirect?: boolean) => *,
onCancel: Function, onCancel: () => void,
onImageUploadStart: Function, onImageUploadStart: () => void,
onImageUploadStop: Function, onImageUploadStop: () => void,
emoji?: string, emoji?: string,
readOnly: boolean, readOnly: boolean,
}; };
@ -37,15 +39,15 @@ type KeyData = {
@observer @observer
class MarkdownEditor extends Component { class MarkdownEditor extends Component {
props: Props; props: Props;
editor: EditorType; editor: Editor;
schema: Object; renderNode: props => *;
plugins: Array<Object>; plugins: Object[];
@observable editorState: State; @observable editorValue: state;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.schema = createSchema({ this.renderNode = createRenderNode({
onInsertImage: this.insertImageFile, onInsertImage: this.insertImageFile,
onChange: this.onChange, onChange: this.onChange,
}); });
@ -55,9 +57,9 @@ class MarkdownEditor extends Component {
}); });
if (props.text.trim().length) { if (props.text.trim().length) {
this.editorState = Markdown.deserialize(props.text); this.editorValue = Markdown.deserialize(props.text);
} else { } else {
this.editorState = Plain.deserialize(''); this.editorValue = Plain.deserialize('');
} }
} }
@ -77,12 +79,11 @@ class MarkdownEditor extends Component {
} }
} }
onChange = (editorState: State) => { onChange = (change: change) => {
if (this.editorState !== editorState) { if (this.editorValue !== change.value) {
this.props.onChange(Markdown.serialize(editorState)); this.props.onChange(Markdown.serialize(change.value));
} }
this.editorValue = change.value;
this.editorState = editorState;
}; };
handleDrop = async (ev: SyntheticEvent) => { handleDrop = async (ev: SyntheticEvent) => {
@ -103,17 +104,16 @@ class MarkdownEditor extends Component {
}; };
insertImageFile = async (file: window.File) => { insertImageFile = async (file: window.File) => {
const state = this.editor.getState(); this.editor.change(
let transform = state.transform(); async change =>
await insertImage(
transform = await insertImage( change,
transform,
file, file,
this.editor, this.editor,
this.props.onImageUploadStart, this.props.onImageUploadStart,
this.props.onImageUploadStop this.props.onImageUploadStop
)
); );
this.editor.onChange(transform.apply());
}; };
cancelEvent = (ev: SyntheticEvent) => { cancelEvent = (ev: SyntheticEvent) => {
@ -136,7 +136,7 @@ class MarkdownEditor extends Component {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.props.onSave({ redirect: false }); this.props.onSave(true);
} }
@keydown('esc') @keydown('esc')
@ -146,37 +146,33 @@ class MarkdownEditor extends Component {
} }
// Handling of keyboard shortcuts within editor focus // Handling of keyboard shortcuts within editor focus
onKeyDown = (ev: SyntheticKeyboardEvent, data: KeyData, state: State) => { onKeyDown = (ev: SyntheticKeyboardEvent, data: KeyData, change: change) => {
if (!data.isMeta) return; if (!data.isMeta) return;
switch (data.key) { switch (data.key) {
case 's': case 's':
this.onSave(ev); this.onSave(ev);
return state; return change;
case 'enter': case 'enter':
this.onSaveAndExit(ev); this.onSaveAndExit(ev);
return state; return change;
case 'escape': case 'escape':
this.onCancel(); this.onCancel();
return state; return change;
default: default:
} }
}; };
focusAtStart = () => { focusAtStart = () => {
const state = this.editor.getState(); this.editor.change(change =>
const transform = state.transform(); change.collapseToStartOf(change.value.document).focus()
transform.collapseToStartOf(state.document); );
transform.focus();
this.editorState = transform.apply();
}; };
focusAtEnd = () => { focusAtEnd = () => {
const state = this.editor.getState(); this.editor.change(change =>
const transform = state.transform(); change.collapseToEndOf(change.value.document).focus()
transform.collapseToEndOf(state.document); );
transform.focus();
this.editorState = transform.apply();
}; };
render = () => { render = () => {
@ -193,14 +189,15 @@ class MarkdownEditor extends Component {
> >
<MaxWidth column auto> <MaxWidth column auto>
<Header onClick={this.focusAtStart} readOnly={readOnly} /> <Header onClick={this.focusAtStart} readOnly={readOnly} />
{readOnly && <Contents state={this.editorState} />} {readOnly && this.editor && <Contents editor={this.editor} />}
{!readOnly && ( {!readOnly &&
<Toolbar state={this.editorState} onChange={this.onChange} /> this.editor && (
<Toolbar value={this.editorValue} editor={this.editor} />
)} )}
{!readOnly && ( {!readOnly &&
this.editor && (
<BlockInsert <BlockInsert
state={this.editorState} editor={this.editor}
onChange={this.onChange}
onInsertImage={this.insertImageFile} onInsertImage={this.insertImageFile}
/> />
)} )}
@ -208,10 +205,11 @@ class MarkdownEditor extends Component {
innerRef={ref => (this.editor = ref)} innerRef={ref => (this.editor = ref)}
placeholder="Start with a title…" placeholder="Start with a title…"
bodyPlaceholder="…the rest is your canvas" bodyPlaceholder="…the rest is your canvas"
schema={this.schema}
plugins={this.plugins} plugins={this.plugins}
emoji={emoji} emoji={emoji}
state={this.editorState} value={this.editorValue}
renderNode={this.renderNode}
renderMark={renderMark}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
onChange={this.onChange} onChange={this.onChange}
onSave={onSave} onSave={onSave}

View File

@ -1,18 +1,16 @@
// @flow // @flow
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Portal } from 'react-portal'; import { Portal } from 'react-portal';
import { findDOMNode, Node } from 'slate'; import { Node } from 'slate';
import { Editor, findDOMNode } from 'slate-react';
import { observable } from 'mobx'; import { observable } from 'mobx';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import styled from 'styled-components'; import styled from 'styled-components';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
import PlusIcon from 'components/Icon/PlusIcon'; import PlusIcon from 'components/Icon/PlusIcon';
import type { State } from '../types';
type Props = { type Props = {
state: State, editor: Editor,
onChange: Function,
onInsertImage: File => Promise<*>,
}; };
function findClosestRootNode(state, ev) { function findClosestRootNode(state, ev) {
@ -53,7 +51,7 @@ export default class BlockInsert extends Component {
handleMouseMove = (ev: SyntheticMouseEvent) => { handleMouseMove = (ev: SyntheticMouseEvent) => {
const windowWidth = window.innerWidth / 2.5; const windowWidth = window.innerWidth / 2.5;
const result = findClosestRootNode(this.props.state, ev); const result = findClosestRootNode(this.props.editor.value, ev);
const movementThreshold = 200; const movementThreshold = 200;
this.mouseMovementSinceClick += this.mouseMovementSinceClick +=
@ -70,7 +68,7 @@ export default class BlockInsert extends Component {
this.closestRootNode = result.node; this.closestRootNode = result.node;
// do not show block menu on title heading or editor // do not show block menu on title heading or editor
const firstNode = this.props.state.document.nodes.first(); const firstNode = this.props.editor.value.document.nodes.first();
if (result.node === firstNode || result.node.type === 'block-toolbar') { if (result.node === firstNode || result.node.type === 'block-toolbar') {
this.left = -1000; this.left = -1000;
} else { } else {
@ -89,23 +87,22 @@ export default class BlockInsert extends Component {
this.mouseMovementSinceClick = 0; this.mouseMovementSinceClick = 0;
this.active = false; this.active = false;
const { state } = this.props; const { editor } = this.props;
const type = { type: 'block-toolbar', isVoid: true }; const type = { type: 'block-toolbar', isVoid: true };
let transform = state.transform();
editor.change(change => {
// remove any existing toolbars in the document as a fail safe // remove any existing toolbars in the document as a fail safe
state.document.nodes.forEach(node => { editor.value.document.nodes.forEach(node => {
if (node.type === 'block-toolbar') { if (node.type === 'block-toolbar') {
transform.removeNodeByKey(node.key); change.removeNodeByKey(node.key);
} }
}); });
transform change
.collapseToStartOf(this.closestRootNode) .collapseToStartOf(this.closestRootNode)
.collapseToEndOfPreviousBlock() .collapseToEndOfPreviousBlock()
.insertBlock(type); .insertBlock(type);
});
this.props.onChange(transform.apply());
}; };
render() { render() {

View File

@ -1,11 +1,11 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import type { props } from 'slate-prop-types';
import CopyButton from './CopyButton'; import CopyButton from './CopyButton';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
import type { Props } from '../types';
export default function Code({ children, node, readOnly, attributes }: Props) { export default function Code({ children, node, readOnly, attributes }: props) {
const language = node.data.get('language') || 'javascript'; const language = node.data.get('language') || 'javascript';
return ( return (

View File

@ -2,14 +2,15 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { observable } from 'mobx'; import { observable } from 'mobx';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Editor } from 'slate-react';
import type { state, block } from 'slate-prop-types';
import { List } from 'immutable'; import { List } from 'immutable';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
import headingToSlug from '../headingToSlug'; import headingToSlug from '../headingToSlug';
import type { State, Block } from '../types';
import styled from 'styled-components'; import styled from 'styled-components';
type Props = { type Props = {
state: State, editor: Editor,
}; };
@observer @observer
@ -53,10 +54,10 @@ class Contents extends Component {
return elements; return elements;
} }
get headings(): List<Block> { get headings(): List<block> {
const { state } = this.props; const { editor } = this.props;
return state.document.nodes.filter((node: Block) => { return editor.value.document.nodes.filter((node: block) => {
if (!node.text) return false; if (!node.text) return false;
return node.type.match(/^heading/); return node.type.match(/^heading/);
}); });
@ -74,7 +75,7 @@ class Contents extends Component {
const active = this.activeHeading === slug; const active = this.activeHeading === slug;
return ( return (
<ListItem type={heading.type} active={active}> <ListItem type={heading.type} active={active} key={slug}>
<Anchor href={`#${slug}`} active={active}> <Anchor href={`#${slug}`} active={active}>
{heading.text} {heading.text}
</Anchor> </Anchor>

View File

@ -1,16 +1,17 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { Document } from 'slate'; import { Document } from 'slate';
import { Editor } from 'slate-react';
import styled from 'styled-components'; import styled from 'styled-components';
import type { node } from 'slate-prop-types';
import headingToSlug from '../headingToSlug'; import headingToSlug from '../headingToSlug';
import type { Node, Editor } from '../types';
import Placeholder from './Placeholder'; import Placeholder from './Placeholder';
type Props = { type Props = {
children: React$Element<*>, children: React$Element<*>,
placeholder?: boolean, placeholder?: boolean,
parent: Node, parent: node,
node: Node, node: node,
editor: Editor, editor: Editor,
readOnly: boolean, readOnly: boolean,
component?: string, component?: string,

View File

@ -1,10 +1,10 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import type { Props } from '../types'; import type { props } from 'slate-prop-types';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
function HorizontalRule(props: Props) { function HorizontalRule(props: props) {
const { state, node, attributes } = props; const { state, node, attributes } = props;
const active = state.isFocused && state.selection.hasEdgeIn(node); const active = state.isFocused && state.selection.hasEdgeIn(node);
return <StyledHr active={active} {...attributes} />; return <StyledHr active={active} {...attributes} />;

View File

@ -2,23 +2,23 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import ImageZoom from 'react-medium-image-zoom'; import ImageZoom from 'react-medium-image-zoom';
import styled from 'styled-components'; import styled from 'styled-components';
import type { Props } from '../types'; import type { props } from 'slate-prop-types';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
class Image extends Component { class Image extends Component {
props: Props; props: props;
handleChange = (ev: SyntheticInputEvent) => { handleChange = (ev: SyntheticInputEvent) => {
const alt = ev.target.value; const alt = ev.target.value;
const { editor, node } = this.props; const { editor, node } = this.props;
const data = node.data.toObject(); const data = node.data.toObject();
const state = editor
.getState()
.transform()
.setNodeByKey(node.key, { data: { ...data, alt } })
.apply();
editor.onChange(state); editor.onChange(
editor
.getState()
.change()
.setNodeByKey(node.key, { data: { ...data, alt } })
);
}; };
handleClick = (ev: SyntheticInputEvent) => { handleClick = (ev: SyntheticInputEvent) => {

View File

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { Link as InternalLink } from 'react-router-dom'; import { Link as InternalLink } from 'react-router-dom';
import type { Props } from '../types'; import type { props } from 'slate-prop-types';
function getPathFromUrl(href: string) { function getPathFromUrl(href: string) {
if (href[0] === '/') return href; if (href[0] === '/') return href;
@ -14,7 +14,7 @@ function getPathFromUrl(href: string) {
} }
} }
function isOutlineUrl(href: string) { function isInternalUrl(href: string) {
if (href[0] === '/') return true; if (href[0] === '/') return true;
try { try {
@ -26,11 +26,11 @@ function isOutlineUrl(href: string) {
} }
} }
export default function Link({ attributes, node, children, readOnly }: Props) { export default function Link({ attributes, node, children, readOnly }: props) {
const href = node.data.get('href'); const href = node.data.get('href');
const path = getPathFromUrl(href); const path = getPathFromUrl(href);
if (isOutlineUrl(href) && readOnly) { if (isInternalUrl(href) && readOnly) {
return ( return (
<InternalLink {...attributes} to={path}> <InternalLink {...attributes} to={path}>
{children} {children}

View File

@ -1,6 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import type { Props } from '../types'; import type { props } from 'slate-prop-types';
import TodoItem from './TodoItem'; import TodoItem from './TodoItem';
export default function ListItem({ export default function ListItem({
@ -8,7 +8,7 @@ export default function ListItem({
node, node,
attributes, attributes,
...props ...props
}: Props) { }: props) {
const checked = node.data.get('checked'); const checked = node.data.get('checked');
if (checked !== undefined) { if (checked !== undefined) {

View File

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { Document } from 'slate'; import { Document } from 'slate';
import type { Props } from '../types'; import type { props } from 'slate-prop-types';
import Placeholder from './Placeholder'; import Placeholder from './Placeholder';
export default function Link({ export default function Link({
@ -11,7 +11,7 @@ export default function Link({
parent, parent,
children, children,
readOnly, readOnly,
}: Props) { }: props) {
const parentIsDocument = parent instanceof Document; const parentIsDocument = parent instanceof Document;
const firstParagraph = parent && parent.nodes.get(1) === node; const firstParagraph = parent && parent.nodes.get(1) === node;
const lastParagraph = parent && parent.nodes.last() === node; const lastParagraph = parent && parent.nodes.last() === node;

View File

@ -2,21 +2,20 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
import type { Props } from '../types'; import type { props } from 'slate-prop-types';
export default class TodoItem extends Component { export default class TodoItem extends Component {
props: Props & { checked: boolean }; props: props & { checked: boolean };
handleChange = (ev: SyntheticInputEvent) => { handleChange = (ev: SyntheticInputEvent) => {
const checked = ev.target.checked; const checked = ev.target.checked;
const { editor, node } = this.props; const { editor, node } = this.props;
const state = editor const change = editor
.getState() .getState()
.transform() .change()
.setNodeByKey(node.key, { data: { checked } }) .setNodeByKey(node.key, { data: { checked } });
.apply();
editor.onChange(state); editor.onChange(change);
}; };
render() { render() {

View File

@ -13,12 +13,12 @@ import HorizontalRuleIcon from 'components/Icon/HorizontalRuleIcon';
import TodoListIcon from 'components/Icon/TodoListIcon'; import TodoListIcon from 'components/Icon/TodoListIcon';
import Flex from 'shared/components/Flex'; import Flex from 'shared/components/Flex';
import ToolbarButton from './components/ToolbarButton'; import ToolbarButton from './components/ToolbarButton';
import type { Props as BaseProps } from '../../types'; import type { props } from 'slate-prop-types';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
import { fadeIn } from 'shared/styles/animations'; import { fadeIn } from 'shared/styles/animations';
import { splitAndInsertBlock } from '../../transforms'; import { splitAndInsertBlock } from '../../transforms';
type Props = BaseProps & { type Props = props & {
onInsertImage: Function, onInsertImage: Function,
onChange: Function, onChange: Function,
}; };
@ -34,16 +34,15 @@ class BlockToolbar extends Component {
file: HTMLInputElement; file: HTMLInputElement;
componentWillReceiveProps(nextProps: Props) { componentWillReceiveProps(nextProps: Props) {
const wasActive = this.props.state.selection.hasEdgeIn(this.props.node); const { editor } = this.props;
const isActive = nextProps.state.selection.hasEdgeIn(nextProps.node); const wasActive = editor.value.selection.hasEdgeIn(this.props.node);
const isActive = nextProps.editor.value.selection.hasEdgeIn(nextProps.node);
const becameInactive = !isActive && wasActive; const becameInactive = !isActive && wasActive;
if (becameInactive) { if (becameInactive) {
const state = nextProps.state nextProps.editor.change(change =>
.transform() change.removeNodeByKey(nextProps.node.key)
.removeNodeByKey(nextProps.node.key) );
.apply();
this.props.onChange(state);
} }
} }
@ -52,24 +51,25 @@ class BlockToolbar extends Component {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
const state = this.props.state this.props.editor.change(change =>
.transform() change.removeNodeByKey(this.props.node.key)
.removeNodeByKey(this.props.node.key) );
.apply();
this.props.onChange(state);
} }
insertBlock = (options: Options) => { insertBlock = (options: Options) => {
const { state } = this.props; const { editor } = this.props;
let transform = splitAndInsertBlock(state.transform(), state, options);
state.document.nodes.forEach(node => { editor.change(change => {
splitAndInsertBlock(change, options);
change.value.document.nodes.forEach(node => {
if (node.type === 'block-toolbar') { if (node.type === 'block-toolbar') {
transform.removeNodeByKey(node.key); change.removeNodeByKey(node.key);
} }
}); });
this.props.onChange(transform.focus().apply()); change.focus();
});
}; };
handleClickBlock = (ev: SyntheticEvent, type: string) => { handleClickBlock = (ev: SyntheticEvent, type: string) => {
@ -126,8 +126,9 @@ class BlockToolbar extends Component {
}; };
render() { render() {
const { state, attributes, node } = this.props; const { editor, attributes, node } = this.props;
const active = state.isFocused && state.selection.hasEdgeIn(node); const active =
editor.value.isFocused && editor.value.selection.hasEdgeIn(node);
return ( return (
<Bar active={active} {...attributes}> <Bar active={active} {...attributes}>

View File

@ -3,9 +3,10 @@ import React, { Component } from 'react';
import { observable } from 'mobx'; import { observable } from 'mobx';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Portal } from 'react-portal'; import { Portal } from 'react-portal';
import { Editor } from 'slate-react';
import type { value } from 'slate-prop-types';
import styled from 'styled-components'; import styled from 'styled-components';
import _ from 'lodash'; import _ from 'lodash';
import type { State } from '../../types';
import FormattingToolbar from './components/FormattingToolbar'; import FormattingToolbar from './components/FormattingToolbar';
import LinkToolbar from './components/LinkToolbar'; import LinkToolbar from './components/LinkToolbar';
@ -18,8 +19,8 @@ export default class Toolbar extends Component {
@observable left: string = ''; @observable left: string = '';
props: { props: {
state: State, editor: Editor,
onChange: (state: State) => void, value: value,
}; };
menu: HTMLElement; menu: HTMLElement;
@ -41,11 +42,11 @@ export default class Toolbar extends Component {
}; };
get linkInSelection(): any { get linkInSelection(): any {
const { state } = this.props; const { value } = this.props;
try { try {
const selectedLinks = state.startBlock const selectedLinks = value.startBlock
.getInlinesAtRange(state.selection) .getInlinesAtRange(value.selection)
.filter(node => node.type === 'link'); .filter(node => node.type === 'link');
if (selectedLinks.size) { if (selectedLinks.size) {
return selectedLinks.first(); return selectedLinks.first();
@ -56,10 +57,10 @@ export default class Toolbar extends Component {
} }
update = () => { update = () => {
const { state } = this.props; const { value } = this.props;
const link = this.linkInSelection; const link = this.linkInSelection;
if (state.isBlurred || (state.isCollapsed && !link)) { if (value.isBlurred || (value.isCollapsed && !link)) {
if (this.active && !this.focused) { if (this.active && !this.focused) {
this.active = false; this.active = false;
this.link = undefined; this.link = undefined;
@ -70,11 +71,11 @@ export default class Toolbar extends Component {
} }
// don't display toolbar for document title // don't display toolbar for document title
const firstNode = state.document.nodes.first(); const firstNode = value.document.nodes.first();
if (firstNode === state.startBlock) return; if (firstNode === value.startBlock) return;
// don't display toolbar for code blocks // don't display toolbar for code blocks
if (state.startBlock.type === 'code') return; if (value.startBlock.type === 'code') return;
this.active = true; this.active = true;
this.focused = !!link; this.focused = !!link;

View File

@ -1,7 +1,7 @@
// @flow // @flow
import React, { Component } from 'react'; import React, { Component } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import type { State } from '../../../types'; import { Editor } from 'slate-react';
import ToolbarButton from './ToolbarButton'; import ToolbarButton from './ToolbarButton';
import BoldIcon from 'components/Icon/BoldIcon'; import BoldIcon from 'components/Icon/BoldIcon';
import CodeIcon from 'components/Icon/CodeIcon'; import CodeIcon from 'components/Icon/CodeIcon';
@ -13,8 +13,7 @@ import StrikethroughIcon from 'components/Icon/StrikethroughIcon';
class FormattingToolbar extends Component { class FormattingToolbar extends Component {
props: { props: {
state: State, editor: Editor,
onChange: Function,
onCreateLink: Function, onCreateLink: Function,
}; };
@ -25,11 +24,11 @@ class FormattingToolbar extends Component {
* @return {Boolean} * @return {Boolean}
*/ */
hasMark = (type: string) => { hasMark = (type: string) => {
return this.props.state.marks.some(mark => mark.type === type); return this.props.editor.value.marks.some(mark => mark.type === type);
}; };
isBlock = (type: string) => { isBlock = (type: string) => {
return this.props.state.startBlock.type === type; return this.props.editor.value.startBlock.type === type;
}; };
/** /**
@ -40,37 +39,23 @@ class FormattingToolbar extends Component {
*/ */
onClickMark = (ev: SyntheticEvent, type: string) => { onClickMark = (ev: SyntheticEvent, type: string) => {
ev.preventDefault(); ev.preventDefault();
let { state } = this.props; this.props.editor.change(change => change.toggleMark(type));
state = state
.transform()
.toggleMark(type)
.apply();
this.props.onChange(state);
}; };
onClickBlock = (ev: SyntheticEvent, type: string) => { onClickBlock = (ev: SyntheticEvent, type: string) => {
ev.preventDefault(); ev.preventDefault();
let { state } = this.props; this.props.editor.change(change => change.setBlock(type));
state = state
.transform()
.setBlock(type)
.apply();
this.props.onChange(state);
}; };
onCreateLink = (ev: SyntheticEvent) => { onCreateLink = (ev: SyntheticEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
let { state } = this.props;
const data = { href: '' }; const data = { href: '' };
state = state this.props.editor.change(change => {
.transform() change.wrapInline({ type: 'link', data });
.wrapInline({ type: 'link', data })
.apply();
this.props.onChange(state);
this.props.onCreateLink(); this.props.onCreateLink();
});
}; };
renderMarkButton = (type: string, IconClass: Function) => { renderMarkButton = (type: string, IconClass: Function) => {

View File

@ -4,11 +4,12 @@ import ReactDOM from 'react-dom';
import { observable, action } from 'mobx'; import { observable, action } from 'mobx';
import { observer, inject } from 'mobx-react'; import { observer, inject } from 'mobx-react';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { Editor } from 'slate-react';
import styled from 'styled-components'; import styled from 'styled-components';
import ArrowKeyNavigation from 'boundless-arrow-key-navigation'; import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
import type { change } from 'slate-prop-types';
import ToolbarButton from './ToolbarButton'; import ToolbarButton from './ToolbarButton';
import DocumentResult from './DocumentResult'; import DocumentResult from './DocumentResult';
import type { State } from '../../../types';
import DocumentsStore from 'stores/DocumentsStore'; import DocumentsStore from 'stores/DocumentsStore';
import keydown from 'react-keydown'; import keydown from 'react-keydown';
import CloseIcon from 'components/Icon/CloseIcon'; import CloseIcon from 'components/Icon/CloseIcon';
@ -23,11 +24,11 @@ class LinkToolbar extends Component {
firstDocument: HTMLElement; firstDocument: HTMLElement;
props: { props: {
state: State, editor: Editor,
link: Object, link: Object,
documents: DocumentsStore, documents: DocumentsStore,
onBlur: () => void, onBlur: () => void,
onChange: State => void, onChange: change => *,
}; };
@observable isEditing: boolean = false; @observable isEditing: boolean = false;
@ -112,17 +113,14 @@ class LinkToolbar extends Component {
save = (href: string) => { save = (href: string) => {
href = href.trim(); href = href.trim();
const { state } = this.props; this.props.editor.change(change => {
const transform = state.transform();
if (href) { if (href) {
transform.setInline({ type: 'link', data: { href } }); change.setInline({ type: 'link', data: { href } });
} else { } else {
transform.unwrapInline('link'); change.unwrapInline('link');
} }
this.props.onChange(transform.apply());
this.props.onBlur(); this.props.onBlur();
});
}; };
setFirstDocumentRef = ref => { setFirstDocumentRef = ref => {

View File

@ -1,9 +1,9 @@
// @flow // @flow
import { escape } from 'lodash'; import { escape } from 'lodash';
import type { Node } from './types'; import type { node } from 'slate-prop-types';
import slug from 'slug'; import slug from 'slug';
export default function headingToSlug(node: Node) { export default function headingToSlug(node: node) {
const level = node.type.replace('heading', 'h'); const level = node.type.replace('heading', 'h');
return escape(`${level}-${slug(node.text)}-${node.key}`); return escape(`${level}-${slug(node.text)}-${node.key}`);
} }

View File

@ -1,10 +1,11 @@
// @flow // @flow
import uuid from 'uuid'; import uuid from 'uuid';
import uploadFile from 'utils/uploadFile'; import uploadFile from 'utils/uploadFile';
import type { Editor, Transform } from './types'; import { Editor } from 'slate-react';
import type { change } from 'slate-prop-types';
export default async function insertImageFile( export default async function insertImageFile(
transform: Transform, change: change,
file: window.File, file: window.File,
editor: Editor, editor: Editor,
onImageUploadStart: () => void, onImageUploadStart: () => void,
@ -21,7 +22,7 @@ export default async function insertImageFile(
const src = reader.result; const src = reader.result;
// insert into document as uploading placeholder // insert into document as uploading placeholder
const state = transform const state = change
.insertBlock({ .insertBlock({
type: 'image', type: 'image',
isVoid: true, isVoid: true,
@ -36,11 +37,11 @@ export default async function insertImageFile(
const asset = await uploadFile(file); const asset = await uploadFile(file);
const src = asset.url; const src = asset.url;
// we dont use the original transform provided to the callback here // we dont use the original change provided to the callback here
// as the state may have changed significantly in the time it took to // as the state may have changed significantly in the time it took to
// upload the file. // upload the file.
const state = editor.getState(); const state = editor.getState();
const finalTransform = state.transform(); const finalTransform = state.change();
const placeholder = state.document.findDescendant( const placeholder = state.document.findDescendant(
node => node.data && node.data.get('id') === id node => node.data && node.data.get('id') === id
); );

View File

@ -0,0 +1,22 @@
// @flow
import React from 'react';
import InlineCode from './components/InlineCode';
import type { props } from 'slate-prop-types';
export default function renderMark(props: props) {
switch (props.mark.type) {
case 'bold':
return <strong>{props.children}</strong>;
case 'code':
return <InlineCode>{props.children}</InlineCode>;
case 'italic':
return <em>{props.children}</em>;
case 'underlined':
return <u>{props.children}</u>;
case 'deleted':
return <del>{props.children}</del>;
case 'added':
return <mark>{props.children}</mark>;
default:
}
}

View File

@ -0,0 +1,75 @@
// @flow
import React from 'react';
import Code from './components/Code';
import BlockToolbar from './components/Toolbar/BlockToolbar';
import HorizontalRule from './components/HorizontalRule';
import Image from './components/Image';
import Link from './components/Link';
import ListItem from './components/ListItem';
import TodoList from './components/TodoList';
import {
Heading1,
Heading2,
Heading3,
Heading4,
Heading5,
Heading6,
} from './components/Heading';
import Paragraph from './components/Paragraph';
import type { props } from 'slate-prop-types';
type Options = {
onInsertImage: *,
};
export default function createRenderNode({ onChange, onInsertImage }: Options) {
return function renderNode(props: props) {
const { attributes } = props;
switch (props.node.type) {
case 'paragraph':
return <Paragraph {...props} />;
case 'block-toolbar':
return <BlockToolbar onInsertImage={onInsertImage} {...props} />;
case 'block-quote':
return <blockquote {...attributes}>{props.children}</blockquote>;
case 'bulleted-list':
return <ul {...attributes}>{props.children}</ul>;
case 'ordered-list':
return <ol {...attributes}>{props.children}</ol>;
case 'todo-list':
return <TodoList {...attributes}>{props.children}</TodoList>;
case 'table':
return <table {...attributes}>{props.children}</table>;
case 'table-row':
return <tr {...attributes}>{props.children}</tr>;
case 'table-head':
return <th {...attributes}>{props.children}</th>;
case 'table-cell':
return <td {...attributes}>{props.children}</td>;
case 'list-item':
return <ListItem {...props} />;
case 'horizontal-rule':
return <HorizontalRule {...props} />;
case 'code':
return <Code {...props} />;
case 'image':
return <Image {...props} />;
case 'link':
return <Link {...props} />;
case 'heading1':
return <Heading1 placeholder {...props} />;
case 'heading2':
return <Heading2 {...props} />;
case 'heading3':
return <Heading3 {...props} />;
case 'heading4':
return <Heading4 {...props} />;
case 'heading5':
return <Heading5 {...props} />;
case 'heading6':
return <Heading6 {...props} />;
default:
}
};
}

View File

@ -1,5 +1,5 @@
// @flow // @flow
import DropOrPasteImages from '@tommoor/slate-drop-or-paste-images'; // import DropOrPasteImages from '@tommoor/slate-drop-or-paste-images';
import PasteLinkify from 'slate-paste-linkify'; import PasteLinkify from 'slate-paste-linkify';
import CollapseOnEscape from 'slate-collapse-on-escape'; import CollapseOnEscape from 'slate-collapse-on-escape';
import TrailingBlock from 'slate-trailing-block'; import TrailingBlock from 'slate-trailing-block';
@ -8,7 +8,7 @@ import Prism from 'slate-prism';
import EditList from './plugins/EditList'; import EditList from './plugins/EditList';
import KeyboardShortcuts from './plugins/KeyboardShortcuts'; import KeyboardShortcuts from './plugins/KeyboardShortcuts';
import MarkdownShortcuts from './plugins/MarkdownShortcuts'; import MarkdownShortcuts from './plugins/MarkdownShortcuts';
import insertImage from './insertImage'; // import insertImage from './insertImage';
const onlyInCode = node => node.type === 'code'; const onlyInCode = node => node.type === 'code';
@ -23,18 +23,18 @@ const createPlugins = ({ onImageUploadStart, onImageUploadStop }: Options) => {
type: 'link', type: 'link',
collapseTo: 'end', collapseTo: 'end',
}), }),
DropOrPasteImages({ // DropOrPasteImages({
extensions: ['png', 'jpg', 'gif'], // extensions: ['png', 'jpg', 'gif'],
applyTransform: (transform, file, editor) => { // applyTransform: (transform, file, editor) => {
return insertImage( // return insertImage(
transform, // transform,
file, // file,
editor, // editor,
onImageUploadStart, // onImageUploadStart,
onImageUploadStop // onImageUploadStop
); // );
}, // },
}), // }),
EditList, EditList,
EditCode({ EditCode({
onlyIn: onlyInCode, onlyIn: onlyInCode,

View File

@ -1,4 +1,5 @@
// @flow // @flow
import type { change } from 'slate-prop-types';
export default function KeyboardShortcuts() { export default function KeyboardShortcuts() {
return { return {
@ -10,38 +11,32 @@ export default function KeyboardShortcuts() {
* @param {State} state * @param {State} state
* @return {State or Null} state * @return {State or Null} state
*/ */
onKeyDown(ev: SyntheticEvent, data: Object, state: Object) { onKeyDown(ev: SyntheticEvent, data: Object, change: change) {
if (!data.isMeta) return null; if (!data.isMeta) return null;
switch (data.key) { switch (data.key) {
case 'b': case 'b':
return this.toggleMark(state, 'bold'); return this.toggleMark(change, 'bold');
case 'i': case 'i':
return this.toggleMark(state, 'italic'); return this.toggleMark(change, 'italic');
case 'u': case 'u':
return this.toggleMark(state, 'underlined'); return this.toggleMark(change, 'underlined');
case 'd': case 'd':
return this.toggleMark(state, 'deleted'); return this.toggleMark(change, 'deleted');
case 'k': case 'k':
return state return change.wrapInline({ type: 'link', data: { href: '' } });
.transform()
.wrapInline({ type: 'link', data: { href: '' } })
.apply();
default: default:
return null; return null;
} }
}, },
toggleMark(state: Object, type: string) { toggleMark(change: change, type: string) {
const { state } = change;
// don't allow formatting of document title // don't allow formatting of document title
const firstNode = state.document.nodes.first(); const firstNode = state.document.nodes.first();
if (firstNode === state.startBlock) return; if (firstNode === state.startBlock) return;
state = state return state.change().toggleMark(type);
.transform()
.toggleMark(type)
.apply();
return state;
}, },
}; };
} }

View File

@ -1,4 +1,11 @@
// @flow // @flow
import type { change } from 'slate-prop-types';
type KeyData = {
isMeta: boolean,
key: string,
};
const inlineShortcuts = [ const inlineShortcuts = [
{ mark: 'bold', shortcut: '**' }, { mark: 'bold', shortcut: '**' },
{ mark: 'bold', shortcut: '__' }, { mark: 'bold', shortcut: '__' },
@ -14,20 +21,20 @@ export default function MarkdownShortcuts() {
/** /**
* On key down, check for our specific key shortcuts. * On key down, check for our specific key shortcuts.
*/ */
onKeyDown(ev: SyntheticEvent, data: Object, state: Object) { onKeyDown(ev: SyntheticEvent, data: KeyData, change: change) {
switch (data.key) { switch (data.key) {
case '-': case '-':
return this.onDash(ev, state); return this.onDash(ev, change);
case '`': case '`':
return this.onBacktick(ev, state); return this.onBacktick(ev, change);
case 'tab': case 'tab':
return this.onTab(ev, state); return this.onTab(ev, change);
case 'space': case 'space':
return this.onSpace(ev, state); return this.onSpace(ev, change);
case 'backspace': case 'backspace':
return this.onBackspace(ev, state); return this.onBackspace(ev, change);
case 'enter': case 'enter':
return this.onEnter(ev, state); return this.onEnter(ev, change);
default: default:
return null; return null;
} }
@ -37,7 +44,8 @@ export default function MarkdownShortcuts() {
* On space, if it was after an auto-markdown shortcut, convert the current * On space, if it was after an auto-markdown shortcut, convert the current
* node into the shortcut's corresponding type. * node into the shortcut's corresponding type.
*/ */
onSpace(ev: SyntheticEvent, state: Object) { onSpace(ev: SyntheticEvent, change: change) {
const { state } = change;
if (state.isExpanded) return; if (state.isExpanded) return;
const { startBlock, startOffset } = state; const { startBlock, startOffset } = state;
const chars = startBlock.text.slice(0, startOffset).trim(); const chars = startBlock.text.slice(0, startOffset).trim();
@ -50,25 +58,19 @@ export default function MarkdownShortcuts() {
let checked; let checked;
if (chars === '[x]') checked = true; if (chars === '[x]') checked = true;
if (chars === '[ ]') checked = false; if (chars === '[ ]') checked = false;
const transform = state const change = state.change().setBlock({ type, data: { checked } });
.transform()
.setBlock({ type, data: { checked } });
if (type === 'list-item') { if (type === 'list-item') {
if (checked !== undefined) { if (checked !== undefined) {
transform.wrapBlock('todo-list'); change.wrapBlock('todo-list');
} else if (chars === '1.') { } else if (chars === '1.') {
transform.wrapBlock('ordered-list'); change.wrapBlock('ordered-list');
} else { } else {
transform.wrapBlock('bulleted-list'); change.wrapBlock('bulleted-list');
} }
} }
state = transform return change.extendToStartOf(startBlock).delete();
.extendToStartOf(startBlock)
.delete()
.apply();
return state;
} }
for (const key of inlineShortcuts) { for (const key of inlineShortcuts) {
@ -97,35 +99,32 @@ export default function MarkdownShortcuts() {
// if we have multiple tags then mark the text between as inline code // if we have multiple tags then mark the text between as inline code
if (inlineTags.length > 1) { if (inlineTags.length > 1) {
const transform = state.transform(); const change = state.change();
const firstText = startBlock.getFirstText(); const firstText = startBlock.getFirstText();
const firstCodeTagIndex = inlineTags[0]; const firstCodeTagIndex = inlineTags[0];
const lastCodeTagIndex = inlineTags[inlineTags.length - 1]; const lastCodeTagIndex = inlineTags[inlineTags.length - 1];
transform.removeTextByKey( change.removeTextByKey(
firstText.key, firstText.key,
lastCodeTagIndex, lastCodeTagIndex,
shortcut.length shortcut.length
); );
transform.removeTextByKey( change.removeTextByKey(
firstText.key, firstText.key,
firstCodeTagIndex, firstCodeTagIndex,
shortcut.length shortcut.length
); );
transform.moveOffsetsTo( change.moveOffsetsTo(
firstCodeTagIndex, firstCodeTagIndex,
lastCodeTagIndex - shortcut.length lastCodeTagIndex - shortcut.length
); );
transform.addMark(mark); change.addMark(mark);
state = transform return change.collapseToEnd().removeMark(mark);
.collapseToEnd()
.removeMark(mark)
.apply();
return state;
} }
} }
}, },
onDash(ev: SyntheticEvent, state: Object) { onDash(ev: SyntheticEvent, change: change) {
const { state } = change;
if (state.isExpanded) return; if (state.isExpanded) return;
const { startBlock, startOffset } = state; const { startBlock, startOffset } = state;
const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, ''); const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, '');
@ -133,7 +132,7 @@ export default function MarkdownShortcuts() {
if (chars === '--') { if (chars === '--') {
ev.preventDefault(); ev.preventDefault();
return state return state
.transform() .change()
.extendToStartOf(startBlock) .extendToStartOf(startBlock)
.delete() .delete()
.setBlock({ .setBlock({
@ -141,12 +140,12 @@ export default function MarkdownShortcuts() {
isVoid: true, isVoid: true,
}) })
.collapseToStartOfNextBlock() .collapseToStartOfNextBlock()
.insertBlock('paragraph') .insertBlock('paragraph');
.apply();
} }
}, },
onBacktick(ev: SyntheticEvent, state: Object) { onBacktick(ev: SyntheticEvent, change: change) {
const { state } = change;
if (state.isExpanded) return; if (state.isExpanded) return;
const { startBlock, startOffset } = state; const { startBlock, startOffset } = state;
const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, ''); const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, '');
@ -154,18 +153,18 @@ export default function MarkdownShortcuts() {
if (chars === '``') { if (chars === '``') {
ev.preventDefault(); ev.preventDefault();
return state return state
.transform() .change()
.extendToStartOf(startBlock) .extendToStartOf(startBlock)
.delete() .delete()
.setBlock({ .setBlock({
type: 'code', type: 'code',
}) });
.apply();
} }
}, },
onBackspace(ev: SyntheticEvent, state: Object) { onBackspace(ev: SyntheticEvent, change: change) {
if (state.isExpanded) return; const { state } = change;
if (change.isExpanded) return;
const { startBlock, selection, startOffset } = state; const { startBlock, selection, startOffset } = state;
// If at the start of a non-paragraph, convert it back into a paragraph // If at the start of a non-paragraph, convert it back into a paragraph
@ -173,13 +172,11 @@ export default function MarkdownShortcuts() {
if (startBlock.type === 'paragraph') return; if (startBlock.type === 'paragraph') return;
ev.preventDefault(); ev.preventDefault();
const transform = state.transform().setBlock('paragraph'); const change = state.change().setBlock('paragraph');
if (startBlock.type === 'list-item') if (startBlock.type === 'list-item')
transform.unwrapBlock('bulleted-list'); change.unwrapBlock('bulleted-list');
return change;
state = transform.apply();
return state;
} }
// If at the end of a code mark hitting backspace should remove the mark // If at the end of a code mark hitting backspace should remove the mark
@ -198,15 +195,14 @@ export default function MarkdownShortcuts() {
.reverse() .reverse()
.takeUntil((v, k) => !v.marks.some(mark => mark.type === 'code')); .takeUntil((v, k) => !v.marks.some(mark => mark.type === 'code'));
const transform = state.transform(); return state
transform.removeMarkByKey( .change()
.removeMarkByKey(
textNode.key, textNode.key,
state.startOffset - charsInCodeBlock.size, change.startOffset - charsInCodeBlock.size,
state.startOffset, change.startOffset,
'code' 'code'
); );
state = transform.apply();
return state;
} }
} }
}, },
@ -215,14 +211,15 @@ export default function MarkdownShortcuts() {
* On tab, if at the end of the heading jump to the main body content * On tab, if at the end of the heading jump to the main body content
* as if it is another input field (act the same as enter). * as if it is another input field (act the same as enter).
*/ */
onTab(ev: SyntheticEvent, state: Object) { onTab(ev: SyntheticEvent, change: change) {
const { state } = change;
if (state.startBlock.type === 'heading1') { if (state.startBlock.type === 'heading1') {
ev.preventDefault(); ev.preventDefault();
return state return state
.transform() .change()
.splitBlock() .splitBlock()
.setBlock('paragraph') .setBlock('paragraph');
.apply();
} }
}, },
@ -230,11 +227,12 @@ export default function MarkdownShortcuts() {
* On return, if at the end of a node type that should not be extended, * On return, if at the end of a node type that should not be extended,
* create a new paragraph below it. * create a new paragraph below it.
*/ */
onEnter(ev: SyntheticEvent, state: Object) { onEnter(ev: SyntheticEvent, change: change) {
const { state } = change;
if (state.isExpanded) return; if (state.isExpanded) return;
const { startBlock, startOffset, endOffset } = state; const { startBlock, startOffset, endOffset } = state;
if (startOffset === 0 && startBlock.length === 0) if (startOffset === 0 && startBlock.length === 0)
return this.onBackspace(ev, state); return this.onBackspace(ev, change);
if (endOffset !== startBlock.length) return; if (endOffset !== startBlock.length) return;
// Hitting enter while an image is selected should jump caret below and // Hitting enter while an image is selected should jump caret below and
@ -242,10 +240,9 @@ export default function MarkdownShortcuts() {
if (startBlock.type === 'image') { if (startBlock.type === 'image') {
ev.preventDefault(); ev.preventDefault();
return state return state
.transform() .change()
.collapseToEnd() .collapseToEnd()
.insertBlock('paragraph') .insertBlock('paragraph');
.apply();
} }
// Hitting enter in a heading or blockquote will split the node at that // Hitting enter in a heading or blockquote will split the node at that
@ -264,10 +261,9 @@ export default function MarkdownShortcuts() {
ev.preventDefault(); ev.preventDefault();
return state return state
.transform() .change()
.splitBlock() .splitBlock()
.setBlock('paragraph') .setBlock('paragraph');
.apply();
}, },
/** /**

View File

@ -1,133 +1,133 @@
// @flow // // @flow
import React from 'react'; // import React from 'react';
import Code from './components/Code'; // import Code from './components/Code';
import HorizontalRule from './components/HorizontalRule'; // import HorizontalRule from './components/HorizontalRule';
import InlineCode from './components/InlineCode'; // import InlineCode from './components/InlineCode';
import Image from './components/Image'; // import Image from './components/Image';
import Link from './components/Link'; // import Link from './components/Link';
import ListItem from './components/ListItem'; // import ListItem from './components/ListItem';
import TodoList from './components/TodoList'; // import TodoList from './components/TodoList';
import { // import {
Heading1, // Heading1,
Heading2, // Heading2,
Heading3, // Heading3,
Heading4, // Heading4,
Heading5, // Heading5,
Heading6, // Heading6,
} from './components/Heading'; // } from './components/Heading';
import Paragraph from './components/Paragraph'; // import Paragraph from './components/Paragraph';
import BlockToolbar from './components/Toolbar/BlockToolbar'; // import BlockToolbar from './components/Toolbar/BlockToolbar';
import type { Props, Node, Transform } from './types'; // import type { Props, Node, Transform } from './types';
//
type Options = { // type Options = {
onInsertImage: Function, // onInsertImage: Function,
onChange: Function, // onChange: Function,
}; // };
//
const createSchema = ({ onInsertImage, onChange }: Options) => { // const createSchema = ({ onInsertImage, onChange }: Options) => {
return { // return {
marks: { // marks: {
bold: (props: Props) => <strong>{props.children}</strong>, // bold: (props: Props) => <strong>{props.children}</strong>,
code: (props: Props) => <InlineCode>{props.children}</InlineCode>, // code: (props: Props) => <InlineCode>{props.children}</InlineCode>,
italic: (props: Props) => <em>{props.children}</em>, // italic: (props: Props) => <em>{props.children}</em>,
underlined: (props: Props) => <u>{props.children}</u>, // underlined: (props: Props) => <u>{props.children}</u>,
deleted: (props: Props) => <del>{props.children}</del>, // deleted: (props: Props) => <del>{props.children}</del>,
added: (props: Props) => <mark>{props.children}</mark>, // added: (props: Props) => <mark>{props.children}</mark>,
}, // },
//
nodes: { // nodes: {
'block-toolbar': (props: Props) => ( // 'block-toolbar': (props: Props) => (
<BlockToolbar // <BlockToolbar
onChange={onChange} // onChange={onChange}
onInsertImage={onInsertImage} // onInsertImage={onInsertImage}
{...props} // {...props}
/> // />
), // ),
paragraph: (props: Props) => <Paragraph {...props} />, // paragraph: (props: Props) => <Paragraph {...props} />,
'block-quote': (props: Props) => ( // 'block-quote': (props: Props) => (
<blockquote {...props.attributes}>{props.children}</blockquote> // <blockquote {...props.attributes}>{props.children}</blockquote>
), // ),
'horizontal-rule': HorizontalRule, // 'horizontal-rule': HorizontalRule,
'bulleted-list': (props: Props) => ( // 'bulleted-list': (props: Props) => (
<ul {...props.attributes}>{props.children}</ul> // <ul {...props.attributes}>{props.children}</ul>
), // ),
'ordered-list': (props: Props) => ( // 'ordered-list': (props: Props) => (
<ol {...props.attributes}>{props.children}</ol> // <ol {...props.attributes}>{props.children}</ol>
), // ),
'todo-list': (props: Props) => ( // 'todo-list': (props: Props) => (
<TodoList {...props.attributes}>{props.children}</TodoList> // <TodoList {...props.attributes}>{props.children}</TodoList>
), // ),
table: (props: Props) => ( // table: (props: Props) => (
<table {...props.attributes}>{props.children}</table> // <table {...props.attributes}>{props.children}</table>
), // ),
'table-row': (props: Props) => ( // 'table-row': (props: Props) => (
<tr {...props.attributes}>{props.children}</tr> // <tr {...props.attributes}>{props.children}</tr>
), // ),
'table-head': (props: Props) => ( // 'table-head': (props: Props) => (
<th {...props.attributes}>{props.children}</th> // <th {...props.attributes}>{props.children}</th>
), // ),
'table-cell': (props: Props) => ( // 'table-cell': (props: Props) => (
<td {...props.attributes}>{props.children}</td> // <td {...props.attributes}>{props.children}</td>
), // ),
code: Code, // code: Code,
image: Image, // image: Image,
link: Link, // link: Link,
'list-item': ListItem, // 'list-item': ListItem,
heading1: (props: Props) => <Heading1 placeholder {...props} />, // heading1: (props: Props) => <Heading1 placeholder {...props} />,
heading2: (props: Props) => <Heading2 {...props} />, // heading2: (props: Props) => <Heading2 {...props} />,
heading3: (props: Props) => <Heading3 {...props} />, // heading3: (props: Props) => <Heading3 {...props} />,
heading4: (props: Props) => <Heading4 {...props} />, // heading4: (props: Props) => <Heading4 {...props} />,
heading5: (props: Props) => <Heading5 {...props} />, // heading5: (props: Props) => <Heading5 {...props} />,
heading6: (props: Props) => <Heading6 {...props} />, // heading6: (props: Props) => <Heading6 {...props} />,
}, // },
//
rules: [ // rules: [
// ensure first node is always a heading // // ensure first node is always a heading
{ // {
match: (node: Node) => { // match: (node: Node) => {
return node.kind === 'document'; // return node.kind === 'document';
}, // },
validate: (document: Node) => { // validate: (document: Node) => {
const firstNode = document.nodes.first(); // const firstNode = document.nodes.first();
return firstNode && firstNode.type === 'heading1' ? null : firstNode; // return firstNode && firstNode.type === 'heading1' ? null : firstNode;
}, // },
normalize: (transform: Transform, document: Node, firstNode: Node) => { // normalize: (transform: Transform, document: Node, firstNode: Node) => {
transform.setBlock({ type: 'heading1' }); // transform.setBlock({ type: 'heading1' });
}, // },
}, // },
//
// automatically removes any marks in first heading // // automatically removes any marks in first heading
{ // {
match: (node: Node) => { // match: (node: Node) => {
return node.kind === 'heading1'; // return node.kind === 'heading1';
}, // },
validate: (heading: Node) => { // validate: (heading: Node) => {
const hasMarks = heading.getMarks().isEmpty(); // const hasMarks = heading.getMarks().isEmpty();
const hasInlines = heading.getInlines().isEmpty(); // const hasInlines = heading.getInlines().isEmpty();
//
return !(hasMarks && hasInlines); // return !(hasMarks && hasInlines);
}, // },
normalize: (transform: Transform, heading: Node) => { // normalize: (transform: Transform, heading: Node) => {
transform.unwrapInlineByKey(heading.key); // transform.unwrapInlineByKey(heading.key);
//
heading.getMarks().forEach(mark => { // heading.getMarks().forEach(mark => {
heading.nodes.forEach(textNode => { // heading.nodes.forEach(textNode => {
if (textNode.kind === 'text') { // if (textNode.kind === 'text') {
transform.removeMarkByKey( // transform.removeMarkByKey(
textNode.key, // textNode.key,
0, // 0,
textNode.text.length, // textNode.text.length,
mark // mark
); // );
} // }
}); // });
}); // });
//
return transform; // return transform;
}, // },
}, // },
], // ],
}; // };
}; // };
//
export default createSchema; // export default createSchema;

View File

@ -1,6 +1,6 @@
// @flow // @flow
import type { change } from 'slate-prop-types';
import EditList from './plugins/EditList'; import EditList from './plugins/EditList';
import type { State, Transform } from './types';
const { transforms } = EditList; const { transforms } = EditList;
@ -10,28 +10,25 @@ type Options = {
append?: string | Object, append?: string | Object,
}; };
export function splitAndInsertBlock( export function splitAndInsertBlock(change: change, options: Options) {
transform: Transform,
state: State,
options: Options
) {
const { type, wrapper, append } = options; const { type, wrapper, append } = options;
const { document } = state; const { value } = change;
const parent = document.getParent(state.startBlock.key); const { document } = value;
const parent = document.getParent(value.startBlock.key);
// lists get some special treatment // lists get some special treatment
if (parent && parent.type === 'list-item') { if (parent && parent.type === 'list-item') {
transform = transforms.unwrapList( change = transforms.unwrapList(
transforms transforms
.splitListItem(transform.collapseToStart()) .splitListItem(change.collapseToStart())
.collapseToEndOfPreviousBlock() .collapseToEndOfPreviousBlock()
); );
} }
transform = transform.insertBlock(type); change = change.insertBlock(type);
if (wrapper) transform = transform.wrapBlock(wrapper); if (wrapper) change = change.wrapBlock(wrapper);
if (append) transform = transform.insertBlock(append); if (append) change = change.insertBlock(append);
return transform; return change;
} }

View File

@ -1,118 +0,0 @@
// @flow
import { List, Set, Map } from 'immutable';
import { Selection } from 'slate';
export type NodeTransform = {
addMarkByKey: Function,
insertNodeByKey: Function,
insertTextByKey: Function,
moveNodeByKey: Function,
removeMarkByKey: Function,
removeNodeByKey: Function,
removeTextByKey: Function,
setMarkByKey: Function,
setNodeByKey: Function,
splitNodeByKey: Function,
unwrapInlineByKey: Function,
unwrapBlockByKey: Function,
unwrapNodeByKey: Function,
wrapBlockByKey: Function,
wrapInlineByKey: Function,
};
export type StateTransform = {
deleteBackward: Function,
deleteForward: Function,
delete: Function,
insertBlock: Function,
insertFragment: Function,
insertInline: Function,
insertText: Function,
addMark: Function,
setBlock: Function,
setInline: Function,
splitBlock: Function,
splitInline: Function,
removeMark: Function,
toggleMark: Function,
unwrapBlock: Function,
unwrapInline: Function,
wrapBlock: Function,
wrapInline: Function,
wrapText: Function,
};
export type SelectionTransform = {
collapseToStart: Function,
collapseToEnd: Function,
};
export type Transform = NodeTransform & StateTransform & SelectionTransform;
export type Editor = {
props: Object,
className: string,
onChange: Function,
onDocumentChange: Function,
onSelectionChange: Function,
plugins: Array<Object>,
readOnly: boolean,
state: Object,
style: Object,
placeholder?: string,
placeholderClassName?: string,
placeholderStyle?: string,
blur: Function,
focus: Function,
getSchema: Function,
getState: Function,
};
export type Node = {
key: string,
kind: string,
type: string,
length: number,
text: string,
data: Map<string, any>,
nodes: List<Node>,
getMarks: Function,
getBlocks: Function,
getParent: Function,
getInlines: Function,
getInlinesAtRange: Function,
setBlock: Function,
};
export type Block = Node & {
type: string,
};
export type Document = Node;
export type State = {
document: Document,
selection: Selection,
startBlock: Block,
endBlock: Block,
startText: Node,
endText: Node,
marks: Set<*>,
blocks: List<Block>,
fragment: Document,
lines: List<Node>,
tests: List<Node>,
startBlock: Block,
transform: Function,
isBlurred: Function,
};
export type Props = {
node: Node,
parent?: Node,
attributes?: Object,
state: State,
editor: Editor,
readOnly?: boolean,
children?: React$Element<any>,
};

View File

@ -105,6 +105,7 @@
"history": "3.0.0", "history": "3.0.0",
"html-webpack-plugin": "2.17.0", "html-webpack-plugin": "2.17.0",
"http-errors": "1.4.0", "http-errors": "1.4.0",
"immutable": "^3.8.2",
"imports-loader": "0.6.5", "imports-loader": "0.6.5",
"invariant": "^2.2.2", "invariant": "^2.2.2",
"isomorphic-fetch": "2.2.1", "isomorphic-fetch": "2.2.1",
@ -160,14 +161,17 @@
"sequelize": "^4.3.1", "sequelize": "^4.3.1",
"sequelize-cli": "^2.7.0", "sequelize-cli": "^2.7.0",
"sequelize-encrypted": "0.1.0", "sequelize-encrypted": "0.1.0",
"slate": "^0.21.4", "slate": "^0.29.0",
"slate-collapse-on-escape": "^0.2.1", "slate-collapse-on-escape": "^0.6.0",
"slate-edit-code": "^0.10.2", "slate-edit-code": "^0.13.2",
"slate-edit-list": "^0.7.0", "slate-edit-list": "^0.10.1",
"slate-md-serializer": "0.5.6", "slate-md-serializer": "1.0.1",
"slate-paste-linkify": "^0.2.1", "slate-paste-linkify": "^0.5.0",
"slate-prism": "^0.2.2", "slate-plain-serializer": "^0.4.12",
"slate-trailing-block": "^0.2.4", "slate-prism": "^0.4.0",
"slate-prop-types": "^0.4.12",
"slate-react": "^0.10.19",
"slate-trailing-block": "^0.4.0",
"slug": "0.9.1", "slug": "0.9.1",
"string-hash": "^1.1.0", "string-hash": "^1.1.0",
"string-replace-to-array": "^1.0.3", "string-replace-to-array": "^1.0.3",

139
yarn.lock
View File

@ -2664,7 +2664,7 @@ es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
es5-ext "^0.10.14" es5-ext "^0.10.14"
es6-symbol "^3.1" es6-symbol "^3.1"
es6-map@^0.1.3, es6-map@^0.1.4: es6-map@^0.1.3:
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
dependencies: dependencies:
@ -3467,7 +3467,7 @@ get-caller-file@^1.0.1:
get-document@1: get-document@1:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b" resolved "https://registry.npmjs.org/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b"
get-stdin@^4.0.1: get-stdin@^4.0.1:
version "4.0.1" version "4.0.1"
@ -3479,7 +3479,7 @@ get-stream@^3.0.0:
get-window@^1.1.1: get-window@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/get-window/-/get-window-1.1.1.tgz#0750f8970c88a54ac1294deb97add9568b3db594" resolved "https://registry.npmjs.org/get-window/-/get-window-1.1.1.tgz#0750f8970c88a54ac1294deb97add9568b3db594"
dependencies: dependencies:
get-document "1" get-document "1"
@ -4125,9 +4125,9 @@ immediate@~3.0.5:
version "3.0.6" version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
immutable@^3.8.1: immutable@^3.8.2:
version "3.8.1" version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" resolved "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
imports-loader@0.6.5: imports-loader@0.6.5:
version "0.6.5" version "0.6.5"
@ -4366,6 +4366,10 @@ is-hexadecimal@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69"
is-hotkey@^0.1.1:
version "0.1.1"
resolved "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.1.tgz#b279a2fd108391be9aa93c6cb317f50357da549a"
is-image@^1.0.1: is-image@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/is-image/-/is-image-1.0.1.tgz#6fd51a752a1a111506d060d952118b0b989b426e" resolved "https://registry.yarnpkg.com/is-image/-/is-image-1.0.1.tgz#6fd51a752a1a111506d060d952118b0b989b426e"
@ -4374,7 +4378,7 @@ is-image@^1.0.1:
is-in-browser@^1.1.3: is-in-browser@^1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" resolved "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
is-lower-case@^1.1.0: is-lower-case@^1.1.0:
version "1.1.3" version "1.1.3"
@ -4435,7 +4439,7 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
is-plain-object@^2.0.1, is-plain-object@^2.0.3: is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
dependencies: dependencies:
@ -4531,7 +4535,7 @@ is-whitespace-character@^1.0.0:
is-window@^1.0.2: is-window@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" resolved "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d"
is-windows@^0.2.0: is-windows@^0.2.0:
version "0.2.0" version "0.2.0"
@ -4571,6 +4575,10 @@ isobject@^3.0.0, isobject@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
isomorphic-base64@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/isomorphic-base64/-/isomorphic-base64-1.0.2.tgz#f426aae82569ba8a4ec5ca73ad21a44ab1ee7803"
isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1: isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
@ -5054,7 +5062,7 @@ jws@^3.0.0, jws@^3.1.4:
keycode@^2.1.2: keycode@^2.1.2:
version "2.1.9" version "2.1.9"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa" resolved "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa"
keygrip@~1.0.2: keygrip@~1.0.2:
version "1.0.2" version "1.0.2"
@ -5670,6 +5678,10 @@ lodash.templatesettings@^3.0.0:
lodash._reinterpolate "^3.0.0" lodash._reinterpolate "^3.0.0"
lodash.escape "^3.0.0" lodash.escape "^3.0.0"
lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
lodash.toarray@^4.4.0: lodash.toarray@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
@ -7403,6 +7415,10 @@ react-helmet@^5.2.0:
prop-types "^15.5.4" prop-types "^15.5.4"
react-side-effect "^1.1.0" react-side-effect "^1.1.0"
react-immutable-proptypes@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"
react-keydown@^1.7.3: react-keydown@^1.7.3:
version "1.9.4" version "1.9.4"
resolved "https://registry.yarnpkg.com/react-keydown/-/react-keydown-1.9.4.tgz#22718ac95edb64dd840dfc4350abf7e693ea0b7f" resolved "https://registry.yarnpkg.com/react-keydown/-/react-keydown-1.9.4.tgz#22718ac95edb64dd840dfc4350abf7e693ea0b7f"
@ -7431,8 +7447,8 @@ react-modal@^3.1.2:
prop-types "^15.5.10" prop-types "^15.5.10"
react-portal@^3.1.0: react-portal@^3.1.0:
version "3.1.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-3.1.0.tgz#865c44fb72a1da106c649206936559ce891ee899" resolved "https://registry.npmjs.org/react-portal/-/react-portal-3.2.0.tgz#4224e19b2b05d5cbe730a7ba0e34ec7585de0043"
dependencies: dependencies:
prop-types "^15.5.8" prop-types "^15.5.8"
@ -7993,7 +8009,7 @@ select@^1.1.2:
selection-is-backward@^1.0.0: selection-is-backward@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/selection-is-backward/-/selection-is-backward-1.0.0.tgz#97a54633188a511aba6419fc5c1fa91b467e6be1" resolved "https://registry.npmjs.org/selection-is-backward/-/selection-is-backward-1.0.0.tgz#97a54633188a511aba6419fc5c1fa91b467e6be1"
semver-diff@^2.0.0: semver-diff@^2.0.0:
version "2.1.0" version "2.1.0"
@ -8189,64 +8205,99 @@ slash@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
slate-collapse-on-escape@^0.2.1: slate-base64-serializer@^0.2.14:
version "0.2.1" version "0.2.14"
resolved "https://registry.yarnpkg.com/slate-collapse-on-escape/-/slate-collapse-on-escape-0.2.1.tgz#988f474439f0a21f94cc0016da52ea3c1a061100" resolved "https://registry.npmjs.org/slate-base64-serializer/-/slate-base64-serializer-0.2.14.tgz#9970bfddde069f76ba78a5ac644a1de352e8ba42"
dependencies:
isomorphic-base64 "^1.0.2"
slate-collapse-on-escape@^0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/slate-collapse-on-escape/-/slate-collapse-on-escape-0.6.0.tgz#668f2318770608a09a25a95623f5fb4329a277bf"
dependencies: dependencies:
to-pascal-case "^1.0.0" to-pascal-case "^1.0.0"
slate-edit-code@^0.10.2: slate-dev-logger@^0.1.25, slate-dev-logger@^0.1.36:
version "0.10.3" version "0.1.36"
resolved "https://registry.yarnpkg.com/slate-edit-code/-/slate-edit-code-0.10.3.tgz#c8211a050a127cdccc9174209f778fe489659801" resolved "https://registry.npmjs.org/slate-dev-logger/-/slate-dev-logger-0.1.36.tgz#ecdb37dbf944dfc742bab23b6a20d5a0472db95e"
slate-edit-code@^0.13.2:
version "0.13.2"
resolved "https://registry.npmjs.org/slate-edit-code/-/slate-edit-code-0.13.2.tgz#682a7640da076906e5b4a4c73ec0e46d31d92c62"
dependencies: dependencies:
detect-indent "^4.0.0" detect-indent "^4.0.0"
detect-newline "^2.1.0" detect-newline "^2.1.0"
ends-with "^0.2.0" ends-with "^0.2.0"
immutable "^3.8.1" is-hotkey "^0.1.1"
slate-edit-list@^0.7.0: slate-edit-list@^0.10.1:
version "0.7.1" version "0.10.1"
resolved "https://registry.yarnpkg.com/slate-edit-list/-/slate-edit-list-0.7.1.tgz#84ee960d2d5b5a20ce267ad9df894395a91b93d5" resolved "https://registry.npmjs.org/slate-edit-list/-/slate-edit-list-0.10.1.tgz#9c6a142a314b0ff22a327f1b50c8f5c85468cb17"
slate-md-serializer@0.5.6: slate-md-serializer@1.0.1:
version "0.5.6" version "1.0.1"
resolved "https://registry.npmjs.org/slate-md-serializer/-/slate-md-serializer-0.5.6.tgz#88048ae62757ce3aaf1096ebd4200c58fdd1e86c" resolved "https://registry.npmjs.org/slate-md-serializer/-/slate-md-serializer-1.0.1.tgz#10fb8118bf0b97addaf9d7fd77c1b19f3d767309"
slate-paste-linkify@^0.2.1: slate-paste-linkify@^0.5.0:
version "0.2.1" version "0.5.0"
resolved "https://registry.yarnpkg.com/slate-paste-linkify/-/slate-paste-linkify-0.2.1.tgz#4647b5207b910d2d084f7d5d256384869b0a9c75" resolved "https://registry.npmjs.org/slate-paste-linkify/-/slate-paste-linkify-0.5.0.tgz#fbb52216ae4c02475b42b3de3609de0ad6b07fd1"
dependencies: dependencies:
is-url "^1.2.2" is-url "^1.2.2"
to-pascal-case "^1.0.0" to-pascal-case "^1.0.0"
slate-prism@^0.2.2: slate-plain-serializer@^0.4.12:
version "0.2.2" version "0.4.12"
resolved "https://registry.yarnpkg.com/slate-prism/-/slate-prism-0.2.2.tgz#0c9d5c2bee0e94a6df5fc564b7a99f6b8e1ea492" resolved "https://registry.npmjs.org/slate-plain-serializer/-/slate-plain-serializer-0.4.12.tgz#802c246bbb7f5c03170a6b3b862251ec51d39cb6"
dependencies:
slate-dev-logger "^0.1.36"
slate-prism@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/slate-prism/-/slate-prism-0.4.0.tgz#9d43b1fafa4c3a8e3bceaa8dbc41dd5ca39445a9"
dependencies: dependencies:
prismjs "^1.6.0" prismjs "^1.6.0"
slate-trailing-block@^0.2.4: slate-prop-types@^0.4.12:
version "0.2.4" version "0.4.12"
resolved "https://registry.yarnpkg.com/slate-trailing-block/-/slate-trailing-block-0.2.4.tgz#6ce9525fa15f9f098d810d9312a4267799cd0e12" resolved "https://registry.npmjs.org/slate-prop-types/-/slate-prop-types-0.4.12.tgz#a600030e8083fbc4a195ed98657e0d4e45caf93b"
dependencies:
slate-dev-logger "^0.1.36"
slate@^0.21.4: slate-react@^0.10.19:
version "0.21.4" version "0.10.19"
resolved "https://registry.yarnpkg.com/slate/-/slate-0.21.4.tgz#ae6113379cd838b7ec68ecd94834ce9741bc36f3" resolved "https://registry.npmjs.org/slate-react/-/slate-react-0.10.19.tgz#917dac40634aeb0dc973100d492e11fc450c80ee"
dependencies: dependencies:
debug "^2.3.2" debug "^2.3.2"
direction "^0.1.5"
es6-map "^0.1.4"
esrever "^0.2.0"
get-window "^1.1.1" get-window "^1.1.1"
immutable "^3.8.1" is-hotkey "^0.1.1"
is-empty "^1.0.0"
is-in-browser "^1.1.3" is-in-browser "^1.1.3"
is-window "^1.0.2" is-window "^1.0.2"
keycode "^2.1.2" keycode "^2.1.2"
lodash "^4.17.4" lodash.throttle "^4.1.1"
prop-types "^15.5.8" prop-types "^15.5.8"
react-immutable-proptypes "^2.1.0"
react-portal "^3.1.0" react-portal "^3.1.0"
selection-is-backward "^1.0.0" selection-is-backward "^1.0.0"
slate-base64-serializer "^0.2.14"
slate-dev-logger "^0.1.36"
slate-plain-serializer "^0.4.12"
slate-prop-types "^0.4.12"
slate-trailing-block@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/slate-trailing-block/-/slate-trailing-block-0.4.0.tgz#92573d7729e4c2a05c45521616ae2db28197476c"
slate@^0.29.0:
version "0.29.1"
resolved "https://registry.npmjs.org/slate/-/slate-0.29.1.tgz#a9df98158e67f92456b9b8f38fb6d279ba8f9f7e"
dependencies:
debug "^2.3.2"
direction "^0.1.5"
esrever "^0.2.0"
is-empty "^1.0.0"
is-plain-object "^2.0.4"
lodash "^4.17.4"
slate-dev-logger "^0.1.25"
type-of "^2.0.1" type-of "^2.0.1"
slice-ansi@0.0.4: slice-ansi@0.0.4: