diff --git a/.eslintrc b/.eslintrc
index 99f891d9..b2c61e21 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -97,7 +97,6 @@ rules:
react/jsx-no-duplicate-props: 1
react/jsx-no-undef: 1
react/jsx-pascal-case: 1
- react/jsx-sort-prop-types: 1
react/jsx-uses-react: 1
react/jsx-uses-vars: 1
react/no-danger: 1
diff --git a/src/components/Widgets/MarkdownControl.js b/src/components/Widgets/MarkdownControl.js
index 15daa406..76e8727f 100644
--- a/src/components/Widgets/MarkdownControl.js
+++ b/src/components/Widgets/MarkdownControl.js
@@ -1,12 +1,10 @@
import React, { PropTypes } from 'react';
import { Editor, Plain } from 'slate';
-import Portal from 'react-portal';
import position from 'selection-position';
import Markdown from 'slate-markdown-serializer';
import { DEFAULT_NODE, NODES, MARKS } from './MarkdownControlElements/localRenderers';
+import StylesMenu from './MarkdownControlElements/StylesMenu';
import AddBlock from './MarkdownControlElements/AddBlock';
-import { Icon } from '../UI';
-import styles from './MarkdownControl.css';
const markdown = new Markdown();
@@ -17,6 +15,13 @@ class MarkdownControl extends React.Component {
constructor(props) {
super(props);
this.blockEdit = false;
+ this.stylesMenuPosition = {
+ top: 0,
+ left: 0,
+ width: 0,
+ height: 0
+ };
+
this.state = {
state: props.value ? markdown.deserialize(props.value) : Plain.deserialize(''),
addBlockButton:{
@@ -24,46 +29,17 @@ class MarkdownControl extends React.Component {
}
};
- this.hasMark = this.hasMark.bind(this);
- this.hasBlock = this.hasBlock.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleDocumentChange = this.handleDocumentChange.bind(this);
this.maybeShowBlockAddButton = this.maybeShowBlockAddButton.bind(this);
- this.onClickMark = this.onClickMark.bind(this);
- this.onClickBlock = this.onClickBlock.bind(this);
+ this.handleMarkStyleClick = this.handleMarkStyleClick.bind(this);
+ this.handleBlockStyleClick = this.handleBlockStyleClick.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
- this.renderMenu = this.renderMenu.bind(this);
this.renderAddBlock = this.renderAddBlock.bind(this);
- this.renderMarkButton = this.renderMarkButton.bind(this);
- this.renderBlockButton = this.renderBlockButton.bind(this);
this.renderNode = this.renderNode.bind(this);
this.renderMark = this.renderMark.bind(this);
- this.handleOpen = this.handleOpen.bind(this);
- this.updateMenu = this.updateMenu.bind(this);
}
- /**
- * On update, update the menu.
- */
- componentDidMount() {
- this.updateMenu();
- }
-
- componentDidUpdate() {
- this.updateMenu();
- }
-
- /**
- * Used to set toolbar buttons to active state
- */
- hasMark(type) {
- const { state } = this.state;
- return state.marks.some(mark => mark.type == type);
- }
- hasBlock(type) {
- const { state } = this.state;
- return state.blocks.some(node => node.type == type);
- }
/**
* Slate keeps track of selections, scroll position etc.
@@ -103,7 +79,7 @@ class MarkdownControl extends React.Component {
/**
* Toggle marks / blocks when button is clicked
*/
- onClickMark(e, type) {
+ handleMarkStyleClick(type) {
let { state } = this.state;
state = state
@@ -114,29 +90,13 @@ class MarkdownControl extends React.Component {
this.setState({ state });
}
- handleKeyDown(evt) {
- if (evt.shiftKey && evt.key === 'Enter') {
- this.blockEdit = true;
- let { state } = this.state;
- state = state
- .transform()
- .insertText(' \n')
- .apply();
-
- this.setState({ state });
- }
- }
-
- onClickBlock(e, type) {
- e.preventDefault();
+ handleBlockStyleClick(type, isActive, isList) {
let { state } = this.state;
let transform = state.transform();
const { document } = state;
// Handle everything but list buttons.
if (type != 'bulleted-list' && type != 'numbered-list') {
- const isActive = this.hasBlock(type);
- const isList = this.hasBlock('list-item');
if (isList) {
transform = transform
@@ -153,7 +113,6 @@ class MarkdownControl extends React.Component {
// Handle the extra wrapping required for list buttons.
else {
- const isList = this.hasBlock('list-item');
const isType = state.blocks.some((block) => {
return !!document.getClosest(block, parent => parent.type == type);
});
@@ -177,29 +136,17 @@ class MarkdownControl extends React.Component {
this.setState({ state });
}
- /**
- * When the portal opens, cache the menu element.
- */
- handleOpen(portal) {
- this.setState({ menu: portal.firstChild });
- }
+ handleKeyDown(evt) {
+ if (evt.shiftKey && evt.key === 'Enter') {
+ this.blockEdit = true;
+ let { state } = this.state;
+ state = state
+ .transform()
+ .insertText(' \n')
+ .apply();
- renderMenu() {
- const { state } = this.state;
- const isOpen = state.isExpanded && state.isFocused;
- return (
-
-
- {this.renderMarkButton('bold', 'bold')}
- {this.renderMarkButton('italic', 'italic')}
- {this.renderMarkButton('code', 'code')}
- {this.renderBlockButton('heading1', 'h1')}
- {this.renderBlockButton('heading2', 'h2')}
- {this.renderBlockButton('block-quote', 'quote-left')}
- {this.renderBlockButton('bulleted-list', 'list-bullet')}
-
-
- );
+ this.setState({ state });
+ }
}
renderAddBlock() {
@@ -208,28 +155,6 @@ class MarkdownControl extends React.Component {
);
}
- renderMarkButton(type, icon) {
- const isActive = this.hasMark(type);
- const onMouseDown = e => this.onClickMark(e, type);
-
- return (
-
-
-
- );
- }
-
- renderBlockButton(type, icon) {
- const isActive = this.hasBlock(type);
- const onMouseDown = e => this.onClickBlock(e, type);
-
- return (
-
-
-
- );
- }
-
/**
* Return renderers for Slate
*/
@@ -243,25 +168,37 @@ class MarkdownControl extends React.Component {
/**
* Update the menu's absolute position.
*/
- updateMenu() {
- const { menu, state } = this.state;
- if (!menu) return;
+ renderStylesMenu() {
+ const { state } = this.state;
+ const rect = position();
- if (state.isBlurred || state.isCollapsed) {
- menu.removeAttribute('style');
- return;
+ const isOpen = !(state.isBlurred || state.isCollapsed);
+
+ if (isOpen) {
+ this.stylesMenuPosition = {
+ top: rect.top + window.scrollY,
+ left: rect.left + window.scrollX,
+ width: rect.width,
+ height: rect.height
+ };
}
- const rect = position();
- menu.style.opacity = 1;
- menu.style.top = `${rect.top + window.scrollY - menu.offsetHeight}px`;
- menu.style.left = `${rect.left + window.scrollX - menu.offsetWidth / 2 + rect.width / 2}px`;
+ return (
+
+ );
}
render() {
return (
- {this.renderMenu()}
+ {this.renderStylesMenu()}
{this.renderAddBlock()}
* {
+ display: inline-block;
+}
+
+.menu > * + * {
+ margin-left: 10px;
+}
+
+.hoverMenu {
+ padding: 8px 7px 6px;
+ position: absolute;
+ z-index: 1;
+ top: -10000px;
+ left: -10000px;
+ margin-top: -6px;
+ opacity: 0;
+ background-color: #222;
+ border-radius: 4px;
+ transition: opacity .75s;
+}
+
+.hoverMenu .button {
+ color: #aaa;
+}
+
+.hoverMenu .button[data-active="true"] {
+ color: #fff;
+}
diff --git a/src/components/Widgets/MarkdownControlElements/StylesMenu.js b/src/components/Widgets/MarkdownControlElements/StylesMenu.js
new file mode 100644
index 00000000..cdce2dd1
--- /dev/null
+++ b/src/components/Widgets/MarkdownControlElements/StylesMenu.js
@@ -0,0 +1,127 @@
+import React, { Component, PropTypes } from 'react';
+import Portal from 'react-portal';
+import { Icon } from '../../UI';
+import styles from './StylesMenu.css';
+
+export default class StylesMenu extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ menu: null
+ };
+
+ this.hasMark = this.hasMark.bind(this);
+ this.hasBlock = this.hasBlock.bind(this);
+ this.renderMarkButton = this.renderMarkButton.bind(this);
+ this.renderBlockButton = this.renderBlockButton.bind(this);
+ this.updateMenuPosition = this.updateMenuPosition.bind(this);
+ this.handleMarkClick = this.handleMarkClick.bind(this);
+ this.handleBlockClick = this.handleBlockClick.bind(this);
+ this.handleOpen = this.handleOpen.bind(this);
+ }
+
+ /**
+ * On update, update the menu.
+ */
+ componentDidMount() {
+ this.updateMenuPosition();
+ }
+
+ componentDidUpdate() {
+ this.updateMenuPosition();
+ }
+
+ updateMenuPosition() {
+ const { menu } = this.state;
+ const { position } = this.props;
+ if (!menu) return;
+
+ menu.style.opacity = 1;
+ menu.style.top = `${position.top - menu.offsetHeight}px`;
+ menu.style.left = `${position.left - menu.offsetWidth / 2 + position.width / 2}px`;
+ }
+
+ /**
+ * Used to set toolbar buttons to active state
+ */
+ hasMark(type) {
+ const { marks } = this.props;
+ return marks.some(mark => mark.type == type);
+ }
+ hasBlock(type) {
+ console.log(type);
+ const { blocks } = this.props;
+ return blocks.some(node => node.type == type);
+ }
+
+ handleMarkClick(e, type) {
+ e.preventDefault();
+ this.props.onClickMark(type);
+ }
+
+ renderMarkButton(type, icon) {
+ const isActive = this.hasMark(type);
+ const onMouseDown = e => this.handleMarkClick(e, type);
+ return (
+
+
+
+ );
+ }
+
+ handleBlockClick(e, type) {
+ e.preventDefault();
+ const isActive = this.hasBlock(type);
+ const isList = this.hasBlock('list-item');
+ this.props.onClickBlock(type, isActive, isList);
+ }
+
+ renderBlockButton(type, icon) {
+ const isActive = this.hasBlock(type);
+ const onMouseDown = e => this.handleBlockClick(e, type);
+ return (
+
+
+
+ );
+ }
+
+ /**
+ * When the portal opens, cache the menu element.
+ */
+ handleOpen(portal) {
+ this.setState({ menu: portal.firstChild });
+ }
+
+ render() {
+ const { isOpen } = this.props;
+ return (
+
+
+ {this.renderMarkButton('bold', 'bold')}
+ {this.renderMarkButton('italic', 'italic')}
+ {this.renderMarkButton('code', 'code')}
+ {this.renderBlockButton('heading1', 'h1')}
+ {this.renderBlockButton('heading2', 'h2')}
+ {this.renderBlockButton('block-quote', 'quote-left')}
+ {this.renderBlockButton('bulleted-list', 'list-bullet')}
+
+
+ );
+ }
+
+}
+
+StylesMenu.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ position: PropTypes.shape({
+ top: PropTypes.number.isRequired,
+ left: PropTypes.number.isRequired
+ }),
+ marks: PropTypes.object.isRequired,
+ blocks: PropTypes.object.isRequired,
+ onClickBlock: PropTypes.func.isRequired,
+ onClickMark: PropTypes.func.isRequired
+};
diff --git a/src/components/Widgets/MarkdownControlElements/localRenderers.js b/src/components/Widgets/MarkdownControlElements/localRenderers.js
index 7e955067..a931c9a7 100644
--- a/src/components/Widgets/MarkdownControlElements/localRenderers.js
+++ b/src/components/Widgets/MarkdownControlElements/localRenderers.js
@@ -10,7 +10,7 @@ export const DEFAULT_NODE = 'paragraph';
// Local node renderers.
export const NODES = {
'block-quote': (props) => {props.children},
- 'bulleted-list': props => ,
+ 'bulleted-list': props => ,
'heading1': props => {props.children},
'heading2': props => {props.children},
'heading3': props => {props.children},