Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Operational Transformation for the Draft.js Editor, experimentation #1

Closed
wants to merge 8 commits into from
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
"copy-webpack-plugin": "^3.0.0",
"css-loader": "^0.25.0",
"del": "^2.1.0",
"diff": "^3.0.0",
"draft-js": "^0.7.0",
"empty": "^0.10.1",
"eslint": "^3.2.2",
"eslint-config-airbnb": "^10.0.0",
Expand All @@ -77,12 +79,14 @@
"gulp-real-favicon": "^0.2.1",
"gulp-util": "^3.0.7",
"immutable": "^3.6.4",
"immutablediff": "^0.4.3",
"intl": "^1.0.0",
"intl-locales-supported": "^1.0.0",
"intl-messageformat": "^1.1.0",
"intl-relativeformat": "^1.1.0",
"invariant": "^2.1.2",
"ip": "^1.0.2",
"lodash": "^4.15.0",
"make-error": "^1.0.4",
"nconf": "^0.8.1",
"node-sass": "^3.1.2",
Expand Down
4 changes: 0 additions & 4 deletions src/browser/app/App.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/* @flow */
import './App.scss';
import * as themes from './themes';
import Footer from './Footer';
import Header from './Header';
import Helmet from 'react-helmet';
import React from 'react';
import favicon from '../../common/app/favicon';
Expand Down Expand Up @@ -44,9 +42,7 @@ let App = ({ children, currentLocale, currentTheme }) => (
...favicon.link,
]}
/>
<Header />
{children}
<Footer />
</Container>
</ThemeProvider>
);
Expand Down
4 changes: 3 additions & 1 deletion src/browser/createRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import App from './app/App';
import Fields from './fields/FieldsPage';
import Firebase from './firebase/FirebasePage';
import Home from './home/HomePage';
import Editor from './editor/EditorPage';
import Intl from './intl/IntlPage';
import Me from './me/MePage';
import NotFound from './notfound/NotFoundPage';
Expand All @@ -26,7 +27,8 @@ const createRoutes = (getState: Function) => {

return (
<Route component={App} path="/">
<IndexRoute component={Home} />
<IndexRoute component={Editor} />
<Route component={Home} path="home" />
<Route component={Fields} path="fields" />
<Route component={Firebase} path="firebase" />
<Route component={Intl} path="intl" />
Expand Down
1 change: 1 addition & 0 deletions src/browser/editor/Editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~draft-js/dist/Draft.css";
87 changes: 87 additions & 0 deletions src/browser/editor/EditorInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* @flow */
import React, { Component } from 'react';
import * as Immutable from 'immutable';
import { Flex } from '../app/components';
import { Editor, EditorState, RichUtils } from 'draft-js';
import { connect } from 'react-redux';
import { setEditorState } from '../../common/editor/actions';
import debounce from 'lodash/debounce';

const EDITOR_DEBOUNCE = 500;

const style = {
width: 580,
margin: '40px auto',
};

class EditorInput extends Component {
constructor(props, context) {
super(props, context);
(this:any).onEditorChange = this.onEditorChange.bind(this);
(this:any).onKeyCommand = this.onKeyCommand.bind(this);
(this:any).setEditorState = debounce(this.setEditorState, EDITOR_DEBOUNCE);
this.state = {
editorState: props.editorState,
};
}

state: {
editorState: EditorState,
};

componentWillReceiveProps(nextProps) {
this.setState({
editorState: nextProps.editorState,
});
}

shouldComponentUpdate(nextProps, nextState) {
const shouldComponentUpdate = (
!Immutable.is(nextProps.editorState, nextState.editorState) ||
!Immutable.is(this.state.editorState, nextState.editorState)
);
return shouldComponentUpdate;
}

onEditorChange(editorState: EditorState) {
if (!editorState) { return false; }
this.setState({ editorState });
this.setEditorState(editorState);
return true;
}

onKeyCommand(command: string) {
const newEditorState = RichUtils.handleKeyCommand(
this.state.editorState,
command
);
if (!newEditorState) { return false; }
this.props.setEditorState(newEditorState);
return true;
}

setEditorState(editorState) {
this.props.setEditorState(editorState);
}

render() {
return (
<Flex style={style}>
<Editor
onChange={this.onEditorChange}
editorState={this.state.editorState}
handleKeyCommand={this.onKeyCommand}
/>
</Flex>
);
}
}

EditorInput.propTypes = {
setEditorState: React.PropTypes.func.isRequired,
editorState: React.PropTypes.instanceOf(EditorState).isRequired,
};

export default connect(state => ({
editorState: state.editor.get('editorState'),
}), { setEditorState })(EditorInput);
39 changes: 39 additions & 0 deletions src/browser/editor/EditorLogger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* @flow */
import { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import diffContentStateText from '../../common/editor/diffContentStateText';
// import diffContentStateStyle from '../../common/editor/diffContentStateStyle';

class EditorLogger extends Component {
componentDidUpdate() {
console.time('diffContentStateText'); //eslint-disable-line
const textChanges = diffContentStateText(
this.props.previousContentState,
this.props.contentState
);
console.timeEnd('diffContentStateText', textChanges.toJSON()); //eslint-disable-line
console.log(textChanges.toJSON()); // eslint-disable-line

// console.time('diffContentStateStyle'); //eslint-disable-line
// const styleChanges = diffContentStateStyle(
// this.props.previousContentState,
// this.props.contentState
// );
// console.timeEnd('diffContentStateStyle', styleChanges.toJSON()); //eslint-disable-line
// console.log(styleChanges.toJSON()); // eslint-disable-line
}

render() {
return null;
}
}

EditorLogger.propTypes = {
contentState: PropTypes.any.isRequired,
previousContentState: PropTypes.any.isRequired,
};

export default connect(state => ({
contentState: state.editor.get('editorState').getCurrentContent(),
previousContentState: state.editor.get('previousEditorState').getCurrentContent(),
}), null)(EditorLogger);
19 changes: 19 additions & 0 deletions src/browser/editor/EditorPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* @flow */
import './Editor.scss';
import React from 'react';
import {
Title,
View,
} from '../app/components';
import EditorInput from './EditorInput';
import EditorLogger from './EditorLogger';

const EditorPage = () => (
<View>
<Title message="Draft.js with OT logging" />
<EditorInput />
<EditorLogger />
</View>
);

export default EditorPage;
2 changes: 2 additions & 0 deletions src/common/configureReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import intl from './intl/reducer';
import nativeRouting from '../native/routing/reducer';
import themes from './themes/reducer';
import todos from './todos/reducer';
import editor from './editor/reducer';
import users from './users/reducer';
import { FIREBASE_ON_AUTH } from '../common/lib/redux-firebase/actions';
import { combineReducers } from 'redux';
Expand Down Expand Up @@ -50,6 +51,7 @@ const configureReducer = (initialState: Object) => {
routing,
themes,
todos,
editor,
users,
});

Expand Down
Loading