Skip to content

Commit

Permalink
Implement the ReferenceEditor component. Ref GH-58
Browse files Browse the repository at this point in the history
  • Loading branch information
goshacmd committed Sep 28, 2016
1 parent ee60fde commit aba0444
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 3 deletions.
10 changes: 10 additions & 0 deletions conf/storybook/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,15 @@ module.exports = {
path.resolve( './src' ),
],
},
module: {
loaders: [
{
test: /plugin\.css$/,
loaders: [
'style', 'css',
],
},
],
}
};

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@
"babel-runtime": "^6.5.0",
"css-loader": "^0.23.1",
"debug": "^2.2.0",
"draft-js": "0.9.0",
"draft-js-mention-plugin": "1.1.0",
"draft-js-plugins-editor": "1.1.0",
"draft-js": "0.9.1",
"draft-js-linkify-plugin": "^2.0.0-beta5",
"draft-js-mention-plugin": "2.0.0-beta5",
"draft-js-plugins-editor": "2.0.0-beta5",
"draft-js-undo-plugin": "^2.0.0-beta5",
"enzyme": "^2.4.1",
"express": "^4.13.4",
"falcor": "^0.1.16",
Expand Down
54 changes: 54 additions & 0 deletions src/components/reference-editor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { PropTypes } from 'react';
import reactStamp from 'react-stamp';

import EditorFactory from 'components/editor';

import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin';
import createUndoPlugin from 'draft-js-undo-plugin';
import createLinkifyPlugin from 'draft-js-linkify-plugin';

import 'draft-js-mention-plugin/lib/plugin.css';
import 'draft-js-undo-plugin/lib/plugin.css';
import 'draft-js-linkify-plugin/lib/plugin.css';

import MentionComponent from './mention-text';
import SuggestionsFactory from './suggestions';

export default function ({
Editor = EditorFactory(React),
mentionPlugin = createMentionPlugin({ mentionComponent: MentionComponent }),
undoPlugin = createUndoPlugin(),
linkifyPlugin = createLinkifyPlugin(),
} = {}) {
const { MentionSuggestions } = mentionPlugin;
const Suggestions = SuggestionsFactory(MentionSuggestions);

return (React, ...behaviours) => reactStamp(React).compose({
propTypes: {
onSearchChange: PropTypes.func,
searchSuggestions: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['character', 'element']).isRequired,
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
link: PropTypes.string.isRequired,
avatar: PropTypes.string.isRequired,
})).isRequired,
},

render() {
const { onSearchChange, searchSuggestions, plugins = [], ...rest } = this.props;
return (
<div>
<Editor
plugins={[...plugins, mentionPlugin, undoPlugin, linkifyPlugin]}
{...rest}
/>
<Suggestions
onSearchChange={onSearchChange}
suggestions={searchSuggestions}
/>
</div>
);
},
});
}
17 changes: 17 additions & 0 deletions src/components/reference-editor/mention-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { cyan700, green700 } from 'material-ui/lib/styles/colors';

const MentionComponent = ({ mention, className, mentionPrefix, children }) => (
<a
href={mention.get('link')}
className={className}
style={{
background: 'none',
color: mention.get('type') === 'character' ? cyan700 : green700,
}}
>
{mentionPrefix}{children}
</a>
);

export default MentionComponent;
58 changes: 58 additions & 0 deletions src/components/reference-editor/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import test from 'tape';
import { shallow, mount } from 'enzyme';
import spy, { createSpy } from '../../utils/spy';

import {
EditorState,
ContentState,
ContentBlock,
CharacterMetadata,
convertToRaw,
convertFromRaw,
genKey,
} from 'draft-js';

import EditorFactory from './';
import UpstreamEditorFactory from 'components/editor';

const UpstreamEditor = UpstreamEditorFactory(React);

test('ReferenceEditor', t => {
const mentionPlugin = { MentionSuggestions: () => (<div />) };
const undoPlugin = {};
const linkifyPlugin = {};

const Editor = EditorFactory({ Editor: UpstreamEditor, mentionPlugin, undoPlugin, linkifyPlugin })(React);
let instance, actual, expected;

const content = {};

instance = shallow(<Editor content={content} searchSuggestions={[]} />);

{
const upstream = instance.find(UpstreamEditor).at(0);
t.ok(upstream, 'should render the editor');
}

{
const upstream = instance.find(UpstreamEditor).at(0);
const plugins = upstream.props().plugins;
t.notEquals(plugins.indexOf(mentionPlugin), -1, 'should manage mention plugin');
t.notEquals(plugins.indexOf(undoPlugin), -1, 'should manage undo plugin');
t.notEquals(plugins.indexOf(linkifyPlugin), -1, 'should manage linkify plugin');
}

{
const onSearchChange = () => {};
const searchSuggestions = [];
instance = shallow(<Editor content={content} onSearchChange={onSearchChange} searchSuggestions={searchSuggestions} />);
const suggestions = instance.children().at(1);

t.ok(suggestions, 'renders suggestion list');
t.equals(suggestions.props().onSearchChange, onSearchChange, 'passed search callback down');
t.equals(suggestions.props().suggestions, searchSuggestions, 'passes suggestion list down');
}

t.end();
});
44 changes: 44 additions & 0 deletions src/components/reference-editor/story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';

import EditorFactory from './';

const Editor = EditorFactory()(React);

import {
EditorState,
ContentState,
ContentBlock,
CharacterMetadata,
convertToRaw,
convertFromRaw,
genKey,
} from 'draft-js';
import { is, fromJS, List, Repeat } from 'immutable';

const genContent = (text) => {
const contentState = ContentState.createFromBlockArray([
new ContentBlock({
key: 'abc',
type: 'unstyled',
text,
characterList: List(Repeat(CharacterMetadata.EMPTY, text.length))
}),
]);
return convertToRaw(contentState);
};

storiesOf('Reference Editor', module)
.add('default', () => {
const initialContent = genContent('Here we go');
return (
<Editor
content={initialContent}
onSearchChange={action('onSearchChange')}
searchSuggestions={[
{ type: 'character', _id: '1', name: 'Abc', link: '123', avatar: 'https://sigil.cupcake.io/Abc' },
{ type: 'element', _id: '2', name: 'Zone', link: '42', avatar: 'https://sigil.cupcake.io/Zone' },
]}
/>
);
});
14 changes: 14 additions & 0 deletions src/components/reference-editor/suggestions/entry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import ListItem from 'material-ui/lib/lists/list-item';
import Avatar from 'material-ui/lib/avatar';

const EntryComponent = ({ mention, className, ...props }) => (
<ListItem
leftAvatar={<Avatar src={mention.get('avatar')} />}
{...props}
>
{mention.get('name')}
</ListItem>
);

export default EntryComponent;
14 changes: 14 additions & 0 deletions src/components/reference-editor/suggestions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { fromJS } from 'immutable';
import EntryComponent from './entry';

export default function (MentionSuggestions) {
return ({ suggestions, ...rest }) => (
<MentionSuggestions
{...rest}
suggestions={fromJS(suggestions)}
entryComponent={EntryComponent}
style={{ boxShadow: 'none' }}
/>
);
}

0 comments on commit aba0444

Please sign in to comment.