);
}
@@ -115,13 +90,15 @@ export default class BlockTypesMenu extends Component {
{this.renderBlockTypeButton('hr', 'dot-3')}
{plugins.map(plugin => this.renderPluginButton(plugin))}
-
+
this._fileInput = el}
+ type="file"
+ accept="image/*"
+ onChange={this.handleFileUploadChange}
+ className={styles.input}
+ ref={el => {
+ this._fileInput = el;
+ }}
/>
);
@@ -130,34 +107,21 @@ export default class BlockTypesMenu extends Component {
}
}
- /**
- * When the portal opens, cache the menu element.
- */
- handleOpen(portal) {
- this.setState({ menu: portal.firstChild });
- }
-
render() {
- const { isOpen } = this.props;
return (
-
-
-
- {this.renderMenu()}
-
-
+
+
+ {this.renderMenu()}
+
);
}
}
BlockTypesMenu.propTypes = {
- isOpen: PropTypes.bool.isRequired,
plugins: PropTypes.array.isRequired,
- position: PropTypes.shape({
- top: PropTypes.number.isRequired,
- left: PropTypes.number.isRequired
- }),
onClickBlock: PropTypes.func.isRequired,
onClickPlugin: PropTypes.func.isRequired,
onClickImage: PropTypes.func.isRequired
};
+
+export default withPortalAtCursorPosition(BlockTypesMenu);
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/StylesMenu.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/StylesMenu.js
index f2aafc3e..e2c464c7 100644
--- a/src/components/Widgets/MarkdownControlElements/VisualEditor/StylesMenu.js
+++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/StylesMenu.js
@@ -1,48 +1,21 @@
import React, { Component, PropTypes } from 'react';
-import Portal from 'react-portal';
+import withPortalAtCursorPosition from './withPortalAtCursorPosition';
import { Icon } from '../../../UI';
import styles from './StylesMenu.css';
-export default class StylesMenu extends Component {
+class StylesMenu extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- menu: null
- };
+ constructor() {
+ super();
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.renderLinkButton = this.renderLinkButton.bind(this);
- this.updateMenuPosition = this.updateMenuPosition.bind(this);
this.handleMarkClick = this.handleMarkClick.bind(this);
this.handleInlineClick = this.handleInlineClick.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`;
}
/**
@@ -52,10 +25,12 @@ export default class StylesMenu extends Component {
const { marks } = this.props;
return marks.some(mark => mark.type == type);
}
+
hasBlock(type) {
const { blocks } = this.props;
return blocks.some(node => node.type == type);
}
+
hasLinks(type) {
const { inlines } = this.props;
return inlines.some(inline => inline.type == 'link');
@@ -109,39 +84,24 @@ export default class StylesMenu extends Component {
);
}
- /**
- * 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.renderLinkButton()}
- {this.renderBlockButton('header_one', 'h1')}
- {this.renderBlockButton('header_two', 'h2')}
- {this.renderBlockButton('blockquote', 'quote-left')}
- {this.renderBlockButton('unordered_list', 'list-bullet', 'list_item')}
-
-
+
+ {this.renderMarkButton('BOLD', 'bold')}
+ {this.renderMarkButton('ITALIC', 'italic')}
+ {this.renderMarkButton('CODE', 'code')}
+ {this.renderLinkButton()}
+ {this.renderBlockButton('header_one', 'h1')}
+ {this.renderBlockButton('header_two', 'h2')}
+ {this.renderBlockButton('blockquote', 'quote-left')}
+ {this.renderBlockButton('unordered_list', 'list-bullet', 'list_item')}
+
);
}
}
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,
inlines: PropTypes.object.isRequired,
@@ -149,3 +109,5 @@ StylesMenu.propTypes = {
onClickMark: PropTypes.func.isRequired,
onClickInline: PropTypes.func.isRequired
};
+
+export default withPortalAtCursorPosition(StylesMenu);
diff --git a/src/components/Widgets/MarkdownControlElements/VisualEditor/withPortalAtCursorPosition.js b/src/components/Widgets/MarkdownControlElements/VisualEditor/withPortalAtCursorPosition.js
new file mode 100644
index 00000000..4aab4ea6
--- /dev/null
+++ b/src/components/Widgets/MarkdownControlElements/VisualEditor/withPortalAtCursorPosition.js
@@ -0,0 +1,59 @@
+import React from 'react';
+import Portal from 'react-portal';
+import position from 'selection-position';
+
+export default function withPortalAtCursorPosition(WrappedComponent) {
+ return class extends React.Component {
+
+ static propTypes = {
+ isOpen: React.PropTypes.bool.isRequired
+ }
+
+ state = {
+ menu: null,
+ cursorPosition: null
+ }
+
+ componentDidMount() {
+ this.adjustPosition();
+ }
+
+ componentDidUpdate() {
+ this.adjustPosition();
+ }
+
+ adjustPosition = () => {
+ const { menu } = this.state;
+
+ if (!menu) return;
+
+ const cursorPosition = position(); // TODO: Results aren't determenistic
+ const centerX = Math.ceil(
+ cursorPosition.left
+ + cursorPosition.width / 2
+ + window.scrollX
+ - menu.offsetWidth / 2
+ );
+ const centerY = cursorPosition.top + window.scrollY;
+ menu.style.opacity = 1;
+ menu.style.top = `${centerY}px`;
+ menu.style.left = `${centerX}px`;
+ }
+
+ /**
+ * When the portal opens, cache the menu element.
+ */
+ handleOpen = (portal) => {
+ this.setState({ menu: portal.firstChild });
+ }
+
+ render() {
+ const { isOpen, ...rest } = this.props;
+ return (
+
+
+
+ );
+ }
+ };
+}