├── .babelrc ├── .editorconfig ├── .eslintrc ├── .github └── FUNDING.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs └── flowchart.png ├── mock-server └── api │ └── newsByPage.json ├── package.json ├── scripts ├── build.js ├── build │ └── index.js ├── config.js ├── dev.js ├── dev │ └── index.js ├── gt.js ├── gt │ └── index.js └── webpack.config.js ├── src ├── api │ └── news.js ├── components │ ├── DemoButton.js │ ├── Logo.js │ └── Toast.js ├── config │ ├── actionTypes.js │ ├── cookie.js │ ├── entries.js │ ├── environments.js │ ├── errors.js │ ├── requestMethod.js │ ├── routes.js │ ├── storage.js │ └── toast.js ├── containers │ ├── Demo │ │ └── PianistDemo.js │ ├── Index │ │ └── Home.js │ └── common │ │ ├── Base.js │ │ ├── Base.less │ │ ├── NoMatch.js │ │ └── Toast.js ├── entries │ ├── about.js │ └── index.js ├── errors │ ├── FetchError.js │ ├── ServerError.js │ ├── TimeoutError.js │ └── index.js ├── html │ └── index.html ├── i18n │ ├── index.js │ └── zh-cn.js ├── images │ └── vivaxy.20150726.jpg ├── lib │ ├── checkLogin.js │ ├── environment.js │ ├── fetch.js │ ├── getURLParameter.js │ ├── logout.js │ ├── processErrors.js │ ├── render.js │ ├── requestPath.js │ ├── setTitle.js │ ├── sleep.js │ ├── store.js │ ├── style.js │ └── style.less ├── pages │ ├── about.js │ └── index.js ├── redux │ ├── button.js │ ├── newsList.js │ ├── pagination.js │ ├── reducers.js │ ├── routing.js │ └── toast.js └── routes │ ├── about.js │ └── index.js ├── test └── get-base-dirname.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "latest", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "react", 10 | "stage-0" 11 | ], 12 | "plugins": [ 13 | "transform-runtime" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | curly_bracket_next_line = false 11 | spaces_around_operators = true 12 | indent_brace_style = 1tbs 13 | 14 | [*.js] 15 | quote_type = single 16 | 17 | [*.{html,less,css,json}] 18 | quote_type = double 19 | 20 | [package.json] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "vivaxy" 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: vivaxy 2 | open_collective: vivaxy_personal 3 | custom: ['https://gist.github.com/vivaxy/58eed1803a2eddda05c90aed99430de2'] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | node_modules 4 | npm-debug.log 5 | release 6 | yarn-error.log 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | # [1.8.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.7.0...v1.8.0) (2017-05-04) 7 | 8 | 9 | ### Features 10 | 11 | * **dependencies:** :arrow_up:Move react react-dom redux redux-thunk into dependencies ([20b4304](https://github.com/vivaxy/gt-react-scaffold/commit/20b4304)) 12 | * **report:** :sparkles:Replace `webpack-visualizer-plugin` with `webpack-bundle-analyzer` ([8057b69](https://github.com/vivaxy/gt-react-scaffold/commit/8057b69)) 13 | 14 | 15 | 16 | 17 | # [1.7.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.6.1...v1.7.0) (2017-05-03) 18 | 19 | 20 | ### Features 21 | 22 | * **babel:** :sparkles:Using babel-runtime instead of babel-polyfill, reduce the release files' size ([f2b729a](https://github.com/vivaxy/gt-react-scaffold/commit/f2b729a)) 23 | 24 | 25 | 26 | 27 | ## [1.6.1](https://github.com/vivaxy/gt-react-scaffold/compare/v1.6.0...v1.6.1) (2017-05-03) 28 | 29 | 30 | 31 | 32 | # [1.6.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.5.2...v1.6.0) (2017-04-18) 33 | 34 | 35 | ### Features 36 | 37 | * **dep:** :alien:Upgrade react to 15.5, and react-dom to 15.5 ([230e3e1](https://github.com/vivaxy/gt-react-scaffold/commit/230e3e1)) 38 | * **eslint:** :sparkles:Add eslint-loader, Upgrade react-pianist and react-router because of prop-ty ([7d695c3](https://github.com/vivaxy/gt-react-scaffold/commit/7d695c3)) 39 | * **openBrowser:** :sparkles:Optimize open browser ([cb55563](https://github.com/vivaxy/gt-react-scaffold/commit/cb55563)) 40 | 41 | 42 | 43 | 44 | ## [1.5.2](https://github.com/vivaxy/gt-react-scaffold/compare/v1.5.1...v1.5.2) (2017-04-10) 45 | 46 | 47 | 48 | 49 | ## [1.5.1](https://github.com/vivaxy/gt-react-scaffold/compare/v1.5.0...v1.5.1) (2017-04-01) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * **gt:** :bug:Fix gt after message; Add yarn install in gt; Update webpack-dev-server to 2.x in pack ([8849562](https://github.com/vivaxy/gt-react-scaffold/commit/8849562)) 55 | * **gt:** :bug:Fix gt.js ([e84665d](https://github.com/vivaxy/gt-react-scaffold/commit/e84665d)) 56 | 57 | 58 | 59 | 60 | # [1.5.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.4.0...v1.5.0) (2017-03-04) 61 | 62 | 63 | ### Features 64 | 65 | * **test:** :sparkles:Add ava test ([a5bf82f](https://github.com/vivaxy/gt-react-scaffold/commit/a5bf82f)) 66 | 67 | 68 | 69 | 70 | # [1.4.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.3.0...v1.4.0) (2017-03-04) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * :bug:Fix yarn.lock resolved urls; Add missing dependency: redux; ([229ac48](https://github.com/vivaxy/gt-react-scaffold/commit/229ac48)) 76 | * **gt:** :bug:Fix copy `.npmrc` ([baf9a1a](https://github.com/vivaxy/gt-react-scaffold/commit/baf9a1a)) 77 | * **gt:** :bug:Fix template, render file project name ([9b72633](https://github.com/vivaxy/gt-react-scaffold/commit/9b72633)) 78 | * **gt.js:** :bug:Fix gt copy file ([5c24c19](https://github.com/vivaxy/gt-react-scaffold/commit/5c24c19)) 79 | * **gt.js:** :bug:Update gt.js ([565f05d](https://github.com/vivaxy/gt-react-scaffold/commit/565f05d)) 80 | * **scripts:** :bug:Fix release script ([04ece5d](https://github.com/vivaxy/gt-react-scaffold/commit/04ece5d)) 81 | 82 | 83 | ### Features 84 | 85 | * **babel:** :sparkles:Update babel-preset-es2015 babel-preset-latest ([5545282](https://github.com/vivaxy/gt-react-scaffold/commit/5545282)) 86 | * **eslint:** :sparkles:Add eslint, rules based on airbnb/javascript ([2b976e2](https://github.com/vivaxy/gt-react-scaffold/commit/2b976e2)) 87 | * **npmrc:** :art:Add .npmrc to maintain npm registry ([5dac42b](https://github.com/vivaxy/gt-react-scaffold/commit/5dac42b)) 88 | 89 | 90 | 91 | 92 | # [1.3.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.2.0...v1.3.0) (2017-01-26) 93 | 94 | 95 | ### Bug Fixes 96 | 97 | * **gt.js:** :bug:Fix gt info in README.md ([00d772b](https://github.com/vivaxy/gt-react-scaffold/commit/00d772b)) 98 | 99 | 100 | ### Features 101 | 102 | * **postcss:** :sparkles:Add postcss ([df5d83a](https://github.com/vivaxy/gt-react-scaffold/commit/df5d83a)) 103 | * **rename:** :sparkles:Rename `react-scaffold` to `gt-react-scaffold` ([5107d7b](https://github.com/vivaxy/gt-react-scaffold/commit/5107d7b)) 104 | * **webpack:** :sparkles:Use `scripts/dev.js` to start webpack dev server, for more controllable. No ([f6b7103](https://github.com/vivaxy/gt-react-scaffold/commit/f6b7103)) 105 | 106 | 107 | 108 | 109 | # [1.2.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.1.0...v1.2.0) (2017-01-09) 110 | 111 | 112 | ### Bug Fixes 113 | 114 | * **gt:** :bug:fix source folder name ([23590ca](https://github.com/vivaxy/gt-react-scaffold/commit/23590ca)) 115 | * **yarn:** :arrow_up: ([e314496](https://github.com/vivaxy/gt-react-scaffold/commit/e314496)) 116 | 117 | 118 | ### Features 119 | 120 | * **fetch:** :art:remove fetch cache ([490b408](https://github.com/vivaxy/gt-react-scaffold/commit/490b408)) 121 | * **fetch:** :sparkles:add timeout for fetch ([8a0cddb](https://github.com/vivaxy/gt-react-scaffold/commit/8a0cddb)) 122 | * :sparkles:improve page transition style ([3552fc6](https://github.com/vivaxy/gt-react-scaffold/commit/3552fc6)) 123 | * **gt:** :sparkles:add sleep in gt.js ([e15b49c](https://github.com/vivaxy/gt-react-scaffold/commit/e15b49c)) 124 | * **page transition:** :sparkles: ([0643b9e](https://github.com/vivaxy/gt-react-scaffold/commit/0643b9e)) 125 | 126 | 127 | 128 | 129 | # [1.1.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.0.2...v1.1.0) (2016-11-27) 130 | 131 | 132 | ### Bug Fixes 133 | 134 | * **fetch:** :bug:fix cached response cannot more than once ([7883557](https://github.com/vivaxy/gt-react-scaffold/commit/7883557)) 135 | * **webpack:** :bug:only open browser when dev server ([9236361](https://github.com/vivaxy/gt-react-scaffold/commit/9236361)) 136 | 137 | 138 | ### Features 139 | 140 | * **fetch:** :art:support CORS ([1ce3a82](https://github.com/vivaxy/gt-react-scaffold/commit/1ce3a82)) 141 | * **webpack:** :sparkles:added OccurrenceOrderPlugin ([e003181](https://github.com/vivaxy/gt-react-scaffold/commit/e003181)) 142 | * **webpack:** :sparkles:after npm start, open the browser ([57b43c9](https://github.com/vivaxy/gt-react-scaffold/commit/57b43c9)) 143 | 144 | 145 | 146 | 147 | ## 1.0.2 (2016-11-24) 148 | 149 | 150 | ### Bug Fixes 151 | 152 | * **fetch:** :bug:fix cached fetch ([61817e2](https://github.com/vivaxy/gt-react-scaffold/commit/61817e2)) 153 | 154 | 155 | 156 | ## 1.0.1 157 | 158 | * optimize `contianers` 159 | 160 | ## 1.0.0 161 | 162 | * first release 163 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Feel free to submit any issue. 2 | 3 | Steps to make contributions: 4 | 5 | - Update codes. 6 | - Submit your changes by [`gacp`](https://github.com/vivaxy/gacp). 7 | - Run `yarn run release` to update `CHANGELOG.md` and bump version. 8 | - Run [`gacp`](https://github.com/vivaxy/gacp) to push changes into origin. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 vivaxy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Initializing 2 | 3 | - Install nodejs. 4 | - Install [gt](https://github.com/vivaxy/granturismo). 5 | 6 | `yarn global add granturismo` 7 | 8 | - Add scaffold to gt `gt config add vivaxy/gt-react-scaffold https://github.com/vivaxy/gt-react-scaffold.git`. 9 | - Create your project directory `mkdir my-project-name && cd my-project-name` or `git clone ...`. 10 | - Run `gt init`. 11 | - Select `vivaxy/gt-react-scaffold`. 12 | 13 | ## Contributing 14 | 15 | [Contributing](CONTRIBUTING.md) 16 | 17 | ---------- 18 | 19 | # gt-react-scaffold 20 | 21 | ## Feature 22 | 23 | - react 24 | - redux 25 | - webpack 26 | - eslint 27 | - page navigation animation 28 | - react router 29 | - react redux 30 | - redux thunk 31 | - react hot reload 32 | - webpack development server 33 | - multiple entries 34 | - mock server 35 | - unified error center 36 | - internationalization 37 | - environments 38 | - webpack visualizer 39 | - postcss 40 | - autoprefixer 41 | - less 42 | - test cases 43 | 44 | ## Concept 45 | 46 | ![flowchart](./docs/flowchart.png) 47 | 48 | As we find out html are mostly same in react projects, we extract the same html into a template file which lays in `html` folder. 49 | 50 | `html-webpack-plugin` is used to generate released html files, which load corresponding js files. 51 | 52 | `entries` stores js main methods, we split main container to `containers` because entry could not be hot-module-replaced. 53 | 54 | `entries`s import `containers`s, which is one to one correspondent. `render` method in entry provides all commonly used setups, such as redux store, provider, injectTapEventPlugin for material-ui, and custom styles. 55 | 56 | `containers` is the headquarters which imports all dummy components, passing through props to them. Also, `containers` interacts with redux `reducers` and `actions`. 57 | 58 | It is recommended that you wrap containers into another. `redux` will re-render a container when and only when mapped reducers have been updated. 59 | 60 | We do not use async redux for ajax requests because ajax status is not that important for us to record. So, we invoke `api` in `containers`, get response, and determine what `actions` to call. 61 | 62 | We store every string in `i18n` for better internationalization. 63 | 64 | Built project files will be in the `release` folder. With `build.log`, you can see every build details. With `stats.html`, you can optimize your dependencies to make project smaller. 65 | 66 | ## Developing 67 | 68 | - `yarn run dev` 69 | 70 | ## Building 71 | 72 | - `yarn run build` 73 | 74 | ## Reference 75 | 76 | - https://github.com/webpack/docs/wiki/webpack-dev-server 77 | - https://github.com/gaearon/react-hot-loader/tree/master/docs 78 | - https://github.com/reactjs/react-router-redux 79 | - https://github.com/ampedandwired/html-webpack-plugin 80 | - https://github.com/ReactTraining/react-router 81 | -------------------------------------------------------------------------------- /docs/flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivaxy/gt-react-scaffold/086082ebcb41401639706c23cae929e07ef046ae/docs/flowchart.png -------------------------------------------------------------------------------- /mock-server/api/newsByPage.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "message": "some error from server", 4 | "data": { 5 | "list": [ 6 | { 7 | "id": 1, 8 | "name": "this is a piece of news" 9 | } 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gt-react-scaffold", 3 | "version": "1.8.0", 4 | "description": "react gt scaffold", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "test": "ava", 8 | "lint": "eslint ./src", 9 | "release": "standard-version && git push --follow-tag", 10 | "dev": "NODE_ENV=development node ./scripts/dev.js", 11 | "beta": "NODE_ENV=beta npm run build", 12 | "production": "NODE_ENV=production npm run build", 13 | "build": "npm run build:clean && npm run build:webpack", 14 | "build:clean": "rm -rf ./release && mkdir ./release", 15 | "build:webpack": "node ./scripts/build.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/vivaxy/gt-react-scaffold.git" 20 | }, 21 | "keywords": [ 22 | "react", 23 | "gt-react-scaffold", 24 | "scaffold", 25 | "gt" 26 | ], 27 | "author": "vivaxy", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/vivaxy/gt-react-scaffold/issues" 31 | }, 32 | "homepage": "https://github.com/vivaxy/gt-react-scaffold#readme", 33 | "devDependencies": { 34 | "autoprefixer": "^6.7.0", 35 | "ava": "^0.18.2", 36 | "babel-core": "^6.22.1", 37 | "babel-loader": "^7.0.0", 38 | "babel-preset-latest": "^6.22.0", 39 | "babel-preset-react": "^6.11.1", 40 | "babel-preset-stage-0": "^6.22.0", 41 | "babel-register": "^6.24.0", 42 | "css-loader": "^0.24.0", 43 | "eslint": "^3.18.0", 44 | "eslint-config-vivaxy": "^0.1.1", 45 | "eslint-loader": "^1.7.1", 46 | "execa": "^0.6.0", 47 | "file-loader": "^0.9.0", 48 | "glob": "^7.1.1", 49 | "html-webpack-plugin": "^2.28.0", 50 | "ip": "^1.1.4", 51 | "isomorphic-fetch": "^2.2.1", 52 | "less": "^2.7.1", 53 | "less-loader": "^2.2.3", 54 | "listr": "^0.8.0", 55 | "log-update": "^1.0.2", 56 | "numeral": "^2.0.4", 57 | "postcss-loader": "^1.2.2", 58 | "prop-types": "^15.5.7", 59 | "react-addons-css-transition-group": "^15.3.2", 60 | "react-dev-utils": "^0.5.2", 61 | "react-hot-loader": "next", 62 | "react-pianist": "^0.0.12", 63 | "react-redux": "^5.0.4", 64 | "react-router": "3", 65 | "react-router-redux": "^4.0.6", 66 | "redux-devtools": "^3.3.1", 67 | "standard-version": "^3.0.0", 68 | "style-loader": "^0.13.1", 69 | "tiny-cookie": "^0.5.5", 70 | "url-loader": "^0.5.7", 71 | "webpack": "^2.2.1", 72 | "webpack-bundle-analyzer": "^2.4.0", 73 | "webpack-dev-server": "^2.4.1" 74 | }, 75 | "browserslist": [ 76 | "> 1%", 77 | "ie >= 9" 78 | ], 79 | "ava": { 80 | "files": [ 81 | "test/*.js" 82 | ], 83 | "source": [ 84 | "src/**/*.js" 85 | ], 86 | "require": [ 87 | "babel-register" 88 | ], 89 | "babel": "inherit", 90 | "verbose": true 91 | }, 92 | "dependencies": { 93 | "babel-plugin-transform-runtime": "^6.23.0", 94 | "babel-polyfill": "^6.23.0", 95 | "babel-runtime": "^6.23.0", 96 | "react": "^15.5.4", 97 | "react-dom": "^15.5.4", 98 | "redux": "^3.6.0", 99 | "redux-create-reducer": "^1.1.1", 100 | "redux-thunk": "^2.2.0" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-04-01 16:33:06 3 | * @author vivaxy 4 | */ 5 | 6 | require('babel-polyfill'); 7 | require('babel-register'); 8 | require('./build/index'); 9 | -------------------------------------------------------------------------------- /scripts/build/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-04-01 16:33:18 3 | * @author vivaxy 4 | */ 5 | 6 | /* eslint-disable no-console */ 7 | import webpack from 'webpack'; 8 | import webpackConfig from '../webpack.config'; 9 | 10 | const compiler = webpack(webpackConfig); 11 | 12 | compiler.run((err) => { 13 | if (err) { 14 | console.error(err); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /scripts/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-01-31 17:19 3 | * @author vivaxy 4 | */ 5 | 6 | const ip = require('ip'); 7 | 8 | exports.DEVELOPMENT_IP = ip.address(); 9 | exports.DEVELOPMENT_PORT = Math.floor(Math.random() * 65536); 10 | exports.RELEASE_PATH = 'release'; 11 | exports.MOCK_SERVER_BASE = 'mock-server'; 12 | -------------------------------------------------------------------------------- /scripts/dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-01-12 17:35 3 | * @author vivaxy 4 | */ 5 | 6 | require('babel-polyfill'); 7 | require('babel-register'); 8 | require('./dev/index'); 9 | -------------------------------------------------------------------------------- /scripts/dev/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-04-01 16:30:03 3 | * @author vivaxy 4 | */ 5 | 6 | /* eslint-disable no-console */ 7 | import open from 'react-dev-utils/openBrowser'; 8 | import webpack from 'webpack'; 9 | import WebpackDevServer from 'webpack-dev-server'; 10 | 11 | import webpackConfig from '../webpack.config'; 12 | import config from '../config'; 13 | 14 | const compiler = webpack(webpackConfig); 15 | 16 | let opened = false; 17 | 18 | const devServerOptions = { 19 | contentBase: [ 20 | config.RELEASE_PATH, 21 | config.MOCK_SERVER_BASE, 22 | ], 23 | hot: true, 24 | historyApiFallback: true, 25 | stats: { 26 | colors: true, 27 | }, 28 | }; 29 | 30 | const server = new WebpackDevServer(compiler, devServerOptions); 31 | 32 | const openBrowser = () => { 33 | const address = server.listeningApp.address(); 34 | const url = `http://${address.address}:${address.port}`; 35 | console.log(` server started: ${url}`); 36 | open(`${url}/html/index.html`); 37 | }; 38 | 39 | compiler.plugin('done', () => { 40 | if (!opened) { 41 | opened = true; 42 | openBrowser(); 43 | } 44 | }); 45 | 46 | server.listen(config.DEVELOPMENT_PORT, config.DEVELOPMENT_IP, (err) => { 47 | if (err) { 48 | console.log(err); 49 | } 50 | }); 51 | 52 | const stdIn = process.stdin; 53 | stdIn.setEncoding('utf8'); 54 | stdIn.on('data', openBrowser); 55 | -------------------------------------------------------------------------------- /scripts/gt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-16 15:37 3 | * @author vivaxy 4 | */ 5 | 6 | if (!global._babelPolyfill) { // eslint-disable-line no-underscore-dangle 7 | require('babel-polyfill'); // eslint-disable-line global-require 8 | } 9 | require('babel-register'); 10 | module.exports = require('./gt/index'); 11 | -------------------------------------------------------------------------------- /scripts/gt/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-03-21 15:55:59 3 | * @author vivaxy 4 | */ 5 | 6 | import Listr from 'listr'; 7 | import execa from 'execa'; 8 | 9 | const sleep = (timeout) => { 10 | return new Promise((resolve) => { 11 | setTimeout(resolve, timeout); 12 | }); 13 | }; 14 | 15 | const copyFiles = (options) => { 16 | const { presets } = options; 17 | const files = [ 18 | 'docs', 19 | 'mock-server', 20 | 'scripts', 21 | 'src', 22 | '.babelrc', 23 | '.editorconfig', 24 | '.eslintrc', 25 | '.gitignore', 26 | '.npmrc', 27 | 'LICENSE', 28 | ]; 29 | return async() => { 30 | await sleep(500); 31 | await presets.copyFiles(files); 32 | }; 33 | }; 34 | 35 | const updatePackageJSON = (options) => { 36 | const { project, presets } = options; 37 | const projectGit = project.git || {}; 38 | const filename = 'package.json'; 39 | 40 | return async() => { 41 | await sleep(500); 42 | await presets.updateJson(filename, (data) => { 43 | const { 44 | version, 45 | description, 46 | main, 47 | scripts, 48 | repository, 49 | keywords, 50 | author, 51 | license, 52 | bugs, 53 | dependencies, 54 | devDependencies, 55 | peerDependencies, 56 | } = data; 57 | 58 | return { 59 | name: project.name, 60 | version: '0.0.0', 61 | gtScaffoldVersion: version, 62 | description, 63 | main, 64 | scripts, 65 | repository: { 66 | ...repository, 67 | url: projectGit.repositoryURL, 68 | }, 69 | keywords, 70 | author: projectGit.username, 71 | license, 72 | bugs: { 73 | ...bugs, 74 | url: undefined, 75 | }, 76 | dependencies, 77 | devDependencies, 78 | peerDependencies, 79 | }; 80 | }); 81 | }; 82 | }; 83 | 84 | const updateREADME = (options) => { 85 | const { project, presets } = options; 86 | const filename = 'README.md'; 87 | 88 | return async() => { 89 | await sleep(500); 90 | await presets.updateFile(filename, (content) => { 91 | const projectData = content.split('----------\n\n')[1]; 92 | return projectData.replace(/gt-react-scaffold/g, `${project.name} 93 | 94 | Initialized by [vivaxy/gt-react-scaffold](https://github.com/vivaxy/gt-react-scaffold)`); 95 | }); 96 | }; 97 | }; 98 | 99 | const updateTemplate = (options) => { 100 | const { project, presets } = options; 101 | const filename = 'src/html/index.html'; 102 | 103 | return async() => { 104 | await sleep(1000); 105 | await presets.updateFile(filename, (content) => { 106 | return content.replace(/gt-react-scaffold/g, project.name); 107 | }); 108 | }; 109 | }; 110 | 111 | const updateRender = (options) => { 112 | const { project, presets } = options; 113 | const filename = 'src/lib/render.js'; 114 | 115 | return async() => { 116 | await sleep(1000); 117 | await presets.updateFile(filename, (content) => { 118 | return content.replace(/gt-react-scaffold/g, project.name); 119 | }); 120 | }; 121 | }; 122 | 123 | const yarnInstall = () => { 124 | return async() => { 125 | await execa('yarn', ['install']); 126 | }; 127 | }; 128 | 129 | export const init = (options) => { 130 | return new Listr([ 131 | { 132 | title: 'copy files', 133 | task: copyFiles(options), 134 | }, 135 | { 136 | title: 'update template', 137 | task: updateTemplate(options), 138 | }, 139 | { 140 | title: 'update render', 141 | task: updateRender(options), 142 | }, 143 | { 144 | title: 'update package.json', 145 | task: updatePackageJSON(options), 146 | }, 147 | { 148 | title: 'update README.md', 149 | task: updateREADME(options), 150 | }, 151 | { 152 | title: 'run yarn install', 153 | task: yarnInstall(options), 154 | }, 155 | ]); 156 | }; 157 | 158 | /* eslint-disable no-console */ 159 | export const after = () => { 160 | console.log(` 161 | please run following command to start dev server 162 | 163 | - yarn run dev 164 | `); 165 | }; 166 | -------------------------------------------------------------------------------- /scripts/webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-05-29 10:50 3 | * @author vivaxy 4 | */ 5 | 6 | const path = require('path'); 7 | const glob = require('glob'); 8 | const webpack = require('webpack'); 9 | const numeral = require('numeral'); 10 | const logUpdate = require('log-update'); 11 | const autoprefixer = require('autoprefixer'); 12 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 13 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 14 | 15 | const config = require('./config'); 16 | 17 | const DEVELOPMENT_IP = config.DEVELOPMENT_IP; 18 | const DEVELOPMENT_PORT = config.DEVELOPMENT_PORT; 19 | const RELEASE_PATH = config.RELEASE_PATH; 20 | const SOURCE_PATH = 'src'; 21 | const DEVELOPMENT = 'development'; 22 | const PRODUCTION = 'production'; 23 | const NODE_MODULES = 'node_modules'; 24 | const ENTRIES_FOLDER = 'entries'; 25 | const HTML_FOLDER = 'html'; 26 | const COMMON_CHUNK_NAME = 'common'; 27 | 28 | const BANNER = '@2016 vivaxy'; 29 | const baseDir = path.join(__dirname, '..'); 30 | 31 | const NODE_ENV = process.env.NODE_ENV || PRODUCTION; 32 | 33 | const postcssLoader = { 34 | loader: 'postcss-loader', 35 | options: { 36 | plugins: () => { 37 | return [ 38 | autoprefixer, 39 | ]; 40 | }, 41 | }, 42 | }; 43 | 44 | const eslintRule = { 45 | enforce: 'pre', 46 | test: /\.js$/, 47 | include: [ 48 | path.resolve(baseDir, SOURCE_PATH), 49 | ], 50 | loader: 'eslint-loader', 51 | }; 52 | 53 | const jsRule = { 54 | test: /\.js$/, 55 | include: [ 56 | path.resolve(baseDir, SOURCE_PATH), 57 | ], 58 | use: [ 59 | 'babel-loader', 60 | ], 61 | }; 62 | 63 | const cssRule = { 64 | test: /\.css$/, 65 | include: [ 66 | path.resolve(baseDir, SOURCE_PATH), 67 | ], 68 | use: [ 69 | 'style-loader', 70 | 'css-loader', 71 | postcssLoader, 72 | ], 73 | }; 74 | 75 | const cssModuleRule = { 76 | test: /\.css$/, 77 | include: [ 78 | path.resolve(baseDir, NODE_MODULES), 79 | ], 80 | use: [ 81 | 'style-loader', 82 | 'css-loader', 83 | postcssLoader, 84 | ], 85 | }; 86 | 87 | const lessRule = { 88 | test: /\.less$/, 89 | include: [ 90 | path.resolve(baseDir, SOURCE_PATH), 91 | ], 92 | use: [ 93 | 'style-loader', 94 | 'css-loader', 95 | postcssLoader, 96 | 'less-loader', 97 | ], 98 | }; 99 | 100 | const lessModuleRule = { 101 | test: /\.less$/, 102 | include: [ 103 | path.resolve(baseDir, NODE_MODULES), 104 | ], 105 | use: [ 106 | 'style-loader', 107 | 'css-loader', 108 | postcssLoader, 109 | 'less-loader', 110 | ], 111 | }; 112 | 113 | const fileRule = { 114 | test: /\.(png|jpg|gif)$/, 115 | use: [ 116 | { 117 | loader: 'url-loader', 118 | options: { 119 | limit: 8192, 120 | name: 'images/[name]-[hash].[ext]', 121 | }, 122 | }, 123 | ], 124 | }; 125 | 126 | const createEntryAndPlugins = () => { 127 | const entry = {}; 128 | const plugins = []; 129 | 130 | // get entry 131 | const entryFileNameList = glob.sync(`${path.join(SOURCE_PATH, ENTRIES_FOLDER)}/*.js`); 132 | const entryNameList = entryFileNameList.map((entryFileName) => { 133 | return path.basename(entryFileName, '.js'); 134 | }); 135 | 136 | // get corresponding html template 137 | const htmlFileNameList = glob.sync(`${path.join(SOURCE_PATH, HTML_FOLDER)}/*.html`); 138 | const htmlNameList = htmlFileNameList.map((htmlFileName) => { 139 | return path.basename(htmlFileName, '.html'); 140 | }); 141 | 142 | // set entry 143 | entryNameList.forEach((entryName) => { 144 | entry[entryName] = [ 145 | path.join(baseDir, `./${SOURCE_PATH}/${ENTRIES_FOLDER}/${entryName}.js`), 146 | ]; 147 | 148 | let htmlTemplateName = 'index'; 149 | if (htmlNameList.indexOf(entryName) !== -1) { 150 | htmlTemplateName = entryName; 151 | } 152 | 153 | plugins.push(new HtmlWebpackPlugin({ 154 | template: `${SOURCE_PATH}/${HTML_FOLDER}/${htmlTemplateName}.html`, 155 | filename: `${HTML_FOLDER}/${entryName}.html`, 156 | hash: true, 157 | inject: 'body', 158 | chunks: [ 159 | COMMON_CHUNK_NAME, 160 | entryName, 161 | ], 162 | })); 163 | }); 164 | return { 165 | entry, 166 | plugins, 167 | }; 168 | }; 169 | 170 | const addDevToEntry = (entry) => { 171 | const newEntry = {}; 172 | Object.keys(entry).forEach((entryName) => { 173 | const ent = entry[entryName]; 174 | newEntry[entryName] = [ 175 | `webpack-dev-server/client?http://${DEVELOPMENT_IP}:${DEVELOPMENT_PORT}`, 176 | 'webpack/hot/log-apply-result', 177 | // webpackConfig.entry[entryName].unshift('webpack/hot/dev-server'); 178 | 'webpack/hot/only-dev-server', 179 | // support react-hot-loader@3, @see https://github.com/gaearon/react-hot-loader/tree/next-docs 180 | 'react-hot-loader/patch', 181 | ...ent, 182 | ]; 183 | }); 184 | return newEntry; 185 | }; 186 | 187 | let { entry, plugins } = createEntryAndPlugins(); 188 | let devtool = 'source-map'; 189 | 190 | // set config according to environment 191 | switch (NODE_ENV) { 192 | case DEVELOPMENT: 193 | devtool = 'eval'; 194 | // support react-hot-loader@3, @see https://github.com/gaearon/react-hot-loader/tree/next-docs 195 | jsRule.use.push('react-hot-loader/webpack'); 196 | entry = addDevToEntry(entry); 197 | plugins = [ 198 | ...plugins, 199 | new webpack.HotModuleReplacementPlugin(), 200 | new webpack.NoEmitOnErrorsPlugin(), 201 | ]; 202 | break; 203 | default: 204 | devtool = 'source-map'; 205 | plugins = [ 206 | ...plugins, 207 | new webpack.BannerPlugin({ 208 | banner: BANNER, 209 | }), 210 | new webpack.optimize.UglifyJsPlugin({ 211 | sourceMap: true, 212 | }), 213 | new BundleAnalyzerPlugin({ 214 | analyzerMode: 'static', 215 | reportFilename: 'report.html', 216 | openAnalyzer: false, 217 | }), 218 | ]; 219 | break; 220 | } 221 | 222 | // default webpack config 223 | module.exports = { 224 | entry, 225 | output: { 226 | path: path.resolve(baseDir, RELEASE_PATH), 227 | filename: 'js/[name].js', 228 | publicPath: '../', 229 | }, 230 | module: { 231 | rules: [ 232 | eslintRule, 233 | jsRule, 234 | cssRule, 235 | lessRule, 236 | fileRule, 237 | cssModuleRule, 238 | lessModuleRule, 239 | ], 240 | }, 241 | plugins: [ 242 | ...plugins, 243 | new webpack.EnvironmentPlugin([ 244 | 'NODE_ENV', 245 | ]), 246 | new webpack.optimize.CommonsChunkPlugin({ 247 | names: [COMMON_CHUNK_NAME], 248 | // pages rests in different folder levels 249 | filename: 'js/[name].js', 250 | minChunks: 2, // Infinity 251 | }), 252 | new webpack.NamedModulesPlugin(), 253 | new webpack.ProgressPlugin((percentage, msg) => { 254 | logUpdate(' progress:', numeral(percentage).format('00.00%'), msg); 255 | }), 256 | ], 257 | devtool, 258 | }; 259 | -------------------------------------------------------------------------------- /src/api/news.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-07-31 18:41 3 | * @author vivaxy 4 | */ 5 | 6 | import fetch from '../lib/fetch'; 7 | 8 | export default async() => { 9 | const result = await fetch({ 10 | url: '/newsByPage', 11 | data: Math.random(), 12 | }); 13 | 14 | return result.list; 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/DemoButton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-07-31 18:44 3 | * @author vivaxy 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { Button } from 'react-pianist/Button'; 9 | 10 | export default class extends Component { 11 | 12 | static propTypes = { 13 | buttonDisabled: PropTypes.bool.isRequired, 14 | onLoadMore: PropTypes.func.isRequired, 15 | }; 16 | 17 | constructor(props) { 18 | super(props); 19 | this.onClick = ::this.onClick; 20 | } 21 | 22 | onClick() { 23 | const { 24 | onLoadMore, 25 | } = this.props; 26 | onLoadMore(); 27 | } 28 | 29 | render() { 30 | const { 31 | buttonDisabled, 32 | } = this.props; 33 | 34 | return ; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Logo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-06 10:09 3 | * @author vivaxy 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | 8 | import logoImage from '../images/vivaxy.20150726.jpg'; 9 | 10 | const style = { 11 | width: '100px', 12 | height: '100px', 13 | }; 14 | 15 | export default class Logo extends Component { 16 | render() { 17 | return ( 18 |
19 | logo 20 |
21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Toast.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-06 16:56 3 | * @author vivaxy 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { AutoHideToast } from 'react-pianist/Toast'; 9 | 10 | import * as toastConfig from '../config/toast'; 11 | 12 | export default class extends Component { 13 | 14 | static propTypes = { 15 | toastState: PropTypes.shape({ 16 | show: PropTypes.bool.isRequired, 17 | message: PropTypes.string.isRequired, 18 | }).isRequired, 19 | hideToastAction: PropTypes.func.isRequired, 20 | }; 21 | 22 | constructor(props) { 23 | super(props); 24 | this.onClose = ::this.onClose; 25 | } 26 | 27 | onClose() { 28 | const { 29 | hideToastAction, 30 | } = this.props; 31 | hideToastAction(); 32 | } 33 | 34 | render() { 35 | const { 36 | toastState, 37 | } = this.props; 38 | 39 | return ( 40 | 45 | {toastState.message} 46 | 47 | ); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/config/actionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-04-10 10:47:44 3 | * @author vivaxy 4 | */ 5 | 6 | export const APPEND_NEWS = 'APPEND_NEWS'; 7 | 8 | export const BUTTON_DISABLED = 'BUTTON_DISABLED'; 9 | export const BUTTON_DEFAULT = 'BUTTON_DEFAULT'; 10 | 11 | export const SHOW_TOAST = 'SHOW_TOAST'; 12 | export const HIDE_TOAST = 'HIDE_TOAST'; 13 | -------------------------------------------------------------------------------- /src/config/cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-01 13:15 3 | * @author vivaxy 4 | */ 5 | 6 | export const LOGIN_TOKEN = 'login-token'; 7 | -------------------------------------------------------------------------------- /src/config/entries.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-01 08:15 3 | * @author vivaxy 4 | */ 5 | 6 | export const INDEX = 'index'; 7 | export const ABOUT = 'about'; 8 | -------------------------------------------------------------------------------- /src/config/environments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-07 12:59 3 | * @author vivaxy 4 | */ 5 | 6 | export const DEVELOPMENT = 'development'; 7 | export const LOCAL = 'local'; 8 | export const BETA = 'beta'; 9 | export const PRODUCTION = 'production'; 10 | -------------------------------------------------------------------------------- /src/config/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-25 11:58 3 | * @author vivaxy 4 | */ 5 | 6 | export const FETCH = 'FETCH'; 7 | export const SERVER = 'SERVER'; 8 | export const TIMEOUT = 'TIMEOUT'; 9 | -------------------------------------------------------------------------------- /src/config/requestMethod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-25 13:08 3 | * @author vivaxy 4 | */ 5 | 6 | export const GET = 'GET'; 7 | export const POST = 'POST'; 8 | -------------------------------------------------------------------------------- /src/config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-10-23 17:02 3 | * @author vivaxy 4 | */ 5 | 6 | export const BASE = '/'; 7 | export const WILDCARD = '*'; 8 | export const DEMO = 'demo'; 9 | -------------------------------------------------------------------------------- /src/config/storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-01 13:15 3 | * @author vivaxy 4 | */ 5 | 6 | export const USER_ID = 'user-id'; 7 | export const USER_NAME = 'username'; 8 | export const USER_AVATAR = 'user-avatar'; 9 | -------------------------------------------------------------------------------- /src/config/toast.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-01 08:34 3 | * @author vivaxy 4 | */ 5 | 6 | export const AUTO_HIDE_DURATION = 2000; 7 | -------------------------------------------------------------------------------- /src/containers/Demo/PianistDemo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-18 11:14 3 | * @author vivaxy 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { connect } from 'react-redux'; 9 | 10 | import { VerticalFlexBox, VerticalFlexItem } from 'react-pianist/VerticalFlex'; 11 | import { HorizontalFlexBox, HorizontalFlexItem } from 'react-pianist/HorizontalFlex'; 12 | import { BorderBox, BorderLine } from 'react-pianist/BorderBox'; 13 | import { Button } from 'react-pianist/Button'; 14 | 15 | import { push as routingPushAction } from '../../redux/routing'; 16 | import setTitle from '../../lib/setTitle'; 17 | import * as i18n from '../../i18n/zh-cn'; 18 | 19 | class PianistDemo extends Component { 20 | 21 | static propTypes = { 22 | routingPush: PropTypes.func.isRequired, 23 | params: PropTypes.object.isRequired, 24 | }; 25 | 26 | constructor(props) { 27 | super(props); 28 | this.getToIndex = ::this.getToIndex; 29 | this.getNavigationItemStyle = ::this.getNavigationItemStyle; 30 | this.navigate = ::this.navigate; 31 | } 32 | 33 | componentDidMount() { 34 | setTitle(i18n.$SOMEONE_S_HOME('react-pianist')); 35 | } 36 | 37 | getToIndex() { 38 | const { 39 | routingPush, 40 | } = this.props; 41 | routingPush('/'); 42 | } 43 | 44 | getNavigationItemStyle = (currentIndex) => { 45 | const { 46 | params: { index }, 47 | } = this.props; 48 | return { 49 | textAlign: 'center', 50 | lineHeight: '50px', 51 | color: index === currentIndex ? '#f63' : '#333', 52 | }; 53 | }; 54 | 55 | navigate(index) { 56 | const { 57 | routingPush, 58 | } = this.props; 59 | return () => { 60 | routingPush(`/demo/${index}`); 61 | }; 62 | } 63 | 64 | render() { 65 | return ( 66 |
67 | 74 | 75 | 76 |
77 | {Math.random()} 78 |
79 |
80 | 81 | 86 | 87 | 88 | 93 | home 94 | 95 | 100 | contact 101 | 102 | 107 | discovery 108 | 109 | 114 | me 115 | 116 | 117 | 118 | 119 |
120 |
121 | ); 122 | } 123 | } 124 | 125 | export default connect(null, { 126 | routingPush: routingPushAction, 127 | })(PianistDemo); 128 | -------------------------------------------------------------------------------- /src/containers/Index/Home.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-10-23 14:48 3 | * @author vivaxy 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { connect } from 'react-redux'; 9 | 10 | import { Button } from 'react-pianist/Button'; 11 | 12 | import setTitle from '../../lib/setTitle'; 13 | 14 | import Logo from '../../components/Logo'; 15 | import DemoButton from '../../components/DemoButton'; 16 | 17 | import i18n from '../../i18n/index'; 18 | import { getMoreNews } from '../../redux/newsList'; 19 | import { push } from '../../redux/routing'; 20 | import { showToast } from '../../redux/toast'; 21 | 22 | let newsIndex = 0; 23 | 24 | const raisedButtonStyle = { 25 | padding: '0 8px', 26 | }; 27 | 28 | class Home extends Component { 29 | 30 | static propTypes = { 31 | routingPush: PropTypes.func.isRequired, 32 | buttonState: PropTypes.bool.isRequired, 33 | newsListState: PropTypes.array.isRequired, 34 | getMoreNewsAction: PropTypes.func.isRequired, 35 | }; 36 | 37 | constructor(props) { 38 | super(props); 39 | this.goToDemo = ::this.goToDemo; 40 | } 41 | 42 | componentDidMount() { 43 | const { getMoreNewsAction } = this.props; 44 | setTitle(i18n.$SOMEONE_S_HOME('vivaxy')); 45 | getMoreNewsAction(); 46 | } 47 | 48 | goToDemo(index) { 49 | const { 50 | routingPush, 51 | } = this.props; 52 | return () => { 53 | routingPush(`/demo/${index}`); 54 | }; 55 | } 56 | 57 | render() { 58 | const { 59 | buttonState, 60 | newsListState, 61 | getMoreNewsAction, 62 | } = this.props; 63 | 64 | return ( 65 |
66 | 67 | {newsListState.map((news) => { 68 | return
{news.name}
; 69 | })} 70 | 71 |
72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 | ); 81 | } 82 | 83 | } 84 | 85 | export default connect((state) => { 86 | return { 87 | buttonState: state.button, 88 | newsListState: state.newsList, 89 | }; 90 | }, { 91 | getMoreNewsAction: getMoreNews, 92 | routingPush: push, 93 | showToastAction: showToast, 94 | })(Home); 95 | -------------------------------------------------------------------------------- /src/containers/common/Base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-01 08:18 3 | * @author vivaxy 4 | */ 5 | 6 | import React, { Component, cloneElement } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { connect } from 'react-redux'; 9 | 10 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 11 | 12 | import Toast from './Toast'; 13 | 14 | import './Base.less'; 15 | 16 | class Base extends Component { 17 | 18 | static propTypes = { 19 | children: PropTypes.node.isRequired, 20 | location: PropTypes.object.isRequired, 21 | }; 22 | 23 | render() { 24 | const { 25 | children, 26 | location, 27 | } = this.props; 28 | 29 | const { 30 | action, 31 | pathname, 32 | } = location; 33 | 34 | const transitionName = `page-transition-${action.toLowerCase()}`; 35 | 36 | return ( 37 |
38 | 44 | {cloneElement(children, { 45 | key: pathname, 46 | })} 47 | 48 | 49 |
50 | ); 51 | } 52 | } 53 | 54 | export default connect(null, {})(Base); 55 | -------------------------------------------------------------------------------- /src/containers/common/Base.less: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-01-05 21:15 3 | * @author vivaxy 4 | */ 5 | 6 | .pageTransitionStyle { 7 | position: fixed; 8 | top: 0; 9 | left: 0; 10 | width: 100%; 11 | height: 100%; 12 | overflow: auto; 13 | } 14 | 15 | .page-transition-push-enter, .page-transition-pop-leave.page-transition-pop-leave-active { 16 | transform: translate3d(100%, 0, 100px); 17 | transition: all 300ms ease; 18 | z-index: 1; 19 | box-shadow: rgba(0, 0, 0, 0.5) 0 0 0; 20 | .pageTransitionStyle 21 | } 22 | 23 | .page-transition-push-enter.page-transition-push-enter-active, .page-transition-pop-leave { 24 | transform: translate3d(0, 0, 0); 25 | box-shadow: rgba(0, 0, 0, 0.5) 0 0 20px; 26 | .pageTransitionStyle 27 | } 28 | 29 | .page-transition-push-leave, .page-transition-pop-enter.page-transition-pop-enter-active { 30 | transform: translate3d(0, 0, 0); 31 | opacity: 1; 32 | transition: all 300ms ease; 33 | .pageTransitionStyle 34 | } 35 | 36 | .page-transition-push-leave.page-transition-push-leave-active, .page-transition-pop-enter { 37 | transform: translate3d(-50%, 0, -100px); 38 | opacity: 0; 39 | .pageTransitionStyle 40 | } 41 | 42 | .page-wrapper { 43 | background-color: #fff; 44 | } 45 | -------------------------------------------------------------------------------- /src/containers/common/NoMatch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-01 08:18 3 | * @author vivaxy 4 | */ 5 | 6 | import React, { Component } from 'react'; 7 | import PropTypes from 'prop-types'; 8 | import { connect } from 'react-redux'; 9 | 10 | class NoMatch extends Component { 11 | 12 | static propTypes = { 13 | params: PropTypes.object.isRequired, 14 | location: PropTypes.object.isRequired, 15 | }; 16 | 17 | render() { 18 | const { 19 | params, 20 | location, 21 | } = this.props; 22 | 23 | return ( 24 |
25 | {JSON.stringify(params)} 26 | {JSON.stringify(location)} 27 |
28 | ); 29 | } 30 | 31 | } 32 | 33 | export default connect(null, {})(NoMatch); 34 | -------------------------------------------------------------------------------- /src/containers/common/Toast.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-11-24 10:26 3 | * @author vivaxy 4 | */ 5 | 6 | import { connect } from 'react-redux'; 7 | 8 | import Toast from '../../components/Toast'; 9 | import { hideToast } from '../../redux/toast'; 10 | 11 | const mapStateToProps = (state) => { 12 | return { 13 | toastState: state.toast, 14 | }; 15 | }; 16 | 17 | export default connect(mapStateToProps, { 18 | hideToastAction: hideToast, 19 | })(Toast); 20 | -------------------------------------------------------------------------------- /src/entries/about.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-03 09:16 3 | * @author vivaxy 4 | */ 5 | 6 | import 'babel-polyfill'; 7 | import page from '../pages/about'; 8 | 9 | page(); 10 | 11 | /* eslint-disable global-require */ 12 | if (module.hot) { 13 | module.hot.accept('../pages/about', () => { 14 | const newPage = require('../pages/about').default; 15 | newPage(); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/entries/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-03 09:16 3 | * @author vivaxy 4 | */ 5 | 6 | import 'babel-polyfill'; 7 | import page from '../pages/index'; 8 | 9 | page(); 10 | 11 | /* eslint-disable global-require */ 12 | if (module.hot) { 13 | module.hot.accept('../pages/index', () => { 14 | const newPage = require('../pages/index').default; 15 | newPage(); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/errors/FetchError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-06-05 11:40 3 | * @author vivaxy 4 | */ 5 | 6 | import * as constant from '../config/errors'; 7 | 8 | export default class FetchError extends Error { 9 | constructor(response) { 10 | super(response.statusText); 11 | this.response = response; 12 | this.name = constant.FETCH; 13 | } 14 | } 15 | 16 | FetchError.create = (response) => { 17 | return new FetchError(response); 18 | }; 19 | -------------------------------------------------------------------------------- /src/errors/ServerError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-01 12:57 3 | * @author vivaxy 4 | */ 5 | 6 | import * as constant from '../config/errors'; 7 | 8 | export default class ServerError extends Error { 9 | constructor(response) { 10 | super(response.message); 11 | this.response = response; 12 | this.name = constant.SERVER; 13 | } 14 | } 15 | 16 | ServerError.create = (response) => { 17 | return new ServerError(response); 18 | }; 19 | -------------------------------------------------------------------------------- /src/errors/TimeoutError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-06-05 11:40 3 | * @author vivaxy 4 | */ 5 | 6 | import * as constant from '../config/errors'; 7 | 8 | export default class TimeoutError extends Error { 9 | constructor(timeout) { 10 | super('timeout'); 11 | this.timeout = timeout; 12 | this.name = constant.TIMEOUT; 13 | } 14 | } 15 | 16 | TimeoutError.create = (timeout) => { 17 | return new TimeoutError(timeout); 18 | }; 19 | -------------------------------------------------------------------------------- /src/errors/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-06-05 12:13 3 | * @author vivaxy 4 | */ 5 | 6 | import FetchError from './FetchError'; 7 | import ServerError from './ServerError'; 8 | import TimeoutError from './TimeoutError'; 9 | 10 | export { 11 | FetchError, 12 | ServerError, 13 | TimeoutError, 14 | }; 15 | -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | gt-react-scaffold 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-17 12:02 3 | * @author vivaxy 4 | */ 5 | 6 | import * as zhCN from './zh-cn'; 7 | 8 | export default zhCN; 9 | -------------------------------------------------------------------------------- /src/i18n/zh-cn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-17 12:02 3 | * @author vivaxy 4 | */ 5 | 6 | /* eslint-disable arrow-body-style */ 7 | export const $SOMEONE_S_HOME = (name) => `${name}的个人主页`; 8 | export const NAME = '姓名'; 9 | -------------------------------------------------------------------------------- /src/images/vivaxy.20150726.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivaxy/gt-react-scaffold/086082ebcb41401639706c23cae929e07ef046ae/src/images/vivaxy.20150726.jpg -------------------------------------------------------------------------------- /src/lib/checkLogin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-01 13:14 3 | * @author vivaxy 4 | */ 5 | 6 | import cookie from 'tiny-cookie'; 7 | 8 | import * as cookieKey from '../config/cookie'; 9 | import * as storageKey from '../config/storage'; 10 | 11 | const get = (key) => { 12 | return localStorage.getItem(key); 13 | }; 14 | 15 | export default (setLoginInfoAction) => { 16 | let login = false; 17 | if (cookie.get(cookieKey.LOGIN_TOKEN)) { 18 | // get user info from localStorage 19 | setLoginInfoAction({ 20 | id: get(storageKey.USER_ID), 21 | name: get(storageKey.USER_NAME), 22 | avatar: get(storageKey.USER_AVATAR), 23 | }); 24 | login = true; 25 | } 26 | return login; 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/environment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-06-11 15:43 3 | * @author vivaxy 4 | */ 5 | 6 | import * as environmentConstant from '../config/environments'; 7 | 8 | export default process.env.NODE_ENV || environmentConstant.PRODUCTION; 9 | -------------------------------------------------------------------------------- /src/lib/fetch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-06-05 09:45 3 | * @author vivaxy 4 | */ 5 | 6 | import url from 'url'; 7 | import fetch from 'isomorphic-fetch'; 8 | 9 | import * as requestMethodConstant from '../config/requestMethod'; 10 | import getRequestPath from './requestPath'; 11 | import { FetchError, ServerError, TimeoutError } from '../errors'; 12 | import sleep from './sleep'; 13 | import environment from './environment'; 14 | import * as environmentType from '../config/environments'; 15 | 16 | const MOCK_DELAY = 1000; 17 | const SUCCESS_CODE_LOWER_BOUND = 200; 18 | const SUCCESS_CODE_HIGHER_BOUND = 300; 19 | 20 | const basicFetchOptions = { 21 | credentials: 'include', 22 | }; 23 | 24 | const sendRequest = async(config) => { 25 | let response = null; 26 | const { 27 | method = requestMethodConstant.GET, 28 | data, 29 | url: passedURL, 30 | } = config; 31 | 32 | let requestPath = getRequestPath(passedURL); 33 | const requestMethod = method.toUpperCase(); 34 | 35 | switch (requestMethod) { 36 | case requestMethodConstant.GET: 37 | requestPath = url.format({ 38 | pathname: requestPath, 39 | query: data, 40 | }); 41 | response = await fetch(requestPath, { 42 | ...basicFetchOptions, 43 | }); 44 | break; 45 | case requestMethodConstant.POST: 46 | response = await fetch(requestPath, { 47 | ...basicFetchOptions, 48 | requestMethod, 49 | body: JSON.stringify(data), 50 | }); 51 | break; 52 | default: 53 | break; 54 | } 55 | 56 | if (environment === environmentType.DEVELOPMENT) { 57 | await sleep(MOCK_DELAY); 58 | } 59 | 60 | return response; 61 | }; 62 | 63 | const timeoutPromise = async(timeout) => { 64 | await sleep(timeout); 65 | throw new TimeoutError(timeout); 66 | }; 67 | 68 | export default async(config) => { 69 | const { 70 | timeout = 60000, 71 | ...fetchConfig 72 | } = config; 73 | 74 | const response = await Promise.race([timeoutPromise(timeout), sendRequest(fetchConfig)]); 75 | 76 | if (response.status < SUCCESS_CODE_LOWER_BOUND || response.status >= SUCCESS_CODE_HIGHER_BOUND) { 77 | throw new FetchError(response); 78 | } else { 79 | const resp = await response.json(); 80 | if (resp.code !== 0) { 81 | throw new ServerError(resp); 82 | } else { 83 | return resp.data; 84 | } 85 | } 86 | }; 87 | -------------------------------------------------------------------------------- /src/lib/getURLParameter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-17 14:30 3 | * @author vivaxy 4 | */ 5 | 6 | import url from 'url'; 7 | 8 | export default (key, href = location.href) => { 9 | const o = url.parse(href, true); 10 | return o.query[key]; 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/logout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-27 10:27 3 | * @author vivaxy 4 | */ 5 | 6 | import cookie from 'tiny-cookie'; 7 | 8 | import * as cookieKey from '../config/cookie'; 9 | 10 | export default () => { 11 | cookie.remove(cookieKey.LOGIN_TOKEN); 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/processErrors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-04-10 10:42:13 3 | * @author vivaxy 4 | */ 5 | 6 | import * as errorType from '../config/errors'; 7 | import { showToast } from '../redux/toast'; 8 | 9 | export default (ex) => { 10 | switch (ex.name) { 11 | case errorType.FETCH: 12 | showToast(ex.message); 13 | break; 14 | case errorType.SERVER: 15 | showToast(ex.message); 16 | break; 17 | default: 18 | throw ex; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/lib/render.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-07-31 19:09 3 | * @author vivaxy 4 | */ 5 | 6 | import { render } from 'react-dom'; 7 | import { Provider } from 'react-redux'; 8 | import { AppContainer } from 'react-hot-loader'; 9 | import { Router, hashHistory } from 'react-router'; 10 | import { syncHistoryWithStore } from 'react-router-redux'; 11 | import React, { Children, createElement } from 'react'; 12 | 13 | import setStyle from './style'; 14 | import store from './store'; 15 | 16 | const ID_SELECTOR = 'gt-react-scaffold'; 17 | const history = syncHistoryWithStore(hashHistory, store); 18 | 19 | setStyle(); 20 | 21 | // hack around https://github.com/gaearon/react-hot-boilerplate/pull/61#issuecomment-211504531 22 | Router.prototype.componentWillReceiveProps = (nextProps) => { 23 | const components = []; 24 | 25 | function grabComponents(element) { 26 | // This only works for JSX routes, adjust accordingly for plain JS config 27 | if (element.props && element.props.component) { 28 | components.push(element.props.component); 29 | } 30 | if (element.props && element.props.children) { 31 | Children.forEach(element.props.children, grabComponents); 32 | } 33 | } 34 | 35 | grabComponents(nextProps.routes || nextProps.children); 36 | components.forEach(createElement); // force patching 37 | }; 38 | 39 | export default (routes) => { 40 | return render( 41 | 42 | 43 | 44 | 45 | , 46 | document.getElementById(ID_SELECTOR), 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /src/lib/requestPath.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-14 13:48 3 | * @author vivaxy 4 | */ 5 | 6 | import * as environmentConstant from '../config/environments'; 7 | 8 | import environment from './environment'; 9 | 10 | const MOCK_API_ROOT = '/api'; 11 | 12 | export default (url) => { 13 | let requestPath = ''; 14 | switch (environment) { 15 | case environmentConstant.DEVELOPMENT: 16 | requestPath = `${MOCK_API_ROOT}${url}.json`; 17 | break; 18 | case environmentConstant.PRODUCTION: 19 | requestPath = `${MOCK_API_ROOT}${url}.json`; 20 | break; 21 | default: 22 | break; 23 | } 24 | return requestPath; 25 | }; 26 | -------------------------------------------------------------------------------- /src/lib/setTitle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-13 14:38 3 | * @author vivaxy 4 | */ 5 | 6 | export default (title) => { 7 | document.title = title; 8 | }; 9 | -------------------------------------------------------------------------------- /src/lib/sleep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-06 15:57 3 | * @author vivaxy 4 | */ 5 | 6 | export default (timeout) => { 7 | return new Promise((resolve) => { 8 | setTimeout(resolve, timeout); 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-09-16 18:11 3 | * @author vivaxy 4 | */ 5 | 6 | import { createStore, combineReducers, compose, applyMiddleware } from 'redux'; 7 | import { routerMiddleware } from 'react-router-redux'; 8 | import { hashHistory } from 'react-router'; 9 | import thunk from 'redux-thunk'; 10 | 11 | import reducers from '../redux/reducers'; 12 | 13 | const routing = routerMiddleware(hashHistory); 14 | 15 | const store = createStore(combineReducers(reducers), compose( 16 | applyMiddleware(thunk, routing), 17 | window.devToolsExtension ? window.devToolsExtension() : (f) => f, // eslint-disable-line arrow-body-style 18 | )); 19 | 20 | if (module.hot) { 21 | // Enable Webpack hot module replacement for reducers 22 | module.hot.accept('../redux/reducers', () => { 23 | const nextRootReducer = require('../redux/reducers').default; // eslint-disable-line global-require 24 | store.replaceReducer(combineReducers(nextRootReducer)); 25 | }); 26 | } 27 | 28 | export default store; 29 | -------------------------------------------------------------------------------- /src/lib/style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-06 12:03 3 | * @author vivaxy 4 | */ 5 | 6 | import iconImage from '../images/vivaxy.20150726.jpg'; 7 | import './style.less'; 8 | 9 | export default () => { 10 | const style = { 11 | margin: '0', 12 | }; 13 | 14 | const body = document.body; 15 | 16 | Object.keys(style).forEach((key) => { 17 | body.style[key] = style[key]; 18 | }); 19 | 20 | const head = document.head; 21 | const icon = document.createElement('link'); 22 | icon.type = 'image/png'; 23 | icon.rel = 'shortcut icon'; 24 | icon.href = iconImage; 25 | head.appendChild(icon); 26 | }; 27 | -------------------------------------------------------------------------------- /src/lib/style.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivaxy/gt-react-scaffold/086082ebcb41401639706c23cae929e07ef046ae/src/lib/style.less -------------------------------------------------------------------------------- /src/pages/about.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-10-24 09:01 3 | * @author vivaxy 4 | */ 5 | 6 | import render from '../lib/render'; 7 | import routes from '../routes/about'; 8 | 9 | export default () => { 10 | return render(routes); 11 | }; 12 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-10-24 09:01 3 | * @author vivaxy 4 | */ 5 | 6 | import render from '../lib/render'; 7 | import routes from '../routes/index'; 8 | 9 | export default () => { 10 | return render(routes); 11 | }; 12 | -------------------------------------------------------------------------------- /src/redux/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-07-31 18:40 3 | * @author vivaxy 4 | */ 5 | 6 | import { createReducer } from 'redux-create-reducer'; 7 | 8 | import { BUTTON_DEFAULT, BUTTON_DISABLED, APPEND_NEWS } from '../config/actionTypes'; 9 | 10 | const defaultState = true; 11 | 12 | export default createReducer(defaultState, { 13 | [BUTTON_DISABLED]: () => { 14 | return false; 15 | }, 16 | [BUTTON_DEFAULT]: () => { 17 | return true; 18 | }, 19 | [APPEND_NEWS]: () => { 20 | return true; 21 | }, 22 | }); 23 | 24 | export const setButtonDisabled = () => { 25 | return { 26 | type: BUTTON_DISABLED, 27 | }; 28 | }; 29 | 30 | export const setButtonDefault = () => { 31 | return { 32 | type: BUTTON_DEFAULT, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/redux/newsList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-06 16:07 3 | * @author vivaxy 4 | */ 5 | 6 | import { createReducer } from 'redux-create-reducer'; 7 | 8 | import processErrors from '../lib/processErrors'; 9 | 10 | import { APPEND_NEWS } from '../config/actionTypes'; 11 | import { setButtonDisabled, setButtonDefault } from './button'; 12 | import getNewsAPI from '../api/news'; 13 | 14 | const defaultState = []; 15 | 16 | export default createReducer(defaultState, { 17 | [APPEND_NEWS]: (state, action) => { 18 | return [...state, ...action.list]; 19 | }, 20 | }); 21 | 22 | export const getMoreNews = () => { 23 | return async(dispatch) => { 24 | try { 25 | dispatch(setButtonDisabled()); 26 | const list = await getNewsAPI(); 27 | dispatch({ 28 | list, 29 | type: APPEND_NEWS, 30 | }); 31 | } catch (ex) { 32 | dispatch(setButtonDefault()); 33 | processErrors(ex); 34 | } 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/redux/pagination.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-31 17:11 3 | * @author vivaxy 4 | */ 5 | 6 | import PropTypes from 'prop-types'; 7 | import { createReducer } from 'redux-create-reducer'; 8 | 9 | const SETUP_PAGINATION = 'SETUP_PAGINATION'; 10 | 11 | const defaultState = { 12 | pageIndex: 0, 13 | pageSize: 0, 14 | pageCount: 0, 15 | rowCount: 0, 16 | needMore: true, 17 | }; 18 | 19 | export default createReducer(defaultState, { 20 | [SETUP_PAGINATION]: (state, action) => { 21 | return { 22 | ...state, 23 | pageSize: action.pageSize, 24 | pageIndex: action.pageIndex, 25 | pageCount: action.pageCount, 26 | rowCount: action.rowCount, 27 | needMore: action.pageIndex !== action.pageCount, 28 | }; 29 | }, 30 | }); 31 | 32 | export const propTypes = PropTypes.shape({ 33 | pageIndex: PropTypes.number.isRequired, 34 | pageSize: PropTypes.number.isRequired, 35 | pageCount: PropTypes.number.isRequired, 36 | rowCount: PropTypes.number.isRequired, 37 | needMore: PropTypes.bool.isRequired, 38 | }); 39 | -------------------------------------------------------------------------------- /src/redux/reducers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-01 21:50 3 | * @author vivaxy 4 | */ 5 | 6 | import { routerReducer } from 'react-router-redux'; 7 | 8 | import button from './button'; 9 | import newsList from './newsList'; 10 | import toast from './toast'; 11 | import pagination from './pagination'; 12 | 13 | export default { 14 | button, 15 | newsList, 16 | toast, 17 | pagination, 18 | routing: routerReducer, 19 | }; 20 | -------------------------------------------------------------------------------- /src/redux/routing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2017-04-10 09:55:20 3 | * @author vivaxy 4 | */ 5 | 6 | import { 7 | push as routerPush, 8 | replace as routerReplace, 9 | go as routerGo, 10 | goBack as routerGoBack, 11 | goForward as routerGoFroward, 12 | } from 'react-router-redux'; 13 | 14 | export const push = (location) => { 15 | return routerPush(location); 16 | }; 17 | export const replace = (location) => { 18 | return routerReplace(location); 19 | }; 20 | export const go = (number) => { 21 | return routerGo(number); 22 | }; 23 | export const goBack = () => { 24 | return routerGoBack(); 25 | }; 26 | export const goForward = () => { 27 | return routerGoFroward(); 28 | }; 29 | -------------------------------------------------------------------------------- /src/redux/toast.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-08-25 09:31 3 | * @author vivaxy 4 | */ 5 | 6 | import { createReducer } from 'redux-create-reducer'; 7 | 8 | import { HIDE_TOAST, SHOW_TOAST } from '../config/actionTypes'; 9 | 10 | const defaultState = { 11 | show: false, 12 | message: '', 13 | }; 14 | 15 | export default createReducer(defaultState, { 16 | [SHOW_TOAST]: (state, action) => { 17 | return { 18 | ...state, 19 | show: true, 20 | message: action.message, 21 | }; 22 | }, 23 | [HIDE_TOAST]: (state) => { 24 | return { 25 | ...state, 26 | show: false, 27 | message: '', 28 | }; 29 | }, 30 | }); 31 | 32 | export const showToast = (message) => { 33 | return { 34 | message, 35 | type: SHOW_TOAST, 36 | }; 37 | }; 38 | 39 | export const hideToast = () => { 40 | return { 41 | type: HIDE_TOAST, 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /src/routes/about.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-10-24 09:02 3 | * @author vivaxy 4 | */ 5 | 6 | import Base from '../containers/common/Base'; 7 | import NoMatch from '../containers/common/NoMatch'; 8 | import Index from '../containers/Index/Home'; 9 | import Demo from '../containers/Demo/PianistDemo'; 10 | 11 | const demoRoute = { 12 | path: 'demo/:index', 13 | component: Demo, 14 | }; 15 | 16 | const noMatchRoute = { 17 | path: '*', 18 | component: NoMatch, 19 | }; 20 | 21 | const route = { 22 | path: '/', 23 | component: Base, 24 | indexRoute: { 25 | component: Index, 26 | }, 27 | childRoutes: [ 28 | demoRoute, 29 | noMatchRoute, 30 | ], 31 | }; 32 | 33 | export default route; 34 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-10-24 09:02 3 | * @author vivaxy 4 | */ 5 | 6 | import Base from '../containers/common/Base'; 7 | import NoMatch from '../containers/common/NoMatch'; 8 | import Index from '../containers/Index/Home'; 9 | import Demo from '../containers/Demo/PianistDemo'; 10 | 11 | const demoRoute = { 12 | path: 'demo/:index', 13 | component: Demo, 14 | }; 15 | 16 | const noMatchRoute = { 17 | path: '*', 18 | component: NoMatch, 19 | }; 20 | 21 | const route = { 22 | path: '/', 23 | component: Base, 24 | indexRoute: { 25 | component: Index, 26 | }, 27 | childRoutes: [ 28 | demoRoute, 29 | noMatchRoute, 30 | ], 31 | }; 32 | 33 | export default route; 34 | -------------------------------------------------------------------------------- /test/get-base-dirname.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2016-10-24 08:47 3 | * @author vivaxy 4 | */ 5 | 6 | import test from 'ava'; 7 | import path from 'path'; 8 | 9 | test('project base directory name', (t) => { 10 | const paths = path.resolve(__dirname, '..'); 11 | t.true(paths === process.cwd()); 12 | }); 13 | --------------------------------------------------------------------------------