From aba0444b0bcb852fa061f7ecc70f0b7d69aaebae Mon Sep 17 00:00:00 2001 From: Gosha Arinich Date: Sat, 24 Sep 2016 12:35:23 +0300 Subject: [PATCH] Implement the ReferenceEditor component. Ref GH-58 --- conf/storybook/webpack.config.js | 10 ++++ package.json | 8 ++- src/components/reference-editor/index.js | 54 +++++++++++++++++ .../reference-editor/mention-text.js | 17 ++++++ src/components/reference-editor/spec.js | 58 +++++++++++++++++++ src/components/reference-editor/story.js | 44 ++++++++++++++ .../reference-editor/suggestions/entry.js | 14 +++++ .../reference-editor/suggestions/index.js | 14 +++++ 8 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/components/reference-editor/index.js create mode 100644 src/components/reference-editor/mention-text.js create mode 100644 src/components/reference-editor/spec.js create mode 100644 src/components/reference-editor/story.js create mode 100644 src/components/reference-editor/suggestions/entry.js create mode 100644 src/components/reference-editor/suggestions/index.js diff --git a/conf/storybook/webpack.config.js b/conf/storybook/webpack.config.js index 370d4ab..7dde8c8 100644 --- a/conf/storybook/webpack.config.js +++ b/conf/storybook/webpack.config.js @@ -6,5 +6,15 @@ module.exports = { path.resolve( './src' ), ], }, + module: { + loaders: [ + { + test: /plugin\.css$/, + loaders: [ + 'style', 'css', + ], + }, + ], + } }; diff --git a/package.json b/package.json index 6d7217c..f2d6db0 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/reference-editor/index.js b/src/components/reference-editor/index.js new file mode 100644 index 0000000..bec2a86 --- /dev/null +++ b/src/components/reference-editor/index.js @@ -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 ( +
+ + +
+ ); + }, + }); +} diff --git a/src/components/reference-editor/mention-text.js b/src/components/reference-editor/mention-text.js new file mode 100644 index 0000000..bb578ec --- /dev/null +++ b/src/components/reference-editor/mention-text.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { cyan700, green700 } from 'material-ui/lib/styles/colors'; + +const MentionComponent = ({ mention, className, mentionPrefix, children }) => ( + + {mentionPrefix}{children} + +); + +export default MentionComponent; diff --git a/src/components/reference-editor/spec.js b/src/components/reference-editor/spec.js new file mode 100644 index 0000000..8c3d2af --- /dev/null +++ b/src/components/reference-editor/spec.js @@ -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: () => (
) }; + const undoPlugin = {}; + const linkifyPlugin = {}; + + const Editor = EditorFactory({ Editor: UpstreamEditor, mentionPlugin, undoPlugin, linkifyPlugin })(React); + let instance, actual, expected; + + const content = {}; + + instance = shallow(); + + { + 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(); + 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(); +}); diff --git a/src/components/reference-editor/story.js b/src/components/reference-editor/story.js new file mode 100644 index 0000000..195d982 --- /dev/null +++ b/src/components/reference-editor/story.js @@ -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 ( + + ); + }); diff --git a/src/components/reference-editor/suggestions/entry.js b/src/components/reference-editor/suggestions/entry.js new file mode 100644 index 0000000..e40d522 --- /dev/null +++ b/src/components/reference-editor/suggestions/entry.js @@ -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 }) => ( + } + {...props} + > + {mention.get('name')} + +); + +export default EntryComponent; diff --git a/src/components/reference-editor/suggestions/index.js b/src/components/reference-editor/suggestions/index.js new file mode 100644 index 0000000..b07a28a --- /dev/null +++ b/src/components/reference-editor/suggestions/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { fromJS } from 'immutable'; +import EntryComponent from './entry'; + +export default function (MentionSuggestions) { + return ({ suggestions, ...rest }) => ( + + ); +}