Slate 30
This commit is contained in:
@@ -1,18 +1,16 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
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 { observer } from 'mobx-react';
|
||||
import styled from 'styled-components';
|
||||
import { color } from 'shared/styles/constants';
|
||||
import PlusIcon from 'components/Icon/PlusIcon';
|
||||
import type { State } from '../types';
|
||||
|
||||
type Props = {
|
||||
state: State,
|
||||
onChange: Function,
|
||||
onInsertImage: File => Promise<*>,
|
||||
editor: Editor,
|
||||
};
|
||||
|
||||
function findClosestRootNode(state, ev) {
|
||||
@@ -53,7 +51,7 @@ export default class BlockInsert extends Component {
|
||||
|
||||
handleMouseMove = (ev: SyntheticMouseEvent) => {
|
||||
const windowWidth = window.innerWidth / 2.5;
|
||||
const result = findClosestRootNode(this.props.state, ev);
|
||||
const result = findClosestRootNode(this.props.editor.value, ev);
|
||||
const movementThreshold = 200;
|
||||
|
||||
this.mouseMovementSinceClick +=
|
||||
@@ -70,7 +68,7 @@ export default class BlockInsert extends Component {
|
||||
this.closestRootNode = result.node;
|
||||
|
||||
// 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') {
|
||||
this.left = -1000;
|
||||
} else {
|
||||
@@ -89,23 +87,22 @@ export default class BlockInsert extends Component {
|
||||
this.mouseMovementSinceClick = 0;
|
||||
this.active = false;
|
||||
|
||||
const { state } = this.props;
|
||||
const { editor } = this.props;
|
||||
const type = { type: 'block-toolbar', isVoid: true };
|
||||
let transform = state.transform();
|
||||
|
||||
// remove any existing toolbars in the document as a fail safe
|
||||
state.document.nodes.forEach(node => {
|
||||
if (node.type === 'block-toolbar') {
|
||||
transform.removeNodeByKey(node.key);
|
||||
}
|
||||
editor.change(change => {
|
||||
// remove any existing toolbars in the document as a fail safe
|
||||
editor.value.document.nodes.forEach(node => {
|
||||
if (node.type === 'block-toolbar') {
|
||||
change.removeNodeByKey(node.key);
|
||||
}
|
||||
});
|
||||
|
||||
change
|
||||
.collapseToStartOf(this.closestRootNode)
|
||||
.collapseToEndOfPreviousBlock()
|
||||
.insertBlock(type);
|
||||
});
|
||||
|
||||
transform
|
||||
.collapseToStartOf(this.closestRootNode)
|
||||
.collapseToEndOfPreviousBlock()
|
||||
.insertBlock(type);
|
||||
|
||||
this.props.onChange(transform.apply());
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { props } from 'slate-prop-types';
|
||||
import CopyButton from './CopyButton';
|
||||
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';
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
import React, { Component } from 'react';
|
||||
import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Editor } from 'slate-react';
|
||||
import type { state, block } from 'slate-prop-types';
|
||||
import { List } from 'immutable';
|
||||
import { color } from 'shared/styles/constants';
|
||||
import headingToSlug from '../headingToSlug';
|
||||
import type { State, Block } from '../types';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type Props = {
|
||||
state: State,
|
||||
editor: Editor,
|
||||
};
|
||||
|
||||
@observer
|
||||
@@ -53,10 +54,10 @@ class Contents extends Component {
|
||||
return elements;
|
||||
}
|
||||
|
||||
get headings(): List<Block> {
|
||||
const { state } = this.props;
|
||||
get headings(): List<block> {
|
||||
const { editor } = this.props;
|
||||
|
||||
return state.document.nodes.filter((node: Block) => {
|
||||
return editor.value.document.nodes.filter((node: block) => {
|
||||
if (!node.text) return false;
|
||||
return node.type.match(/^heading/);
|
||||
});
|
||||
@@ -74,7 +75,7 @@ class Contents extends Component {
|
||||
const active = this.activeHeading === slug;
|
||||
|
||||
return (
|
||||
<ListItem type={heading.type} active={active}>
|
||||
<ListItem type={heading.type} active={active} key={slug}>
|
||||
<Anchor href={`#${slug}`} active={active}>
|
||||
{heading.text}
|
||||
</Anchor>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { Document } from 'slate';
|
||||
import { Editor } from 'slate-react';
|
||||
import styled from 'styled-components';
|
||||
import type { node } from 'slate-prop-types';
|
||||
import headingToSlug from '../headingToSlug';
|
||||
import type { Node, Editor } from '../types';
|
||||
import Placeholder from './Placeholder';
|
||||
|
||||
type Props = {
|
||||
children: React$Element<*>,
|
||||
placeholder?: boolean,
|
||||
parent: Node,
|
||||
node: Node,
|
||||
parent: node,
|
||||
node: node,
|
||||
editor: Editor,
|
||||
readOnly: boolean,
|
||||
component?: string,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { Props } from '../types';
|
||||
import type { props } from 'slate-prop-types';
|
||||
import { color } from 'shared/styles/constants';
|
||||
|
||||
function HorizontalRule(props: Props) {
|
||||
function HorizontalRule(props: props) {
|
||||
const { state, node, attributes } = props;
|
||||
const active = state.isFocused && state.selection.hasEdgeIn(node);
|
||||
return <StyledHr active={active} {...attributes} />;
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
import React, { Component } from 'react';
|
||||
import ImageZoom from 'react-medium-image-zoom';
|
||||
import styled from 'styled-components';
|
||||
import type { Props } from '../types';
|
||||
import type { props } from 'slate-prop-types';
|
||||
import { color } from 'shared/styles/constants';
|
||||
|
||||
class Image extends Component {
|
||||
props: Props;
|
||||
props: props;
|
||||
|
||||
handleChange = (ev: SyntheticInputEvent) => {
|
||||
const alt = ev.target.value;
|
||||
const { editor, node } = this.props;
|
||||
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) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { Link as InternalLink } from 'react-router-dom';
|
||||
import type { Props } from '../types';
|
||||
import type { props } from 'slate-prop-types';
|
||||
|
||||
function getPathFromUrl(href: string) {
|
||||
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;
|
||||
|
||||
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 path = getPathFromUrl(href);
|
||||
|
||||
if (isOutlineUrl(href) && readOnly) {
|
||||
if (isInternalUrl(href) && readOnly) {
|
||||
return (
|
||||
<InternalLink {...attributes} to={path}>
|
||||
{children}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import type { Props } from '../types';
|
||||
import type { props } from 'slate-prop-types';
|
||||
import TodoItem from './TodoItem';
|
||||
|
||||
export default function ListItem({
|
||||
@@ -8,7 +8,7 @@ export default function ListItem({
|
||||
node,
|
||||
attributes,
|
||||
...props
|
||||
}: Props) {
|
||||
}: props) {
|
||||
const checked = node.data.get('checked');
|
||||
|
||||
if (checked !== undefined) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { Document } from 'slate';
|
||||
import type { Props } from '../types';
|
||||
import type { props } from 'slate-prop-types';
|
||||
import Placeholder from './Placeholder';
|
||||
|
||||
export default function Link({
|
||||
@@ -11,7 +11,7 @@ export default function Link({
|
||||
parent,
|
||||
children,
|
||||
readOnly,
|
||||
}: Props) {
|
||||
}: props) {
|
||||
const parentIsDocument = parent instanceof Document;
|
||||
const firstParagraph = parent && parent.nodes.get(1) === node;
|
||||
const lastParagraph = parent && parent.nodes.last() === node;
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { color } from 'shared/styles/constants';
|
||||
import type { Props } from '../types';
|
||||
import type { props } from 'slate-prop-types';
|
||||
|
||||
export default class TodoItem extends Component {
|
||||
props: Props & { checked: boolean };
|
||||
props: props & { checked: boolean };
|
||||
|
||||
handleChange = (ev: SyntheticInputEvent) => {
|
||||
const checked = ev.target.checked;
|
||||
const { editor, node } = this.props;
|
||||
const state = editor
|
||||
const change = editor
|
||||
.getState()
|
||||
.transform()
|
||||
.setNodeByKey(node.key, { data: { checked } })
|
||||
.apply();
|
||||
.change()
|
||||
.setNodeByKey(node.key, { data: { checked } });
|
||||
|
||||
editor.onChange(state);
|
||||
editor.onChange(change);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -13,12 +13,12 @@ import HorizontalRuleIcon from 'components/Icon/HorizontalRuleIcon';
|
||||
import TodoListIcon from 'components/Icon/TodoListIcon';
|
||||
import Flex from 'shared/components/Flex';
|
||||
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 { fadeIn } from 'shared/styles/animations';
|
||||
import { splitAndInsertBlock } from '../../transforms';
|
||||
|
||||
type Props = BaseProps & {
|
||||
type Props = props & {
|
||||
onInsertImage: Function,
|
||||
onChange: Function,
|
||||
};
|
||||
@@ -34,16 +34,15 @@ class BlockToolbar extends Component {
|
||||
file: HTMLInputElement;
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const wasActive = this.props.state.selection.hasEdgeIn(this.props.node);
|
||||
const isActive = nextProps.state.selection.hasEdgeIn(nextProps.node);
|
||||
const { editor } = this.props;
|
||||
const wasActive = editor.value.selection.hasEdgeIn(this.props.node);
|
||||
const isActive = nextProps.editor.value.selection.hasEdgeIn(nextProps.node);
|
||||
const becameInactive = !isActive && wasActive;
|
||||
|
||||
if (becameInactive) {
|
||||
const state = nextProps.state
|
||||
.transform()
|
||||
.removeNodeByKey(nextProps.node.key)
|
||||
.apply();
|
||||
this.props.onChange(state);
|
||||
nextProps.editor.change(change =>
|
||||
change.removeNodeByKey(nextProps.node.key)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,24 +51,25 @@ class BlockToolbar extends Component {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const state = this.props.state
|
||||
.transform()
|
||||
.removeNodeByKey(this.props.node.key)
|
||||
.apply();
|
||||
this.props.onChange(state);
|
||||
this.props.editor.change(change =>
|
||||
change.removeNodeByKey(this.props.node.key)
|
||||
);
|
||||
}
|
||||
|
||||
insertBlock = (options: Options) => {
|
||||
const { state } = this.props;
|
||||
let transform = splitAndInsertBlock(state.transform(), state, options);
|
||||
const { editor } = this.props;
|
||||
|
||||
state.document.nodes.forEach(node => {
|
||||
if (node.type === 'block-toolbar') {
|
||||
transform.removeNodeByKey(node.key);
|
||||
}
|
||||
editor.change(change => {
|
||||
splitAndInsertBlock(change, options);
|
||||
|
||||
change.value.document.nodes.forEach(node => {
|
||||
if (node.type === 'block-toolbar') {
|
||||
change.removeNodeByKey(node.key);
|
||||
}
|
||||
});
|
||||
|
||||
change.focus();
|
||||
});
|
||||
|
||||
this.props.onChange(transform.focus().apply());
|
||||
};
|
||||
|
||||
handleClickBlock = (ev: SyntheticEvent, type: string) => {
|
||||
@@ -126,8 +126,9 @@ class BlockToolbar extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { state, attributes, node } = this.props;
|
||||
const active = state.isFocused && state.selection.hasEdgeIn(node);
|
||||
const { editor, attributes, node } = this.props;
|
||||
const active =
|
||||
editor.value.isFocused && editor.value.selection.hasEdgeIn(node);
|
||||
|
||||
return (
|
||||
<Bar active={active} {...attributes}>
|
||||
|
||||
@@ -3,9 +3,10 @@ import React, { Component } from 'react';
|
||||
import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Portal } from 'react-portal';
|
||||
import { Editor } from 'slate-react';
|
||||
import type { value } from 'slate-prop-types';
|
||||
import styled from 'styled-components';
|
||||
import _ from 'lodash';
|
||||
import type { State } from '../../types';
|
||||
import FormattingToolbar from './components/FormattingToolbar';
|
||||
import LinkToolbar from './components/LinkToolbar';
|
||||
|
||||
@@ -18,8 +19,8 @@ export default class Toolbar extends Component {
|
||||
@observable left: string = '';
|
||||
|
||||
props: {
|
||||
state: State,
|
||||
onChange: (state: State) => void,
|
||||
editor: Editor,
|
||||
value: value,
|
||||
};
|
||||
|
||||
menu: HTMLElement;
|
||||
@@ -41,11 +42,11 @@ export default class Toolbar extends Component {
|
||||
};
|
||||
|
||||
get linkInSelection(): any {
|
||||
const { state } = this.props;
|
||||
const { value } = this.props;
|
||||
|
||||
try {
|
||||
const selectedLinks = state.startBlock
|
||||
.getInlinesAtRange(state.selection)
|
||||
const selectedLinks = value.startBlock
|
||||
.getInlinesAtRange(value.selection)
|
||||
.filter(node => node.type === 'link');
|
||||
if (selectedLinks.size) {
|
||||
return selectedLinks.first();
|
||||
@@ -56,10 +57,10 @@ export default class Toolbar extends Component {
|
||||
}
|
||||
|
||||
update = () => {
|
||||
const { state } = this.props;
|
||||
const { value } = this.props;
|
||||
const link = this.linkInSelection;
|
||||
|
||||
if (state.isBlurred || (state.isCollapsed && !link)) {
|
||||
if (value.isBlurred || (value.isCollapsed && !link)) {
|
||||
if (this.active && !this.focused) {
|
||||
this.active = false;
|
||||
this.link = undefined;
|
||||
@@ -70,11 +71,11 @@ export default class Toolbar extends Component {
|
||||
}
|
||||
|
||||
// don't display toolbar for document title
|
||||
const firstNode = state.document.nodes.first();
|
||||
if (firstNode === state.startBlock) return;
|
||||
const firstNode = value.document.nodes.first();
|
||||
if (firstNode === value.startBlock) return;
|
||||
|
||||
// don't display toolbar for code blocks
|
||||
if (state.startBlock.type === 'code') return;
|
||||
if (value.startBlock.type === 'code') return;
|
||||
|
||||
this.active = true;
|
||||
this.focused = !!link;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { State } from '../../../types';
|
||||
import { Editor } from 'slate-react';
|
||||
import ToolbarButton from './ToolbarButton';
|
||||
import BoldIcon from 'components/Icon/BoldIcon';
|
||||
import CodeIcon from 'components/Icon/CodeIcon';
|
||||
@@ -13,8 +13,7 @@ import StrikethroughIcon from 'components/Icon/StrikethroughIcon';
|
||||
|
||||
class FormattingToolbar extends Component {
|
||||
props: {
|
||||
state: State,
|
||||
onChange: Function,
|
||||
editor: Editor,
|
||||
onCreateLink: Function,
|
||||
};
|
||||
|
||||
@@ -25,11 +24,11 @@ class FormattingToolbar extends Component {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
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) => {
|
||||
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) => {
|
||||
ev.preventDefault();
|
||||
let { state } = this.props;
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.toggleMark(type)
|
||||
.apply();
|
||||
this.props.onChange(state);
|
||||
this.props.editor.change(change => change.toggleMark(type));
|
||||
};
|
||||
|
||||
onClickBlock = (ev: SyntheticEvent, type: string) => {
|
||||
ev.preventDefault();
|
||||
let { state } = this.props;
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.setBlock(type)
|
||||
.apply();
|
||||
this.props.onChange(state);
|
||||
this.props.editor.change(change => change.setBlock(type));
|
||||
};
|
||||
|
||||
onCreateLink = (ev: SyntheticEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
let { state } = this.props;
|
||||
|
||||
const data = { href: '' };
|
||||
state = state
|
||||
.transform()
|
||||
.wrapInline({ type: 'link', data })
|
||||
.apply();
|
||||
this.props.onChange(state);
|
||||
this.props.onCreateLink();
|
||||
this.props.editor.change(change => {
|
||||
change.wrapInline({ type: 'link', data });
|
||||
this.props.onCreateLink();
|
||||
});
|
||||
};
|
||||
|
||||
renderMarkButton = (type: string, IconClass: Function) => {
|
||||
|
||||
@@ -4,11 +4,12 @@ import ReactDOM from 'react-dom';
|
||||
import { observable, action } from 'mobx';
|
||||
import { observer, inject } from 'mobx-react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Editor } from 'slate-react';
|
||||
import styled from 'styled-components';
|
||||
import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
|
||||
import type { change } from 'slate-prop-types';
|
||||
import ToolbarButton from './ToolbarButton';
|
||||
import DocumentResult from './DocumentResult';
|
||||
import type { State } from '../../../types';
|
||||
import DocumentsStore from 'stores/DocumentsStore';
|
||||
import keydown from 'react-keydown';
|
||||
import CloseIcon from 'components/Icon/CloseIcon';
|
||||
@@ -23,11 +24,11 @@ class LinkToolbar extends Component {
|
||||
firstDocument: HTMLElement;
|
||||
|
||||
props: {
|
||||
state: State,
|
||||
editor: Editor,
|
||||
link: Object,
|
||||
documents: DocumentsStore,
|
||||
onBlur: () => void,
|
||||
onChange: State => void,
|
||||
onChange: change => *,
|
||||
};
|
||||
|
||||
@observable isEditing: boolean = false;
|
||||
@@ -112,17 +113,14 @@ class LinkToolbar extends Component {
|
||||
|
||||
save = (href: string) => {
|
||||
href = href.trim();
|
||||
const { state } = this.props;
|
||||
const transform = state.transform();
|
||||
|
||||
if (href) {
|
||||
transform.setInline({ type: 'link', data: { href } });
|
||||
} else {
|
||||
transform.unwrapInline('link');
|
||||
}
|
||||
|
||||
this.props.onChange(transform.apply());
|
||||
this.props.onBlur();
|
||||
this.props.editor.change(change => {
|
||||
if (href) {
|
||||
change.setInline({ type: 'link', data: { href } });
|
||||
} else {
|
||||
change.unwrapInline('link');
|
||||
}
|
||||
this.props.onBlur();
|
||||
});
|
||||
};
|
||||
|
||||
setFirstDocumentRef = ref => {
|
||||
|
||||
Reference in New Issue
Block a user