2017-05-18 02:36:31 +00:00
|
|
|
// @flow
|
|
|
|
import React, { Component } from 'react';
|
2017-11-13 01:05:17 +00:00
|
|
|
import { observable } from 'mobx';
|
|
|
|
import { observer } from 'mobx-react';
|
2017-11-10 21:42:33 +00:00
|
|
|
import { Portal } from 'react-portal';
|
2017-12-06 04:05:43 +00:00
|
|
|
import { Editor, findDOMNode } from 'slate-react';
|
2017-12-10 23:39:02 +00:00
|
|
|
import { Node, Value } from 'slate';
|
2017-09-10 20:27:15 +00:00
|
|
|
import styled from 'styled-components';
|
2017-05-18 02:36:31 +00:00
|
|
|
import _ from 'lodash';
|
|
|
|
import FormattingToolbar from './components/FormattingToolbar';
|
|
|
|
import LinkToolbar from './components/LinkToolbar';
|
|
|
|
|
2017-12-10 23:39:02 +00:00
|
|
|
function getLinkInSelection(value): any {
|
|
|
|
try {
|
|
|
|
const selectedLinks = value.document
|
|
|
|
.getInlinesAtRange(value.selection)
|
|
|
|
.filter(node => node.type === 'link');
|
|
|
|
if (selectedLinks.size) {
|
|
|
|
return selectedLinks.first();
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
// It's okay.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-13 01:05:17 +00:00
|
|
|
@observer
|
2017-05-18 02:36:31 +00:00
|
|
|
export default class Toolbar extends Component {
|
2017-11-13 01:05:17 +00:00
|
|
|
@observable active: boolean = false;
|
2017-12-10 23:39:02 +00:00
|
|
|
@observable link: ?Node;
|
2017-11-13 01:05:17 +00:00
|
|
|
@observable top: string = '';
|
|
|
|
@observable left: string = '';
|
|
|
|
|
2017-05-18 02:36:31 +00:00
|
|
|
props: {
|
2017-12-03 07:14:27 +00:00
|
|
|
editor: Editor,
|
2017-12-03 19:13:35 +00:00
|
|
|
value: Value,
|
2017-05-18 02:36:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
menu: HTMLElement;
|
|
|
|
|
|
|
|
componentDidMount = () => {
|
|
|
|
this.update();
|
|
|
|
};
|
|
|
|
|
|
|
|
componentDidUpdate = () => {
|
|
|
|
this.update();
|
|
|
|
};
|
|
|
|
|
2017-12-10 23:39:02 +00:00
|
|
|
hideLinkToolbar = () => {
|
|
|
|
this.link = undefined;
|
2017-05-18 02:36:31 +00:00
|
|
|
};
|
|
|
|
|
2017-12-10 23:39:02 +00:00
|
|
|
showLinkToolbar = (ev: SyntheticEvent) => {
|
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
2017-05-18 02:36:31 +00:00
|
|
|
|
2017-12-10 23:39:02 +00:00
|
|
|
const link = getLinkInSelection(this.props.value);
|
|
|
|
this.link = link;
|
|
|
|
};
|
2017-05-18 02:36:31 +00:00
|
|
|
|
|
|
|
update = () => {
|
2017-12-03 07:14:27 +00:00
|
|
|
const { value } = this.props;
|
2017-12-10 23:39:02 +00:00
|
|
|
const link = getLinkInSelection(value);
|
2017-05-18 02:36:31 +00:00
|
|
|
|
2017-12-03 07:14:27 +00:00
|
|
|
if (value.isBlurred || (value.isCollapsed && !link)) {
|
2017-12-10 23:39:02 +00:00
|
|
|
if (this.active && !this.link) {
|
2017-11-13 01:05:17 +00:00
|
|
|
this.active = false;
|
|
|
|
this.link = undefined;
|
|
|
|
this.top = '';
|
|
|
|
this.left = '';
|
2017-05-18 02:36:31 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't display toolbar for document title
|
2017-12-03 07:14:27 +00:00
|
|
|
const firstNode = value.document.nodes.first();
|
|
|
|
if (firstNode === value.startBlock) return;
|
2017-05-18 02:36:31 +00:00
|
|
|
|
2017-12-06 04:05:43 +00:00
|
|
|
// don't display toolbar for code blocks, code-lines inline code.
|
|
|
|
if (value.startBlock.type.match(/code/)) return;
|
2017-05-18 02:36:31 +00:00
|
|
|
|
2017-11-13 01:05:17 +00:00
|
|
|
this.active = true;
|
2017-12-10 23:39:02 +00:00
|
|
|
this.link = this.link || link;
|
2017-05-18 02:36:31 +00:00
|
|
|
|
2017-11-13 01:05:17 +00:00
|
|
|
const padding = 16;
|
|
|
|
const selection = window.getSelection();
|
2017-12-10 23:39:02 +00:00
|
|
|
let rect;
|
|
|
|
|
|
|
|
if (link) {
|
|
|
|
rect = findDOMNode(link).getBoundingClientRect();
|
|
|
|
} else if (selection.rangeCount > 0) {
|
|
|
|
const range = selection.getRangeAt(0);
|
|
|
|
rect = range.getBoundingClientRect();
|
|
|
|
}
|
2017-05-18 02:36:31 +00:00
|
|
|
|
2017-12-10 23:39:02 +00:00
|
|
|
if (!rect || (rect.top === 0 && rect.left === 0)) {
|
2017-11-13 01:05:17 +00:00
|
|
|
return;
|
2017-05-18 02:36:31 +00:00
|
|
|
}
|
2017-11-13 01:05:17 +00:00
|
|
|
|
|
|
|
const left =
|
|
|
|
rect.left + window.scrollX - this.menu.offsetWidth / 2 + rect.width / 2;
|
|
|
|
this.top = `${Math.round(
|
|
|
|
rect.top + window.scrollY - this.menu.offsetHeight
|
|
|
|
)}px`;
|
|
|
|
this.left = `${Math.round(Math.max(padding, left))}px`;
|
2017-05-18 02:36:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
setRef = (ref: HTMLElement) => {
|
|
|
|
this.menu = ref;
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const style = {
|
2017-11-13 01:05:17 +00:00
|
|
|
top: this.top,
|
|
|
|
left: this.left,
|
2017-05-18 02:36:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2017-11-10 21:42:33 +00:00
|
|
|
<Portal>
|
2017-11-13 01:05:17 +00:00
|
|
|
<Menu active={this.active} innerRef={this.setRef} style={style}>
|
|
|
|
{this.link ? (
|
|
|
|
<LinkToolbar
|
|
|
|
{...this.props}
|
|
|
|
link={this.link}
|
2017-12-10 23:39:02 +00:00
|
|
|
onBlur={this.hideLinkToolbar}
|
2017-11-13 01:05:17 +00:00
|
|
|
/>
|
|
|
|
) : (
|
2017-05-18 02:36:31 +00:00
|
|
|
<FormattingToolbar
|
2017-12-10 23:39:02 +00:00
|
|
|
onCreateLink={this.showLinkToolbar}
|
2017-05-18 02:36:31 +00:00
|
|
|
{...this.props}
|
2017-11-10 22:14:30 +00:00
|
|
|
/>
|
|
|
|
)}
|
2017-09-10 20:27:15 +00:00
|
|
|
</Menu>
|
2017-05-18 02:36:31 +00:00
|
|
|
</Portal>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2017-09-10 20:27:15 +00:00
|
|
|
|
|
|
|
const Menu = styled.div`
|
|
|
|
padding: 8px 16px;
|
|
|
|
position: absolute;
|
2017-11-09 07:19:26 +00:00
|
|
|
z-index: 2;
|
2017-09-10 20:27:15 +00:00
|
|
|
top: -10000px;
|
|
|
|
left: -10000px;
|
|
|
|
opacity: 0;
|
2017-11-10 22:14:30 +00:00
|
|
|
background-color: #2f3336;
|
2017-09-10 20:27:15 +00:00
|
|
|
border-radius: 4px;
|
2017-11-10 22:14:30 +00:00
|
|
|
transform: scale(0.95);
|
|
|
|
transition: opacity 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
|
|
|
transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
2017-09-10 20:27:15 +00:00
|
|
|
line-height: 0;
|
|
|
|
height: 40px;
|
|
|
|
min-width: 260px;
|
|
|
|
|
2017-11-10 22:14:30 +00:00
|
|
|
${({ active }) =>
|
|
|
|
active &&
|
|
|
|
`
|
2017-10-21 23:21:43 +00:00
|
|
|
transform: translateY(-6px) scale(1);
|
2017-09-10 20:27:15 +00:00
|
|
|
opacity: 1;
|
2017-11-10 22:14:30 +00:00
|
|
|
`};
|
2017-12-03 06:50:24 +00:00
|
|
|
|
|
|
|
@media print {
|
|
|
|
display: none;
|
|
|
|
}
|
2017-09-10 20:27:15 +00:00
|
|
|
`;
|