├── .meteor ├── .gitignore ├── release ├── platforms ├── .id ├── .finished-upgraders ├── packages └── versions ├── src ├── server.js ├── client │ ├── modules │ │ ├── core │ │ │ ├── actions │ │ │ │ ├── index.js │ │ │ │ ├── posts.js │ │ │ │ └── tests │ │ │ │ │ └── posts.js │ │ │ ├── configs │ │ │ │ └── method_stubs │ │ │ │ │ ├── index.js │ │ │ │ │ └── posts.js │ │ │ ├── index.js │ │ │ ├── components │ │ │ │ ├── navigations.jsx │ │ │ │ ├── postlist.jsx │ │ │ │ ├── post.jsx │ │ │ │ ├── layout.main.jsx │ │ │ │ ├── tests │ │ │ │ │ ├── layout.main.js │ │ │ │ │ ├── navigations.js │ │ │ │ │ ├── post.js │ │ │ │ │ ├── postlist.js │ │ │ │ │ └── newpost.js │ │ │ │ ├── newpost.jsx │ │ │ │ └── style.css │ │ │ ├── containers │ │ │ │ ├── postlist.js │ │ │ │ ├── newpost.js │ │ │ │ ├── tests │ │ │ │ │ ├── newpost.js │ │ │ │ │ ├── postlist.js │ │ │ │ │ └── post.js │ │ │ │ └── post.js │ │ │ └── routes.jsx │ │ └── comments │ │ │ ├── configs │ │ │ └── method_stubs │ │ │ │ ├── index.js │ │ │ │ └── comments.js │ │ │ ├── index.js │ │ │ ├── actions │ │ │ ├── index.js │ │ │ └── comments.js │ │ │ ├── components │ │ │ ├── style.css │ │ │ ├── comment_list.jsx │ │ │ └── create_comment.jsx │ │ │ └── containers │ │ │ ├── comment_list.js │ │ │ └── create_comment.js │ ├── main.js │ └── configs │ │ └── context.js ├── server │ ├── methods │ │ ├── index.js │ │ └── posts.js │ ├── publications │ │ ├── index.js │ │ └── posts.js │ ├── main.js │ └── configs │ │ └── initial_adds.js ├── client.js ├── lib │ └── collections.js └── meteor.startup.js ├── .gitignore ├── .babeldc ├── mantra.cson ├── webpack.json ├── .editorconfig ├── .scripts └── mocha_boot.js ├── wallaby.js ├── README.md ├── package.json └── .eslintrc /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.3.2.4 2 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | browser 2 | server 3 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | import './server/main'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.babeldc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["jsx-control-statements"] 3 | } 4 | -------------------------------------------------------------------------------- /mantra.cson: -------------------------------------------------------------------------------- 1 | 'language': 'js', 2 | 'root': 'src', 3 | 'libFolderName': 'lib' 4 | -------------------------------------------------------------------------------- /webpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "src", 3 | "devServer": { 4 | "host": "localhost" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/client/modules/core/actions/index.js: -------------------------------------------------------------------------------- 1 | import posts from './posts'; 2 | 3 | export default { 4 | posts 5 | }; 6 | -------------------------------------------------------------------------------- /src/server/methods/index.js: -------------------------------------------------------------------------------- 1 | import posts from './posts'; 2 | 3 | export default function () { 4 | posts(); 5 | } 6 | -------------------------------------------------------------------------------- /src/server/publications/index.js: -------------------------------------------------------------------------------- 1 | import posts from './posts'; 2 | 3 | export default function () { 4 | posts(); 5 | } 6 | -------------------------------------------------------------------------------- /src/client/modules/core/configs/method_stubs/index.js: -------------------------------------------------------------------------------- 1 | import posts from './posts'; 2 | 3 | export default function (context) { 4 | posts(context); 5 | } 6 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | import { FlowRouter } from 'meteor/kadira:flow-router'; 2 | 3 | import './client/main'; 4 | 5 | Meteor.startup(function() { 6 | FlowRouter.initialize(); 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib/collections.js: -------------------------------------------------------------------------------- 1 | import {Mongo} from 'meteor/mongo'; 2 | 3 | export const Posts = new Mongo.Collection('posts'); 4 | export const Comments = new Mongo.Collection('comments'); 5 | -------------------------------------------------------------------------------- /src/meteor.startup.js: -------------------------------------------------------------------------------- 1 | // This file is sent directly to Meteor without going through Webpack 2 | // You can initialize anything you need before your app start here 3 | 4 | 5 | FlowRouter.wait(); 6 | 7 | -------------------------------------------------------------------------------- /src/client/modules/comments/configs/method_stubs/index.js: -------------------------------------------------------------------------------- 1 | import comments from './comments'; 2 | 3 | // XXX: Here we can auto generate this file based on the method stubs 4 | export default function (context) { 5 | comments(context); 6 | } 7 | -------------------------------------------------------------------------------- /src/client/modules/comments/index.js: -------------------------------------------------------------------------------- 1 | import methodStubs from './configs/method_stubs'; 2 | import actions from './actions'; 3 | 4 | export default { 5 | actions, 6 | load(context) { 7 | methodStubs(context); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/client/modules/comments/actions/index.js: -------------------------------------------------------------------------------- 1 | import comments from './comments'; 2 | 3 | // XXX: Here, we can automatically generate this file based on the 4 | // actions inside this directory. 5 | const actions = { 6 | comments 7 | }; 8 | 9 | export default actions; 10 | -------------------------------------------------------------------------------- /src/client/modules/core/index.js: -------------------------------------------------------------------------------- 1 | import methodStubs from './configs/method_stubs'; 2 | import actions from './actions'; 3 | import routes from './routes.jsx'; 4 | 5 | export default { 6 | routes, 7 | actions, 8 | load(context) { 9 | methodStubs(context); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/client/modules/core/components/navigations.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Navigations = () => ( 4 |
5 | Navigations: 6 | Home | 7 | New Post 8 |
9 | ); 10 | 11 | export default Navigations; 12 | -------------------------------------------------------------------------------- /src/server/main.js: -------------------------------------------------------------------------------- 1 | import addInitialData from './configs/initial_adds.js'; 2 | 3 | addInitialData(); 4 | 5 | // import publications and methods 6 | 7 | import publications from './publications/index'; 8 | import methods from './methods/index'; 9 | 10 | publications(); 11 | methods(); 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # @see http://editorconfig.org/ 2 | 3 | # This is the top-most .editorconfig file; do not search in parent directories. 4 | root = true 5 | 6 | # All files. 7 | [*] 8 | end_of_line = LF 9 | indent_style = space 10 | indent_size = 2 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | a247lv2k0c6tl3kml5 8 | -------------------------------------------------------------------------------- /src/client/modules/comments/components/style.css: -------------------------------------------------------------------------------- 1 | .comments { 2 | font-size: 13px; 3 | } 4 | 5 | .comments textarea { 6 | width: 250px; 7 | outline: 0; 8 | border: 1px solid #ddd; 9 | padding: 5px 5px; 10 | } 11 | 12 | .comments .comment-list { 13 | margin-top: 15px; 14 | } 15 | 16 | .comments .error { 17 | color: red; 18 | padding: 5px 0; 19 | } 20 | -------------------------------------------------------------------------------- /src/server/configs/initial_adds.js: -------------------------------------------------------------------------------- 1 | import {Posts} from 'lib/collections'; 2 | 3 | export default function () { 4 | if (!Posts.findOne()) { 5 | for (let lc = 1; lc <= 5; lc++) { 6 | const title = `This is the post title: ${lc}`; 7 | const content = `Post ${lc}'s content is great!`; 8 | Posts.insert({title, content}); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client/modules/core/components/postlist.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PostList = ({posts}) => ( 4 |
5 | 12 |
13 | ); 14 | 15 | export default PostList; 16 | -------------------------------------------------------------------------------- /src/client/modules/comments/configs/method_stubs/comments.js: -------------------------------------------------------------------------------- 1 | export default function ({Collections, Meteor}) { 2 | Meteor.methods({ 3 | 'posts.createComment'(_id, postId, text) { 4 | const saving = true; 5 | const createdAt = new Date(); 6 | const author = 'Me'; 7 | Collections.Comments.insert({ 8 | _id, postId, text, saving, createdAt, author 9 | }); 10 | } 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/client/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'mantra-core'; 2 | import initContext from './configs/context'; 3 | 4 | // modules 5 | import coreModule from './modules/core'; 6 | import commentsModule from './modules/comments'; 7 | 8 | // init context 9 | const context = initContext(); 10 | 11 | // create app 12 | const app = createApp(context); 13 | app.loadModule(coreModule); 14 | app.loadModule(commentsModule); 15 | app.init(); 16 | -------------------------------------------------------------------------------- /src/client/configs/context.js: -------------------------------------------------------------------------------- 1 | import * as Collections from 'lib/collections'; 2 | import {Meteor} from 'meteor/meteor'; 3 | import {FlowRouter} from 'meteor/kadira:flow-router'; 4 | import {ReactiveDict} from 'meteor/reactive-dict'; 5 | import {Tracker} from 'meteor/tracker'; 6 | 7 | export default function () { 8 | return { 9 | Meteor, 10 | FlowRouter, 11 | Collections, 12 | LocalState: new ReactiveDict(), 13 | Tracker 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | -------------------------------------------------------------------------------- /src/client/modules/core/components/post.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CommentList from 'client/modules/comments/containers/comment_list.js'; 3 | 4 | const Post = ({post}) => ( 5 |
6 | {post.saving ?

Saving...

: null} 7 |

{post.title}

8 |

9 | {post.content} 10 |

11 |
12 |

Comments

13 | 14 |
15 |
16 | ); 17 | 18 | export default Post; 19 | -------------------------------------------------------------------------------- /.scripts/mocha_boot.js: -------------------------------------------------------------------------------- 1 | var jsdom = require('jsdom').jsdom; 2 | 3 | var exposedProperties = ['window', 'navigator', 'document']; 4 | 5 | global.document = jsdom(''); 6 | global.window = document.defaultView; 7 | Object.keys(document.defaultView).forEach((property) => { 8 | if (typeof global[property] === 'undefined') { 9 | exposedProperties.push(property); 10 | global[property] = document.defaultView[property]; 11 | } 12 | }); 13 | 14 | global.navigator = { 15 | userAgent: 'node.js' 16 | }; 17 | -------------------------------------------------------------------------------- /src/client/modules/core/components/layout.main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Navigations from './navigations.jsx'; 3 | 4 | const Layout = ({content = () => null }) => ( 5 |
6 |
7 |

Mantra Voice

8 | 9 |
10 | 11 |
12 | {content()} 13 |
14 | 15 | 18 |
19 | ); 20 | 21 | export default Layout; 22 | -------------------------------------------------------------------------------- /src/client/modules/core/containers/postlist.js: -------------------------------------------------------------------------------- 1 | import PostList from '../components/postlist.jsx'; 2 | import {useDeps, composeWithTracker, composeAll} from 'mantra-core'; 3 | 4 | export const composer = ({context}, onData) => { 5 | const {Meteor, Collections} = context(); 6 | if (Meteor.subscribe('posts.list').ready()) { 7 | const posts = Collections.Posts.find().fetch(); 8 | onData(null, {posts}); 9 | } 10 | }; 11 | 12 | export default composeAll( 13 | composeWithTracker(composer), 14 | useDeps() 15 | )(PostList); 16 | -------------------------------------------------------------------------------- /src/client/modules/core/configs/method_stubs/posts.js: -------------------------------------------------------------------------------- 1 | import {check} from 'meteor/check'; 2 | 3 | export default function ({Meteor, Collections}) { 4 | Meteor.methods({ 5 | 'posts.create'(_id, title, content) { 6 | check(_id, String); 7 | check(title, String); 8 | check(content, String); 9 | 10 | const createdAt = new Date(); 11 | const post = { 12 | _id, title, content, createdAt, 13 | saving: true 14 | }; 15 | 16 | Collections.Posts.insert(post); 17 | } 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/client/modules/comments/actions/comments.js: -------------------------------------------------------------------------------- 1 | export default { 2 | create({Meteor, LocalState}, postId, text) { 3 | LocalState.set('CREATE_COMMENT_ERROR', null); 4 | if (!text) { 5 | LocalState.set('CREATE_COMMENT_ERROR', 'Comment text is required.'); 6 | return; 7 | } 8 | 9 | const id = Meteor.uuid(); 10 | Meteor.call('posts.createComment', id, postId, text, (err) => { 11 | if (err) { 12 | alert(`Post creating failed: ${err.message}`); 13 | } 14 | }); 15 | }, 16 | 17 | clearErrors({LocalState}) { 18 | return LocalState.set('CREATE_COMMENT_ERROR', null); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/client/modules/comments/components/comment_list.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CreateComment from '../containers/create_comment.js'; 3 | 4 | const CommentList = ({comments, postId}) => ( 5 |
6 |
7 | 8 |
9 |
10 | {comments.length === 0 ?

No Comments Yet!

: null} 11 | {comments.map(comment => ( 12 |
13 | {comment.author}: {comment.text} 14 | {comment.saving ? '...' : null} 15 |
16 | ))} 17 |
18 |
19 | ); 20 | 21 | export default CommentList; 22 | -------------------------------------------------------------------------------- /src/client/modules/comments/containers/comment_list.js: -------------------------------------------------------------------------------- 1 | import { 2 | useDeps, composeWithTracker, composeAll 3 | } from 'mantra-core'; 4 | import Component from '../components/comment_list.jsx'; 5 | 6 | export const composer = ({context, clearErrors, postId}, onData) => { 7 | const {Meteor, Collections} = context(); 8 | if (Meteor.subscribe('posts.comments', postId).ready()) { 9 | const options = { 10 | sort: {createdAt: -1} 11 | }; 12 | const comments = Collections.Comments.find({postId}, options).fetch(); 13 | onData(null, {comments}); 14 | } else { 15 | onData(); 16 | } 17 | }; 18 | 19 | export default composeAll( 20 | composeWithTracker(composer), 21 | useDeps() 22 | )(Component); 23 | -------------------------------------------------------------------------------- /src/client/modules/core/components/tests/layout.main.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = global; 2 | import {expect} from 'chai'; 3 | import {shallow} from 'enzyme'; 4 | import MainLayout from '../layout.main.jsx'; 5 | import Navigations from '../navigations.jsx'; 6 | 7 | describe('components.layouts.main', () => { 8 | it('should contain navigations', () => { 9 | const el = shallow(); 10 | expect(el.contains()).to.be.equal(true); 11 | }); 12 | 13 | it('should render childrens', () => { 14 | const Comp = () => (

Hello

); 15 | const el = shallow( 16 | ()}/> 17 | ); 18 | 19 | expect(el.contains()).to.be.equal(true); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/client/modules/comments/containers/create_comment.js: -------------------------------------------------------------------------------- 1 | import { 2 | useDeps, composeWithTracker, composeAll 3 | } from 'mantra-core'; 4 | import Component from '../components/create_comment.jsx'; 5 | 6 | export const composer = ({context, clearErrors}, onData) => { 7 | const {LocalState} = context(); 8 | const error = LocalState.get('CREATE_COMMENT_ERROR'); 9 | onData(null, {error}); 10 | 11 | return clearErrors; 12 | }; 13 | 14 | export const depsMapper = (context, actions) => ({ 15 | create: actions.comments.create, 16 | clearErrors: actions.comments.clearErrors, 17 | context: () => context 18 | }); 19 | 20 | export default composeAll( 21 | composeWithTracker(composer), 22 | useDeps(depsMapper) 23 | )(Component); 24 | -------------------------------------------------------------------------------- /src/client/modules/core/containers/newpost.js: -------------------------------------------------------------------------------- 1 | import NewPost from '../components/newpost.jsx'; 2 | import {useDeps, composeWithTracker, composeAll} from 'mantra-core'; 3 | 4 | export const composer = ({context, clearErrors}, onData) => { 5 | const {LocalState} = context(); 6 | const error = LocalState.get('SAVING_ERROR'); 7 | onData(null, {error}); 8 | 9 | // clearErrors when unmounting the component 10 | return clearErrors; 11 | }; 12 | 13 | export const depsMapper = (context, actions) => ({ 14 | create: actions.posts.create, 15 | clearErrors: actions.posts.clearErrors, 16 | context: () => context 17 | }); 18 | 19 | export default composeAll( 20 | composeWithTracker(composer), 21 | useDeps(depsMapper) 22 | )(NewPost); 23 | -------------------------------------------------------------------------------- /src/client/modules/core/actions/posts.js: -------------------------------------------------------------------------------- 1 | export default { 2 | create({Meteor, LocalState, FlowRouter}, title, content) { 3 | if (!title || !content) { 4 | return LocalState.set('SAVING_ERROR', 'Title & Content are required!'); 5 | } 6 | 7 | LocalState.set('SAVING_ERROR', null); 8 | 9 | const id = Meteor.uuid(); 10 | // There is a method stub for this in the config/method_stubs 11 | // That's how we are doing latency compensation 12 | Meteor.call('posts.create', id, title, content, (err) => { 13 | if (err) { 14 | return LocalState.set('SAVING_ERROR', err.message); 15 | } 16 | }); 17 | FlowRouter.go(`/post/${id}`); 18 | }, 19 | 20 | clearErrors({LocalState}) { 21 | return LocalState.set('SAVING_ERROR', null); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/client/modules/core/components/tests/navigations.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = global; 2 | import {expect} from 'chai'; 3 | import {shallow} from 'enzyme'; 4 | import Navigations from '../navigations.jsx'; 5 | 6 | describe('components.navigations', () => { 7 | it('should contain a link to home', () => { 8 | const el = shallow(); 9 | const homeLink = el.find('a').at(0); 10 | expect(homeLink.text()).to.be.equal('Home'); 11 | expect(homeLink.prop('href')).to.be.equal('/'); 12 | }); 13 | 14 | it('should contain a link to create a new post', () => { 15 | const el = shallow(); 16 | const newPostLink = el.find('a').at(1); 17 | expect(newPostLink.text()).to.be.equal('New Post'); 18 | expect(newPostLink.prop('href')).to.be.equal('/new-post'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/server/publications/posts.js: -------------------------------------------------------------------------------- 1 | import {Posts, Comments} from 'lib/collections'; 2 | import {Meteor} from 'meteor/meteor'; 3 | import {check} from 'meteor/check'; 4 | 5 | export default function () { 6 | Meteor.publish('posts.list', function () { 7 | const selector = {}; 8 | const options = { 9 | fields: {_id: 1, title: 1}, 10 | sort: {createdAt: -1}, 11 | limit: 10 12 | }; 13 | 14 | return Posts.find(selector, options); 15 | }); 16 | 17 | Meteor.publish('posts.single', function (postId) { 18 | check(postId, String); 19 | const selector = {_id: postId}; 20 | return Posts.find(selector); 21 | }); 22 | 23 | Meteor.publish('posts.comments', function (postId) { 24 | check(postId, String); 25 | const selector = {postId}; 26 | return Comments.find(selector); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/client/modules/core/components/newpost.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class NewPost extends React.Component { 4 | render() { 5 | const {error} = this.props; 6 | return ( 7 |
8 |

Add New Post

9 | {error ?

{error}

: null} 10 | 11 |
12 | 12 |
13 | 14 |
15 | ); 16 | } 17 | 18 | _create() { 19 | const text = this.refs.text.value; 20 | const {create, postId} = this.props; 21 | create(postId, text); 22 | this.refs.text.value = ''; 23 | } 24 | 25 | _renderError(error) { 26 | return ( 27 |
28 | {error} 29 |
30 | ); 31 | } 32 | } 33 | 34 | export default CreateComment; 35 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base # Packages every Meteor app needs to have 8 | mobile-experience # Packages for a great mobile UX 9 | mongo # The database Meteor supports right now 10 | jquery # Helpful client-side library 11 | tracker # Meteor's client-side reactive programming library 12 | 13 | es5-shim # ECMAScript 5 compatibility for older browsers. 14 | 15 | react-runtime 16 | 17 | webpack:webpack 18 | webpack:react 19 | webpack:css 20 | webpack:assets 21 | 22 | accounts-password 23 | kadira:flow-router 24 | standard-minifier-css 25 | standard-minifier-js 26 | react-meteor-data -------------------------------------------------------------------------------- /src/client/modules/core/components/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif; 3 | padding: 10px 20px; 4 | } 5 | 6 | header { 7 | border-bottom: 1px solid #EAEAEA; 8 | padding-bottom: 10px; 9 | margin-bottom: 30px; 10 | } 11 | 12 | footer { 13 | margin-top: 50px; 14 | border-top: 1px solid #EAEAEA; 15 | padding-top: 10px; 16 | font-size: 12px; 17 | } 18 | 19 | .postlist li { 20 | margin: 8px 0; 21 | } 22 | 23 | .postlist li a { 24 | text-decoration: none; 25 | } 26 | 27 | .new-post input, 28 | .new-post textarea { 29 | width: 350px; 30 | outline: 0; 31 | margin-bottom: 5px; 32 | border: 1px solid #ddd; 33 | font-size: 13px; 34 | padding: 4px 6px; 35 | } 36 | 37 | .new-post textarea { 38 | height: 200px; 39 | } 40 | 41 | footer { 42 | margin-top: 20px; 43 | border-top: 1px solid #eee; 44 | padding-top: 5px; 45 | } 46 | -------------------------------------------------------------------------------- /src/client/modules/core/components/tests/post.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = global; 2 | import {expect} from 'chai'; 3 | import {shallow} from 'enzyme'; 4 | import Post from '../post.jsx'; 5 | 6 | describe('components.post', () => { 7 | it('should display the post title', () => { 8 | const post = {title: 'Nice One'}; 9 | const el = shallow(); 10 | expect(el.find('h2').text()).to.be.match(/Nice One/); 11 | }); 12 | 13 | it('should display the post content', () => { 14 | const post = {content: 'Nice content'}; 15 | const el = shallow(); 16 | expect(el.find('p').text()).to.be.match(/Nice content/); 17 | }); 18 | 19 | it('should display saving indicator if saving prop is there', () => { 20 | const post = {saving: true}; 21 | const el = shallow(); 22 | expect(el.find('p').first().text()).to.be.match(/saving/i); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/client/modules/core/containers/post.js: -------------------------------------------------------------------------------- 1 | import Post from '../components/post.jsx'; 2 | import {useDeps, composeWithTracker, composeAll} from 'mantra-core'; 3 | 4 | export const composer = ({context, postId}, onData) => { 5 | const {Meteor, Collections, Tracker} = context(); 6 | 7 | Meteor.subscribe('posts.single', postId, () => { 8 | const post = Collections.Posts.findOne(postId); 9 | onData(null, {post}); 10 | }); 11 | 12 | // support latency compensation 13 | // we don't need to invalidate tracker because of the 14 | // data fetching from the cache. 15 | const postFromCache = Tracker.nonreactive(() => { 16 | return Collections.Posts.findOne(postId); 17 | }); 18 | 19 | if (postFromCache) { 20 | onData(null, {post: postFromCache}); 21 | } else { 22 | onData(); 23 | } 24 | }; 25 | 26 | export default composeAll( 27 | composeWithTracker(composer), 28 | useDeps() 29 | )(Post); 30 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | module.exports = function (wallaby) { 2 | // There is a weird error with the mui and mantra. 3 | // See: https://goo.gl/cLH8ib 4 | // Using require here seems to be the error. 5 | // Renaming it into `load` just fixed the issue. 6 | var load = require; 7 | 8 | return { 9 | files: [ 10 | 'client/modules/**/components/*.jsx', 11 | 'client/modules/**/actions/*.js', 12 | 'client/modules/**/containers/*.js', 13 | 'client/modules/**/libs/*.js' 14 | ], 15 | tests: [ 16 | 'client/**/tests/*.js' 17 | ], 18 | compilers: { 19 | '**/*.js*': wallaby.compilers.babel({ 20 | babel: load('babel-core'), 21 | presets: ['es2015', 'stage-2', 'react'] 22 | }) 23 | }, 24 | env: { 25 | type: 'node' 26 | }, 27 | testFramework: 'mocha', 28 | setup: function() { 29 | global.React = require('react'); 30 | } 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /src/server/methods/posts.js: -------------------------------------------------------------------------------- 1 | import {Posts, Comments} from 'lib/collections'; 2 | import {Meteor} from 'meteor/meteor'; 3 | import {check} from 'meteor/check'; 4 | 5 | export default function () { 6 | Meteor.methods({ 7 | 'posts.create'(_id, title, content) { 8 | check(_id, String); 9 | check(title, String); 10 | check(content, String); 11 | 12 | // Show the latency compensations 13 | Meteor._sleepForMs(500); 14 | 15 | // XXX: Do some user authorization 16 | const createdAt = new Date(); 17 | const post = {_id, title, content, createdAt}; 18 | Posts.insert(post); 19 | } 20 | }); 21 | 22 | Meteor.methods({ 23 | 'posts.createComment'(_id, postId, text) { 24 | check(_id, String); 25 | check(postId, String); 26 | check(text, String); 27 | 28 | // Show the latency compensations 29 | Meteor._sleepForMs(500); 30 | 31 | // XXX: Do some user authorization 32 | const createdAt = new Date(); 33 | const author = 'The User'; 34 | const comment = {_id, postId, author, text, createdAt}; 35 | Comments.insert(comment); 36 | } 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/client/modules/core/components/tests/postlist.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = global; 2 | import {expect} from 'chai'; 3 | import {shallow} from 'enzyme'; 4 | import PostList from '../postlist.jsx'; 5 | 6 | describe('components.postlist', () => { 7 | const posts = [ 8 | {title: 't-one', _id: 'one'}, 9 | {title: 't-two', _id: 'two'}, 10 | ]; 11 | 12 | it('should list given number of items', () => { 13 | const el = shallow(); 14 | expect(el.find('li').length).to.be.equal(posts.length); 15 | }); 16 | 17 | it('should list post title for each item', () => { 18 | const el = shallow(); 19 | const lis = el.find('li'); 20 | lis.forEach((li, index) => { 21 | const aText = li.find('a').first().text(); 22 | expect(aText).to.be.equal(posts[index].title); 23 | }); 24 | }); 25 | 26 | it('shallow list post link for each items', () => { 27 | const el = shallow(); 28 | const lis = el.find('li'); 29 | lis.forEach((li, index) => { 30 | const href = li.find('a').first().prop('href'); 31 | expect(href).to.be.equal(`/post/${posts[index]._id}`); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/client/modules/core/routes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {mount} from 'react-mounter'; 3 | 4 | import {FlowRouter} from 'meteor/kadira:flow-router'; 5 | import MainLayout from 'client/modules/core/components/layout.main.jsx'; 6 | import PostList from 'client/modules/core/containers/postlist'; 7 | import Post from 'client/modules/core/containers/post'; 8 | import NewPost from 'client/modules/core/containers/newpost'; 9 | 10 | export default function (injectDeps) { 11 | const MainLayoutCtx = injectDeps(MainLayout); 12 | 13 | // Move these as a module and call this from a main file 14 | FlowRouter.route('/', { 15 | name: 'posts.list', 16 | action() { 17 | mount(MainLayoutCtx, { 18 | content: () => () 19 | }); 20 | } 21 | }); 22 | 23 | FlowRouter.route('/post/:postId', { 24 | name: 'posts.single', 25 | action({postId}) { 26 | mount(MainLayoutCtx, { 27 | content: () => () 28 | }); 29 | } 30 | }); 31 | 32 | FlowRouter.route('/new-post', { 33 | name: 'newpost', 34 | action() { 35 | mount(MainLayoutCtx, { 36 | content: () => () 37 | }); 38 | } 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /src/client/modules/core/containers/tests/postlist.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = global; 2 | import {expect} from 'chai'; 3 | import {stub, spy} from 'sinon'; 4 | import {composer} from '../postlist'; 5 | 6 | describe('containers.postlist', () => { 7 | describe('composer', () => { 8 | it('should subscribe to posts.list', () => { 9 | const Meteor = {subscribe: stub()}; 10 | Meteor.subscribe.returns({ready: () => false}); 11 | 12 | const context = () => ({Meteor}); 13 | const onData = spy(); 14 | 15 | composer({context}, onData); 16 | expect(Meteor.subscribe.args[0]).to.deep.equal([ 17 | 'posts.list' 18 | ]); 19 | }); 20 | 21 | describe('after subscribed', () => { 22 | it('should fetch data from all posts & pass to onData', () => { 23 | const Meteor = {subscribe: stub()}; 24 | Meteor.subscribe.returns({ready: () => true}); 25 | 26 | const posts = [ {_id: 'aa'} ]; 27 | const Collections = {Posts: {find: stub()}}; 28 | Collections.Posts.find.returns({fetch: () => posts}); 29 | 30 | const context = () => ({Meteor, Collections}); 31 | const onData = spy(); 32 | 33 | composer({context}, onData); 34 | expect(onData.args[0]).to.deep.equal([ null, {posts} ]); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/client/modules/core/components/tests/newpost.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = global; 2 | import {expect} from 'chai'; 3 | import {shallow} from 'enzyme'; 4 | import NewPost from '../newpost.jsx'; 5 | 6 | describe('components.newpost', () => { 7 | it('should show the error if there are any', () => { 8 | const error = 'TheError'; 9 | const el = shallow(); 10 | expect(el.html()).to.match(/TheError/); 11 | }); 12 | 13 | it('should display the create post form', () => { 14 | const el = shallow(); 15 | const title = el.find('input').first(); 16 | const content = el.find('textarea').first(); 17 | const button = el.find('button').first(); 18 | 19 | expect(title.node.ref).to.be.equal('titleRef'); 20 | expect(content.node.ref).to.be.equal('contentRef'); 21 | expect(button.prop('onClick')).to.be.a('function'); 22 | }); 23 | 24 | it('should create a new post when click on the button', done => { 25 | const title = 'the-title'; 26 | const content = 'the-content'; 27 | 28 | const onCreate = (t, c) => { 29 | expect(t).to.be.equal(title); 30 | expect(c).to.be.equal(content); 31 | done(); 32 | }; 33 | 34 | const el = shallow(); 35 | const instance = el.instance(); 36 | 37 | instance.refs = { 38 | titleRef: {value: title}, 39 | contentRef: {value: content} 40 | }; 41 | 42 | el.find('button').simulate('click'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kickstart-mantrajs-webpack 2 | 3 | Kickstart a project with Meteor, Mantra, React and FlowRouter fast! 4 | 5 | 1. git clone https://github.com/mantrajs/kickstart-mantrajs-webpack.git 6 | 1. cd kickstart-mantrajs-webpack 7 | 1. npm install 8 | 1. meteor 9 | 10 | You can try [other kickstart projects](https://github.com/thereactivestack/kickstart) with ReactRouter, FlowRouter or Redux. 11 | 12 | # Hot Module Reload (HMR) 13 | 14 | Mantra prefers to use stateless (bare) react components. These components do not support HMR. If you want to use HRM, you need to use standard React components: 15 | 16 | ```javascript 17 | // NO support for HRM 18 | const Navigations = () => ( 19 |
20 | Navigations: 21 |
22 | ); 23 | 24 | // FULL Support for HRM 25 | class NewPost extends React.Component { 26 | render() { 27 | return
Text
; 28 | } 29 | } 30 | ``` 31 | 32 | # Production 33 | You can use meteor run, meteor build, mup or anything working with Meteor. 34 | 35 | ## Run in production mode 36 | `meteor run --production` 37 | 38 | ## Build for production 39 | `meteor build .` 40 | 41 | ## Deploy with Meteor-up 42 | `mup deploy` 43 | 44 | # Cordova 45 | You need to do those 3 steps to make it works with iOS or Android: 46 | 47 | 1. Add the platform to your Meteor project 48 | 49 | ```javascript 50 | meteor add-platform ios 51 | meteor add-platform android 52 | ``` 53 | 1. Allow access to your dev server in your `/mobile-config.js` file: 54 | 55 | ```javascript 56 | App.accessRule('http://192.168.1.100:3500/*'); 57 | ``` 58 | 59 | 1. Replace localhost by your local ip address in `webpack.json`. 60 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.7 2 | accounts-password@1.1.8 3 | allow-deny@1.0.4 4 | autoupdate@1.2.9 5 | babel-compiler@6.6.4 6 | babel-runtime@0.1.8 7 | base64@1.0.8 8 | binary-heap@1.0.8 9 | blaze@2.1.7 10 | blaze-tools@1.0.8 11 | boilerplate-generator@1.0.8 12 | callback-hook@1.0.8 13 | check@1.2.1 14 | ddp@1.2.5 15 | ddp-client@1.2.7 16 | ddp-common@1.2.5 17 | ddp-rate-limiter@1.0.4 18 | ddp-server@1.2.6 19 | deps@1.0.12 20 | diff-sequence@1.0.5 21 | ecmascript@0.4.3 22 | ecmascript-runtime@0.2.10 23 | ejson@1.0.11 24 | email@1.0.12 25 | es5-shim@4.5.10 26 | fastclick@1.0.11 27 | geojson-utils@1.0.8 28 | hot-code-push@1.0.4 29 | html-tools@1.0.9 30 | htmljs@1.0.9 31 | http@1.1.5 32 | id-map@1.0.7 33 | jquery@1.11.8 34 | kadira:flow-router@2.12.1 35 | launch-screen@1.0.11 36 | livedata@1.0.18 37 | localstorage@1.0.9 38 | logging@1.0.12 39 | meteor@1.1.14 40 | meteor-base@1.0.4 41 | minifier-css@1.1.11 42 | minifier-js@1.1.11 43 | minimongo@1.0.16 44 | mobile-experience@1.0.4 45 | mobile-status-bar@1.0.12 46 | modules@0.6.1 47 | modules-runtime@0.6.3 48 | mongo@1.1.7 49 | mongo-id@1.0.4 50 | npm-bcrypt@0.8.5 51 | npm-mongo@1.4.43 52 | observe-sequence@1.0.11 53 | ordered-dict@1.0.7 54 | promise@0.6.7 55 | random@1.0.9 56 | rate-limit@1.0.4 57 | react-meteor-data@0.2.9 58 | react-runtime@15.0.1 59 | reactive-dict@1.1.7 60 | reactive-var@1.0.9 61 | reload@1.1.8 62 | retry@1.0.7 63 | routepolicy@1.0.10 64 | service-configuration@1.0.9 65 | sha@1.0.7 66 | spacebars@1.0.11 67 | spacebars-compiler@1.0.11 68 | srp@1.0.8 69 | standard-minifier-css@1.0.6 70 | standard-minifier-js@1.0.6 71 | tmeasday:check-npm-versions@0.3.1 72 | tracker@1.0.13 73 | ui@1.0.11 74 | underscore@1.0.8 75 | url@1.0.9 76 | webapp@1.2.8 77 | webapp-hashing@1.0.9 78 | webpack:assets@1.0.1 79 | webpack:core-config@1.0.1 80 | webpack:css@1.1.1 81 | webpack:json@1.0.1 82 | webpack:npmworkaround@1.0.0 83 | webpack:react@1.2.2 84 | webpack:reload@1.0.1 85 | webpack:webpack@1.3.2 86 | -------------------------------------------------------------------------------- /src/client/modules/core/containers/tests/post.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = global; 2 | import {expect} from 'chai'; 3 | import {stub, spy} from 'sinon'; 4 | import {composer} from '../post'; 5 | 6 | describe('containers.post', () => { 7 | describe('composer', () => { 8 | const Tracker = {nonreactive: cb => cb()}; 9 | const getCollections = (post) => { 10 | const Collections = { 11 | Posts: {findOne: stub()} 12 | }; 13 | Collections.Posts.findOne.returns(post); 14 | return Collections; 15 | }; 16 | 17 | it('should subscribe to the given postId via prop', () => { 18 | const Meteor = {subscribe: stub()}; 19 | Meteor.subscribe.returns({ready: () => false}); 20 | const Collections = getCollections(); 21 | 22 | const context = () => ({Meteor, Tracker, Collections}); 23 | const postId = 'dwd'; 24 | const onData = spy(); 25 | 26 | composer({context, postId}, onData); 27 | const args = Meteor.subscribe.args[0]; 28 | expect(args.slice(0, 2)).to.deep.equal([ 29 | 'posts.single', postId 30 | ]); 31 | }); 32 | 33 | describe('while subscribing', () => { 34 | it('should call just onData()', () => { 35 | const Meteor = {subscribe: stub()}; 36 | const Collections = getCollections(); 37 | 38 | const context = () => ({Meteor, Tracker, Collections}); 39 | const postId = 'dwd'; 40 | const onData = spy(); 41 | 42 | composer({context, postId}, onData); 43 | expect(onData.args[0]).to.deep.equal([]); 44 | }); 45 | }); 46 | 47 | describe('after subscribed', () => { 48 | it('should find the post and send it to onData', () => { 49 | const Meteor = {subscribe: stub()}; 50 | 51 | const post = {_id: 'post'}; 52 | const Collections = getCollections(post); 53 | 54 | const context = () => ({Meteor, Collections, Tracker}); 55 | const postId = 'dwd'; 56 | const onData = spy(); 57 | 58 | composer({context, postId}, onData); 59 | expect(onData.args[0]).to.deep.equal([ null, {post} ]); 60 | }); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kickstart-mantrajs-webpack", 3 | "version": "1.0.0", 4 | "description": "Kickstart a project with Meteor, React and react-router fast!", 5 | "main": "src/server.js", 6 | "browser": "src/client.js", 7 | "scripts": { 8 | "lint": "eslint ./src/lib ./src/client ./src/server --ext .js --ext .jsx", 9 | "lintfix": "npm run lint -- --fix", 10 | "testonly": "mocha .scripts/mocha_boot.js src/client/**/tests/**/*.js --compilers js:babel-core/register", 11 | "test": "npm run lint && npm run testonly", 12 | "test-watch": "npm run testonly -- --watch --watch-extensions js,jsx" 13 | }, 14 | "dependencies": { 15 | "domready": "^1.0.8", 16 | "mantra-core": "^1.2.0", 17 | "react": "^15.x.x", 18 | "react-addons-create-fragment": "^15.x.x", 19 | "react-addons-css-transition-group": "^15.x.x", 20 | "react-addons-linked-state-mixin": "^15.x.x", 21 | "react-addons-perf": "^15.x.x", 22 | "react-addons-pure-render-mixin": "^15.x.x", 23 | "react-addons-transition-group": "^15.x.x", 24 | "react-addons-update": "^15.x.x", 25 | "react-dom": "^15.x.x", 26 | "react-mounter": "^1.2.0" 27 | }, 28 | "devDependencies": { 29 | "babel-core": "6.x.x", 30 | "babel-plugin-react-require": "2.x.x", 31 | "babel-polyfill": "6.x.x", 32 | "babel-loader": "^6.2.0", 33 | "babel-plugin-add-module-exports": "^0.1.2", 34 | "babel-plugin-react-transform": "^2.0.0", 35 | "babel-plugin-transform-decorators-legacy": "^1.3.2", 36 | "babel-preset-es2015": "^6.3.13", 37 | "babel-preset-react": "^6.3.13", 38 | "babel-preset-stage-0": "^6.3.13", 39 | "babel-root-slash-import": "1.x.x", 40 | "chai": "3.x.x", 41 | "css-loader": "^0.23.0", 42 | "enzyme": "^2.0.0", 43 | "eslint": "1.10.x", 44 | "eslint-plugin-react": "3.15.x", 45 | "extract-text-webpack-plugin": "^0.9.1", 46 | "file-loader": "^0.8.5", 47 | "jsdom": "^8.0.4", 48 | "jsx-control-statements": "^3.1.0", 49 | "mocha": "2.x.x", 50 | "react-addons-test-utils": "^15.x.x", 51 | "react-transform-catch-errors": "^1.0.0", 52 | "react-transform-hmr": "^1.0.1", 53 | "redbox-react": "^1.2.0", 54 | "sinon": "1.17.x", 55 | "style-loader": "^0.13.0", 56 | "url-loader": "^0.5.7", 57 | "webpack": "^1.13.0", 58 | "webpack-hot-middleware": "^2.4.1", 59 | "expose-loader": "^0.7.1", 60 | "babel": "^6.3.26", 61 | "json-loader": "^0.5.4" 62 | } 63 | } -------------------------------------------------------------------------------- /src/client/modules/core/actions/tests/posts.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = global; 2 | import {expect} from 'chai'; 3 | import {spy, stub} from 'sinon'; 4 | import actions from '../posts'; 5 | 6 | describe('actions.posts', () => { 7 | describe('create', () => { 8 | it('should reject if title is not there', () => { 9 | const LocalState = {set: spy()}; 10 | actions.create({LocalState}, null, 'content'); 11 | const args = LocalState.set.args[0]; 12 | 13 | expect(args[0]).to.be.equal('SAVING_ERROR'); 14 | expect(args[1]).to.match(/required/); 15 | }); 16 | 17 | it('should reject if content is not there', () => { 18 | const LocalState = {set: spy()}; 19 | actions.create({LocalState}, 'title', null); 20 | const args = LocalState.set.args[0]; 21 | 22 | expect(args[0]).to.be.equal('SAVING_ERROR'); 23 | expect(args[1]).to.match(/required/); 24 | }); 25 | 26 | it('should clear older LocalState for SAVING_ERROR', () => { 27 | const Meteor = {uuid: spy(), call: spy()}; 28 | const LocalState = {set: spy()}; 29 | const FlowRouter = {go: spy()}; 30 | 31 | actions.create({LocalState, Meteor, FlowRouter}, 't', 'c'); 32 | expect(LocalState.set.args[0]).to.deep.equal([ 'SAVING_ERROR', null ]); 33 | }); 34 | 35 | it('should call Meteor.call to save the post', () => { 36 | const Meteor = {uuid: () => 'id', call: spy()}; 37 | const LocalState = {set: spy()}; 38 | const FlowRouter = {go: spy()}; 39 | 40 | actions.create({LocalState, Meteor, FlowRouter}, 't', 'c'); 41 | const methodArgs = Meteor.call.args[0]; 42 | 43 | expect(methodArgs.slice(0, 4)).to.deep.equal([ 44 | 'posts.create', 'id', 't', 'c' 45 | ]); 46 | expect(methodArgs[4]).to.be.a('function'); 47 | }); 48 | 49 | it('should redirect user to the post', () => { 50 | const id = 'dsds'; 51 | const Meteor = {uuid: () => id, call: stub()}; 52 | const LocalState = {set: spy()}; 53 | const FlowRouter = {go: spy()}; 54 | Meteor.call.callsArg(4); 55 | 56 | actions.create({Meteor, LocalState, FlowRouter}, 't', 'c'); 57 | expect(FlowRouter.go.args[0][0]).to.be.equal(`/post/${id}`); 58 | }); 59 | 60 | describe('after Meteor.call', () => { 61 | describe('if there is error', () => { 62 | it('should set SAVING_ERROR with the error message', () => { 63 | const Meteor = {uuid: () => 'id', call: stub()}; 64 | const LocalState = {set: spy()}; 65 | const FlowRouter = {go: spy()}; 66 | const err = {message: 'Oops'}; 67 | Meteor.call.callsArgWith(4, err); 68 | 69 | actions.create({Meteor, LocalState, FlowRouter}, 't', 'c'); 70 | expect(LocalState.set.args[1]).to.deep.equal([ 'SAVING_ERROR', err.message ]); 71 | }); 72 | }); 73 | }); 74 | }); 75 | 76 | describe('clearErrors', () => { 77 | it('should clear SAVING_ERROR local state', () => { 78 | const LocalState = {set: spy()}; 79 | actions.clearErrors({LocalState}); 80 | expect(LocalState.set.callCount).to.be.equal(1); 81 | expect(LocalState.set.args[0]).to.deep.equal([ 'SAVING_ERROR', null ]); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "browser": true, 5 | "node": true 6 | }, 7 | 8 | "plugins": [ 9 | "react" 10 | ], 11 | 12 | "ecmaFeatures": { 13 | "arrowFunctions": true, 14 | "binaryLiterals": true, 15 | "blockBindings": true, 16 | "classes": true, 17 | "defaultParams": true, 18 | "destructuring": true, 19 | "experimentalObjectRestSpread": true, 20 | "forOf": true, 21 | "generators": true, 22 | "globalReturn": true, 23 | "jsx": true, 24 | "modules": true, 25 | "objectLiteralComputedProperties": true, 26 | "objectLiteralDuplicateProperties": true, 27 | "objectLiteralShorthandMethods": true, 28 | "objectLiteralShorthandProperties": true, 29 | "octalLiterals": true, 30 | "regexUFlag": true, 31 | "regexYFlag": true, 32 | "restParams": true, 33 | "spread": true, 34 | "superInFunctions": true, 35 | "templateStrings": true, 36 | "unicodeCodePointEscapes": true 37 | }, 38 | 39 | "rules": { 40 | "array-bracket-spacing": [2, "always"], 41 | "arrow-spacing": 2, 42 | "block-scoped-var": 0, 43 | "brace-style": [2, "1tbs", {"allowSingleLine": true}], 44 | "callback-return": 2, 45 | "camelcase": [2, {"properties": "always"}], 46 | "comma-dangle": 0, 47 | "comma-spacing": 0, 48 | "comma-style": [2, "last"], 49 | "complexity": 0, 50 | "computed-property-spacing": [2, "never"], 51 | "consistent-return": 0, 52 | "consistent-this": 0, 53 | "curly": [2, "all"], 54 | "default-case": 0, 55 | "dot-location": [2, "property"], 56 | "dot-notation": 0, 57 | "eol-last": 2, 58 | "eqeqeq": 2, 59 | "func-names": 0, 60 | "func-style": 0, 61 | "generator-star-spacing": [0, {"before": true, "after": false}], 62 | "guard-for-in": 2, 63 | "handle-callback-err": [2, "error"], 64 | "id-length": 0, 65 | "id-match": [2, "^(?:_?[a-zA-Z0-9]*)|[_A-Z0-9]+$"], 66 | "indent": [2, 2, {"SwitchCase": 1}], 67 | "init-declarations": 0, 68 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}], 69 | "linebreak-style": 2, 70 | "lines-around-comment": 0, 71 | "max-depth": 0, 72 | "max-len": [2, 100, 4], 73 | "max-nested-callbacks": 0, 74 | "max-params": 0, 75 | "max-statements": 0, 76 | "new-cap": 0, 77 | "new-parens": 2, 78 | "newline-after-var": 0, 79 | "no-array-constructor": 2, 80 | "no-bitwise": 0, 81 | "no-caller": 2, 82 | "no-catch-shadow": 0, 83 | "no-class-assign": 2, 84 | "no-cond-assign": 2, 85 | "no-console": 1, 86 | "no-const-assign": 2, 87 | "no-constant-condition": 2, 88 | "no-continue": 0, 89 | "no-control-regex": 0, 90 | "no-debugger": 1, 91 | "no-delete-var": 2, 92 | "no-div-regex": 2, 93 | "no-dupe-args": 2, 94 | "no-dupe-keys": 2, 95 | "no-duplicate-case": 2, 96 | "no-else-return": 2, 97 | "no-empty": 2, 98 | "no-empty-character-class": 2, 99 | "no-empty-label": 2, 100 | "no-eq-null": 0, 101 | "no-eval": 2, 102 | "no-ex-assign": 2, 103 | "no-extend-native": 2, 104 | "no-extra-bind": 2, 105 | "no-extra-boolean-cast": 2, 106 | "no-extra-parens": 0, 107 | "no-extra-semi": 2, 108 | "no-fallthrough": 2, 109 | "no-floating-decimal": 2, 110 | "no-func-assign": 2, 111 | "no-implicit-coercion": 2, 112 | "no-implied-eval": 2, 113 | "no-inline-comments": 0, 114 | "no-inner-declarations": [2, "functions"], 115 | "no-invalid-regexp": 2, 116 | "no-invalid-this": 0, 117 | "no-irregular-whitespace": 2, 118 | "no-iterator": 2, 119 | "no-label-var": 2, 120 | "no-labels": 0, 121 | "no-lone-blocks": 2, 122 | "no-lonely-if": 2, 123 | "no-loop-func": 0, 124 | "no-mixed-requires": [2, true], 125 | "no-mixed-spaces-and-tabs": 2, 126 | "no-multi-spaces": 2, 127 | "no-multi-str": 2, 128 | "no-multiple-empty-lines": 0, 129 | "no-native-reassign": 0, 130 | "no-negated-in-lhs": 2, 131 | "no-nested-ternary": 0, 132 | "no-new": 2, 133 | "no-new-func": 0, 134 | "no-new-object": 2, 135 | "no-new-require": 2, 136 | "no-new-wrappers": 2, 137 | "no-obj-calls": 2, 138 | "no-octal": 2, 139 | "no-octal-escape": 2, 140 | "no-param-reassign": 2, 141 | "no-path-concat": 2, 142 | "no-plusplus": 0, 143 | "no-process-env": 0, 144 | "no-process-exit": 0, 145 | "no-proto": 2, 146 | "no-redeclare": 2, 147 | "no-regex-spaces": 2, 148 | "no-restricted-modules": 0, 149 | "no-return-assign": 2, 150 | "no-script-url": 2, 151 | "no-self-compare": 0, 152 | "no-sequences": 2, 153 | "no-shadow": 2, 154 | "no-shadow-restricted-names": 2, 155 | "no-spaced-func": 2, 156 | "no-sparse-arrays": 2, 157 | "no-sync": 2, 158 | "no-ternary": 0, 159 | "no-this-before-super": 2, 160 | "no-throw-literal": 2, 161 | "no-trailing-spaces": 2, 162 | "no-undef": 2, 163 | "no-undef-init": 2, 164 | "no-undefined": 0, 165 | "no-underscore-dangle": 0, 166 | "no-unexpected-multiline": 2, 167 | "no-unneeded-ternary": 2, 168 | "no-unreachable": 2, 169 | "no-unused-expressions": 2, 170 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 171 | "no-use-before-define": 0, 172 | "no-useless-call": 2, 173 | "no-var": 0, 174 | "no-void": 2, 175 | "no-warning-comments": 0, 176 | "no-with": 2, 177 | "object-curly-spacing": [0, "always"], 178 | "object-shorthand": [2, "always"], 179 | "one-var": [2, "never"], 180 | "operator-assignment": [2, "always"], 181 | "operator-linebreak": [2, "after"], 182 | "padded-blocks": 0, 183 | "prefer-const": 0, 184 | "prefer-reflect": 0, 185 | "prefer-spread": 0, 186 | "quote-props": [2, "as-needed"], 187 | "quotes": [2, "single"], 188 | "radix": 2, 189 | "require-yield": 2, 190 | "semi": [2, "always"], 191 | "semi-spacing": [2, {"before": false, "after": true}], 192 | "sort-vars": 0, 193 | "space-after-keywords": [2, "always"], 194 | "space-before-blocks": [2, "always"], 195 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], 196 | "space-in-parens": 0, 197 | "space-infix-ops": [2, {"int32Hint": false}], 198 | "space-return-throw-case": 2, 199 | "space-unary-ops": [2, {"words": true, "nonwords": false}], 200 | "spaced-comment": [2, "always"], 201 | "strict": 0, 202 | "use-isnan": 2, 203 | "valid-jsdoc": 0, 204 | "valid-typeof": 2, 205 | "vars-on-top": 0, 206 | "wrap-iife": 2, 207 | "wrap-regex": 0, 208 | "yoda": [2, "never", {"exceptRange": true}], 209 | "react/jsx-uses-react": 1 210 | } 211 | } 212 | --------------------------------------------------------------------------------