Editor: Performance improvement
This commit is contained in:
parent
095b70890e
commit
3b1590be72
@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
import { Editor, Plain } from 'slate';
|
import { Editor, Plain } from 'slate';
|
||||||
import position from 'selection-position';
|
import position from 'selection-position';
|
||||||
import Markdown from 'slate-markdown-serializer';
|
import Markdown from 'slate-markdown-serializer';
|
||||||
@ -41,7 +42,8 @@ class MarkdownControl extends React.Component {
|
|||||||
this.handleInlineClick = this.handleInlineClick.bind(this);
|
this.handleInlineClick = this.handleInlineClick.bind(this);
|
||||||
this.handleBlockTypeClick = this.handleBlockTypeClick.bind(this);
|
this.handleBlockTypeClick = this.handleBlockTypeClick.bind(this);
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
this.calculateMenuPositions = this.calculateMenuPositions.bind(this);
|
this.calculateHoverMenuPosition = _.throttle(this.calculateHoverMenuPosition.bind(this), 100);
|
||||||
|
this.calculateBlockMenuPosition = _.throttle(this.calculateBlockMenuPosition.bind(this), 100);
|
||||||
this.renderBlockTypesMenu = this.renderBlockTypesMenu.bind(this);
|
this.renderBlockTypesMenu = this.renderBlockTypesMenu.bind(this);
|
||||||
this.renderNode = this.renderNode.bind(this);
|
this.renderNode = this.renderNode.bind(this);
|
||||||
this.renderMark = this.renderMark.bind(this);
|
this.renderMark = this.renderMark.bind(this);
|
||||||
@ -57,7 +59,8 @@ class MarkdownControl extends React.Component {
|
|||||||
if (this.blockEdit) {
|
if (this.blockEdit) {
|
||||||
this.blockEdit = false;
|
this.blockEdit = false;
|
||||||
} else {
|
} else {
|
||||||
this.setState({ state }, this.calculateMenuPositions);
|
this.calculateHoverMenuPosition();
|
||||||
|
this.setState({ state }, this.calculateBlockMenuPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,29 +68,30 @@ class MarkdownControl extends React.Component {
|
|||||||
this.props.onChange(markdown.serialize(state));
|
this.props.onChange(markdown.serialize(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
calculateHoverMenuPosition() {
|
||||||
* All menu positions are calculated accessing dom elements
|
const rect = position();
|
||||||
* That's why calculateMenuPositions is called on handleChange's setState callback
|
|
||||||
*/
|
|
||||||
calculateMenuPositions() {
|
|
||||||
const rect1 = position();
|
|
||||||
this.menuPositions.stylesMenu = {
|
this.menuPositions.stylesMenu = {
|
||||||
top: rect1.top + window.scrollY,
|
top: rect.top + window.scrollY,
|
||||||
left: rect1.left + window.scrollX,
|
left: rect.left + window.scrollX,
|
||||||
width: rect1.width,
|
width: rect.width,
|
||||||
height: rect1.height
|
height: rect.height
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const blockElement = document.querySelectorAll(`[data-key='${this.state.state.selection.focusKey}']`);
|
calculateBlockMenuPosition() {
|
||||||
if (blockElement.length > 0) {
|
// Don't bother calculating position if block is not empty
|
||||||
const rect2 = blockElement[0].getBoundingClientRect();
|
if (this.state.state.blocks.get(0).isEmpty) {
|
||||||
this.menuPositions.blockTypesMenu = {
|
const blockElement = document.querySelectorAll(`[data-key='${this.state.state.selection.focusKey}']`);
|
||||||
top: rect2.top + window.scrollY,
|
if (blockElement.length > 0) {
|
||||||
left: rect2.left + window.scrollX
|
const rect = blockElement[0].getBoundingClientRect();
|
||||||
};
|
this.menuPositions.blockTypesMenu = {
|
||||||
this.forceUpdate();
|
top: rect.top + window.scrollY,
|
||||||
|
left: rect.left + window.scrollX
|
||||||
|
};
|
||||||
|
// Force re-render so the menu is positioned on these new coordinates
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -191,7 +195,10 @@ class MarkdownControl extends React.Component {
|
|||||||
|
|
||||||
state = state
|
state = state
|
||||||
.transform()
|
.transform()
|
||||||
.insertBlock(type)
|
.insertBlock({
|
||||||
|
type: type,
|
||||||
|
isVoid: true
|
||||||
|
})
|
||||||
.apply();
|
.apply();
|
||||||
|
|
||||||
this.setState({ state }, () => {
|
this.setState({ state }, () => {
|
||||||
@ -235,7 +242,7 @@ class MarkdownControl extends React.Component {
|
|||||||
|
|
||||||
renderBlockTypesMenu() {
|
renderBlockTypesMenu() {
|
||||||
const currentBlock = this.state.state.blocks.get(0);
|
const currentBlock = this.state.state.blocks.get(0);
|
||||||
const isOpen = (currentBlock.isEmpty && currentBlock.type !== 'list-item' && currentBlock.type !== 'horizontal-rule');
|
const isOpen = (currentBlock.isEmpty && currentBlock.type !== 'horizontal-rule');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockTypesMenu
|
<BlockTypesMenu
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
.divider {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider:before {
|
|
||||||
font-family: Georgia,Cambria,"Times New Roman",Times,serif;
|
|
||||||
font-size: 28px;
|
|
||||||
letter-spacing: .6em;
|
|
||||||
content: '...';
|
|
||||||
color: rgba(0,0,0,.6);
|
|
||||||
position: relative;
|
|
||||||
top: -12px;
|
|
||||||
margin-left: calc(50% - 60px);
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react';
|
|
||||||
import styles from './BlockStatic.css';
|
|
||||||
|
|
||||||
const AVAILABLE_TYPES = [
|
|
||||||
'divider'
|
|
||||||
];
|
|
||||||
|
|
||||||
export function BlockStatic({ type, children }) {
|
|
||||||
return (
|
|
||||||
<div className={`${styles[type]}`} contentEditable={false}>{children}</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockStatic.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
type: PropTypes.oneOf(AVAILABLE_TYPES).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BlockStatic;
|
|
@ -0,0 +1,3 @@
|
|||||||
|
.active {
|
||||||
|
box-shadow: 0 0 0 2px blue;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Block from './Block';
|
import Block from './Block';
|
||||||
import BlockStatic from './BlockStatic';
|
import styles from './localRenderers.css'
|
||||||
|
|
||||||
/* eslint react/prop-types: 0, react/no-multi-comp: 0 */
|
/* eslint react/prop-types: 0, react/no-multi-comp: 0 */
|
||||||
|
|
||||||
@ -19,7 +19,14 @@ export const NODES = {
|
|||||||
'heading6': props => <Block type='Heading2' {...props.attributes}>{props.children}</Block>,
|
'heading6': props => <Block type='Heading2' {...props.attributes}>{props.children}</Block>,
|
||||||
'list-item': props => <li {...props.attributes}>{props.children}</li>,
|
'list-item': props => <li {...props.attributes}>{props.children}</li>,
|
||||||
'paragraph': props => <Block type='Paragraph' {...props.attributes}>{props.children}</Block>,
|
'paragraph': props => <Block type='Paragraph' {...props.attributes}>{props.children}</Block>,
|
||||||
'horizontal-rule': props => <BlockStatic type='divider' {...props.attributes}>{props.children}</BlockStatic>,
|
'horizontal-rule': props => {
|
||||||
|
const { node, state } = props;
|
||||||
|
const isFocused = state.selection.hasEdgeIn(node);
|
||||||
|
const className = isFocused ? styles.active : null;
|
||||||
|
return (
|
||||||
|
<hr className={className} {...props.attributes} />
|
||||||
|
);
|
||||||
|
},
|
||||||
'link': (props) => {
|
'link': (props) => {
|
||||||
const { data } = props.node;
|
const { data } = props.node;
|
||||||
const href = data.get('href');
|
const href = data.get('href');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user