-
Notifications
You must be signed in to change notification settings - Fork 4
Implement the ReferenceEditor component #120
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,8 @@ | ||
| global.STORYSHOP_API_URI = 'http://nonexistent'; | ||
| global.STORYSHOP_API_URI = 'http://nonexistent'; | ||
| global.window = { | ||
| navigator: { | ||
| userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36', | ||
| } | ||
| }; | ||
|
|
||
| require.extensions['.css'] = function() {}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { 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 const mentionPlugin = createMentionPlugin({ mentionComponent: MentionComponent }); | ||
| export const undoPlugin = createUndoPlugin(); | ||
| export const linkifyPlugin = createLinkifyPlugin(); | ||
|
|
||
| const { MentionSuggestions } = mentionPlugin; | ||
| const Suggestions = SuggestionsFactory(MentionSuggestions); | ||
|
|
||
| export default (React, ...behaviours) => { | ||
| const Editor = EditorFactory(React); | ||
| return 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> | ||
| ); | ||
| }, | ||
| }); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import React from 'react'; | ||
| import { Link } from 'react-router'; | ||
| import { cyan700, green700 } from 'material-ui/lib/styles/colors'; | ||
|
|
||
| const MentionComponent = ({ mention, className, mentionPrefix, children }) => ( | ||
| <Link | ||
| to={mention.get('link')} | ||
| className={className} | ||
| style={{ | ||
| background: 'none', | ||
| color: mention.get('type') === 'character' ? cyan700 : green700, | ||
| }} | ||
| > | ||
| {mentionPrefix}{children} | ||
| </Link> | ||
| ); | ||
|
|
||
| export default MentionComponent; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import React from 'react'; | ||
| import test from 'tape'; | ||
| import { shallow } from 'enzyme'; | ||
| import spy, { createSpy } from '../../utils/spy'; | ||
|
|
||
| import EditorFactory, { mentionPlugin, linkifyPlugin, undoPlugin } from './'; | ||
| import UpstreamEditorFactory from 'components/editor'; | ||
|
|
||
| const Editor = EditorFactory(React); | ||
| const UpstreamEditor = UpstreamEditorFactory(React); | ||
|
|
||
| test('ReferenceEditor', t => { | ||
| let instance, actual, expected; | ||
|
|
||
| const content = {}; | ||
|
|
||
| instance = shallow(<Editor content={content} searchSuggestions={[]} />); | ||
|
|
||
| { | ||
| const upstream = instance.children().at(0); | ||
| t.ok(upstream, 'should render the editor'); | ||
|
|
||
| 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(); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import React from 'react'; | ||
| import { storiesOf, action } from '@kadira/storybook'; | ||
|
|
||
| import EditorFactory from './'; | ||
|
|
||
| const Editor = EditorFactory(React); | ||
|
|
||
| import { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't look like we need all of these. |
||
| ContentState, | ||
| ContentBlock, | ||
| CharacterMetadata, | ||
| convertToRaw, | ||
| } 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' }, | ||
| ]} | ||
| /> | ||
| ); | ||
| }); | ||
| 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; |
| 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it help with styling if we wrapped this in a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really, looks like there isn't a lot of styling in |
||
| {...rest} | ||
| suggestions={fromJS(suggestions)} | ||
| entryComponent={EntryComponent} | ||
| style={{ boxShadow: 'none' }} | ||
| /> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From looking at these tests, it seems the whole discussion about the "factory factory" might be related to just these three lines. If so, in the future, I don't feel this is something we would necessarily need to test. To some degree, it's just testing for a typo. If we wanted to be extra sure, we could check that the number of plugins passed matches our expectation, but I don't think we got a lot of value out of this particular set of assertions.
It's fine to leave, since the work is already done, but for the future, I consider stuff like this superfluous, so you need't worry about testing it too much.