Editor: Performance improvement

This commit is contained in:
Cássio Zen 2016-08-08 11:57:49 -03:00
parent 095b70890e
commit 3b1590be72
5 changed files with 42 additions and 58 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -0,0 +1,3 @@
.active {
box-shadow: 0 0 0 2px blue;
}

View File

@ -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');