2016-08-04 15:49:43 -03:00
|
|
|
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);
|
2016-08-06 18:16:30 -03:00
|
|
|
this.renderLinkButton = this.renderLinkButton.bind(this);
|
2016-08-04 15:49:43 -03:00
|
|
|
this.updateMenuPosition = this.updateMenuPosition.bind(this);
|
|
|
|
this.handleMarkClick = this.handleMarkClick.bind(this);
|
2016-08-06 18:16:30 -03:00
|
|
|
this.handleInlineClick = this.handleInlineClick.bind(this);
|
2016-08-04 15:49:43 -03:00
|
|
|
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) {
|
|
|
|
const { blocks } = this.props;
|
|
|
|
return blocks.some(node => node.type == type);
|
|
|
|
}
|
2016-08-06 18:16:30 -03:00
|
|
|
hasLinks(type) {
|
|
|
|
const { inlines } = this.props;
|
|
|
|
return inlines.some(inline => inline.type == 'link');
|
|
|
|
}
|
2016-08-04 15:49:43 -03:00
|
|
|
|
|
|
|
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 (
|
|
|
|
<span className={styles.button} onMouseDown={onMouseDown} data-active={isActive}>
|
|
|
|
<Icon type={icon}/>
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-08-06 18:16:30 -03:00
|
|
|
handleInlineClick(e, type, isActive) {
|
|
|
|
e.preventDefault();
|
|
|
|
this.props.onClickInline(type, isActive);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderLinkButton() {
|
|
|
|
const isActive = this.hasLinks();
|
|
|
|
const onMouseDown = e => this.handleInlineClick(e, 'link', isActive);
|
|
|
|
return (
|
|
|
|
<span className={styles.button} onMouseDown={onMouseDown} data-active={isActive}>
|
|
|
|
<Icon type="link"/>
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-08-04 15:49:43 -03:00
|
|
|
handleBlockClick(e, type) {
|
|
|
|
e.preventDefault();
|
|
|
|
const isActive = this.hasBlock(type);
|
|
|
|
const isList = this.hasBlock('list-item');
|
|
|
|
this.props.onClickBlock(type, isActive, isList);
|
|
|
|
}
|
|
|
|
|
2016-08-04 16:00:56 -03:00
|
|
|
renderBlockButton(type, icon, checkType) {
|
|
|
|
checkType = checkType || type;
|
|
|
|
const isActive = this.hasBlock(checkType);
|
2016-08-04 15:49:43 -03:00
|
|
|
const onMouseDown = e => this.handleBlockClick(e, type);
|
|
|
|
return (
|
|
|
|
<span className={styles.button} onMouseDown={onMouseDown} data-active={isActive}>
|
|
|
|
<Icon type={icon}/>
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When the portal opens, cache the menu element.
|
|
|
|
*/
|
|
|
|
handleOpen(portal) {
|
|
|
|
this.setState({ menu: portal.firstChild });
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { isOpen } = this.props;
|
|
|
|
return (
|
|
|
|
<Portal isOpened={isOpen} onOpen={this.handleOpen}>
|
|
|
|
<div className={`${styles.menu} ${styles.hoverMenu}`}>
|
|
|
|
{this.renderMarkButton('bold', 'bold')}
|
|
|
|
{this.renderMarkButton('italic', 'italic')}
|
|
|
|
{this.renderMarkButton('code', 'code')}
|
2016-08-06 18:16:30 -03:00
|
|
|
{this.renderLinkButton()}
|
2016-08-04 15:49:43 -03:00
|
|
|
{this.renderBlockButton('heading1', 'h1')}
|
|
|
|
{this.renderBlockButton('heading2', 'h2')}
|
|
|
|
{this.renderBlockButton('block-quote', 'quote-left')}
|
2016-08-04 16:00:56 -03:00
|
|
|
{this.renderBlockButton('bulleted-list', 'list-bullet', 'list-item')}
|
2016-08-04 15:49:43 -03:00
|
|
|
</div>
|
|
|
|
</Portal>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2016-08-06 18:16:30 -03:00
|
|
|
inlines: PropTypes.object.isRequired,
|
2016-08-04 15:49:43 -03:00
|
|
|
onClickBlock: PropTypes.func.isRequired,
|
2016-08-06 18:16:30 -03:00
|
|
|
onClickMark: PropTypes.func.isRequired,
|
|
|
|
onClickInline: PropTypes.func.isRequired
|
2016-08-04 15:49:43 -03:00
|
|
|
};
|