Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion conf/specs.js
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() {};
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 @@ -60,9 +60,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 { 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>
);
},
});
};
18 changes: 18 additions & 0 deletions src/components/reference-editor/mention-text.js
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;
41 changes: 41 additions & 0 deletions src/components/reference-editor/spec.js
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');

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.

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();
});
41 changes: 41 additions & 0 deletions src/components/reference-editor/story.js
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 {

Choose a reason for hiding this comment

The 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' },
]}
/>
);
});
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it help with styling if we wrapped this in a List?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, looks like there isn't a lot of styling in List itself and ListItems are just enough

{...rest}
suggestions={fromJS(suggestions)}
entryComponent={EntryComponent}
style={{ boxShadow: 'none' }}
/>
);
}