+
{children}
);
}
- return {children};
+ return {children};
}
diff --git a/app/components/Editor/components/Paragraph.js b/app/components/Editor/components/Paragraph.js
index 1716373b1..101227609 100644
--- a/app/components/Editor/components/Paragraph.js
+++ b/app/components/Editor/components/Paragraph.js
@@ -23,7 +23,7 @@ export default function Link({
!node.text;
return (
-
+
{children}
{showPlaceholder &&
diff --git a/app/components/Editor/components/TodoItem.js b/app/components/Editor/components/TodoItem.js
index 7b18c7b88..a26182ce7 100644
--- a/app/components/Editor/components/TodoItem.js
+++ b/app/components/Editor/components/TodoItem.js
@@ -20,10 +20,10 @@ export default class TodoItem extends Component {
};
render() {
- const { children, checked, readOnly } = this.props;
+ const { children, checked, attributes, readOnly } = this.props;
return (
-
+
{
+ const { state } = this.props;
+ let transform = splitAndInsertBlock(state.transform(), state, options);
+
+ state.document.nodes.forEach(node => {
+ if (node.type === 'block-toolbar') {
+ transform.removeNodeByKey(node.key);
+ }
+ });
+
+ this.props.onChange(transform.focus().apply());
+ };
+
+ handleClickBlock = (ev: SyntheticEvent, type: string) => {
+ ev.preventDefault();
+
+ switch (type) {
+ case 'heading1':
+ case 'heading2':
+ case 'code':
+ return this.insertBlock({ type });
+ case 'horizontal-rule':
+ return this.insertBlock({
+ type: { type: 'horizontal-rule', isVoid: true },
+ });
+ case 'bulleted-list':
+ return this.insertBlock({
+ type: 'list-item',
+ wrapper: 'bulleted-list',
+ });
+ case 'ordered-list':
+ return this.insertBlock({
+ type: 'list-item',
+ wrapper: 'ordered-list',
+ });
+ case 'todo-list':
+ return this.insertBlock({
+ type: { type: 'list-item', data: { checked: false } },
+ wrapper: 'todo-list',
+ });
+ case 'image':
+ return this.onPickImage();
+ default:
+ }
+ };
+
+ onPickImage = () => {
+ // simulate a click on the file upload input element
+ this.file.click();
+ };
+
+ onImagePicked = async (ev: SyntheticEvent) => {
+ const files = getDataTransferFiles(ev);
+ for (const file of files) {
+ await this.props.onInsertImage(file);
+ }
+ };
+
+ renderBlockButton = (type: string, IconClass: Function) => {
+ return (
+ this.handleClickBlock(ev, type)}>
+
+
+ );
+ };
+
+ render() {
+ const { state, attributes, node } = this.props;
+ const active = state.isFocused && state.selection.hasEdgeIn(node);
+
+ return (
+
+ (this.file = ref)}
+ onChange={this.onImagePicked}
+ accept="image/*"
+ />
+ {this.renderBlockButton('heading1', Heading1Icon)}
+ {this.renderBlockButton('heading2', Heading2Icon)}
+
+ {this.renderBlockButton('bulleted-list', BulletedListIcon)}
+ {this.renderBlockButton('ordered-list', OrderedListIcon)}
+ {this.renderBlockButton('todo-list', TodoListIcon)}
+
+ {this.renderBlockButton('code', CodeIcon)}
+ {this.renderBlockButton('horizontal-rule', HorizontalRuleIcon)}
+ {this.renderBlockButton('image', ImageIcon)}
+
+ );
+ }
+}
+
+const Separator = styled.div`
+ height: 100%;
+ width: 1px;
+ background: ${color.smokeDark};
+ display: inline-block;
+ margin-left: 10px;
+`;
+
+const Bar = styled(Flex)`
+ z-index: 100;
+ animation: ${fadeIn} 150ms ease-in-out;
+ position: relative;
+ align-items: center;
+ background: ${color.smoke};
+ height: 44px;
+
+ &:before,
+ &:after {
+ content: "";
+ position: absolute;
+ left: -100%;
+ width: 100%;
+ height: 44px;
+ background: ${color.smoke};
+ }
+
+ &:after {
+ left: auto;
+ right: -100%;
+ }
+`;
+
+const HiddenInput = styled.input`
+ position: absolute;
+ top: -100px;
+ left: -100px;
+ visibility: hidden;
+`;
+
+export default BlockToolbar;
diff --git a/app/components/Editor/components/Toolbar/Toolbar.js b/app/components/Editor/components/Toolbar/Toolbar.js
index 281d40cbc..d34966d90 100644
--- a/app/components/Editor/components/Toolbar/Toolbar.js
+++ b/app/components/Editor/components/Toolbar/Toolbar.js
@@ -140,7 +140,7 @@ export default class Toolbar extends Component {
const Menu = styled.div`
padding: 8px 16px;
position: absolute;
- z-index: 1;
+ z-index: 2;
top: -10000px;
left: -10000px;
opacity: 0;
diff --git a/app/components/Editor/schema.js b/app/components/Editor/schema.js
index 2010e4c22..d4ed674b6 100644
--- a/app/components/Editor/schema.js
+++ b/app/components/Editor/schema.js
@@ -16,9 +16,15 @@ import {
Heading6,
} from './components/Heading';
import Paragraph from './components/Paragraph';
+import BlockToolbar from './components/Toolbar/BlockToolbar';
import type { Props, Node, Transform } from './types';
-const createSchema = () => {
+type Options = {
+ onInsertImage: Function,
+ onChange: Function,
+};
+
+const createSchema = ({ onInsertImage, onChange }: Options) => {
return {
marks: {
bold: (props: Props) => {props.children},
@@ -30,18 +36,39 @@ const createSchema = () => {
},
nodes: {
+ 'block-toolbar': (props: Props) => (
+
+ ),
paragraph: (props: Props) => ,
'block-quote': (props: Props) => (
- {props.children}
+ {props.children}
),
'horizontal-rule': HorizontalRule,
- 'bulleted-list': (props: Props) => ,
- 'ordered-list': (props: Props) => {props.children}
,
- 'todo-list': (props: Props) => {props.children},
- table: (props: Props) => ,
- 'table-row': (props: Props) => {props.children}
,
- 'table-head': (props: Props) => {props.children} | ,
- 'table-cell': (props: Props) => {props.children} | ,
+ 'bulleted-list': (props: Props) => (
+
+ ),
+ 'ordered-list': (props: Props) => (
+ {props.children}
+ ),
+ 'todo-list': (props: Props) => (
+ {props.children}
+ ),
+ table: (props: Props) => (
+
+ ),
+ 'table-row': (props: Props) => (
+ {props.children}
+ ),
+ 'table-head': (props: Props) => (
+ {props.children} |
+ ),
+ 'table-cell': (props: Props) => (
+ {props.children} |
+ ),
code: Code,
image: Image,
link: Link,
diff --git a/app/components/Editor/transforms.js b/app/components/Editor/transforms.js
new file mode 100644
index 000000000..d404c570f
--- /dev/null
+++ b/app/components/Editor/transforms.js
@@ -0,0 +1,37 @@
+// @flow
+import EditList from './plugins/EditList';
+import type { State, Transform } from './types';
+
+const { transforms } = EditList;
+
+type Options = {
+ type: string | Object,
+ wrapper?: string | Object,
+ append?: string | Object,
+};
+
+export function splitAndInsertBlock(
+ transform: Transform,
+ state: State,
+ options: Options
+) {
+ const { type, wrapper, append } = options;
+ const { document } = state;
+ const parent = document.getParent(state.startBlock.key);
+
+ // lists get some special treatment
+ if (parent && parent.type === 'list-item') {
+ transform = transforms.unwrapList(
+ transforms
+ .splitListItem(transform.collapseToStart())
+ .collapseToEndOfPreviousBlock()
+ );
+ }
+
+ transform = transform.insertBlock(type);
+
+ if (wrapper) transform = transform.wrapBlock(wrapper);
+ if (append) transform = transform.insertBlock(append);
+
+ return transform;
+}
diff --git a/app/components/Editor/types.js b/app/components/Editor/types.js
index 4406f20f9..51562825a 100644
--- a/app/components/Editor/types.js
+++ b/app/components/Editor/types.js
@@ -42,7 +42,12 @@ export type StateTransform = {
wrapText: Function,
};
-export type Transform = NodeTransform & StateTransform;
+export type SelectionTransform = {
+ collapseToStart: Function,
+ collapseToEnd: Function,
+};
+
+export type Transform = NodeTransform & StateTransform & SelectionTransform;
export type Editor = {
props: Object,
diff --git a/app/components/Icon/CollectionIcon.js b/app/components/Icon/CollectionIcon.js
index 411d0920f..948ccec65 100644
--- a/app/components/Icon/CollectionIcon.js
+++ b/app/components/Icon/CollectionIcon.js
@@ -10,7 +10,7 @@ export default function CollectionIcon({
return (
{expanded
- ?
+ ?
: }
);
diff --git a/app/components/Icon/Heading1Icon.js b/app/components/Icon/Heading1Icon.js
index 41fd8552f..1fddb1075 100644
--- a/app/components/Icon/Heading1Icon.js
+++ b/app/components/Icon/Heading1Icon.js
@@ -6,7 +6,7 @@ import type { Props } from './Icon';
export default function Heading1Icon(props: Props) {
return (
-
+
);
}
diff --git a/app/components/Icon/Heading2Icon.js b/app/components/Icon/Heading2Icon.js
index 567fa4181..37a269aa8 100644
--- a/app/components/Icon/Heading2Icon.js
+++ b/app/components/Icon/Heading2Icon.js
@@ -6,7 +6,7 @@ import type { Props } from './Icon';
export default function Heading2Icon(props: Props) {
return (
-
+
);
}
diff --git a/app/components/Icon/KeyboardIcon.js b/app/components/Icon/KeyboardIcon.js
new file mode 100644
index 000000000..d11264b05
--- /dev/null
+++ b/app/components/Icon/KeyboardIcon.js
@@ -0,0 +1,12 @@
+// @flow
+import React from 'react';
+import Icon from './Icon';
+import type { Props } from './Icon';
+
+export default function KeyboardIcon(props: Props) {
+ return (
+
+
+
+ );
+}
diff --git a/app/components/Icon/OrderedListIcon.js b/app/components/Icon/OrderedListIcon.js
index 855794bea..488520e52 100644
--- a/app/components/Icon/OrderedListIcon.js
+++ b/app/components/Icon/OrderedListIcon.js
@@ -6,7 +6,7 @@ import type { Props } from './Icon';
export default function OrderedListIcon(props: Props) {
return (
-
+
);
}
diff --git a/app/menus/BlockMenu.js b/app/menus/BlockMenu.js
deleted file mode 100644
index 08192ce35..000000000
--- a/app/menus/BlockMenu.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// @flow
-import React, { Component } from 'react';
-import ImageIcon from 'components/Icon/ImageIcon';
-import BulletedListIcon from 'components/Icon/BulletedListIcon';
-import HorizontalRuleIcon from 'components/Icon/HorizontalRuleIcon';
-import TodoListIcon from 'components/Icon/TodoListIcon';
-import { observer } from 'mobx-react';
-import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
-
-@observer class BlockMenu extends Component {
- props: {
- label?: React$Element<*>,
- onPickImage: SyntheticEvent => void,
- onInsertList: SyntheticEvent => void,
- onInsertTodoList: SyntheticEvent => void,
- onInsertBreak: SyntheticEvent => void,
- };
-
- render() {
- const {
- label,
- onPickImage,
- onInsertList,
- onInsertTodoList,
- onInsertBreak,
- ...rest
- } = this.props;
-
- return (
-
-
- Add images
-
-
- Start list
-
-
- Start checklist
-
-
- Add break
-
-
- );
- }
-}
-
-export default BlockMenu;