Skip to content

Commit

Permalink
Add mention draft-js plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
arisgk committed Sep 15, 2017
1 parent 1c3db21 commit 40e0002
Show file tree
Hide file tree
Showing 27 changed files with 231 additions and 132 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"rules": {
"no-underscore-dangle": 0,
"global-require": 0,
"import/prefer-default-export": 0,
"import/no-extraneous-dependencies": [
"error",
{ "devDependencies": ["**/*.test.js", "**/*.spec.js", "**/*.test.jsx", "**/*.spec.jsx"] }
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"react-router": "^4.2.0",
"react-router-redux": "^5.0.0-alpha.6",
"react-scripts": "1.0.13",
"redux": "^3.7.2"
"redux": "^3.7.2",
"reselect": "^3.0.1"
},
"devDependencies": {
"deep-freeze": "0.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/actions/mentions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './mentions';
6 changes: 6 additions & 0 deletions src/actions/mentions/mentions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as types from './types';

export const mentionSearch = value => ({
type: types.MENTION_SEARCH,
value,
});
18 changes: 18 additions & 0 deletions src/actions/mentions/mentions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* global describe, it, expect */
import * as actions from './mentions';
import * as types from './types';

describe('Actions', () => {
describe('Mentions', () => {
it('Creates an action to search for persons', () => {
const value = 'John';

const expected = {
type: types.MENTION_SEARCH,
value,
};

expect(actions.mentionSearch(value)).toEqual(expected);
});
});
});
1 change: 1 addition & 0 deletions src/actions/mentions/types/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MENTION_SEARCH = 'MENTION_SEARCH';
66 changes: 66 additions & 0 deletions src/components/Home/AutocompleteEditor/AutocompleteEditor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { Component } from 'react';
import { CompositeDecorator, EditorState } from 'draft-js';
import Editor from 'draft-js-plugins-editor';
import 'draft-js-mention-plugin/lib/plugin.css';
import createMentionPlugin from 'draft-js-mention-plugin';

const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];

const styles = {
root: {
padding: 20,
width: 600,
},
editor: {
border: '1px solid #ddd',
cursor: 'text',
fontSize: 16,
minHeight: 40,
padding: 10,
},
};

class AutocompleteEditor extends Component {
constructor() {
super();

this.state = {
editorState: EditorState.createEmpty(),
};

this.focus = () => this.refs.editor.focus();
this.onChange = editorState => this.setState({ editorState });
this.onSearchChange = this.onSearchChange.bind(this);
}

onSearchChange({ value }) {
const { onMentionSearch } = this.props;
onMentionSearch(value);
}

render() {
const { mentionSuggestions } = this.props;

return (
<div style={styles.root}>
<div style={styles.editor} onClick={this.focus}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
ref="editor"
plugins={plugins}
spellCheck
/>
<MentionSuggestions
onSearchChange={this.onSearchChange}
suggestions={mentionSuggestions}
/>
</div>
</div>
);
}
}

export default AutocompleteEditor;
20 changes: 0 additions & 20 deletions src/components/Home/AutocompleteEditor/HandleSpan/index.jsx

This file was deleted.

18 changes: 0 additions & 18 deletions src/components/Home/AutocompleteEditor/HashtagSpan/index.jsx

This file was deleted.

67 changes: 1 addition & 66 deletions src/components/Home/AutocompleteEditor/index.jsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,3 @@
import React, { Component } from 'react';
import { CompositeDecorator, EditorState } from 'draft-js';
import Editor from 'draft-js-plugins-editor';
import 'draft-js-mention-plugin/lib/plugin.css';
import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin';
import mentions from '../../../data/mentions';

const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];

const styles = {
root: {
padding: 20,
width: 600,
},
editor: {
border: '1px solid #ddd',
cursor: 'text',
fontSize: 16,
minHeight: 40,
padding: 10,
},
};

class AutocompleteEditor extends Component {
constructor() {
super();

this.state = {
editorState: EditorState.createEmpty(),
suggestions: mentions,
};

this.focus = () => this.refs.editor.focus();
this.onChange = editorState => this.setState({ editorState });
this.onSearchChange = this.onSearchChange.bind(this);
}

onSearchChange({ value }) {
this.setState({
suggestions: defaultSuggestionsFilter(value, mentions),
});
}

render() {
return (
<div style={styles.root}>
<div style={styles.editor} onClick={this.focus}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
ref="editor"
plugins={plugins}
spellCheck
/>
<MentionSuggestions
onSearchChange={this.onSearchChange}
suggestions={this.state.suggestions}
onAddMention={this.onAddMention}
/>
</div>
</div>
);
}
}
import AutocompleteEditor from './AutocompleteEditor';

export default AutocompleteEditor;
4 changes: 2 additions & 2 deletions src/components/Home/Home.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import AppBar from 'material-ui/AppBar';
import AutocompleteEditor from './AutocompleteEditor';
import AutocompleteEditorContainer from '../../containers/AutocompleteEditorContainer';

const styles = {
container: {
Expand All @@ -18,7 +18,7 @@ const Home = () => (
<div style={styles.container}>
<AppBar title="Autocomplete" />
<div style={styles.main}>
<AutocompleteEditor />
<AutocompleteEditorContainer />
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Home/__snapshots__/Home.spec.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ exports[`Home renders correctly 1`] = `
}
}
>
<AutocompleteEditor />
<Connect(AutocompleteEditor) />
</div>
</div>
`;
19 changes: 19 additions & 0 deletions src/containers/AutocompleteEditorContainer/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import { mentionSearch } from '../../actions/mentions';
import AutocompleteEditor from '../../components/Home/AutocompleteEditor';
import * as mentions from '../../selectors/mentions';

const mapStateToProps = state => ({
mentionSuggestions: mentions.getSuggestions(state),
});

const mapDispatchToProps = dispatch => ({
onMentionSearch: value => dispatch(mentionSearch(value)),
});

const AutocompleteEditorContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(AutocompleteEditor);

export default AutocompleteEditorContainer;
8 changes: 8 additions & 0 deletions src/reducers/home/entities/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { combineReducers } from 'redux';
import mentions from './mentions';

const reducer = combineReducers({
mentions,
});

export default reducer;
10 changes: 10 additions & 0 deletions src/reducers/home/entities/mentions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import mentions from '../../../../data/mentions';

const initialState = mentions;

export default function reducer(state = initialState, action) {
switch (action.type) {
default:
return state;
}
}
10 changes: 10 additions & 0 deletions src/reducers/home/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { combineReducers } from 'redux';
import entities from './entities';
import ui from './ui';

const reducer = combineReducers({
entities,
ui,
});

export default reducer;
8 changes: 8 additions & 0 deletions src/reducers/home/ui/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { combineReducers } from 'redux';
import mentions from './mentions';

const reducer = combineReducers({
mentions,
});

export default reducer;
3 changes: 3 additions & 0 deletions src/reducers/home/ui/mentions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import mentions from './mentions';

export default mentions;
14 changes: 14 additions & 0 deletions src/reducers/home/ui/mentions/mentions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as types from '../../../../actions/mentions/types';

const initialState = {
search: '',
};

export default function reducer(state = initialState, action) {
switch (action.type) {
case types.MENTION_SEARCH:
return { ...state, search: action.value };
default:
return state;
}
}
35 changes: 35 additions & 0 deletions src/reducers/home/ui/mentions/mentions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* global describe, it, expect */
import deepFreeze from 'deep-freeze';
import * as types from '../../../../actions/mentions/types';
import reducer from './mentions';

describe('Home', () => {
describe('UI Reducer', () => {
describe('Mentions', () => {
it('Returns the initial state', () => {
expect(reducer(undefined, {})).toEqual({
search: '',
});
});

it('Updates mention search value', () => {
const state = {
search: 'Joh',
};

deepFreeze(state);

const action = {
type: types.MENTION_SEARCH,
value: 'John',
};

const expected = {
search: 'John',
};

expect(reducer(state, action)).toEqual(expected);
});
});
});
});
2 changes: 2 additions & 0 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { combineReducers } from 'redux';
import { routerReducer as routing } from 'react-router-redux';
import home from './home';

const reducer = combineReducers({
routing,
home,
});

export default reducer;
8 changes: 8 additions & 0 deletions src/schemas/propTypes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const mentionSuggestions = (props, propName, componentName) => {
if (!List.isList(props[propName])) {
return new Error(
`Invalid prop \`${propName}\` supplied to \`${componentName}\`. should be an instance of immutable list.`
);
}
return undefined;
},
Loading

0 comments on commit 40e0002

Please sign in to comment.