├── docs └── .gitkeep ├── __tests__ ├── .gitkeep └── index.spec.js ├── src ├── fonts │ └── .gitkeep ├── images │ └── .gitkeep ├── javascripts │ ├── .gitkeep │ ├── utils │ │ ├── index.js │ │ ├── createStore.js │ │ └── validation.js │ ├── modules │ │ ├── Home │ │ │ ├── actions │ │ │ │ └── index.js │ │ │ ├── constants │ │ │ │ └── index.js │ │ │ ├── reducers │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.jsx │ │ ├── About │ │ │ ├── constants │ │ │ │ └── index.js │ │ │ ├── actions │ │ │ │ └── index.js │ │ │ ├── reducers │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.jsx │ │ ├── Layout │ │ │ ├── reducers │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.jsx │ │ └── index.js │ ├── routes.js │ └── index.jsx ├── stylesheets │ └── .gitkeep └── index.html ├── .eslintignore ├── .npmignore ├── .gitignore ├── .babelrc ├── configs ├── webpack │ ├── index.js │ ├── build.config.js │ ├── production.config.js │ ├── development.config.js │ └── common.config.js ├── karma │ ├── karma.entry.js │ ├── development.config.js │ ├── production.config.js │ └── index.js ├── environments │ ├── dependencies.js │ └── index.js └── webpack-dev-server │ └── index.js ├── .travis.yml ├── .eslintrc ├── .editorconfig ├── gulpfile.js ├── CONTRIBUTING.md ├── README.md └── package.json /docs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /__tests__/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/fonts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/javascripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | /build 3 | /node_modules 4 | -------------------------------------------------------------------------------- /src/javascripts/utils/index.js: -------------------------------------------------------------------------------- 1 | export const createStore = require('./createStore'); 2 | -------------------------------------------------------------------------------- /src/javascripts/modules/Home/actions/index.js: -------------------------------------------------------------------------------- 1 | import {createAction} from 'redux-actions'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /configs/karma/build 2 | 3 | /node_modules 4 | /build 5 | /index.html 6 | /lib 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "optional": ["runtime"], 4 | "plugins": ["object-assign"] 5 | } -------------------------------------------------------------------------------- /configs/webpack/index.js: -------------------------------------------------------------------------------- 1 | module.exports = exports = [ 2 | require('./build.config')(), 3 | ]; 4 | -------------------------------------------------------------------------------- /__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | describe('Karma', function () { 2 | it('start', function () { 3 | expect(true).toBeTruthy(); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /src/javascripts/modules/About/constants/index.js: -------------------------------------------------------------------------------- 1 | import keyMirror from 'keymirror'; 2 | 3 | module.exports = keyMirror({ 4 | ABOUT_DEV_MODE: null, 5 | }); 6 | -------------------------------------------------------------------------------- /configs/karma/karma.entry.js: -------------------------------------------------------------------------------- 1 | const context = require.context('../../__tests__', true, /\.spec\.js$/); 2 | context.keys().forEach(context); 3 | module.exports = context; 4 | -------------------------------------------------------------------------------- /src/javascripts/modules/Home/constants/index.js: -------------------------------------------------------------------------------- 1 | import keyMirror from 'keymirror'; 2 | 3 | export default { 4 | ActionTypes: keyMirror({ 5 | HOME_WELCOME_TO: null, 6 | }), 7 | }; 8 | -------------------------------------------------------------------------------- /configs/karma/development.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function karmaDevelopmentConfigModifier(config) { 2 | config.singleRun = false; 3 | config.autoWatch = true; 4 | return config; 5 | }; 6 | -------------------------------------------------------------------------------- /src/javascripts/modules/About/actions/index.js: -------------------------------------------------------------------------------- 1 | import {createAction} from 'redux-actions'; 2 | import {ABOUT_DEV_MODE} from '../constants/index'; 3 | 4 | export const enableDeveloperMode = createAction(ABOUT_DEV_MODE); 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '4.1.1' 5 | cache: 6 | directories: 7 | - node_modules 8 | branches: 9 | only: 10 | - master 11 | env: 12 | global: 13 | - NODE_ENV=production 14 | -------------------------------------------------------------------------------- /src/javascripts/modules/Home/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {handleActions} from 'redux-actions'; 2 | import Immutable from 'immutable'; 3 | 4 | const initialState = Immutable.fromJS({}); 5 | 6 | export default handleActions({}, initialState); 7 | -------------------------------------------------------------------------------- /src/javascripts/modules/Layout/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {handleActions} from 'redux-actions'; 2 | import Immutable from 'immutable'; 3 | 4 | const initialState = Immutable.fromJS({}); 5 | 6 | export default handleActions({}, initialState); 7 | -------------------------------------------------------------------------------- /configs/webpack/build.config.js: -------------------------------------------------------------------------------- 1 | const env = require('../environments'); 2 | const config = require('./common.config')(); 3 | 4 | module.exports = function makeClientConfig(type) { 5 | return require('./' + (type || env.NODE_ENV) + '.config')(config); 6 | }; 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "jasmine": true, 7 | "es6": true 8 | }, 9 | "globals": { 10 | "__DEV__": true, 11 | "__PROD__": true, 12 | "__DEBUG__": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /configs/karma/production.config.js: -------------------------------------------------------------------------------- 1 | const DIR_DIST = require('../environments').DIR_DIST; 2 | 3 | module.exports = function karmaProductionConfigModifier(config) { 4 | config.singleRun = true; 5 | config.reporters = ['spec', 'coverage']; 6 | config.coverageReporter = { 7 | type: 'html', 8 | dir: DIR_DIST + '/coverage/', 9 | }; 10 | 11 | return config; 12 | }; 13 | -------------------------------------------------------------------------------- /src/javascripts/routes.js: -------------------------------------------------------------------------------- 1 | import * as modules from './modules/index'; 2 | export default () => { 3 | const {Views: {Layout, Home}, Routes: {About}} = modules; 4 | return { 5 | component: 'div', 6 | childRoutes: [{ 7 | path: '/', 8 | component: Layout, 9 | indexRoute: {component: Home}, 10 | childRoutes: [ 11 | About, 12 | ], 13 | }], 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React China new Forum Frontend 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/javascripts/modules/Layout/index.js: -------------------------------------------------------------------------------- 1 | import {connect} from 'react-redux'; 2 | import {bindActionCreators} from 'redux'; 3 | 4 | function mapStateToProps() { 5 | return {}; 6 | } 7 | 8 | function mapDispatchToProps(dispatch) { 9 | return bindActionCreators({}, dispatch); 10 | } 11 | 12 | export const reducers = require('./reducers/index'); 13 | export const view = connect(mapStateToProps, mapDispatchToProps)(require('./components/index')); 14 | -------------------------------------------------------------------------------- /src/javascripts/modules/Layout/components/index.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | export default class Layout extends Component { 3 | render() { 4 | return ( 5 |
6 |
7 | {this.props.children} 8 |
9 |
10 | ); 11 | } 12 | } 13 | 14 | Layout.propTypes = { 15 | children: PropTypes.element, 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /src/javascripts/modules/About/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {handleActions} from 'redux-actions'; 2 | import Immutable from 'immutable'; 3 | 4 | import {ABOUT_DEV_MODE} from '../constants/index'; 5 | 6 | const initialState = Immutable.fromJS({ 7 | count: 0, 8 | hint: 'You are now a developer!', 9 | }); 10 | 11 | export default handleActions({ 12 | [ABOUT_DEV_MODE]: state => state.update('count', count => count < 5 ? count + 1 : count), 13 | }, initialState); 14 | -------------------------------------------------------------------------------- /src/javascripts/modules/Home/index.js: -------------------------------------------------------------------------------- 1 | import {connect} from 'react-redux'; 2 | import {bindActionCreators} from 'redux'; 3 | 4 | import {pushState} from 'redux-router'; 5 | 6 | function mapStateToProps() { 7 | return {}; 8 | } 9 | 10 | function mapDispatchToProps(dispatch) { 11 | return bindActionCreators({pushState}, dispatch); 12 | } 13 | 14 | export const reducers = require('./reducers/index'); 15 | export const view = connect(mapStateToProps, mapDispatchToProps)(require('./components/index')); 16 | -------------------------------------------------------------------------------- /src/javascripts/modules/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | 3 | import * as layout from './Layout/index'; 4 | import * as home from './Home/index'; 5 | import * as about from './About/index'; 6 | 7 | export default { 8 | Views: { 9 | Layout: layout.view, 10 | Home: home.view, 11 | }, 12 | Routes: { 13 | About: about.route, 14 | }, 15 | Reducers: combineReducers({ 16 | layout: layout.reducers, 17 | home: home.reducers, 18 | about: about.reducers, 19 | }), 20 | }; 21 | -------------------------------------------------------------------------------- /configs/environments/dependencies.js: -------------------------------------------------------------------------------- 1 | module.exports = exports = { 2 | vendors: [ 3 | 'immutable', 4 | 'react', 5 | 'react-redux', 6 | 'react-router', 7 | 'redux', 8 | 'redux-devtools', 9 | 'redux-devtools/lib/react', 10 | ], 11 | aliases: [ 12 | 'actions', 13 | 'apis', 14 | 'components', 15 | 'constants', 16 | 'containers', 17 | 'entries', 18 | 'dispatchers', 19 | 'layouts', 20 | 'models', 21 | 'reducers', 22 | 'routes', 23 | 'services', 24 | 'stores', 25 | 'styles', 26 | 'utils', 27 | 'views', 28 | ], 29 | }; 30 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_style = space 11 | insert_final_newline = true 12 | max_line_length = 120 13 | trim_trailing_whitespace = true 14 | 15 | [*.js] 16 | indent_size = 2 17 | 18 | [*.json] 19 | indent_size = 2 20 | 21 | [package.json] 22 | indent_size = 2 23 | 24 | [*.md] 25 | max_line_length = 0 26 | trim_trailing_whitespace = false 27 | 28 | [COMMIT_EDITMSG] 29 | max_line_length = 0 30 | -------------------------------------------------------------------------------- /src/javascripts/modules/Home/components/index.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | export default class Home extends Component { 3 | _handlePushState(event) { 4 | event.preventDefault(); 5 | 6 | const {pushState} = this.props; 7 | pushState(null, '/about'); 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 |

Welcome to React-China

14 | 15 |
16 | ); 17 | } 18 | } 19 | 20 | Home.propTypes = { 21 | pushState: PropTypes.func.isRequired, 22 | }; 23 | -------------------------------------------------------------------------------- /src/javascripts/modules/About/index.js: -------------------------------------------------------------------------------- 1 | import {connect} from 'react-redux'; 2 | import {bindActionCreators} from 'redux'; 3 | 4 | import {enableDeveloperMode} from './actions/index'; 5 | 6 | function mapStateToProps(state) { 7 | return {store: state.store.about}; 8 | } 9 | 10 | function mapDispatchToProps(dispatch) { 11 | return bindActionCreators({enableDeveloperMode}, dispatch); 12 | } 13 | 14 | export const reducers = require('./reducers/index'); 15 | export const route = { 16 | path: 'about', 17 | getComponent(location, callback) { 18 | require.ensure([], (require) => { 19 | callback(null, connect(mapStateToProps, mapDispatchToProps)(require('./components/index'))); 20 | }); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/javascripts/modules/About/components/index.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | export default class About extends Component { 3 | render() { 4 | const {store, enableDeveloperMode} = this.props; 5 | const {count, hint} = store.toJS(); 6 | 7 | const divHint = count > 4 ?
{hint}
: null; 8 | return ( 9 |
10 |

About

11 |
click 5 times, and become a react developer!
12 | {divHint} 13 |
14 | ); 15 | } 16 | } 17 | 18 | About.propTypes = { 19 | store: PropTypes.object.isRequired, 20 | enableDeveloperMode: PropTypes.func.isRequired, 21 | }; 22 | -------------------------------------------------------------------------------- /configs/webpack-dev-server/index.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const argv = require('yargs').argv; 3 | const env = require('../environments'); 4 | const WebpackDevServer = require('webpack-dev-server'); 5 | const makeCompiler = require('../webpack/build.config'); 6 | 7 | const QUIET_MODE = !!argv.quiet; 8 | 9 | const server = new WebpackDevServer(webpack(makeCompiler()), { 10 | contentBase: env.inProject(env.DIR_SRC), 11 | hot: true, 12 | quiet: QUIET_MODE, 13 | noInfo: QUIET_MODE, 14 | lazy: false, 15 | stats: {colors: true}, 16 | historyApiFallback: true, 17 | }); 18 | 19 | server.listen(env.WEBPACK_PORT, 'localhost', () => { 20 | console.log('Webpack dev server running at localhost:' + env.WEBPACK_PORT); 21 | }); 22 | 23 | module.exports = exports = server; 24 | -------------------------------------------------------------------------------- /configs/webpack/production.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | module.exports = function makeClientProductionConfig(config) { 5 | config.plugins.push( 6 | new ExtractTextPlugin('[name].[contenthash].css'), 7 | new webpack.optimize.UglifyJsPlugin({ 8 | sourceMap: false, 9 | output: { 10 | 'comments': false, 11 | }, 12 | compress: { 13 | 'unused': true, 14 | 'dead_code': true, 15 | }, 16 | }) 17 | ); 18 | 19 | config.module.loaders = config.module.loaders.map((loader) => { 20 | // Extract CSS to a file 21 | if (/css/.test(loader.test)) { 22 | loader.loader = ExtractTextPlugin.extract( 23 | loader.loaders[0], loader.loaders.slice(1).join('!') 24 | ); 25 | delete loader.loaders; 26 | } 27 | 28 | return loader; 29 | }); 30 | 31 | return config; 32 | }; 33 | -------------------------------------------------------------------------------- /src/javascripts/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import {Provider} from 'react-redux'; 5 | import {ReduxRouter} from 'redux-router'; 6 | 7 | import {createStore} from './utils/index'; 8 | import createRoutes from './routes'; 9 | 10 | import * as modules from './modules/index'; 11 | 12 | const store = createStore(modules.Reducers, {}); 13 | const routes = createRoutes(store); 14 | 15 | let component = ( 16 | 17 | 18 | 19 | ); 20 | 21 | if (__DEBUG__) { 22 | const {DevTools, LogMonitor, DebugPanel} = require('redux-devtools/lib/react'); 23 | component = ( 24 |
25 | {component} 26 | 27 | 28 | 29 |
30 | ); 31 | } 32 | 33 | const target = document.getElementById('mount'); 34 | ReactDOM.render(component, target); 35 | -------------------------------------------------------------------------------- /configs/karma/index.js: -------------------------------------------------------------------------------- 1 | const env = require('../environments'); 2 | const KARMA_ENTRY_FILE = 'karma.entry.js'; 3 | 4 | function makeDefaultConfig() { 5 | const preprocessors = {}; 6 | 7 | preprocessors[KARMA_ENTRY_FILE] = ['webpack']; 8 | preprocessors[env.DIR_SRC + '/**/*.js'] = ['webpack']; 9 | 10 | return { 11 | files: [ 12 | '../../node_modules/phantomjs-polyfill/bind-polyfill.js', 13 | KARMA_ENTRY_FILE, 14 | ], 15 | frameworks: ['jasmine'], 16 | preprocessors: preprocessors, 17 | reporters: ['dots'], 18 | browsers: ['PhantomJS'], 19 | webpack: (() => require('../webpack/build.config')())(), 20 | webpackMiddleware: { 21 | noInfo: true, 22 | }, 23 | plugins: [ 24 | require('karma-webpack'), 25 | require('karma-jasmine'), 26 | require('karma-coverage'), 27 | require('karma-phantomjs-launcher'), 28 | require('karma-spec-reporter'), 29 | ], 30 | }; 31 | } 32 | 33 | module.exports = (karmaConfig) => { 34 | return karmaConfig.set( 35 | require('./' + env.NODE_ENV + '.config')(makeDefaultConfig()) 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/javascripts/utils/createStore.js: -------------------------------------------------------------------------------- 1 | import {compose, applyMiddleware, combineReducers, createStore as _createStore} from 'redux'; 2 | 3 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 4 | 5 | import thunk from 'redux-thunk'; 6 | import promise from 'redux-promise'; 7 | import createLogger from 'redux-logger'; 8 | 9 | import {reducer as form} from 'redux-form'; 10 | import {reduxReactRouter, routerStateReducer as router} from 'redux-router'; 11 | 12 | const logger = createLogger(); 13 | const middleware = [thunk, promise]; 14 | 15 | if (__DEV__) { 16 | middleware.push(logger); 17 | } 18 | const createStoreWithMiddleware = applyMiddleware(...middleware); 19 | 20 | const composes = [createStoreWithMiddleware]; 21 | if (__DEBUG__) { 22 | const {devTools, persistState} = require('redux-devtools'); 23 | composes.push(devTools(), persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))); 24 | } 25 | 26 | let buildStore = compose(...composes)(_createStore); 27 | buildStore = reduxReactRouter({createHistory: createBrowserHistory})(buildStore); 28 | 29 | export default function createStore(reducers, initialState = {}) { 30 | return buildStore(combineReducers({store: reducers, form, router}), initialState); 31 | } 32 | -------------------------------------------------------------------------------- /configs/webpack/development.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | module.exports = function makeClientDevelopmentConfig(config) { 4 | config.debug = true; 5 | config.displayErrorDetails = true; 6 | config.outputPathinfo = true; 7 | config.devtool = 'eval-source-map'; 8 | config.entry.app.push( 9 | 'webpack-dev-server/client?http://localhost:3000', 10 | 'webpack/hot/dev-server' 11 | ); 12 | 13 | config.plugins.push( 14 | new webpack.HotModuleReplacementPlugin(), 15 | new webpack.NoErrorsPlugin() 16 | ); 17 | 18 | // We need to apply the react-transform HMR plugin to the Babel configuration, 19 | // but _only_ when HMR is enabled. Putting this in the default development 20 | // configuration will break other tasks such as test:unit because Webpack 21 | // HMR is not enabled there, and these transforms require it. 22 | config.module.loaders = config.module.loaders.map((loader) => { 23 | if (/js/.test(loader.test)) { 24 | // loader.loaders.unshift('react-hot'); 25 | loader.query.env.development.extra['react-transform'].transforms.push({ 26 | transform: 'react-transform-hmr', 27 | imports: ['react'], 28 | locals: ['module'], 29 | }); 30 | } 31 | return loader; 32 | }); 33 | 34 | return config; 35 | }; 36 | -------------------------------------------------------------------------------- /configs/environments/index.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = (process.env.NODE_ENV || 'development').trim(); 2 | 3 | const vendors = require('./dependencies').vendors; 4 | const aliases = require('./dependencies').aliases; 5 | const resolve = require('path').resolve; 6 | const argv = require('yargs').argv; 7 | const _slice = [].slice; 8 | 9 | const DIR_SRC = 'src'; 10 | const DIR_DIST = 'build'; 11 | const DIR_CONFIG = 'configs'; 12 | const PROJECT_PATH = resolve(__dirname, '../../'); 13 | 14 | function inProject() { 15 | return resolve.apply(resolve, [PROJECT_PATH].concat(_slice.apply(arguments))); 16 | } 17 | 18 | // ------------------------------------ 19 | // Configuration Definition 20 | // ------------------------------------ 21 | module.exports = exports = { 22 | 23 | // environment 24 | NODE_ENV: process.env.NODE_ENV, 25 | __PROD__: process.env.NODE_ENV === 'production', 26 | __DEV__: process.env.NODE_ENV === 'development', 27 | __DEBUG__: process.env.NODE_ENV === 'development' && !!argv.debug, 28 | 29 | // path helpers 30 | DIR_SRC: DIR_SRC, 31 | DIR_DIST: DIR_DIST, 32 | DIR_CONFIG: DIR_CONFIG, 33 | PROJECT_PATH: PROJECT_PATH, 34 | inProject: inProject, 35 | inSrc: inProject.bind(undefined, DIR_SRC), 36 | inDist: inProject.bind(undefined, DIR_DIST), 37 | 38 | // build system 39 | VENDOR_DEPENDENCIES: vendors, 40 | ALIAS: aliases, 41 | 42 | // server configuration 43 | WEBPACK_PORT: 3000, 44 | //SERVER_PORT: process.env.PORT || 4000 45 | }; 46 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const eslint = require('gulp-eslint'); 3 | const webpack = require('webpack'); 4 | const del = require('del'); 5 | const gutil = require('gulp-util'); 6 | const WebpackDevServer = require('webpack-dev-server'); 7 | 8 | gulp.task('clean', function (callback) { 9 | del(['build'], callback); 10 | }); 11 | 12 | // Note: To have the process exit with an error code (1) on 13 | // lint error, return the stream and pipe to failOnError last. 14 | gulp.task('lint', function gulpLint() { 15 | return gulp.src(['src/**/*.jsx', 'src/**/*.js']) 16 | .pipe(eslint()) 17 | .pipe(eslint.format()) 18 | .pipe(eslint.failOnError()); 19 | }); 20 | 21 | gulp.task('dev', ['clean', 'webpack:dev'], function () { 22 | gulp.watch(['src/**/*'], ['webpack:build-dev']); 23 | }); 24 | 25 | gulp.task('build', ['clean', 'webpack:build']); 26 | 27 | gulp.task('webpack:dev', function (callback) { 28 | var webpackConfig = require('./configs/webpack/development.config'); 29 | webpack(Object.create(webpackConfig)).run(function (err, stats) { 30 | if (err) throw new gutil.PluginError('webpack:dev', err); 31 | gutil.log('[webpack:dev]', stats.toString({ 32 | colors: true 33 | })); 34 | callback(); 35 | }); 36 | }); 37 | 38 | gulp.task('webpack:build', function (callback) { 39 | var webpackConfig = Object.create(require('./configs/webpack/production.config')); 40 | webpack(webpackConfig, function (err, stats) { 41 | if (err) throw new gutil.PluginError('webpack:build', err); 42 | gutil.log('[webpack:build]', stats.toString({ 43 | colors: true 44 | })); 45 | callback(); 46 | }); 47 | }); 48 | 49 | gulp.task('default', ['lint'], function gulpDefault() { 50 | // This will only run if the lint task is successful... 51 | }); 52 | -------------------------------------------------------------------------------- /src/javascripts/utils/validation.js: -------------------------------------------------------------------------------- 1 | const isEmpty = value => value === undefined || value === null || value === ''; 2 | const join = (rules) => value => rules.map(rule => rule(value)).filter(error => !!error)[0 /* first error */]; 3 | 4 | export function email(value) { 5 | // Let's not start a debate on email regex. This is just for an example app! 6 | if (!isEmpty(value) && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) { 7 | return 'Invalid email address'; 8 | } 9 | } 10 | 11 | export function required(value) { 12 | if (isEmpty(value)) { 13 | return 'Required'; 14 | } 15 | } 16 | 17 | export function minLength(min) { 18 | return value => { 19 | if (!isEmpty(value) && value.length < min) { 20 | return `Must be at least ${min} characters`; 21 | } 22 | }; 23 | } 24 | 25 | export function maxLength(max) { 26 | return value => { 27 | if (!isEmpty(value) && value.length > max) { 28 | return `Must be no more than ${max} characters`; 29 | } 30 | }; 31 | } 32 | 33 | export function integer(value) { 34 | if (!Number.isInteger(Number(value))) { 35 | return 'Must be an integer'; 36 | } 37 | } 38 | 39 | export function oneOf(enumeration) { 40 | return value => { 41 | if (!~enumeration.indexOf(value)) { 42 | return `Must be one of: ${enumeration.join(', ')}`; 43 | } 44 | }; 45 | } 46 | 47 | export function createValidator(rules) { 48 | return (data = {}) => { 49 | const errors = {valid: true}; 50 | Object.keys(rules).forEach((key) => { 51 | const rule = join([].concat(rules[key])); // concat enables both functions and arrays of functions 52 | const error = rule(data[key]); 53 | if (error) { 54 | errors[key] = error; 55 | errors.valid = false; 56 | } 57 | }); 58 | return errors; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | #Contributing 2 | 3 | So, you want to contribute to this project! That's awesome. However, before 4 | doing so, please read the following simple steps how to contribute. This will 5 | make the life easier and will avoid wasting time on things which are not 6 | requested. 7 | 8 | ## Discuss the changes before doing them 9 | - First of all, open an issue in the repository, using the [bug tracker][1], 10 | describing the contribution you'd like to make, the bug you found or any 11 | other ideas you have. This will help us to get you started on the right 12 | foot. 13 | 14 | - If it makes sense, add the platform and software information (e.g. operating 15 | system, Node.JS version etc), screenshots (so we can see what you're seeing). 16 | 17 | - It's recommended to wait for feedback before continuing to next steps. However, 18 | if the issue is clear (e.g. a typo) and the fix is simple, you can continue 19 | and fix it. 20 | 21 | ## Fixing issues 22 | - Fork the project in your account and create a branch with your fix: 23 | `some-great-feature` or `some-issue-fix`. 24 | 25 | - Commit your changes in that branch, writing the code following the 26 | [code style][2] from [Airbnb][3]. If the project contains tests (generally, the `test` 27 | directory), you are encouraged to add a test as well. 28 | 29 | - If the project contains a `package.json` file add yourself in the 30 | `contributors` array (if it doesn't exist, create it): 31 | 32 | ```json 33 | { 34 | "contributors": [ 35 | "Your Name (http://your.website) 36 | ] 37 | } 38 | ``` 39 | 40 | ## Creating a pull request 41 | 42 | - Open a pull request, and reference the initial issue in the pull request 43 | message (e.g. *fixes # { 24 | module[name] = env.inSrc('javascripts/' + name); 25 | return module; 26 | }, {}), 27 | }, 28 | module: { 29 | preLoaders: [ 30 | {test: /\.(js|jsx)$/, loaders: ['eslint-loader'], include: env.inProject(env.DIR_SRC, 'javascripts')}, 31 | ], 32 | loaders: [ 33 | { 34 | test: /\.(js|jsx)$/, 35 | include: [env.inProject(env.DIR_SRC), env.inProject(env.DIR_CONFIG)], 36 | loader: 'babel', 37 | query: { 38 | stage: 0, 39 | optional: ['runtime'], 40 | env: { 41 | development: { 42 | plugins: ['react-transform'], 43 | extra: { 44 | 'react-transform': { 45 | transforms: [{ 46 | transform: 'react-transform-catch-errors', 47 | imports: ['react', 'redbox-react'], 48 | }], 49 | }, 50 | }, 51 | }, 52 | }, 53 | }, 54 | }, 55 | { 56 | test: /\.scss$/, 57 | loaders: [ 58 | 'style-loader', 59 | 'css-loader', 60 | 'autoprefixer?browsers=last 2 version', 61 | 'sass-loader?includePaths[]=' + env.inSrc('stylesheets'), 62 | ], 63 | }, 64 | ], 65 | }, 66 | eslint: {configFile: env.inProject('.eslintrc'), failOnError: env.__PROD__, emitWarning: env.__DEV__}, 67 | plugins: [ 68 | new webpack.DefinePlugin({ 69 | 'process.env': { 70 | 'NODE_ENV': JSON.stringify(env.NODE_ENV), 71 | }, 72 | '__DEBUG__': env.__DEBUG__, 73 | '__DEV__': env.__DEV__, 74 | '__PROD__': env.__PROD__, 75 | }), 76 | new webpack.optimize.OccurrenceOrderPlugin(), 77 | new webpack.optimize.DedupePlugin(), 78 | new HtmlWebpackPlugin({ 79 | template: env.inSrc('index.html'), 80 | hash: true, 81 | filename: 'index.html', 82 | minify: env.__PROD__, 83 | inject: 'body', 84 | }), 85 | ], 86 | }; 87 | } 88 | 89 | module.exports = function makeConfig(configModifier) { 90 | return assign({}, makeDefaultConfig(), configModifier); 91 | }; 92 | 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unmaintained 2 | Sorry everyone, but we are no longer maintaining this project. 3 | 4 | # React China new Forum Frontend 5 | 6 | [![Build Status][travis-ci-image]][travis-ci-url] 7 | 8 | [![Gitter chat][gitter-image]][gitter-url] 9 | [![GitHub license][license-image]][license-url] 10 | 11 | [![dependencies][dependencies-image]][dependencies-url] 12 | [![devDependency][dev-deps-image]][dev-deps-url] 13 | [![peerDependency][peer-deps-image]][peer-deps-url] 14 | 15 | ---- 16 | 17 | > React 中文社区 React.js 版前端界面 18 | 19 | ## 状态 20 | 21 | > 正在初始化项目 22 | 23 | ## 计划 24 | 25 | * 完成 JSX 基本开发框架 26 | * 梳理 Discourse API 27 | * 制定 Actions, Store, Router 规范 28 | * 完成顶层组件 29 | * 组件细化 30 | 31 | ## Develop 32 | 33 | ```text 34 | npm i 35 | ``` 36 | 37 | You need a static file server for the HTML files. Personally I suggest using Nginx. 38 | 39 | Develop: 40 | 41 | ```bash 42 | gulp html # regenerate index.html 43 | webpack-dev-server --hot # enable live-reloading 44 | ``` 45 | 46 | Build (Pack and optimize js, reivision js and add entry in `index.html`): 47 | 48 | ```bash 49 | gulp build 50 | ``` 51 | 52 | ## Usage 53 | 54 | #### `npm run clean` 55 | Remove folder `./build` 56 | 57 | #### `npm run dev` 58 | Runs the webpack build system just like in `compile` but enables HMR and react hot-loader. The webpack dev server can be found at `localhost:3000`. 59 | 60 | #### `npm run dev:debug` 61 | Same as `npm run dev` but enables `--debug` flag automatically. 62 | 63 | #### `npm run dev:quiet` 64 | Same as `npm run dev` but disables verbose debugging information. 65 | 66 | #### `npm run compile` 67 | Runs the Webpack build system with your current NODE_ENV and compiles the application to disk (`~/build`). 68 | 69 | #### `npm run test` 70 | Runs all tests for the application. 71 | 72 | #### `npm run test:unit` 73 | Similar to `npm run test`, but only runs unit tests. In development mode this will run in watch mode and re-run individual test files when they change. 74 | 75 | #### `npm run test:e2e` 76 | *TODO* 77 | 78 | ## Contribution 79 | 80 | - If you want to discuss something or just need help, here is our [gitter.im room](https://gitter.im/react-china/forum-frontend). 81 | - Have an idea? Found a bug? See [how to contribute][contributing-url]. 82 | 83 | ## License 84 | 85 | MIT (http://www.opensource.org/licenses/mit-license.php) 86 | 87 | [contributing-url]: /CONTRIBUTING.md 88 | 89 | [gitter-url]: https://gitter.im/react-china/forum-frontend 90 | [gitter-image]: https://badges.gitter.im/Join%20Chat.svg 91 | 92 | [license-image]: https://img.shields.io/github/license/mashape/apistatus.svg 93 | [license-url]: http://www.opensource.org/licenses/mit-license.php 94 | 95 | [travis-ci-image]: https://travis-ci.org/react-china/forum-frontend.svg 96 | [travis-ci-url]: https://travis-ci.org/react-china/forum-frontend 97 | 98 | [dependencies-image]: https://david-dm.org/react-china/forum-frontend.svg 99 | [dependencies-url]: https://david-dm.org/react-china/forum-frontend 100 | 101 | [dev-deps-image]: https://david-dm.org/react-china/forum-frontend/dev-status.svg 102 | [dev-deps-url]: https://david-dm.org/react-china/forum-frontend#info=devDependencies 103 | 104 | [peer-deps-image]: https://david-dm.org/react-china/forum-frontend/peer-status.svg 105 | [peer-deps-url]: https://david-dm.org/react-china/forum-frontend#info=peerDependencies 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forum-frontend", 3 | "version": "0.0.1", 4 | "description": "React implemented frontend for react-china.org", 5 | "engines": { 6 | "node": "4.1.2", 7 | "npm": "3.3.5" 8 | }, 9 | "keywords": [ 10 | "react-china" 11 | ], 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/react-china/forum-frontend.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/react-china/forum-frontend/issues" 19 | }, 20 | "homepage": "https://github.com/react-china/forum-frontend", 21 | "scripts": { 22 | "clean": "rm -rf build", 23 | "compile": "webpack --config ./configs/webpack", 24 | "start": "npm run dev", 25 | "dev": "node --harmony ./configs/webpack-dev-server", 26 | "dev:debug": "npm run dev -- --debug", 27 | "dev:quiet": "npm run dev -- --quiet", 28 | "test": "npm run test:unit", 29 | "test:unit": "karma start ./configs/karma/index.js" 30 | }, 31 | "dependencies": { 32 | "babel-core": "^5.8.25", 33 | "babel-loader": "^5.3.2", 34 | "babel-plugin-object-assign": "^1.2.1", 35 | "babel-runtime": "^5.8.25", 36 | "classnames": "^2.2.0", 37 | "expose-loader": "^0.7.1", 38 | "extract-text-webpack-plugin": "^0.9.1", 39 | "history": "^1.13.0", 40 | "html-webpack-plugin": "^1.6.2", 41 | "immutable": "^3.7.5", 42 | "jasmine": "^2.3.2", 43 | "jasmine-core": "^2.3.4", 44 | "jquery": "^2.1.4", 45 | "karma": "^0.13.15", 46 | "karma-coverage": "^0.5.3", 47 | "karma-jasmine": "^0.3.6", 48 | "karma-phantomjs-launcher": "^0.2.1", 49 | "karma-spec-reporter": "0.0.22", 50 | "karma-webpack": "^1.7.0", 51 | "keymirror": "^0.1.1", 52 | "lodash": "^3.10.1", 53 | "normalizr": "^1.4.0", 54 | "object-assign": "^4.0.1", 55 | "phantomjs": "^1.9.18", 56 | "phantomjs-polyfill": "0.0.1", 57 | "react": "^0.14.2", 58 | "react-document-meta": "^2.0.0", 59 | "react-dom": "^0.14.2", 60 | "react-mixin": "^3.0.3", 61 | "react-redux": "^4.0.0", 62 | "react-router": "^1.0.0", 63 | "redux": "^3.0.4", 64 | "redux-actions": "^0.8.0", 65 | "redux-form": "^3.0.6", 66 | "redux-logger": "^2.0.4", 67 | "redux-promise": "^0.5.0", 68 | "redux-router": "^1.0.0-beta4", 69 | "redux-thunk": "^1.0.0", 70 | "webpack": "^1.12.4", 71 | "yargs": "^3.29.0" 72 | }, 73 | "devDependencies": { 74 | "autoprefixer": "^6.1.0", 75 | "autoprefixer-loader": "^3.1.0", 76 | "babel-eslint": "^4.1.5", 77 | "babel-plugin-react-transform": "^1.1.1", 78 | "cirru-script": "^0.5.0-alpha1", 79 | "css-loader": "^0.22.0", 80 | "del": "^2.0.2", 81 | "eslint": "^1.9.0", 82 | "eslint-config-airbnb": "1.0.0", 83 | "eslint-loader": "^1.1.1", 84 | "eslint-plugin-react": "^3.8.0", 85 | "file-loader": "^0.8.4", 86 | "gulp": "^3.9.0", 87 | "gulp-eslint": "^1.1.0", 88 | "gulp-util": "^3.0.7", 89 | "imports-loader": "^0.6.5", 90 | "loader-utils": "^0.2.11", 91 | "node-libs-browser": "^0.5.3", 92 | "node-sass": "^3.4.2", 93 | "react-hot-loader": "^2.0.0-alpha-2", 94 | "react-transform-catch-errors": "^1.0.0", 95 | "react-transform-hmr": "^1.0.1", 96 | "redbox-react": "^1.1.1", 97 | "redux-devtools": "github:tomatau/redux-devtools#e126f799bb81a61e4a2ce69fbc501e09b15b5f5d", 98 | "rsyncwrapper": "^0.4.3", 99 | "run-sequence": "^1.1.4", 100 | "sass-loader": "^3.1.1", 101 | "stir-template": "^0.1.4", 102 | "style-loader": "^0.13.0", 103 | "url-loader": "^0.5.6", 104 | "volubile-ui": "0.0.11", 105 | "webpack-dev-server": "^1.12.1", 106 | "webpack-stream": "^2.1.1" 107 | } 108 | } 109 | --------------------------------------------------------------------------------