Flowtyping

This commit is contained in:
Tom Moor
2017-12-03 11:13:35 -08:00
parent 15e8e50601
commit 802f6e6594
23 changed files with 168 additions and 189 deletions

View File

@ -2,8 +2,9 @@
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 { Value, Change } from 'slate';
import { Editor } from 'slate-react'; import { Editor } from 'slate-react';
import type { state, props, change } from 'slate-prop-types'; import type { SlateNodeProps } from './types';
import Plain from 'slate-plain-serializer'; import Plain from 'slate-plain-serializer';
import keydown from 'react-keydown'; import keydown from 'react-keydown';
import getDataTransferFiles from 'utils/getDataTransferFiles'; import getDataTransferFiles from 'utils/getDataTransferFiles';
@ -22,7 +23,7 @@ import styled from 'styled-components';
type Props = { type Props = {
text: string, text: string,
onChange: change => *, onChange: Change => *,
onSave: (redirect?: boolean) => *, onSave: (redirect?: boolean) => *,
onCancel: () => void, onCancel: () => void,
onImageUploadStart: () => void, onImageUploadStart: () => void,
@ -31,18 +32,13 @@ type Props = {
readOnly: boolean, readOnly: boolean,
}; };
type KeyData = {
isMeta: boolean,
key: string,
};
@observer @observer
class MarkdownEditor extends Component { class MarkdownEditor extends Component {
props: Props; props: Props;
editor: Editor; editor: Editor;
renderNode: props => *; renderNode: SlateNodeProps => *;
plugins: Object[]; plugins: Object[];
@observable editorValue: state; @observable editorValue: Value;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -79,7 +75,7 @@ class MarkdownEditor extends Component {
} }
} }
onChange = (change: change) => { onChange = (change: Change) => {
if (this.editorValue !== change.value) { if (this.editorValue !== change.value) {
this.props.onChange(Markdown.serialize(change.value)); this.props.onChange(Markdown.serialize(change.value));
} }
@ -146,17 +142,17 @@ class MarkdownEditor extends Component {
} }
// Handling of keyboard shortcuts within editor focus // Handling of keyboard shortcuts within editor focus
onKeyDown = (ev: SyntheticKeyboardEvent, data: KeyData, change: change) => { onKeyDown = (ev: SyntheticKeyboardEvent, change: Change) => {
if (!data.isMeta) return; if (!ev.metaKey) return;
switch (data.key) { switch (ev.key) {
case 's': case 's':
this.onSave(ev); this.onSave(ev);
return change; return change;
case 'enter': case 'Enter':
this.onSaveAndExit(ev); this.onSaveAndExit(ev);
return change; return change;
case 'escape': case 'Escape':
this.onCancel(); this.onCancel();
return change; return change;
default: default:

View File

@ -13,10 +13,10 @@ type Props = {
editor: Editor, editor: Editor,
}; };
function findClosestRootNode(state, ev) { function findClosestRootNode(value, ev) {
let previous; let previous;
for (const node of state.document.nodes) { for (const node of value.document.nodes) {
const element = findDOMNode(node); const element = findDOMNode(node);
const bounds = element.getBoundingClientRect(); const bounds = element.getBoundingClientRect();
if (bounds.top > ev.clientY) return previous; if (bounds.top > ev.clientY) return previous;

View File

@ -1,11 +1,16 @@
// @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 type { SlateNodeProps } from '../types';
import CopyButton from './CopyButton'; import CopyButton from './CopyButton';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
export default function Code({ children, node, readOnly, attributes }: props) { export default function Code({
children,
node,
readOnly,
attributes,
}: SlateNodeProps) {
const language = node.data.get('language') || 'javascript'; const language = node.data.get('language') || 'javascript';
return ( return (

View File

@ -3,7 +3,7 @@ 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 { Editor } from 'slate-react';
import type { state, block } from 'slate-prop-types'; import { Block } from 'slate';
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';
@ -54,10 +54,10 @@ class Contents extends Component {
return elements; return elements;
} }
get headings(): List<block> { get headings(): List<Block> {
const { editor } = this.props; const { editor } = this.props;
return editor.value.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/);
}); });

View File

@ -1,22 +1,15 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { Document } from 'slate'; import { Document } from 'slate';
import { Editor } from 'slate-react'; import type { SlateNodeProps } from '../types';
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 Placeholder from './Placeholder'; import Placeholder from './Placeholder';
type Props = { type Props = SlateNodeProps & {
children: React$Element<*>, component: string,
placeholder?: boolean, className: string,
parent: node, placeholder: string,
node: node,
editor: Editor,
readOnly: boolean,
component?: string,
attributes: Object,
className?: string,
}; };
function Heading(props: Props) { function Heading(props: Props) {
@ -61,7 +54,7 @@ function Heading(props: Props) {
const Wrapper = styled.div` const Wrapper = styled.div`
display: inline; display: inline;
margin-left: ${(props: Props) => (props.hasEmoji ? '-1.2em' : 0)}; margin-left: ${(props: SlateNodeProps) => (props.hasEmoji ? '-1.2em' : 0)};
`; `;
const Anchor = styled.a` const Anchor = styled.a`
@ -84,21 +77,21 @@ export const StyledHeading = styled(Heading)`
} }
} }
`; `;
export const Heading1 = (props: Props) => ( export const Heading1 = (props: SlateNodeProps) => (
<StyledHeading component="h1" {...props} /> <StyledHeading component="h1" {...props} />
); );
export const Heading2 = (props: Props) => ( export const Heading2 = (props: SlateNodeProps) => (
<StyledHeading component="h2" {...props} /> <StyledHeading component="h2" {...props} />
); );
export const Heading3 = (props: Props) => ( export const Heading3 = (props: SlateNodeProps) => (
<StyledHeading component="h3" {...props} /> <StyledHeading component="h3" {...props} />
); );
export const Heading4 = (props: Props) => ( export const Heading4 = (props: SlateNodeProps) => (
<StyledHeading component="h4" {...props} /> <StyledHeading component="h4" {...props} />
); );
export const Heading5 = (props: Props) => ( export const Heading5 = (props: SlateNodeProps) => (
<StyledHeading component="h5" {...props} /> <StyledHeading component="h5" {...props} />
); );
export const Heading6 = (props: Props) => ( export const Heading6 = (props: SlateNodeProps) => (
<StyledHeading component="h6" {...props} /> <StyledHeading component="h6" {...props} />
); );

View File

@ -1,12 +1,13 @@
// @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 type { SlateNodeProps } from '../types';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
function HorizontalRule(props: props) { function HorizontalRule(props: SlateNodeProps) {
const { state, node, attributes } = props; const { editor, node, attributes } = props;
const active = state.isFocused && state.selection.hasEdgeIn(node); const active =
editor.value.isFocused && editor.value.selection.hasEdgeIn(node);
return <StyledHr active={active} {...attributes} />; return <StyledHr active={active} {...attributes} />;
} }

View File

@ -2,11 +2,11 @@
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 'slate-prop-types'; import type { SlateNodeProps } from '../types';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
class Image extends Component { class Image extends Component {
props: props; props: SlateNodeProps;
handleChange = (ev: SyntheticInputEvent) => { handleChange = (ev: SyntheticInputEvent) => {
const alt = ev.target.value; const alt = ev.target.value;
@ -26,11 +26,12 @@ class Image extends Component {
}; };
render() { render() {
const { attributes, state, node, readOnly } = this.props; const { attributes, editor, node, readOnly } = this.props;
const loading = node.data.get('loading'); const loading = node.data.get('loading');
const caption = node.data.get('alt'); const caption = node.data.get('alt');
const src = node.data.get('src'); const src = node.data.get('src');
const active = state.isFocused && state.selection.hasEdgeIn(node); const active =
editor.value.isFocused && editor.value.selection.hasEdgeIn(node);
const showCaption = !readOnly || caption; const showCaption = !readOnly || caption;
return ( return (

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 'slate-prop-types'; import type { SlateNodeProps } from '../types';
function getPathFromUrl(href: string) { function getPathFromUrl(href: string) {
if (href[0] === '/') return href; if (href[0] === '/') return href;
@ -26,7 +26,12 @@ function isInternalUrl(href: string) {
} }
} }
export default function Link({ attributes, node, children, readOnly }: props) { export default function Link({
attributes,
node,
children,
readOnly,
}: SlateNodeProps) {
const href = node.data.get('href'); const href = node.data.get('href');
const path = getPathFromUrl(href); const path = getPathFromUrl(href);

View File

@ -1,6 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import type { props } from 'slate-prop-types'; import type { SlateNodeProps } from '../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) { }: SlateNodeProps) {
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 'slate-prop-types'; import type { SlateNodeProps } from '../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) { }: SlateNodeProps) {
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,10 +2,10 @@
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 'slate-prop-types'; import type { SlateNodeProps } from '../types';
export default class TodoItem extends Component { export default class TodoItem extends Component {
props: props & { checked: boolean }; props: SlateNodeProps & { checked: boolean };
handleChange = (ev: SyntheticInputEvent) => { handleChange = (ev: SyntheticInputEvent) => {
const checked = ev.target.checked; const checked = ev.target.checked;

View File

@ -13,14 +13,13 @@ 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 } from 'slate-prop-types'; import type { SlateNodeProps } from '../../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 = props & { type Props = SlateNodeProps & {
onInsertImage: Function, onInsertImage: *,
onChange: Function,
}; };
type Options = { type Options = {

View File

@ -4,7 +4,7 @@ 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 { Editor } from 'slate-react';
import type { value } from 'slate-prop-types'; import { Value } from 'slate';
import styled from 'styled-components'; import styled from 'styled-components';
import _ from 'lodash'; import _ from 'lodash';
import FormattingToolbar from './components/FormattingToolbar'; import FormattingToolbar from './components/FormattingToolbar';
@ -20,7 +20,7 @@ export default class Toolbar extends Component {
props: { props: {
editor: Editor, editor: Editor,
value: value, value: Value,
}; };
menu: HTMLElement; menu: HTMLElement;

View File

@ -4,10 +4,10 @@ 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 { Change } from 'slate';
import { Editor } from 'slate-react'; 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 DocumentsStore from 'stores/DocumentsStore'; import DocumentsStore from 'stores/DocumentsStore';
@ -28,7 +28,7 @@ class LinkToolbar extends Component {
link: Object, link: Object,
documents: DocumentsStore, documents: DocumentsStore,
onBlur: () => void, onBlur: () => void,
onChange: change => *, onChange: Change => *,
}; };
@observable isEditing: boolean = false; @observable isEditing: boolean = false;

View File

@ -1,9 +1,9 @@
// @flow // @flow
import { escape } from 'lodash'; import { escape } from 'lodash';
import type { node } from 'slate-prop-types'; import { Node } from 'slate';
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,11 +1,11 @@
// @flow // @flow
import uuid from 'uuid'; import uuid from 'uuid';
import uploadFile from 'utils/uploadFile'; import uploadFile from 'utils/uploadFile';
import { Change } from 'slate';
import { Editor } from 'slate-react'; import { Editor } from 'slate-react';
import type { change } from 'slate-prop-types';
export default async function insertImageFile( export default async function insertImageFile(
change: change, change: Change,
file: window.File, file: window.File,
editor: Editor, editor: Editor,
onImageUploadStart: () => void, onImageUploadStart: () => void,
@ -22,14 +22,11 @@ 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 = change change.insertBlock({
.insertBlock({ type: 'image',
type: 'image', isVoid: true,
isVoid: true, data: { src, id, alt, loading: true },
data: { src, id, alt, loading: true }, });
})
.apply();
editor.onChange(state);
}); });
reader.readAsDataURL(file); reader.readAsDataURL(file);
@ -40,9 +37,8 @@ export default async function insertImageFile(
// we dont use the original change 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 finalTransform = editor.value.change();
const finalTransform = state.change(); const placeholder = editor.value.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

@ -1,9 +1,14 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import InlineCode from './components/InlineCode'; import InlineCode from './components/InlineCode';
import type { props } from 'slate-prop-types'; import { Mark } from 'slate';
export default function renderMark(props: props) { type Props = {
children: React$Element<*>,
mark: Mark,
};
export default function renderMark(props: Props) {
switch (props.mark.type) { switch (props.mark.type) {
case 'bold': case 'bold':
return <strong>{props.children}</strong>; return <strong>{props.children}</strong>;

View File

@ -16,14 +16,14 @@ import {
Heading6, Heading6,
} from './components/Heading'; } from './components/Heading';
import Paragraph from './components/Paragraph'; import Paragraph from './components/Paragraph';
import type { props } from 'slate-prop-types'; import type { SlateNodeProps } from './types';
type Options = { type Options = {
onInsertImage: *, onInsertImage: *,
}; };
export default function createRenderNode({ onChange, onInsertImage }: Options) { export default function createRenderNode({ onChange, onInsertImage }: Options) {
return function renderNode(props: props) { return function renderNode(props: SlateNodeProps) {
const { attributes } = props; const { attributes } = props;
switch (props.node.type) { switch (props.node.type) {

View File

@ -1,20 +1,12 @@
// @flow // @flow
import type { change } from 'slate-prop-types'; import { Change } from 'slate';
export default function KeyboardShortcuts() { export default function KeyboardShortcuts() {
return { return {
/** onKeyDown(ev: SyntheticKeyboardEvent, change: Change) {
* On key down, check for our specific key shortcuts. if (!ev.metaKey) return null;
*
* @param {Event} e
* @param {Data} data
* @param {State} state
* @return {State or Null} state
*/
onKeyDown(ev: SyntheticEvent, data: Object, change: change) {
if (!data.isMeta) return null;
switch (data.key) { switch (ev.key) {
case 'b': case 'b':
return this.toggleMark(change, 'bold'); return this.toggleMark(change, 'bold');
case 'i': case 'i':
@ -30,13 +22,13 @@ export default function KeyboardShortcuts() {
} }
}, },
toggleMark(change: change, type: string) { toggleMark(change: Change, type: string) {
const { state } = change; const { value } = change;
// don't allow formatting of document title // don't allow formatting of document title
const firstNode = state.document.nodes.first(); const firstNode = value.document.nodes.first();
if (firstNode === state.startBlock) return; if (firstNode === value.startBlock) return;
return state.change().toggleMark(type); change.toggleMark(type);
}, },
}; };
} }

View File

@ -1,10 +1,5 @@
// @flow // @flow
import type { change } from 'slate-prop-types'; import { Change } from 'slate';
type KeyData = {
isMeta: boolean,
key: string,
};
const inlineShortcuts = [ const inlineShortcuts = [
{ mark: 'bold', shortcut: '**' }, { mark: 'bold', shortcut: '**' },
@ -18,22 +13,19 @@ const inlineShortcuts = [
export default function MarkdownShortcuts() { export default function MarkdownShortcuts() {
return { return {
/** onKeyDown(ev: SyntheticKeyboardEvent, change: Change) {
* On key down, check for our specific key shortcuts. switch (ev.key) {
*/
onKeyDown(ev: SyntheticEvent, data: KeyData, change: change) {
switch (data.key) {
case '-': case '-':
return this.onDash(ev, change); return this.onDash(ev, change);
case '`': case '`':
return this.onBacktick(ev, change); return this.onBacktick(ev, change);
case 'tab': case 'Tab':
return this.onTab(ev, change); return this.onTab(ev, change);
case 'space': case ' ':
return this.onSpace(ev, change); return this.onSpace(ev, change);
case 'backspace': case 'Backspace':
return this.onBackspace(ev, change); return this.onBackspace(ev, change);
case 'enter': case 'Enter':
return this.onEnter(ev, change); return this.onEnter(ev, change);
default: default:
return null; return null;
@ -44,10 +36,10 @@ 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, change: change) { onSpace(ev: SyntheticKeyboardEvent, change: Change) {
const { state } = change; const { value } = change;
if (state.isExpanded) return; if (value.isExpanded) return;
const { startBlock, startOffset } = state; const { startBlock, startOffset } = value;
const chars = startBlock.text.slice(0, startOffset).trim(); const chars = startBlock.text.slice(0, startOffset).trim();
const type = this.getType(chars); const type = this.getType(chars);
@ -58,7 +50,7 @@ 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 change = state.change().setBlock({ type, data: { checked } }); change.setBlock({ type, data: { checked } });
if (type === 'list-item') { if (type === 'list-item') {
if (checked !== undefined) { if (checked !== undefined) {
@ -99,40 +91,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 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];
change.removeTextByKey( change
firstText.key, .removeTextByKey(firstText.key, lastCodeTagIndex, shortcut.length)
lastCodeTagIndex, .removeTextByKey(firstText.key, firstCodeTagIndex, shortcut.length)
shortcut.length .moveOffsetsTo(
); firstCodeTagIndex,
change.removeTextByKey( lastCodeTagIndex - shortcut.length
firstText.key, )
firstCodeTagIndex, .addMark(mark)
shortcut.length .collapseToEnd()
); .removeMark(mark);
change.moveOffsetsTo(
firstCodeTagIndex,
lastCodeTagIndex - shortcut.length
);
change.addMark(mark);
return change.collapseToEnd().removeMark(mark);
} }
} }
}, },
onDash(ev: SyntheticEvent, change: change) { onDash(ev: SyntheticKeyboardEvent, change: Change) {
const { state } = change; const { value } = change;
if (state.isExpanded) return; if (value.isExpanded) return;
const { startBlock, startOffset } = state; const { startBlock, startOffset } = value;
const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, ''); const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, '');
if (chars === '--') { if (chars === '--') {
ev.preventDefault(); ev.preventDefault();
return state return change
.change()
.extendToStartOf(startBlock) .extendToStartOf(startBlock)
.delete() .delete()
.setBlock({ .setBlock({
@ -144,16 +128,15 @@ export default function MarkdownShortcuts() {
} }
}, },
onBacktick(ev: SyntheticEvent, change: change) { onBacktick(ev: SyntheticKeyboardEvent, change: Change) {
const { state } = change; const { value } = change;
if (state.isExpanded) return; if (value.isExpanded) return;
const { startBlock, startOffset } = state; const { startBlock, startOffset } = value;
const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, ''); const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, '');
if (chars === '``') { if (chars === '``') {
ev.preventDefault(); ev.preventDefault();
return state return change
.change()
.extendToStartOf(startBlock) .extendToStartOf(startBlock)
.delete() .delete()
.setBlock({ .setBlock({
@ -162,20 +145,22 @@ export default function MarkdownShortcuts() {
} }
}, },
onBackspace(ev: SyntheticEvent, change: change) { onBackspace(ev: SyntheticKeyboardEvent, change: Change) {
const { state } = change; const { value } = change;
if (change.isExpanded) return; if (value.isExpanded) return;
const { startBlock, selection, startOffset } = state; const { startBlock, selection, startOffset } = value;
// 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
if (startOffset === 0) { if (startOffset === 0) {
if (startBlock.type === 'paragraph') return; if (startBlock.type === 'paragraph') return;
ev.preventDefault(); ev.preventDefault();
const change = state.change().setBlock('paragraph'); change.setBlock('paragraph');
if (startBlock.type === 'list-item') if (startBlock.type === 'list-item') {
change.unwrapBlock('bulleted-list'); change.unwrapBlock('bulleted-list');
}
return change; return change;
} }
@ -195,14 +180,12 @@ 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'));
return state change.removeMarkByKey(
.change() textNode.key,
.removeMarkByKey( change.startOffset - charsInCodeBlock.size,
textNode.key, change.startOffset,
change.startOffset - charsInCodeBlock.size, 'code'
change.startOffset, );
'code'
);
} }
} }
}, },
@ -211,15 +194,12 @@ 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, change: change) { onTab(ev: SyntheticKeyboardEvent, change: Change) {
const { state } = change; const { value } = change;
if (state.startBlock.type === 'heading1') { if (value.startBlock.type === 'heading1') {
ev.preventDefault(); ev.preventDefault();
return state change.splitBlock().setBlock('paragraph');
.change()
.splitBlock()
.setBlock('paragraph');
} }
}, },
@ -227,10 +207,10 @@ 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, change: change) { onEnter(ev: SyntheticKeyboardEvent, change: Change) {
const { state } = change; const { value } = change;
if (state.isExpanded) return; if (value.isExpanded) return;
const { startBlock, startOffset, endOffset } = state; const { startBlock, startOffset, endOffset } = value;
if (startOffset === 0 && startBlock.length === 0) if (startOffset === 0 && startBlock.length === 0)
return this.onBackspace(ev, change); return this.onBackspace(ev, change);
if (endOffset !== startBlock.length) return; if (endOffset !== startBlock.length) return;
@ -239,10 +219,7 @@ export default function MarkdownShortcuts() {
// insert a new paragraph // insert a new paragraph
if (startBlock.type === 'image') { if (startBlock.type === 'image') {
ev.preventDefault(); ev.preventDefault();
return state return change.collapseToEnd().insertBlock('paragraph');
.change()
.collapseToEnd()
.insertBlock('paragraph');
} }
// 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
@ -260,10 +237,7 @@ export default function MarkdownShortcuts() {
} }
ev.preventDefault(); ev.preventDefault();
return state change.splitBlock().setBlock('paragraph');
.change()
.splitBlock()
.setBlock('paragraph');
}, },
/** /**

View File

@ -1,8 +1,8 @@
// @flow // @flow
import type { change } from 'slate-prop-types'; import { Change } from 'slate';
import EditList from './plugins/EditList'; import EditList from './plugins/EditList';
const { transforms } = EditList; const { changes } = EditList;
type Options = { type Options = {
type: string | Object, type: string | Object,
@ -10,7 +10,7 @@ type Options = {
append?: string | Object, append?: string | Object,
}; };
export function splitAndInsertBlock(change: change, options: Options) { export function splitAndInsertBlock(change: Change, options: Options) {
const { type, wrapper, append } = options; const { type, wrapper, append } = options;
const { value } = change; const { value } = change;
const { document } = value; const { document } = value;
@ -18,8 +18,8 @@ export function splitAndInsertBlock(change: change, options: Options) {
// lists get some special treatment // lists get some special treatment
if (parent && parent.type === 'list-item') { if (parent && parent.type === 'list-item') {
change = transforms.unwrapList( change = changes.unwrapList(
transforms changes
.splitListItem(change.collapseToStart()) .splitListItem(change.collapseToStart())
.collapseToEndOfPreviousBlock() .collapseToEndOfPreviousBlock()
); );

View File

@ -0,0 +1,13 @@
// @flow
import { Value, Node } from 'slate';
import { Editor } from 'slate-react';
export type SlateNodeProps = {
children: React$Element<*>,
readOnly: boolean,
attributes: Object,
value: Value,
editor: Editor,
node: Node,
parent: Node,
};

View File

@ -169,7 +169,6 @@
"slate-paste-linkify": "^0.5.0", "slate-paste-linkify": "^0.5.0",
"slate-plain-serializer": "^0.4.12", "slate-plain-serializer": "^0.4.12",
"slate-prism": "^0.4.0", "slate-prism": "^0.4.0",
"slate-prop-types": "^0.4.12",
"slate-react": "^0.10.19", "slate-react": "^0.10.19",
"slate-trailing-block": "^0.4.0", "slate-trailing-block": "^0.4.0",
"slug": "0.9.1", "slug": "0.9.1",