Flowtyping
This commit is contained in:
@ -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:
|
||||||
|
@ -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;
|
||||||
|
@ -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 (
|
||||||
|
@ -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/);
|
||||||
});
|
});
|
||||||
|
@ -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} />
|
||||||
);
|
);
|
||||||
|
@ -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} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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 = {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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}`);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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>;
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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()
|
||||||
);
|
);
|
||||||
|
13
app/components/Editor/types.js
Normal file
13
app/components/Editor/types.js
Normal 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,
|
||||||
|
};
|
@ -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",
|
||||||
|
Reference in New Issue
Block a user