Working
This commit is contained in:
@ -44,7 +44,10 @@ type KeyData = {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.schema = createSchema();
|
||||
this.schema = createSchema({
|
||||
onInsertImage: this.insertImageFile,
|
||||
onChange: this.onChange,
|
||||
});
|
||||
this.plugins = createPlugins({
|
||||
onImageUploadStart: props.onImageUploadStart,
|
||||
onImageUploadStop: props.onImageUploadStop,
|
||||
|
@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import EditList from '../plugins/EditList';
|
||||
import getDataTransferFiles from 'utils/getDataTransferFiles';
|
||||
import Portal from 'react-portal';
|
||||
import { observable } from 'mobx';
|
||||
@ -9,8 +8,7 @@ import styled from 'styled-components';
|
||||
import { color } from 'shared/styles/constants';
|
||||
import PlusIcon from 'components/Icon/PlusIcon';
|
||||
import type { State } from '../types';
|
||||
|
||||
const { transforms } = EditList;
|
||||
import { splitAndInsertBlock } from '../transforms';
|
||||
|
||||
type Props = {
|
||||
state: State,
|
||||
@ -22,7 +20,6 @@ type Props = {
|
||||
export default class BlockInsert extends Component {
|
||||
props: Props;
|
||||
mouseMoveTimeout: number;
|
||||
file: HTMLInputElement;
|
||||
|
||||
@observable active: boolean = false;
|
||||
@observable menuOpen: boolean = false;
|
||||
@ -89,77 +86,28 @@ export default class BlockInsert extends Component {
|
||||
this.left = Math.round(boxRect.left + window.scrollX - 20);
|
||||
};
|
||||
|
||||
insertBlock = (
|
||||
ev: SyntheticEvent,
|
||||
options: {
|
||||
type: string | Object,
|
||||
wrapper?: string | Object,
|
||||
append?: string | Object,
|
||||
}
|
||||
) => {
|
||||
ev.preventDefault();
|
||||
const { type, wrapper, append } = options;
|
||||
let { state } = this.props;
|
||||
let transform = state.transform();
|
||||
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);
|
||||
|
||||
state = transform.focus().apply();
|
||||
handleClick = () => {
|
||||
const transform = splitAndInsertBlock(this.props.state, {
|
||||
type: { type: 'block-toolbar', isVoid: true },
|
||||
});
|
||||
const state = transform.apply();
|
||||
this.props.onChange(state);
|
||||
this.active = false;
|
||||
};
|
||||
|
||||
onPickImage = (ev: SyntheticEvent) => {
|
||||
// simulate a click on the file upload input element
|
||||
this.file.click();
|
||||
};
|
||||
|
||||
onChooseImage = async (ev: SyntheticEvent) => {
|
||||
const files = getDataTransferFiles(ev);
|
||||
for (const file of files) {
|
||||
await this.props.onInsertImage(file);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = { top: `${this.top}px`, left: `${this.left}px` };
|
||||
const todo = { type: 'list-item', data: { checked: false } };
|
||||
const rule = { type: 'horizontal-rule', isVoid: true };
|
||||
|
||||
return (
|
||||
<Portal isOpened>
|
||||
<Trigger active={this.active} style={style}>
|
||||
<PlusIcon
|
||||
onClick={ev =>
|
||||
this.insertBlock(ev, { type: 'block-toolbar', isVoid: true })}
|
||||
/>
|
||||
<PlusIcon onClick={this.handleClick} />
|
||||
</Trigger>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const HiddenInput = styled.input`
|
||||
position: absolute;
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
visibility: hidden;
|
||||
`;
|
||||
|
||||
const Trigger = styled.div`
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
@ -167,10 +115,16 @@ const Trigger = styled.div`
|
||||
background-color: ${color.white};
|
||||
transition: opacity 250ms ease-in-out, transform 250ms ease-in-out;
|
||||
line-height: 0;
|
||||
margin-top: -2px;
|
||||
margin-left: -4px;
|
||||
margin-top: -3px;
|
||||
margin-left: -10px;
|
||||
box-shadow: inset 0 0 0 2px ${color.slateDark};
|
||||
border-radius: 100%;
|
||||
transform: scale(.9);
|
||||
|
||||
&:hover {
|
||||
background-color: ${color.smokeDark};
|
||||
}
|
||||
|
||||
${({ active }) => active && `
|
||||
transform: scale(1);
|
||||
opacity: .9;
|
||||
|
@ -1,84 +0,0 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Heading1Icon from 'components/Icon/Heading1Icon';
|
||||
import Heading2Icon from 'components/Icon/Heading2Icon';
|
||||
import ImageIcon from 'components/Icon/ImageIcon';
|
||||
import CodeIcon from 'components/Icon/CodeIcon';
|
||||
import BulletedListIcon from 'components/Icon/BulletedListIcon';
|
||||
import OrderedListIcon from 'components/Icon/OrderedListIcon';
|
||||
import HorizontalRuleIcon from 'components/Icon/HorizontalRuleIcon';
|
||||
import TodoListIcon from 'components/Icon/TodoListIcon';
|
||||
import Flex from 'shared/components/Flex';
|
||||
import type { Props } from '../types';
|
||||
import { color } from 'shared/styles/constants';
|
||||
import ToolbarButton from './Toolbar/components/ToolbarButton';
|
||||
|
||||
class BlockToolbar extends Component {
|
||||
props: Props;
|
||||
|
||||
onClickBlock = (ev: SyntheticEvent, type: string) => {
|
||||
// TODO
|
||||
};
|
||||
|
||||
renderBlockButton = (type: string, IconClass: Function) => {
|
||||
return (
|
||||
<ToolbarButton onMouseDown={ev => this.onClickBlock(ev, type)}>
|
||||
<IconClass color={color.text} />
|
||||
</ToolbarButton>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { state, node } = this.props;
|
||||
const active = state.isFocused && state.selection.hasEdgeIn(node);
|
||||
|
||||
return (
|
||||
<Bar active={active}>
|
||||
{this.renderBlockButton('heading1', Heading1Icon)}
|
||||
{this.renderBlockButton('heading2', Heading2Icon)}
|
||||
<Separator />
|
||||
{this.renderBlockButton('bulleted-list', BulletedListIcon)}
|
||||
{this.renderBlockButton('ordered-list', OrderedListIcon)}
|
||||
{this.renderBlockButton('todo-list', TodoListIcon)}
|
||||
<Separator />
|
||||
{this.renderBlockButton('code', CodeIcon)}
|
||||
{this.renderBlockButton('horizontal-rule', HorizontalRuleIcon)}
|
||||
{this.renderBlockButton('image', ImageIcon)}
|
||||
</Bar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Separator = styled.div`
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
background: ${color.smokeDark};
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
const Bar = styled(Flex)`
|
||||
position: relative;
|
||||
align-items: center;
|
||||
background: ${color.smoke};
|
||||
padding: 10px 0;
|
||||
height: 44px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: ${color.smoke};
|
||||
}
|
||||
|
||||
&:after {
|
||||
left: auto;
|
||||
right: -100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default BlockToolbar;
|
@ -11,6 +11,7 @@ function HorizontalRule(props: Props) {
|
||||
}
|
||||
|
||||
const StyledHr = styled.hr`
|
||||
border: 0;
|
||||
border-bottom: 1px solid ${props => (props.active ? color.slate : color.slateLight)};
|
||||
`;
|
||||
|
||||
|
182
app/components/Editor/components/Toolbar/BlockToolbar.js
Normal file
182
app/components/Editor/components/Toolbar/BlockToolbar.js
Normal file
@ -0,0 +1,182 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import getDataTransferFiles from 'utils/getDataTransferFiles';
|
||||
import Heading1Icon from 'components/Icon/Heading1Icon';
|
||||
import Heading2Icon from 'components/Icon/Heading2Icon';
|
||||
import ImageIcon from 'components/Icon/ImageIcon';
|
||||
import CodeIcon from 'components/Icon/CodeIcon';
|
||||
import BulletedListIcon from 'components/Icon/BulletedListIcon';
|
||||
import OrderedListIcon from 'components/Icon/OrderedListIcon';
|
||||
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 { color } from 'shared/styles/constants';
|
||||
import { fadeIn } from 'shared/styles/animations';
|
||||
import { splitAndInsertBlock } from '../../transforms';
|
||||
|
||||
type Props = BaseProps & {
|
||||
onInsertImage: Function,
|
||||
onChange: Function,
|
||||
};
|
||||
|
||||
type Options = {
|
||||
type: string | Object,
|
||||
wrapper?: string | Object,
|
||||
append?: string | Object,
|
||||
};
|
||||
|
||||
class BlockToolbar extends Component {
|
||||
props: Props;
|
||||
file: HTMLInputElement;
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const wasActive = this.props.state.selection.hasEdgeIn(this.props.node);
|
||||
const isActive = nextProps.state.selection.hasEdgeIn(nextProps.node);
|
||||
const becameInactive = !isActive && wasActive;
|
||||
|
||||
if (becameInactive) {
|
||||
console.log('becameInactive');
|
||||
const state = nextProps.state
|
||||
.transform()
|
||||
.removeNodeByKey(nextProps.node.key)
|
||||
.apply();
|
||||
this.props.onChange(state);
|
||||
}
|
||||
}
|
||||
|
||||
insertBlock = (options: Options) => {
|
||||
let transform = splitAndInsertBlock(this.props.state, options);
|
||||
|
||||
this.props.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();
|
||||
ev.stopPropagation();
|
||||
|
||||
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 (
|
||||
<ToolbarButton onMouseDown={ev => this.handleClickBlock(ev, type)}>
|
||||
<IconClass color={color.text} />
|
||||
</ToolbarButton>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { state, node } = this.props;
|
||||
const active = state.isFocused && state.selection.hasEdgeIn(node);
|
||||
|
||||
return (
|
||||
<Bar active={active}>
|
||||
<HiddenInput
|
||||
type="file"
|
||||
innerRef={ref => (this.file = ref)}
|
||||
onChange={this.onImagePicked}
|
||||
accept="image/*"
|
||||
/>
|
||||
{this.renderBlockButton('heading1', Heading1Icon)}
|
||||
{this.renderBlockButton('heading2', Heading2Icon)}
|
||||
<Separator />
|
||||
{this.renderBlockButton('bulleted-list', BulletedListIcon)}
|
||||
{this.renderBlockButton('ordered-list', OrderedListIcon)}
|
||||
{this.renderBlockButton('todo-list', TodoListIcon)}
|
||||
<Separator />
|
||||
{this.renderBlockButton('code', CodeIcon)}
|
||||
{this.renderBlockButton('horizontal-rule', HorizontalRuleIcon)}
|
||||
{this.renderBlockButton('image', ImageIcon)}
|
||||
</Bar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
@ -16,10 +16,15 @@ import {
|
||||
Heading6,
|
||||
} from './components/Heading';
|
||||
import Paragraph from './components/Paragraph';
|
||||
import BlockToolbar from './components/BlockToolbar';
|
||||
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) => <strong>{props.children}</strong>,
|
||||
@ -31,7 +36,13 @@ const createSchema = () => {
|
||||
},
|
||||
|
||||
nodes: {
|
||||
'block-toolbar': (props: Props) => <BlockToolbar {...props} />,
|
||||
'block-toolbar': (props: Props) => (
|
||||
<BlockToolbar
|
||||
onChange={onChange}
|
||||
onInsertImage={onInsertImage}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
paragraph: (props: Props) => <Paragraph {...props} />,
|
||||
'block-quote': (props: Props) => (
|
||||
<blockquote>{props.children}</blockquote>
|
||||
|
34
app/components/Editor/transforms.js
Normal file
34
app/components/Editor/transforms.js
Normal file
@ -0,0 +1,34 @@
|
||||
// @flow
|
||||
import EditList from './plugins/EditList';
|
||||
import type { State } from './types';
|
||||
|
||||
const { transforms } = EditList;
|
||||
|
||||
type Options = {
|
||||
type: string | Object,
|
||||
wrapper?: string | Object,
|
||||
append?: string | Object,
|
||||
};
|
||||
|
||||
export function splitAndInsertBlock(state: State, options: Options) {
|
||||
const { type, wrapper, append } = options;
|
||||
let transform = state.transform();
|
||||
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;
|
||||
}
|
@ -6,7 +6,7 @@ import type { Props } from './Icon';
|
||||
export default function Heading1Icon(props: Props) {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<path d="M7,4 L7,4 C7.55228475,4 8,4.44771525 8,5 L8,19 C8,19.5522847 7.55228475,20 7,20 C6.44771525,20 6,19.5522847 6,19 L6,5 L6,5 C6,4.44771525 6.44771525,4 7,4 L7,4 Z M8,11 L16,11 L16,13 L8,13 L8,11 Z M17,4 C17.5522847,4 18,4.44771525 18,5 L18,19 C18,19.5522847 17.5522847,20 17,20 C16.4477153,20 16,19.5522847 16,19 L16,5 L16,5 C16,4.44771525 16.4477153,4 17,4 Z" />
|
||||
<path d="M18,15 L18,8 C18,7.86192881 17.9720178,7.73039322 17.921415,7.61075487 C17.8779612,7.50332041 17.8047379,7.39052429 17.7071068,7.29289322 C17.3165825,6.90236893 16.6834175,6.90236893 16.2928932,7.29289322 L14.2928932,9.29289322 C13.9023689,9.68341751 13.9023689,10.3165825 14.2928932,10.7071068 C14.6834175,11.0976311 15.3165825,11.0976311 15.7071068,10.7071068 L16,10.4142136 L16,15 L15,15 C14.4477153,15 14,15.4477153 14,16 C14,16.5522847 14.4477153,17 15,17 L19,17 C19.5522847,17 20,16.5522847 20,16 C20,15.4477153 19.5522847,15 19,15 L18,15 Z M10,13 L6,13 L6,16 C6,16.5522847 5.55228475,17 5,17 C4.44771525,17 4,16.5522847 4,16 L4,8 L4,8 C4,7.44771525 4.44771525,7 5,7 L5,7 L5,7 C5.55228475,7 6,7.44771525 6,8 L6,11 L10,11 L10,8 L10,8 C10,7.44771525 10.4477153,7 11,7 C11.5522847,7 12,7.44771525 12,8 L12,16 C12,16.5522847 11.5522847,17 11,17 C10.4477153,17 10,16.5522847 10,16 L10,13 Z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import type { Props } from './Icon';
|
||||
export default function Heading2Icon(props: Props) {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<path d="M14,13 L10,13 L10,16 C10,16.5522847 9.55228475,17 9,17 C8.44771525,17 8,16.5522847 8,16 L8,8 L8,8 C8,7.44771525 8.44771525,7 9,7 L9,7 L9,7 C9.55228475,7 10,7.44771525 10,8 L10,11 L14,11 L14,8 L14,8 C14,7.44771525 14.4477153,7 15,7 C15.5522847,7 16,7.44771525 16,8 L16,16 C16,16.5522847 15.5522847,17 15,17 C14.4477153,17 14,16.5522847 14,16 L14,13 Z" />
|
||||
<path d="M10,13 L6,13 L6,16 C6,16.5522847 5.55228475,17 5,17 C4.44771525,17 4,16.5522847 4,16 L4,8 L4,8 C4,7.44771525 4.44771525,7 5,7 L5,7 L5,7 C5.55228475,7 6,7.44771525 6,8 L6,11 L10,11 L10,8 L10,8 C10,7.44771525 10.4477153,7 11,7 C11.5522847,7 12,7.44771525 12,8 L12,16 C12,16.5522847 11.5522847,17 11,17 C10.4477153,17 10,16.5522847 10,16 L10,13 Z M19.8087361,11.0881717 L16.96377,15 L19,15 C19.5522847,15 20,15.4477153 20,16 C20,16.5522847 19.5522847,17 19,17 L15,17 C14.1827132,17 13.710559,16.0727976 14.1912639,15.4118283 L18,10.1748162 L18,9 L16,9 L16,10 C16,10.5522847 15.5522847,11 15,11 C14.4477153,11 14,10.5522847 14,10 L14,9 C14,7.8954305 14.8954305,7 16,7 L18,7 C19.1045695,7 20,7.8954305 20,9 L20,10.5 C20,10.7113425 19.9330418,10.9172514 19.8087361,11.0881717 Z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
<DropdownMenu
|
||||
style={{ marginRight: -70, marginTop: 5 }}
|
||||
label={label}
|
||||
{...rest}
|
||||
>
|
||||
<DropdownMenuItem onClick={onPickImage}>
|
||||
<ImageIcon /> <span>Add images</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onInsertList}>
|
||||
<BulletedListIcon /> Start list
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onInsertTodoList}>
|
||||
<TodoListIcon /> Start checklist
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onInsertBreak}>
|
||||
<HorizontalRuleIcon /> Add break
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BlockMenu;
|
Reference in New Issue
Block a user