├── chapter2 ├── part1 │ ├── hello.js │ ├── index.js │ └── index.html ├── part2 │ ├── index.css │ ├── index.js │ ├── package.json │ └── index.html ├── part3 │ ├── index.css │ ├── index.js │ ├── package.json │ ├── index.html │ ├── webpack.config.js │ └── npm-shrinkwrap.json └── part4 │ ├── index.css │ ├── index.js │ ├── package.json │ └── webpack.config.js ├── chapter5 ├── part2 │ ├── .babelrc │ ├── .eslintrc │ ├── package.json │ ├── webpack.config.js │ ├── app │ │ └── app.js │ └── npm-shrinkwrap.json └── part1 │ ├── app │ ├── dispatcher │ │ └── AppDispatcher.js │ ├── app.jsx │ ├── actions │ │ └── TodoAction.js │ ├── components │ │ ├── CreateButton.jsx │ │ ├── List.jsx │ │ └── Todo.jsx │ └── stores │ │ └── TodoStore.js │ ├── .eslintrc │ ├── .babelrc │ ├── package.json │ ├── webpack.config.js │ └── npm-shrinkwrap.json ├── chapter6 ├── part2 │ ├── .babelrc │ ├── app │ │ ├── components │ │ │ ├── Deskmark │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ │ ├── CreateBar │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ │ ├── ItemShowLayer │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ │ ├── List │ │ │ │ └── index.jsx │ │ │ ├── ListItem │ │ │ │ └── index.jsx │ │ │ └── ItemEditor │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── entries │ │ │ │ ├── index.js │ │ │ │ ├── list.js │ │ │ │ └── detail.js │ │ │ └── editor.js │ │ ├── utils │ │ │ ├── ajax.js │ │ │ ├── firebase.js │ │ │ ├── firebaseStorage.js │ │ │ └── storage.js │ │ ├── app.jsx │ │ └── actions │ │ │ └── index.js │ ├── README.md │ ├── .eslintrc │ ├── db.json │ ├── webpack.config.js │ ├── package.json │ └── npm-shrinkwrap.json └── part1 │ ├── app │ ├── components │ │ ├── Deskmark │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── CreateBar │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── ItemShowLayer │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── List │ │ │ └── index.jsx │ │ ├── ListItem │ │ │ └── index.jsx │ │ └── ItemEditor │ │ │ ├── style.scss │ │ │ └── index.jsx │ ├── reducers │ │ ├── index.js │ │ ├── items.js │ │ └── editor.js │ ├── app.jsx │ ├── utils │ │ └── storage.js │ └── actions │ │ └── index.js │ ├── .babelrc │ ├── .eslintrc │ ├── webpack.config.js │ ├── package.json │ └── npm-shrinkwrap.json ├── online ├── .babelrc ├── app │ ├── components │ │ ├── Deskmark │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── CreateBar │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── Loader │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── ItemShowLayer │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── List │ │ │ └── index.jsx │ │ ├── ItemEditor │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ └── ListItem │ │ │ └── index.jsx │ ├── reducers │ │ ├── index.js │ │ ├── entries │ │ │ ├── index.js │ │ │ ├── list.js │ │ │ └── detail.js │ │ └── editor.js │ ├── utils │ │ ├── leancloud.js │ │ ├── firebase.js │ │ ├── firebaseStorage.js │ │ └── storage.js │ ├── app.jsx │ └── actions │ │ └── index.js ├── .eslintrc ├── README.md ├── webpack.config.js ├── webpack.prod.config.js └── package.json ├── chapter3 ├── part3 │ ├── .eslintrc │ ├── .babelrc │ ├── app │ │ └── app.jsx │ ├── package.json │ ├── webpack.config.js │ └── npm-shrinkwrap.json └── part4 │ ├── .eslintrc │ ├── .babelrc │ ├── app │ ├── Hobby.jsx │ ├── app.jsx │ └── Profile.jsx │ ├── webpack.config.js │ ├── package.json │ └── npm-shrinkwrap.json ├── chapter4 └── part1 │ ├── app │ ├── components │ │ ├── Deskmark │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── CreateBar │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── ItemShowLayer │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── List │ │ │ └── index.jsx │ │ ├── ListItem │ │ │ └── index.jsx │ │ └── ItemEditor │ │ │ ├── style.scss │ │ │ └── index.jsx │ └── app.jsx │ ├── .babelrc │ ├── test │ ├── setup.js │ ├── SFC.test.js │ └── SFC-enzyme.test.js │ ├── .eslintrc │ ├── webpack.config.js │ ├── package.json │ └── npm-shrinkwrap.json ├── .gitignore ├── README.md └── corrigendum.md /chapter2/part1/hello.js: -------------------------------------------------------------------------------- 1 | module.exports = 'Hello world!'; -------------------------------------------------------------------------------- /chapter5/part2/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /chapter2/part1/index.js: -------------------------------------------------------------------------------- 1 | var text = require('./hello'); 2 | console.log(text); -------------------------------------------------------------------------------- /chapter2/part2/index.css: -------------------------------------------------------------------------------- 1 | div { 2 | width: 100px; 3 | height: 100px; 4 | background-color: red; 5 | } -------------------------------------------------------------------------------- /chapter2/part3/index.css: -------------------------------------------------------------------------------- 1 | div { 2 | width: 100px; 3 | height: 100px; 4 | background-color: red; 5 | } -------------------------------------------------------------------------------- /chapter2/part4/index.css: -------------------------------------------------------------------------------- 1 | div { 2 | width: 100px; 3 | height: 100px; 4 | background-color: red; 5 | } -------------------------------------------------------------------------------- /chapter2/part3/index.js: -------------------------------------------------------------------------------- 1 | require('./index.css'); 2 | document.body.appendChild(document.createElement('div')); -------------------------------------------------------------------------------- /chapter2/part4/index.js: -------------------------------------------------------------------------------- 1 | require('./index.css'); 2 | document.body.appendChild(document.createElement('div')); -------------------------------------------------------------------------------- /chapter2/part2/index.js: -------------------------------------------------------------------------------- 1 | require('style!css!./index.css'); 2 | document.body.appendChild(document.createElement('div')); -------------------------------------------------------------------------------- /chapter6/part2/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": ["transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /chapter5/part1/app/dispatcher/AppDispatcher.js: -------------------------------------------------------------------------------- 1 | import { Dispatcher } from 'flux'; 2 | 3 | export default new Dispatcher(); 4 | -------------------------------------------------------------------------------- /online/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-2"], 3 | "plugins": ["transform-object-rest-spread"] 4 | } 5 | -------------------------------------------------------------------------------- /chapter3/part3/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "comma-dangle": ["error", "never"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /chapter3/part4/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "comma-dangle": ["error", "never"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /chapter5/part1/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "comma-dangle": ["error", "never"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /chapter5/part2/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "comma-dangle": ["error", "never"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /chapter2/part2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "part2", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "css-loader": "^0.23.1", 6 | "style-loader": "^0.13.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter2/part3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "part2", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "css-loader": "^0.23.1", 6 | "style-loader": "^0.13.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter3/part3/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"], 3 | //在开发的时候才启用HMR和Catch Error 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /chapter3/part4/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"], 3 | //在开发的时候才启用HMR和Catch Error 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /chapter5/part1/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"], 3 | //在开发的时候才启用HMR和Catch Error 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /online/app/components/Deskmark/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component Deskmark 3 | */ 4 | 5 | .deskmark-component { 6 | .container { 7 | margin-top: 50px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter4/part1/app/components/Deskmark/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component Deskmark 3 | */ 4 | 5 | .deskmark-component { 6 | .container { 7 | margin-top: 50px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter6/part1/app/components/Deskmark/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component Deskmark 3 | */ 4 | 5 | .deskmark-component { 6 | .container { 7 | margin-top: 50px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter6/part2/app/components/Deskmark/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component Deskmark 3 | */ 4 | 5 | .deskmark-component { 6 | .container { 7 | margin-top: 50px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter2/part1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hello 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter2/part2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | loader 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter2/part3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | loader 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter6/part2/README.md: -------------------------------------------------------------------------------- 1 | # Deskmark with react, redux and async feature 2 | 3 | ### We are using firebase as real time database 4 | 5 | **run webpack dev env** 6 | 7 | ```bash 8 | npm run dev 9 | ``` 10 | -------------------------------------------------------------------------------- /chapter4/part1/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": ["transform-object-rest-spread"], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter6/part1/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": ["transform-object-rest-spread"], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter5/part1/app/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Todo from './components/Todo'; 4 | 5 | const app = document.createElement('div'); 6 | document.body.appendChild(app); 7 | ReactDOM.render(, app); 8 | -------------------------------------------------------------------------------- /chapter2/part4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "part2", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "css-loader": "^0.23.1", 6 | "html-webpack-plugin": "^2.21.0", 7 | "style-loader": "^0.13.1", 8 | "webpack": "^1.13.3" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /online/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers 3 | */ 4 | 5 | import { combineReducers } from 'redux'; 6 | import entries from './entries'; 7 | import editor from './editor'; 8 | 9 | export default combineReducers({ 10 | entries, 11 | editor, 12 | }); 13 | -------------------------------------------------------------------------------- /chapter6/part2/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers 3 | */ 4 | 5 | import { combineReducers } from 'redux'; 6 | import entries from './entries'; 7 | import editor from './editor'; 8 | 9 | export default combineReducers({ 10 | entries, 11 | editor, 12 | }); 13 | -------------------------------------------------------------------------------- /chapter4/part1/test/setup.js: -------------------------------------------------------------------------------- 1 | import jsdom from 'jsdom'; 2 | 3 | if (typeof document === 'undefined') { 4 | global.document = jsdom.jsdom(''); 5 | global.window = document.defaultView; 6 | global.navigator = global.window.navigator; 7 | } 8 | -------------------------------------------------------------------------------- /online/app/reducers/entries/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers for entries 3 | */ 4 | 5 | import { combineReducers } from 'redux'; 6 | import list from './list'; 7 | import detail from './detail'; 8 | 9 | export default combineReducers({ 10 | list, 11 | detail, 12 | }); 13 | -------------------------------------------------------------------------------- /chapter6/part2/app/reducers/entries/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers for entries 3 | */ 4 | 5 | import { combineReducers } from 'redux'; 6 | import list from './list'; 7 | import detail from './detail'; 8 | 9 | export default combineReducers({ 10 | list, 11 | detail, 12 | }); 13 | -------------------------------------------------------------------------------- /chapter3/part4/app/Hobby.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const propTypes = { 4 | hobby: PropTypes.string.isRequired 5 | }; 6 | 7 | function Hobby(props) { 8 | return
  • {props.hobby}
  • ; 9 | } 10 | 11 | Hobby.propTypes = propTypes; 12 | 13 | export default Hobby; 14 | -------------------------------------------------------------------------------- /chapter4/part1/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parserOptions": { 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": true, 6 | "jsx": true 7 | }, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "no-use-before-define": [2, "nofunc"] 12 | } 13 | } -------------------------------------------------------------------------------- /chapter6/part1/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parserOptions": { 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": true, 6 | "jsx": true 7 | }, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "no-use-before-define": [2, "nofunc"] 12 | } 13 | } -------------------------------------------------------------------------------- /chapter6/part2/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parserOptions": { 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": true, 6 | "jsx": true 7 | }, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "no-use-before-define": [2, "nofunc"] 12 | } 13 | } -------------------------------------------------------------------------------- /chapter3/part4/app/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import Profile from './Profile'; 4 | 5 | const ele = document.createElement('div'); 6 | document.body.appendChild(ele); 7 | const props = { 8 | name: 'viking', 9 | age: 20 10 | }; 11 | render(, ele); 12 | -------------------------------------------------------------------------------- /chapter6/part1/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file main file for reducers 3 | */ 4 | 5 | import { combineReducers } from 'redux'; 6 | import items from './items'; 7 | import editor from './editor'; 8 | 9 | const rootReducer = combineReducers({ 10 | items, 11 | editor, 12 | }); 13 | 14 | export default rootReducer; 15 | -------------------------------------------------------------------------------- /online/app/components/CreateBar/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component CreateBar 3 | */ 4 | 5 | .list-component { 6 | .create-bar-component { 7 | background: #0275d8; 8 | color: #fff; 9 | &:hover { 10 | background: darken(#0275d8, 20%); 11 | color: #fff; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter2/part3/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = { 3 | entry: path.join(__dirname, 'index'), 4 | output: { 5 | path: __dirname, 6 | filename: 'bundle.js' 7 | }, 8 | module: { 9 | loaders: [ 10 | { 11 | test: /\.css$/, 12 | loaders: ['style', 'css'] 13 | } 14 | ] 15 | } 16 | }; -------------------------------------------------------------------------------- /chapter4/part1/app/components/CreateBar/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component CreateBar 3 | */ 4 | 5 | .list-component { 6 | .create-bar-component { 7 | background: #0275d8; 8 | color: #fff; 9 | &:hover { 10 | background: darken(#0275d8, 20%); 11 | color: #fff; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter6/part1/app/components/CreateBar/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component CreateBar 3 | */ 4 | 5 | .list-component { 6 | .create-bar-component { 7 | background: #0275d8; 8 | color: #fff; 9 | &:hover { 10 | background: darken(#0275d8, 20%); 11 | color: #fff; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter6/part2/app/components/CreateBar/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component CreateBar 3 | */ 4 | 5 | .list-component { 6 | .create-bar-component { 7 | background: #0275d8; 8 | color: #fff; 9 | &:hover { 10 | background: darken(#0275d8, 20%); 11 | color: #fff; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /online/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "parserOptions": { 5 | "ecmaFeatures": { 6 | "experimentalObjectRestSpread": true, 7 | "jsx": true 8 | }, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "no-use-before-define": [2, "nofunc"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter3/part3/app/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | function App() { 5 | return ( 6 |
    7 |

    Hello React!

    8 |
    9 | ); 10 | } 11 | 12 | const app = document.createElement('div'); 13 | document.body.appendChild(app); 14 | ReactDOM.render(, app); 15 | -------------------------------------------------------------------------------- /chapter4/part1/app/app.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file app main file 3 | */ 4 | 5 | import 'babel-polyfill'; 6 | import 'bootstrap/scss/bootstrap.scss'; 7 | 8 | import React from 'react'; 9 | import ReactDOM from 'react-dom'; 10 | import Deskmark from 'components/Deskmark'; 11 | 12 | const app = document.createElement('div'); 13 | document.body.appendChild(app); 14 | ReactDOM.render(, app); 15 | -------------------------------------------------------------------------------- /chapter6/part1/app/reducers/items.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers for items 3 | */ 4 | 5 | import { UPDATE_ENTRY_LIST } from '../actions'; 6 | 7 | const initialState = []; 8 | 9 | export default function items(state = initialState, action) { 10 | switch (action.type) { 11 | case UPDATE_ENTRY_LIST: 12 | return action.items; 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter5/part1/app/actions/TodoAction.js: -------------------------------------------------------------------------------- 1 | import AppDispatcher from '../dispatcher/AppDispatcher'; 2 | 3 | const TodoAction = { 4 | create(todo) { 5 | AppDispatcher.dispatch({ 6 | actionType: 'CREATE_TODO', 7 | todo 8 | }); 9 | }, 10 | delete(id) { 11 | AppDispatcher.dispatch({ 12 | actionType: 'DELETE_TODO', 13 | id 14 | }); 15 | } 16 | }; 17 | 18 | export default TodoAction; 19 | -------------------------------------------------------------------------------- /chapter5/part1/app/components/CreateButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const propTypes = { 4 | onClick: PropTypes.func.isRequired 5 | }; 6 | function CreateButton({ onClick }) { 7 | return ( 8 |
    9 | 10 |
    11 | ); 12 | } 13 | 14 | CreateButton.propTypes = propTypes; 15 | 16 | export default CreateButton; 17 | -------------------------------------------------------------------------------- /online/README.md: -------------------------------------------------------------------------------- 1 | # This is the online version of Deskmark 2 | 3 | ### [http://vikingmute.com/deskmark/](http://vikingmute.com/deskmark/) 4 | 5 | ### We are using the latest React(15.6), Webpack(3) & Bootstrap(4.beta) 6 | 7 | ### As firebase is almost blocked in China, we use leanCloud as an alternate solution 8 | 9 | **run webpack dev env** 10 | 11 | ```bash 12 | npm run dev 13 | ``` 14 | 15 | **build for production** 16 | 17 | ```bash 18 | npm run build 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /online/app/components/Loader/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import './style.scss'; 5 | 6 | function Loader({ backDrop = false }) { 7 | return ( 8 |
    9 | {(backDrop) ?
    : ''} 10 |
    11 |
    12 | ); 13 | } 14 | Loader.propTypes = { 15 | backDrop: PropTypes.bool.isRequired, 16 | }; 17 | export default Loader; 18 | -------------------------------------------------------------------------------- /chapter2/part4/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: path.join(__dirname, 'index'), 6 | output: { 7 | path: __dirname, 8 | filename: 'bundle.js' 9 | }, 10 | module: { 11 | loaders: [ 12 | { 13 | test: /\.css$/, 14 | loaders: ['style', 'css'] 15 | } 16 | ] 17 | }, 18 | plugins: [ 19 | new HtmlWebpackPlugin({ 20 | title: 'use plugin' 21 | }) 22 | ] 23 | }; -------------------------------------------------------------------------------- /chapter4/part1/app/components/CreateBar/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component CreateBar 3 | */ 4 | 5 | import './style.scss'; 6 | 7 | import React, { PropTypes } from 'react'; 8 | 9 | const propTypes = { 10 | onClick: PropTypes.func.isRequired, 11 | }; 12 | 13 | function CreateBar({ onClick }) { 14 | return ( 15 | 16 | + 创建新的文章 17 | 18 | ); 19 | } 20 | 21 | CreateBar.propTypes = propTypes; 22 | 23 | export default CreateBar; 24 | -------------------------------------------------------------------------------- /chapter6/part1/app/components/CreateBar/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component CreateBar 3 | */ 4 | 5 | import './style.scss'; 6 | 7 | import React, { PropTypes } from 'react'; 8 | 9 | const propTypes = { 10 | onClick: PropTypes.func.isRequired, 11 | }; 12 | 13 | function CreateBar({ onClick }) { 14 | return ( 15 | 16 | + 创建新的文章 17 | 18 | ); 19 | } 20 | 21 | CreateBar.propTypes = propTypes; 22 | 23 | export default CreateBar; 24 | -------------------------------------------------------------------------------- /chapter6/part2/app/components/CreateBar/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component CreateBar 3 | */ 4 | 5 | import './style.scss'; 6 | 7 | import React, { PropTypes } from 'react'; 8 | 9 | const propTypes = { 10 | onClick: PropTypes.func.isRequired, 11 | }; 12 | 13 | function CreateBar({ onClick }) { 14 | return ( 15 | 16 | + 创建新的文章 17 | 18 | ); 19 | } 20 | 21 | CreateBar.propTypes = propTypes; 22 | 23 | export default CreateBar; 24 | -------------------------------------------------------------------------------- /online/app/components/CreateBar/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component CreateBar 3 | */ 4 | 5 | import './style.scss'; 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | 10 | const propTypes = { 11 | onClick: PropTypes.func.isRequired, 12 | }; 13 | 14 | function CreateBar({ onClick }) { 15 | return ( 16 | 17 | + 创建新的文章 18 | 19 | ); 20 | } 21 | 22 | CreateBar.propTypes = propTypes; 23 | 24 | export default CreateBar; 25 | -------------------------------------------------------------------------------- /chapter5/part1/app/components/List.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const propType = { 4 | items: PropTypes.array.isRequired, 5 | onDelete: PropTypes.func.isRequired 6 | }; 7 | function List({ items, onDelete }) { 8 | let itemList = items.map(item => ( 9 |
  • 10 | 11 | {item.content} 12 |
  • 13 | )); 14 | return ( 15 |
      16 |

      这是今天的待办事项

      17 | {itemList} 18 |
    19 | ); 20 | } 21 | 22 | List.propTypes = propType; 23 | 24 | export default List; 25 | -------------------------------------------------------------------------------- /chapter4/part1/app/components/ItemShowLayer/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component ItemShowLayer 3 | */ 4 | 5 | .item-show-layer-component { 6 | padding: 10px; 7 | border-top: 1px solid #efefef; 8 | position: relative; 9 | margin-top: 10px; 10 | 11 | .no-select { 12 | font-size: 30px; 13 | height: 300px; 14 | line-height: 300px; 15 | color: #ccc; 16 | text-align: center; 17 | } 18 | 19 | .control-area { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | 24 | button { 25 | display: inline-block; 26 | margin: 0 10px; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter6/part1/app/components/ItemShowLayer/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component ItemShowLayer 3 | */ 4 | 5 | .item-show-layer-component { 6 | padding: 10px; 7 | border-top: 1px solid #efefef; 8 | position: relative; 9 | margin-top: 10px; 10 | 11 | .no-select { 12 | font-size: 30px; 13 | height: 300px; 14 | line-height: 300px; 15 | color: #ccc; 16 | text-align: center; 17 | } 18 | 19 | .control-area { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | 24 | button { 25 | display: inline-block; 26 | margin: 0 10px; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter6/part2/app/components/ItemShowLayer/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component ItemShowLayer 3 | */ 4 | 5 | .item-show-layer-component { 6 | padding: 10px; 7 | border-top: 1px solid #efefef; 8 | position: relative; 9 | margin-top: 10px; 10 | 11 | .no-select { 12 | font-size: 30px; 13 | height: 300px; 14 | line-height: 300px; 15 | color: #ccc; 16 | text-align: center; 17 | } 18 | 19 | .control-area { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | 24 | button { 25 | display: inline-block; 26 | margin: 0 10px; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /online/app/components/ItemShowLayer/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component ItemShowLayer 3 | */ 4 | 5 | .item-show-layer-component { 6 | padding: 10px; 7 | border-top: 1px solid #efefef; 8 | position: relative; 9 | margin-top: 10px; 10 | 11 | .no-select { 12 | font-size: 30px; 13 | height: 300px; 14 | line-height: 300px; 15 | color: #ccc; 16 | text-align: center; 17 | } 18 | 19 | .control-area { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | 24 | button { 25 | display: inline-block; 26 | margin: 0 10px; 27 | } 28 | } 29 | .item-text { 30 | margin-top: 30px; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter6/part2/app/utils/ajax.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file ajax helpers 3 | */ 4 | 5 | export function get(url) { 6 | return new Promise(function(resolve, reject) { 7 | const xhr = new XMLHttpRequest(); 8 | xhr.open('GET', url); 9 | xhr.send(null); 10 | xhr.addEventListener('readystatechange', function() { 11 | if (xhr.readyState === 4 && xhr.status === 200) { 12 | try { 13 | const data = JSON.parse(xhr.responseText); 14 | resolve(data); 15 | } catch (e) { 16 | reject(e); 17 | } 18 | } 19 | }); 20 | xhr.addEventListener('error', function(e) { 21 | reject(error); 22 | }); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /chapter4/part1/app/components/List/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component List 3 | */ 4 | 5 | import React, { PropTypes } from 'react'; 6 | import ListItem from '../ListItem'; 7 | 8 | const propTypes = { 9 | items: PropTypes.array.isRequired, 10 | onSelect: PropTypes.func.isRequired, 11 | }; 12 | 13 | function List({ items, onSelect }) { 14 | const itemsContent = items.map( 15 | item => ( 16 | onSelect(item.id)} 20 | /> 21 | ) 22 | ); 23 | 24 | return ( 25 |
    26 | {itemsContent} 27 |
    28 | ); 29 | } 30 | 31 | List.propTypes = propTypes; 32 | 33 | export default List; 34 | -------------------------------------------------------------------------------- /chapter6/part1/app/components/List/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component List 3 | */ 4 | 5 | import React, { PropTypes } from 'react'; 6 | import ListItem from 'components/ListItem'; 7 | 8 | const propTypes = { 9 | items: PropTypes.array.isRequired, 10 | onSelect: PropTypes.func.isRequired, 11 | }; 12 | 13 | function List({ items, onSelect }) { 14 | const itemsContent = items.map( 15 | item => ( 16 | onSelect(item.id)} 20 | /> 21 | ) 22 | ); 23 | 24 | return ( 25 |
    26 | {itemsContent} 27 |
    28 | ); 29 | } 30 | 31 | List.propTypes = propTypes; 32 | 33 | export default List; 34 | -------------------------------------------------------------------------------- /chapter6/part2/app/components/List/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component List 3 | */ 4 | 5 | import React, { PropTypes } from 'react'; 6 | import ListItem from 'components/ListItem'; 7 | 8 | const propTypes = { 9 | items: PropTypes.array.isRequired, 10 | onSelect: PropTypes.func.isRequired, 11 | }; 12 | 13 | function List({ items, onSelect }) { 14 | const itemsContent = items.map( 15 | item => ( 16 | onSelect(item.id)} 20 | /> 21 | ) 22 | ); 23 | 24 | return ( 25 |
    26 | {itemsContent} 27 |
    28 | ); 29 | } 30 | 31 | List.propTypes = propTypes; 32 | 33 | export default List; 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build files 2 | build 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | -------------------------------------------------------------------------------- /chapter6/part2/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "entries": [ 3 | { 4 | "title": "testing something fancy", 5 | "content": "# hello there title this hahaha\n*this is cool*\n* how about a new girl", 6 | "time": 1458193520873, 7 | "id": "6c84fb90-12c4-11e1-840d-7b25c5ee775b" 8 | }, 9 | { 10 | "id": "574db8da-cb40-4618-83ad-eb938260800d", 11 | "title": "look", 12 | "content": "this is good", 13 | "time": 1458114987491 14 | }, 15 | { 16 | "id": "50012045-ad14-4ff6-ba63-b2543bf5c35a", 17 | "title": "hey there", 18 | "content": "check out my new stuff", 19 | "time": 1458188680638 20 | } 21 | ], 22 | "users": [ 23 | { 24 | "id": 1, 25 | "name": "viking", 26 | "postId": 1 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /online/app/reducers/entries/list.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers for entries' list 3 | */ 4 | 5 | import * as ActionTypes from 'actions'; 6 | 7 | const initialState = { 8 | isFetching: false, 9 | data: [], 10 | }; 11 | 12 | const { pendingOf, fulfilledOf } = ActionTypes; 13 | 14 | export default function (state = initialState, action) { 15 | const { type, payload } = action; 16 | 17 | switch (type) { 18 | 19 | case pendingOf(ActionTypes.FETCH_ENTRY_LIST): 20 | return { 21 | ...state, 22 | isFetching: true, 23 | }; 24 | 25 | case fulfilledOf(ActionTypes.FETCH_ENTRY_LIST): 26 | return { 27 | ...state, 28 | isFetching: false, 29 | data: payload, 30 | }; 31 | 32 | default: 33 | return state; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter6/part2/app/reducers/entries/list.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers for entries' list 3 | */ 4 | 5 | import * as ActionTypes from 'actions'; 6 | 7 | const initialState = { 8 | isFetching: false, 9 | data: [], 10 | }; 11 | 12 | const { pendingOf, fulfilledOf } = ActionTypes; 13 | 14 | export default function (state = initialState, action) { 15 | const { type, payload } = action; 16 | 17 | switch (type) { 18 | 19 | case pendingOf(ActionTypes.FETCH_ENTRY_LIST): 20 | return { 21 | ...state, 22 | isFetching: true, 23 | }; 24 | 25 | case fulfilledOf(ActionTypes.FETCH_ENTRY_LIST): 26 | return { 27 | ...state, 28 | isFetching: false, 29 | data: payload, 30 | }; 31 | 32 | default: 33 | return state; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter6/part1/app/components/ListItem/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component Item 3 | */ 4 | 5 | import React, { PropTypes } from 'react'; 6 | 7 | const propTypes = { 8 | item: PropTypes.object.isRequired, 9 | onClick: PropTypes.func.isRequired, 10 | }; 11 | 12 | function ListItem({ item, onClick }) { 13 | let formatTime = '未知时间'; 14 | if (item.time) { 15 | formatTime = new Date(item.time).toISOString().match(/(\d{4}-\d{2}-\d{2})/)[1]; 16 | } 17 | return ( 18 | 23 | 24 | {formatTime} 25 | 26 | {item.title} 27 | 28 | ); 29 | } 30 | 31 | ListItem.propTypes = propTypes; 32 | 33 | export default ListItem; 34 | -------------------------------------------------------------------------------- /chapter6/part2/app/components/ListItem/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component Item 3 | */ 4 | 5 | import React, { PropTypes } from 'react'; 6 | 7 | const propTypes = { 8 | item: PropTypes.object.isRequired, 9 | onClick: PropTypes.func.isRequired, 10 | }; 11 | 12 | function ListItem({ item, onClick }) { 13 | let formatTime = '未知时间'; 14 | if (item.time) { 15 | formatTime = new Date(item.time).toISOString().match(/(\d{4}-\d{2}-\d{2})/)[1]; 16 | } 17 | return ( 18 | 23 | 24 | {formatTime} 25 | 26 | {item.title} 27 | 28 | ); 29 | } 30 | 31 | ListItem.propTypes = propTypes; 32 | 33 | export default ListItem; 34 | -------------------------------------------------------------------------------- /chapter4/part1/app/components/ListItem/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component Item 3 | */ 4 | 5 | import React, { PropTypes } from 'react'; 6 | 7 | const propTypes = { 8 | item: PropTypes.object.isRequired, 9 | onClick: PropTypes.func.isRequired, 10 | }; 11 | 12 | function ListItem({ item, onClick }) { 13 | let formatTime = '未知时间'; 14 | if (item.time) { 15 | formatTime = new Date(item.time).toISOString().match(/(\d{4}-\d{2}-\d{2})/)[1]; 16 | } 17 | return ( 18 | 23 | 24 | {formatTime} 25 | 26 | {item.title} 27 | 28 | ); 29 | } 30 | 31 | ListItem.propTypes = propTypes; 32 | 33 | export default ListItem; 34 | -------------------------------------------------------------------------------- /chapter5/part2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "part2", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "dev": "webpack-dev-server --hot" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel-core": "^6.9.1", 15 | "babel-loader": "^6.2.4", 16 | "babel-preset-es2015": "^6.9.0", 17 | "eslint": "^2.13.0", 18 | "eslint-config-airbnb": "^9.0.1", 19 | "eslint-loader": "^1.3.0", 20 | "eslint-plugin-import": "^1.8.1", 21 | "eslint-plugin-jsx-a11y": "^1.5.3", 22 | "eslint-plugin-react": "^5.2.2", 23 | "html-webpack-plugin": "^2.21.0", 24 | "webpack": "^1.13.1", 25 | "webpack-dev-server": "^1.14.1" 26 | }, 27 | "dependencies": { 28 | "handlebars": "^4.0.5", 29 | "redux": "^3.5.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /online/app/components/Loader/style.scss: -------------------------------------------------------------------------------- 1 | $base-line-height: 35px; 2 | 3 | $white: #007bff; 4 | $off-white: rgba($white, 0.2); 5 | $spin-duration: 1s; 6 | $pulse-duration: 750ms; 7 | 8 | @keyframes spin { 9 | 0% { 10 | transform: rotate(0deg); 11 | } 12 | 100% { 13 | transform: rotate(360deg); 14 | } 15 | } 16 | 17 | .backdrop { 18 | position: fixed; 19 | width: 100%; 20 | height: 100%; 21 | background: rgba(255, 255, 255, 0.7); 22 | z-index: 50; 23 | } 24 | .loader { 25 | position: absolute; 26 | z-index: 51; 27 | top: 35%; 28 | left: 50%; 29 | border-radius: 50%; 30 | width: $base-line-height; 31 | height: $base-line-height; 32 | border: .25rem solid $off-white; 33 | border-top-color: $white; 34 | animation: spin $spin-duration infinite linear; 35 | &--double { 36 | border-style: double; 37 | border-width: .5rem; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /online/app/components/List/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component List 3 | */ 4 | 5 | import React from 'react'; 6 | import PropTypes from 'prop-types'; 7 | import ListItem from 'components/ListItem'; 8 | 9 | const propTypes = { 10 | items: PropTypes.array.isRequired, 11 | onSelect: PropTypes.func.isRequired, 12 | selectedId: PropTypes.string, 13 | }; 14 | 15 | function List({ items, onSelect, selectedId }) { 16 | const itemsContent = items.map( 17 | item => { 18 | const selected = item.objectId === selectedId; 19 | return ( 20 | onSelect(item.objectId)} 25 | /> 26 | ); 27 | } 28 | ); 29 | 30 | return ( 31 |
    32 | {itemsContent} 33 |
    34 | ); 35 | } 36 | 37 | List.propTypes = propTypes; 38 | 39 | export default List; 40 | -------------------------------------------------------------------------------- /chapter3/part3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "part3", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "dev": "webpack-dev-server --hot" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel-core": "^6.9.1", 15 | "babel-loader": "^6.2.4", 16 | "babel-preset-es2015": "^6.9.0", 17 | "babel-preset-react": "^6.5.0", 18 | "babel-preset-react-hmre": "^1.1.1", 19 | "eslint": "^2.13.0", 20 | "eslint-config-airbnb": "^9.0.1", 21 | "eslint-loader": "^1.3.0", 22 | "eslint-plugin-import": "^1.8.1", 23 | "eslint-plugin-jsx-a11y": "^1.5.3", 24 | "eslint-plugin-react": "^5.2.2", 25 | "html-webpack-plugin": "^2.21.0", 26 | "webpack": "^1.13.1", 27 | "webpack-dev-server": "^1.14.1" 28 | }, 29 | "dependencies": { 30 | "react": "^15.1.0", 31 | "react-dom": "^15.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /online/app/components/ItemEditor/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component ItemEditor 3 | */ 4 | 5 | .item-editor-component { 6 | padding: 10px; 7 | border-top: 1px solid #efefef; 8 | position: relative; 9 | margin-top: 10px; 10 | 11 | .control-area { 12 | position: absolute; 13 | top: 0; 14 | right: 0; 15 | 16 | button { 17 | display: inline-block; 18 | margin: 0 10px; 19 | } 20 | } 21 | 22 | .edit-area { 23 | 24 | input, textarea{ 25 | border: 1px solid #efefef; 26 | border-radius: 5px; 27 | display: block; 28 | } 29 | 30 | input { 31 | padding:0 10px; 32 | line-height: 40px; 33 | height: 40px; 34 | font-weight: bold; 35 | font-size: 18px; 36 | width: 60%; 37 | margin-bottom: 10px; 38 | } 39 | 40 | textarea { 41 | padding: 10px; 42 | width: 100%; 43 | line-height: 1.4em; 44 | height: 300px; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chapter4/part1/app/components/ItemEditor/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component ItemEditor 3 | */ 4 | 5 | .item-editor-component { 6 | padding: 10px; 7 | border-top: 1px solid #efefef; 8 | position: relative; 9 | margin-top: 10px; 10 | 11 | .control-area { 12 | position: absolute; 13 | top: 0; 14 | right: 0; 15 | 16 | button { 17 | display: inline-block; 18 | margin: 0 10px; 19 | } 20 | } 21 | 22 | .edit-area { 23 | 24 | input, textarea{ 25 | border: 1px solid #efefef; 26 | border-radius: 5px; 27 | display: block; 28 | } 29 | 30 | input { 31 | padding:0 10px; 32 | line-height: 40px; 33 | height: 40px; 34 | font-weight: bold; 35 | font-size: 18px; 36 | width: 60%; 37 | margin-bottom: 10px; 38 | } 39 | 40 | textarea { 41 | padding: 10px; 42 | width: 60%; 43 | line-height: 1.4em; 44 | height: 220px; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chapter6/part1/app/components/ItemEditor/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component ItemEditor 3 | */ 4 | 5 | .item-editor-component { 6 | padding: 10px; 7 | border-top: 1px solid #efefef; 8 | position: relative; 9 | margin-top: 10px; 10 | 11 | .control-area { 12 | position: absolute; 13 | top: 0; 14 | right: 0; 15 | 16 | button { 17 | display: inline-block; 18 | margin: 0 10px; 19 | } 20 | } 21 | 22 | .edit-area { 23 | 24 | input, textarea{ 25 | border: 1px solid #efefef; 26 | border-radius: 5px; 27 | display: block; 28 | } 29 | 30 | input { 31 | padding:0 10px; 32 | line-height: 40px; 33 | height: 40px; 34 | font-weight: bold; 35 | font-size: 18px; 36 | width: 60%; 37 | margin-bottom: 10px; 38 | } 39 | 40 | textarea { 41 | padding: 10px; 42 | width: 60%; 43 | line-height: 1.4em; 44 | height: 220px; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chapter6/part2/app/components/ItemEditor/style.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @file style for component ItemEditor 3 | */ 4 | 5 | .item-editor-component { 6 | padding: 10px; 7 | border-top: 1px solid #efefef; 8 | position: relative; 9 | margin-top: 10px; 10 | 11 | .control-area { 12 | position: absolute; 13 | top: 0; 14 | right: 0; 15 | 16 | button { 17 | display: inline-block; 18 | margin: 0 10px; 19 | } 20 | } 21 | 22 | .edit-area { 23 | 24 | input, textarea{ 25 | border: 1px solid #efefef; 26 | border-radius: 5px; 27 | display: block; 28 | } 29 | 30 | input { 31 | padding:0 10px; 32 | line-height: 40px; 33 | height: 40px; 34 | font-weight: bold; 35 | font-size: 18px; 36 | width: 60%; 37 | margin-bottom: 10px; 38 | } 39 | 40 | textarea { 41 | padding: 10px; 42 | width: 60%; 43 | line-height: 1.4em; 44 | height: 220px; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chapter5/part1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "part1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "dev": "webpack-dev-server --hot" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel-core": "^6.9.1", 15 | "babel-loader": "^6.2.4", 16 | "babel-preset-es2015": "^6.9.0", 17 | "babel-preset-react": "^6.5.0", 18 | "babel-preset-react-hmre": "^1.1.1", 19 | "eslint": "^2.13.0", 20 | "eslint-config-airbnb": "^9.0.1", 21 | "eslint-loader": "^1.3.0", 22 | "eslint-plugin-import": "^1.8.1", 23 | "eslint-plugin-jsx-a11y": "^1.5.3", 24 | "eslint-plugin-react": "^5.2.2", 25 | "html-webpack-plugin": "^2.21.0", 26 | "webpack": "^1.13.1", 27 | "webpack-dev-server": "^1.14.1" 28 | }, 29 | "dependencies": { 30 | "events": "^1.1.0", 31 | "flux": "^2.1.1", 32 | "object-assign": "^4.1.0", 33 | "react": "^15.1.0", 34 | "react-dom": "^15.1.0", 35 | "uuid": "^2.0.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter6/part1/app/reducers/editor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers for editor state 3 | */ 4 | 5 | import * as ActionTypes from '../actions'; 6 | 7 | const initialState = { 8 | selectedId: null, 9 | isEditing: false, 10 | }; 11 | 12 | export default function editor(state = initialState, action) { 13 | switch (action.type) { 14 | case ActionTypes.SELECT_ENTRY: 15 | return Object.assign({}, state, { selectedId: action.id }); 16 | case ActionTypes.UPDATE_SAVED_ENTRY: 17 | return Object.assign({}, state, { selectedId: action.id, isEditing: false }); 18 | case ActionTypes.CREATE_NEW_ENTRY: 19 | return Object.assign({}, state, { selectedId: null, isEditing: true }); 20 | case ActionTypes.EDIT_ENTRY: 21 | return Object.assign({}, state, { selectedId: action.id, isEditing: true }); 22 | case ActionTypes.FINISH_DELETE_ENTRY: 23 | return Object.assign({}, state, { selectedId: null, isEditing: false }); 24 | case ActionTypes.CANCEL_EDIT: 25 | return Object.assign({}, state, { isEditing: false }); 26 | default: 27 | return state; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webpack-react-codes 2 | 3 | 这个 repo 是随书附带的所有代码,会根据读者发现的问题做一些维护,因此若在执行时遇到问题,可以先将代码本身更新为最新的版本,看问题是否得到解决。 4 | 5 | 运行前请先装好 Node.js 和 npm。(本书写作时使用 Node.js v5.0.0,考虑到 Node.js 版本更迭较快,若发现问题,报告时麻烦附上 Node.js 及 npm 版本信息,可以帮助定位问题) 6 | 7 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/webpack-react-codes/Lobby) 8 | 9 | ### 在线环境 10 | 11 | 我们提供了 Deskmark 的在线环境 [http://vikingmute.com/deskmark/](http://vikingmute.com/deskmark/) 读者可以在上面随意操作,该环境的源代码在 /online 文件夹下。 12 | 13 | **因时间精力有限,书中难免存在错误或不严谨的部分,如发现欢迎提给我们,以 Pull Request 的形式将错误描述直接添加到本仓库中的[勘误表](./corrigendum.md)就更好了,谢谢!** 14 | 15 | * 第二章 Webpack 的示例代码 16 | * part1 简单的 hello world 17 | * part2 使用 Loaders 18 | * part3 使用 webpack 配置文件 19 | * part4 使用 plugins 20 | * 第三章 21 | * part3 搭建 React + webpack 的开发环境 22 | * part4 React 组件简单例子: 制作自己的介绍页面 23 | * 第四章 24 | * part1 Deskmark 应用的实现 25 | * 第五章 26 | * part1 使用 Flux 架构完成一个 Todo List 27 | * part2 使用 Redux 和 Handlebars 完成一个简单数据状态变化的页面 28 | * 第六章 29 | * part1 使用 React + Redux 完成 Deskmark 的改造 30 | * part2 使用 React + Redux + redux-promise-middleware 实现 Deskmark 的异步请求 31 | * 勘误(corrigendum) 32 | * 全书勘误表 -------------------------------------------------------------------------------- /chapter6/part1/app/app.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file main file for app deskmark 3 | */ 4 | 5 | import React from 'react'; 6 | import { render } from 'react-dom'; 7 | import { bindActionCreators, createStore, applyMiddleware } from 'redux'; 8 | import { connect, Provider } from 'react-redux'; 9 | import thunkMiddleware from 'redux-thunk'; 10 | 11 | import Deskmark from 'components/Deskmark'; 12 | import rootReducer from 'reducers'; 13 | import * as actionCreators from 'actions'; 14 | 15 | import 'bootstrap/scss/bootstrap.scss'; 16 | 17 | // create store with middlewares 18 | const store = applyMiddleware( 19 | thunkMiddleware 20 | )(createStore)(rootReducer); 21 | 22 | // create root component based on component Deskmark 23 | const App = connect( 24 | state => ({ state }), 25 | dispatch => ({ 26 | actions: bindActionCreators(actionCreators, dispatch), 27 | }) 28 | )(Deskmark); 29 | 30 | // create DOM container 31 | const container = document.body.appendChild( 32 | document.createElement('div') 33 | ); 34 | 35 | // render root conponent with store to DOM container 36 | render( 37 | 38 | 39 | , 40 | container 41 | ); 42 | -------------------------------------------------------------------------------- /online/app/components/ListItem/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component Item 3 | */ 4 | 5 | import React, { PropTypes } from 'react'; 6 | import classNames from 'classnames'; 7 | const propTypes = { 8 | item: PropTypes.object.isRequired, 9 | onClick: PropTypes.func.isRequired, 10 | selected: PropTypes.bool.isRequired, 11 | }; 12 | 13 | function ListItem({ item, onClick, selected }) { 14 | let formatTime = '未知时间'; 15 | if (item.time) { 16 | formatTime = new Date(item.time).toISOString().match(/(\d{4}-\d{2}-\d{2})/)[1]; 17 | } 18 | const liKlass = classNames({ 19 | 'list-group-item d-flex justify-content-between align-items-center item-component': true, 20 | active: selected, 21 | }); 22 | const spanKlass = classNames({ 23 | 'badge badge-pill': true, 24 | 'badge-secondary': !selected, 25 | 'badge-light': selected, 26 | }); 27 | return ( 28 | 33 | {item.title} 34 | 35 | {formatTime} 36 | 37 | 38 | ); 39 | } 40 | 41 | ListItem.propTypes = propTypes; 42 | 43 | export default ListItem; 44 | -------------------------------------------------------------------------------- /online/app/utils/leancloud.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage'; 2 | 3 | const APP_ID = '3beldflev5pnsYjPjr9lCucK-gzGzoHsz'; 4 | const APP_KEY = '4yxrNDznib4EtjRSHek2O0K5'; 5 | 6 | AV.init({ 7 | appId: APP_ID, 8 | appKey: APP_KEY, 9 | }); 10 | 11 | const Entry = AV.Object.extend('Entry'); 12 | 13 | export function getAll() { 14 | return new AV.Query(Entry).find().then(entries => entries.map(entry => entry.toJSON()) 15 | ); 16 | } 17 | 18 | export function getEntry(id) { 19 | return new AV.Query(Entry).get(id).then(entry => entry.toJSON()); 20 | } 21 | 22 | export function insertEntry(title, content) { 23 | const summary = { 24 | title, 25 | time: new Date().getTime(), 26 | content, 27 | }; 28 | return new Entry(summary).save().then(entry => entry.toJSON()); 29 | } 30 | 31 | export function deleteEntry(id) { 32 | const selectedEntry = AV.Object.createWithoutData('Entry', id); 33 | return selectedEntry.destroy(); 34 | } 35 | 36 | export function updateEntry(id, title, content) { 37 | const entry = AV.Object.createWithoutData('Entry', id); 38 | entry.set('title', title); 39 | entry.set('content', content); 40 | return entry.save().then(updatedEntry => updatedEntry.toJSON()); 41 | } 42 | -------------------------------------------------------------------------------- /chapter3/part4/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var ROOT_PATH = path.resolve(__dirname); 6 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 7 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 8 | module.exports= { 9 | entry: { 10 | app: path.resolve(APP_PATH, 'app.jsx') 11 | }, 12 | output: { 13 | path: BUILD_PATH, 14 | filename: 'bundle.js' 15 | }, 16 | 17 | //enable dev source map 18 | devtool: 'eval-source-map', 19 | //enable dev server 20 | devServer: { 21 | historyApiFallback: true, 22 | hot: true, 23 | inline: true, 24 | progress: true 25 | }, 26 | resolve: { 27 | extensions: ['', '.js', '.jsx'], 28 | root: APP_PATH 29 | }, 30 | module: { 31 | preLoaders: [ 32 | { 33 | test: /\.jsx?$/, 34 | loaders: ['eslint'], 35 | include: APP_PATH 36 | } 37 | ], 38 | loaders: [ 39 | { 40 | test: /\.jsx?$/, 41 | loaders: ['babel'], 42 | include: APP_PATH 43 | } ] 44 | }, 45 | plugins: [ 46 | new HtmlwebpackPlugin({ 47 | title: 'Profile app' 48 | }) 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /chapter3/part3/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var ROOT_PATH = path.resolve(__dirname); 6 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 7 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 8 | module.exports= { 9 | entry: { 10 | app: path.resolve(APP_PATH, 'app.jsx') 11 | }, 12 | output: { 13 | path: BUILD_PATH, 14 | filename: 'bundle.js' 15 | }, 16 | 17 | //enable dev source map 18 | devtool: 'eval-source-map', 19 | //enable dev server 20 | devServer: { 21 | historyApiFallback: true, 22 | hot: true, 23 | inline: true, 24 | progress: true 25 | }, 26 | resolve: { 27 | extensions: ['', '.js', '.jsx'], 28 | root: APP_PATH 29 | }, 30 | module: { 31 | preLoaders: [ 32 | { 33 | test: /\.jsx?$/, 34 | loaders: ['eslint'], 35 | include: APP_PATH 36 | } 37 | ], 38 | loaders: [ 39 | { 40 | test: /\.jsx?$/, 41 | loaders: ['babel'], 42 | include: APP_PATH 43 | } ] 44 | }, 45 | plugins: [ 46 | new HtmlwebpackPlugin({ 47 | title: 'My first react app' 48 | }) 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /chapter5/part1/app/stores/TodoStore.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events'; 2 | import assign from 'object-assign'; 3 | import AppDispatcher from '../dispatcher/AppDispatcher'; 4 | import uuid from 'uuid'; 5 | 6 | const TodoStore = assign({}, EventEmitter.prototype, { 7 | todos: [{ id: uuid.v4(), content: 'first one' }, { id: uuid.v4(), content: '2nd one' }], 8 | getAll() { 9 | return this.todos; 10 | }, 11 | addTodo(todo) { 12 | this.todos.push(todo); 13 | }, 14 | deleteTodo(id) { 15 | this.todos = this.todos.filter(item => item.id !== id); 16 | }, 17 | emitChange() { 18 | this.emit('change'); 19 | }, 20 | addChangeListener(callback) { 21 | this.on('change', callback); 22 | }, 23 | removeChangeListener(callback) { 24 | this.removeListener('change', callback); 25 | } 26 | }); 27 | 28 | AppDispatcher.register((action) => { 29 | switch (action.actionType) { 30 | case 'CREATE_TODO': 31 | TodoStore.addTodo(action.todo); 32 | TodoStore.emitChange(); 33 | break; 34 | case 'DELETE_TODO': 35 | TodoStore.deleteTodo(action.id); 36 | TodoStore.emitChange(); 37 | break; 38 | default: 39 | // nothing to do here 40 | 41 | } 42 | }); 43 | export default TodoStore; 44 | -------------------------------------------------------------------------------- /chapter5/part1/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var ROOT_PATH = path.resolve(__dirname); 6 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 7 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 8 | module.exports= { 9 | entry: { 10 | app: path.resolve(APP_PATH, 'app.jsx') 11 | }, 12 | output: { 13 | path: BUILD_PATH, 14 | filename: 'bundle.js' 15 | }, 16 | 17 | //enable dev source map 18 | devtool: 'eval-source-map', 19 | //enable dev server 20 | devServer: { 21 | historyApiFallback: true, 22 | hot: true, 23 | inline: true, 24 | progress: true 25 | }, 26 | resolve: { 27 | extensions: ['', '.js', '.jsx'], 28 | root: APP_PATH 29 | }, 30 | module: { 31 | preLoaders: [ 32 | { 33 | test: /\.jsx?$/, 34 | loaders: ['eslint'], 35 | include: APP_PATH 36 | } 37 | ], 38 | loaders: [ 39 | { 40 | test: /\.jsx?$/, 41 | loaders: ['babel'], 42 | include: APP_PATH 43 | } ] 44 | }, 45 | plugins: [ 46 | new HtmlwebpackPlugin({ 47 | title: 'My first react+flux todo app' 48 | }) 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /online/app/app.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file main file for app deskmark 3 | */ 4 | 5 | import React from 'react'; 6 | import { render } from 'react-dom'; 7 | import { bindActionCreators, createStore, applyMiddleware } from 'redux'; 8 | import { connect, Provider } from 'react-redux'; 9 | import thunkMiddleware from 'redux-thunk'; 10 | import promiseMiddleware from 'redux-promise-middleware'; 11 | 12 | import Deskmark from 'components/Deskmark'; 13 | import rootReducer from 'reducers'; 14 | import * as actionCreators from 'actions'; 15 | 16 | import 'bootstrap/scss/bootstrap.scss'; 17 | 18 | // create store with middlewares 19 | const store = applyMiddleware( 20 | thunkMiddleware, 21 | promiseMiddleware() 22 | )(createStore)(rootReducer); 23 | 24 | // create root component based on component Deskmark 25 | const App = connect( 26 | state => ({ state }), 27 | dispatch => ({ 28 | actions: bindActionCreators(actionCreators, dispatch), 29 | }) 30 | )(Deskmark); 31 | 32 | // create DOM container 33 | const container = document.body.appendChild( 34 | document.createElement('div') 35 | ); 36 | 37 | // render root conponent with store to DOM container 38 | render( 39 | 40 | 41 | , 42 | container 43 | ); 44 | -------------------------------------------------------------------------------- /chapter6/part2/app/app.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file main file for app deskmark 3 | */ 4 | 5 | import React from 'react'; 6 | import { render } from 'react-dom'; 7 | import { bindActionCreators, createStore, applyMiddleware } from 'redux'; 8 | import { connect, Provider } from 'react-redux'; 9 | import thunkMiddleware from 'redux-thunk'; 10 | import promiseMiddleware from 'redux-promise-middleware'; 11 | 12 | import Deskmark from 'components/Deskmark'; 13 | import rootReducer from 'reducers'; 14 | import * as actionCreators from 'actions'; 15 | 16 | import 'bootstrap/scss/bootstrap.scss'; 17 | 18 | // create store with middlewares 19 | const store = applyMiddleware( 20 | thunkMiddleware, 21 | promiseMiddleware() 22 | )(createStore)(rootReducer); 23 | 24 | // create root component based on component Deskmark 25 | const App = connect( 26 | state => ({ state }), 27 | dispatch => ({ 28 | actions: bindActionCreators(actionCreators, dispatch), 29 | }) 30 | )(Deskmark); 31 | 32 | // create DOM container 33 | const container = document.body.appendChild( 34 | document.createElement('div') 35 | ); 36 | 37 | // render root conponent with store to DOM container 38 | render( 39 | 40 | 41 | , 42 | container 43 | ); 44 | -------------------------------------------------------------------------------- /chapter5/part2/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var ROOT_PATH = path.resolve(__dirname); 6 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 7 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 8 | module.exports= { 9 | entry: { 10 | app: path.resolve(APP_PATH, 'app.js') 11 | }, 12 | output: { 13 | path: BUILD_PATH, 14 | filename: 'bundle.js' 15 | }, 16 | node: { 17 | fs: "empty" 18 | }, 19 | //enable dev source map 20 | devtool: 'eval-source-map', 21 | //enable dev server 22 | devServer: { 23 | historyApiFallback: true, 24 | hot: true, 25 | inline: true, 26 | progress: true 27 | }, 28 | resolve: { 29 | extensions: ['', '.js', '.jsx'], 30 | root: APP_PATH 31 | }, 32 | module: { 33 | preLoaders: [ 34 | { 35 | test: /\.jsx?$/, 36 | loaders: ['eslint'], 37 | include: APP_PATH 38 | } 39 | ], 40 | loaders: [ 41 | { 42 | test: /\.jsx?$/, 43 | loaders: ['babel'], 44 | include: APP_PATH 45 | } ] 46 | }, 47 | plugins: [ 48 | new HtmlwebpackPlugin({ 49 | title: 'My first redux app' 50 | }) 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /chapter5/part1/app/components/Todo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import List from './List'; 3 | import CreateButton from './CreateButton'; 4 | import TodoStore from '../stores/TodoStore'; 5 | import TodoAction from '../actions/TodoAction'; 6 | import uuid from 'uuid'; 7 | 8 | class Todo extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | todos: TodoStore.getAll() 13 | }; 14 | this.createTodo = this.createTodo.bind(this); 15 | this.deleteTodo = this.deleteTodo.bind(this); 16 | this.onChange = this.onChange.bind(this); 17 | } 18 | componentDidMount() { 19 | TodoStore.addChangeListener(this.onChange); 20 | } 21 | componentWillUnmount() { 22 | TodoStore.removeChangeListener(this.onChange); 23 | } 24 | onChange() { 25 | this.setState({ 26 | todos: TodoStore.getAll() 27 | }); 28 | } 29 | createTodo() { 30 | TodoAction.create({ id: uuid.v4(), content: '3rd stuff' }); 31 | } 32 | deleteTodo(id) { 33 | TodoAction.delete(id); 34 | } 35 | render() { 36 | return ( 37 |
    38 | 39 | 40 |
    41 | ); 42 | } 43 | } 44 | 45 | export default Todo; 46 | -------------------------------------------------------------------------------- /chapter4/part1/app/components/ItemShowLayer/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component ItemShowLayer 3 | */ 4 | 5 | /* eslint react/no-danger: 0 */ 6 | 7 | import './style.scss'; 8 | 9 | import React, { PropTypes } from 'react'; 10 | import marked from 'marked'; 11 | 12 | const propTypes = { 13 | item: PropTypes.object, 14 | onEdit: PropTypes.func.isRequired, 15 | onDelete: PropTypes.func.isRequired, 16 | }; 17 | 18 | function ItemShowLayer({ item, onEdit, onDelete }) { 19 | if (!item || !item.id) { 20 | return ( 21 |
    22 |
    请选择左侧列表里面的文章
    23 |
    24 | ); 25 | } 26 | 27 | const content = marked(item.content); 28 | 29 | return ( 30 |
    31 |
    32 | 33 | 34 |
    35 |

    {item.title}

    36 |
    37 |
    38 |
    39 |
    40 | ); 41 | } 42 | 43 | ItemShowLayer.propTypes = propTypes; 44 | 45 | export default ItemShowLayer; 46 | -------------------------------------------------------------------------------- /chapter6/part1/app/components/ItemShowLayer/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component ItemShowLayer 3 | */ 4 | 5 | /* eslint react/no-danger: 0 */ 6 | 7 | import './style.scss'; 8 | 9 | import React, { PropTypes } from 'react'; 10 | import marked from 'marked'; 11 | 12 | const propTypes = { 13 | item: PropTypes.object, 14 | onEdit: PropTypes.func.isRequired, 15 | onDelete: PropTypes.func.isRequired, 16 | }; 17 | 18 | function ItemShowLayer({ item, onEdit, onDelete }) { 19 | if (!item || !item.id) { 20 | return ( 21 |
    22 |
    请选择左侧列表里面的文章
    23 |
    24 | ); 25 | } 26 | 27 | const content = marked(item.content); 28 | 29 | return ( 30 |
    31 |
    32 | 33 | 34 |
    35 |

    {item.title}

    36 |
    37 |
    38 |
    39 |
    40 | ); 41 | } 42 | 43 | ItemShowLayer.propTypes = propTypes; 44 | 45 | export default ItemShowLayer; 46 | -------------------------------------------------------------------------------- /chapter6/part2/app/components/ItemShowLayer/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component ItemShowLayer 3 | */ 4 | 5 | /* eslint react/no-danger: 0 */ 6 | 7 | import './style.scss'; 8 | 9 | import React, { PropTypes } from 'react'; 10 | import marked from 'marked'; 11 | 12 | const propTypes = { 13 | item: PropTypes.object, 14 | onEdit: PropTypes.func.isRequired, 15 | onDelete: PropTypes.func.isRequired, 16 | }; 17 | 18 | function ItemShowLayer({ item, onEdit, onDelete }) { 19 | if (!item || !item.id) { 20 | return ( 21 |
    22 |
    请选择左侧列表里面的文章
    23 |
    24 | ); 25 | } 26 | 27 | const content = marked(item.content); 28 | 29 | return ( 30 |
    31 |
    32 | 33 | 34 |
    35 |

    {item.title}

    36 |
    37 |
    38 |
    39 |
    40 | ); 41 | } 42 | 43 | ItemShowLayer.propTypes = propTypes; 44 | 45 | export default ItemShowLayer; 46 | -------------------------------------------------------------------------------- /chapter3/part4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "profile", 3 | "version": "1.0.0", 4 | "description": "Our first test project", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "dev": "webpack-dev-server --hot" 9 | }, 10 | "directories": { 11 | "test": "test" 12 | }, 13 | "keywords": [ 14 | "javascript", 15 | "react", 16 | "webpack" 17 | ], 18 | "author": "vikingmute", 19 | "license": "MIT", 20 | "homepage": "", 21 | "engines": { 22 | "node": ">=0.12.0" 23 | }, 24 | "devDependencies": { 25 | "babel-core": "^6.3.15", 26 | "babel-loader": "^6.2.0", 27 | "babel-plugin-transform-object-rest-spread": "^6.5.0", 28 | "babel-preset-es2015": "^6.3.13", 29 | "babel-preset-react": "^6.3.13", 30 | "babel-preset-react-hmre": "^1.1.1", 31 | "eslint": "^2.12.0", 32 | "eslint-config-airbnb": "^9.0.1", 33 | "eslint-loader": "^1.3.0", 34 | "eslint-plugin-import": "^1.8.1", 35 | "eslint-plugin-jsx-a11y": "^1.5.3", 36 | "eslint-plugin-react": "^5.2.1", 37 | "expect": "^1.13.4", 38 | "html-webpack-plugin": "^2.21.0", 39 | "webpack": "^1.12.9", 40 | "webpack-dev-server": "^1.14.1" 41 | }, 42 | "dependencies": { 43 | "babel-polyfill": "^6.6.1", 44 | "react": "^15.0.1", 45 | "react-dom": "^15.0.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chapter4/part1/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var ROOT_PATH = path.resolve(__dirname); 6 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 7 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 8 | module.exports= { 9 | entry: { 10 | app: path.resolve(APP_PATH, 'app.jsx') 11 | }, 12 | output: { 13 | path: BUILD_PATH, 14 | filename: 'bundle.js' 15 | }, 16 | 17 | //enable dev source map 18 | devtool: 'eval-source-map', 19 | //enable dev server 20 | devServer: { 21 | historyApiFallback: true, 22 | hot: true, 23 | inline: true, 24 | progress: true 25 | }, 26 | resolve: { 27 | extensions: ['', '.js', '.jsx'], 28 | root: APP_PATH 29 | }, 30 | module: { 31 | preLoaders: [ 32 | { 33 | test: /\.jsx?$/, 34 | loaders: ['eslint'], 35 | include: APP_PATH 36 | } 37 | ], 38 | loaders: [ 39 | { 40 | test: /\.jsx?$/, 41 | loaders: ['babel'], 42 | include: APP_PATH 43 | }, 44 | { 45 | test: /\.scss$/, 46 | loaders: ['style', 'css', 'sass'] 47 | } 48 | ] 49 | }, 50 | plugins: [ 51 | new HtmlwebpackPlugin({ 52 | title: 'Deskmark app' 53 | }) 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /chapter6/part1/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var ROOT_PATH = path.resolve(__dirname); 6 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 7 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 8 | 9 | module.exports= { 10 | entry: { 11 | app: path.resolve(APP_PATH, 'app.jsx') 12 | }, 13 | output: { 14 | path: BUILD_PATH, 15 | filename: 'bundle.js' 16 | }, 17 | //enable dev source map 18 | devtool: 'eval-source-map', 19 | //enable dev server 20 | devServer: { 21 | historyApiFallback: true, 22 | hot: true, 23 | inline: true, 24 | progress: true 25 | }, 26 | resolve: { 27 | extensions: ['', '.js', '.jsx'], 28 | root: APP_PATH 29 | }, 30 | module: { 31 | preLoaders: [ 32 | { 33 | test: /\.jsx?$/, 34 | loaders: ['eslint'], 35 | include: APP_PATH 36 | } 37 | ], 38 | loaders: [ 39 | { 40 | test: /\.jsx?$/, 41 | loaders: ['babel'], 42 | include: APP_PATH 43 | }, 44 | { 45 | test: /\.scss$/, 46 | loaders: ['style', 'css', 'sass'] 47 | } 48 | ] 49 | }, 50 | plugins: [ 51 | new HtmlwebpackPlugin({ 52 | title: 'Deskmark app' 53 | }) 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /online/app/utils/firebase.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file firebase storage helpers 3 | */ 4 | 5 | const FIREBASE_SCRIPT_URL = 'https://cdn.firebase.com/js/client/2.2.1/firebase.js'; 6 | const FIREBASE_DATA_URL = 'https://deskmark-demo.firebaseio.com/entries/'; 7 | 8 | export const FirebasePromise = new Promise((resolve, reject) => { 9 | const script = Object.assign(document.createElement('script'), { 10 | src: FIREBASE_SCRIPT_URL, 11 | charset: 'utf-8', 12 | onload: () => resolve(window.Firebase), 13 | onerror: (err) => reject(err), 14 | }); 15 | document.head.appendChild(script); 16 | }); 17 | 18 | export function getRef(name) { 19 | return FirebasePromise.then( 20 | Firebase => new Firebase(`${FIREBASE_DATA_URL}${name}/`) 21 | ); 22 | } 23 | 24 | export function fetch(name) { 25 | return getRef(name).then( 26 | ref => new Promise( 27 | (resolve, reject) => ref.once( 28 | 'value', 29 | snapshot => resolve(snapshot.val()), 30 | reject 31 | ) 32 | ) 33 | ); 34 | } 35 | 36 | export function save(name, data) { 37 | return getRef(name).then( 38 | ref => new Promise( 39 | (resolve, reject) => ref.set( 40 | data, 41 | err => ( 42 | err 43 | ? reject(err) 44 | : resolve() 45 | ) 46 | ) 47 | ) 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /chapter6/part2/app/utils/firebase.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file firebase storage helpers 3 | */ 4 | 5 | const FIREBASE_SCRIPT_URL = 'https://cdn.firebase.com/js/client/2.2.1/firebase.js'; 6 | const FIREBASE_DATA_URL = 'https://deskmark-demo.firebaseio.com/entries/'; 7 | 8 | export const FirebasePromise = new Promise((resolve, reject) => { 9 | const script = Object.assign(document.createElement('script'), { 10 | src: FIREBASE_SCRIPT_URL, 11 | charset: 'utf-8', 12 | onload: () => resolve(window.Firebase), 13 | onerror: (err) => reject(err), 14 | }); 15 | document.head.appendChild(script); 16 | }); 17 | 18 | export function getRef(name) { 19 | return FirebasePromise.then( 20 | Firebase => new Firebase(`${FIREBASE_DATA_URL}${name}/`) 21 | ); 22 | } 23 | 24 | export function fetch(name) { 25 | return getRef(name).then( 26 | ref => new Promise( 27 | (resolve, reject) => ref.once( 28 | 'value', 29 | snapshot => resolve(snapshot.val()), 30 | reject 31 | ) 32 | ) 33 | ); 34 | } 35 | 36 | export function save(name, data) { 37 | return getRef(name).then( 38 | ref => new Promise( 39 | (resolve, reject) => ref.set( 40 | data, 41 | err => ( 42 | err 43 | ? reject(err) 44 | : resolve() 45 | ) 46 | ) 47 | ) 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /online/app/components/ItemShowLayer/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component ItemShowLayer 3 | */ 4 | 5 | /* eslint react/no-danger: 0 */ 6 | 7 | import './style.scss'; 8 | 9 | import React from 'react'; 10 | import PropTypes from 'prop-types'; 11 | import marked from 'marked'; 12 | 13 | const propTypes = { 14 | item: PropTypes.object, 15 | onEdit: PropTypes.func.isRequired, 16 | onDelete: PropTypes.func.isRequired, 17 | }; 18 | 19 | function ItemShowLayer({ item, onEdit, onDelete }) { 20 | if (!item || !item.objectId) { 21 | return ( 22 |
    23 |
    请选择左侧列表里面的文章
    24 |
    25 | ); 26 | } 27 | 28 | const content = marked(item.content); 29 | 30 | return ( 31 |
    32 |
    33 | 34 | 35 |
    36 |

    {item.title}

    37 |
    38 |
    39 |
    40 |
    41 | ); 42 | } 43 | 44 | ItemShowLayer.propTypes = propTypes; 45 | 46 | export default ItemShowLayer; 47 | -------------------------------------------------------------------------------- /online/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var ROOT_PATH = path.resolve(__dirname); 6 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 7 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 8 | 9 | module.exports= { 10 | entry: { 11 | app: path.resolve(APP_PATH, 'app.jsx') 12 | }, 13 | output: { 14 | path: BUILD_PATH, 15 | filename: 'bundle.js' 16 | }, 17 | //enable dev source map 18 | devtool: 'eval-source-map', 19 | //enable dev server 20 | devServer: { 21 | historyApiFallback: true, 22 | hot: true 23 | }, 24 | resolve: { 25 | modules: [APP_PATH, "node_modules"], 26 | extensions: ['.js', '.jsx'] 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.jsx?$/, 32 | enforce: "pre", 33 | loaders: ['eslint-loader'], 34 | include: APP_PATH 35 | }, 36 | { 37 | test: /\.jsx?$/, 38 | loaders: ['babel-loader'], 39 | include: APP_PATH 40 | }, 41 | { 42 | test: /\.scss$/, 43 | loaders: ['style-loader', 'css-loader', 'sass-loader'] 44 | } 45 | ] 46 | }, 47 | plugins: [ 48 | new HtmlwebpackPlugin({ 49 | title: 'Deskmark app' 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /online/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 5 | 6 | var ROOT_PATH = path.resolve(__dirname); 7 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 8 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 9 | 10 | module.exports= { 11 | entry: { 12 | app: path.resolve(APP_PATH, 'app.jsx'), 13 | vendor: ['react', 'react-dom', 'prop-types', 'marked', 'leancloud-storage'] 14 | }, 15 | output: { 16 | path: BUILD_PATH, 17 | filename: '[name].[hash].bundle.js' 18 | }, 19 | resolve: { 20 | modules: [APP_PATH, "node_modules"], 21 | extensions: ['.js', '.jsx'] 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.jsx?$/, 27 | loaders: ['babel-loader'], 28 | include: APP_PATH 29 | }, 30 | { 31 | test: /\.scss$/, 32 | use: ExtractTextPlugin.extract({ 33 | fallback: "style-loader", 34 | use: ['css-loader', 'sass-loader'] 35 | }) 36 | } 37 | ] 38 | }, 39 | plugins: [ 40 | new webpack.optimize.CommonsChunkPlugin({ 41 | name: "vendor" 42 | }), 43 | new ExtractTextPlugin("styles.css"), 44 | new HtmlwebpackPlugin({ 45 | title: 'Deskmark app' 46 | }) 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /chapter6/part2/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var ROOT_PATH = path.resolve(__dirname); 6 | var APP_PATH = path.resolve(ROOT_PATH, 'app'); 7 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 8 | 9 | module.exports= { 10 | entry: { 11 | app: path.resolve(APP_PATH, 'app.jsx') 12 | }, 13 | output: { 14 | path: BUILD_PATH, 15 | filename: 'bundle.js' 16 | }, 17 | //enable dev source map 18 | devtool: 'eval-source-map', 19 | //enable dev server 20 | devServer: { 21 | historyApiFallback: true, 22 | hot: true, 23 | inline: true, 24 | progress: true 25 | }, 26 | resolve: { 27 | extensions: ['', '.js', '.jsx'], 28 | root: APP_PATH 29 | }, 30 | module: { 31 | preLoaders: [ 32 | { 33 | test: /\.jsx?$/, 34 | loaders: ['eslint'], 35 | include: APP_PATH 36 | } 37 | ], 38 | loaders: [ 39 | { 40 | test: /\.jsx?$/, 41 | loaders: ['react-hot', 'babel'], 42 | include: APP_PATH 43 | }, 44 | { 45 | test: /\.scss$/, 46 | loaders: ['style', 'css', 'sass'] 47 | } 48 | ] 49 | }, 50 | jshint: { 51 | "esnext": true 52 | }, 53 | plugins: [ 54 | new HtmlwebpackPlugin({ 55 | title: 'Deskmark app' 56 | }) 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /online/app/reducers/editor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers for editor 3 | */ 4 | 5 | import * as ActionTypes from 'actions'; 6 | 7 | const initialState = { 8 | isEditing: false, 9 | selectedId: null, 10 | }; 11 | 12 | const { fulfilledOf } = ActionTypes; 13 | 14 | export default function (state = initialState, action) { 15 | const { type, payload } = action; 16 | 17 | switch (type) { 18 | 19 | case ActionTypes.SELECT_ENTRY: 20 | return { 21 | ...state, 22 | isEditing: false, 23 | selectedId: payload, 24 | }; 25 | 26 | case ActionTypes.CREATE_NEW_ENTRY: 27 | return { 28 | ...state, 29 | isEditing: true, 30 | selectedId: null, 31 | }; 32 | 33 | case ActionTypes.EDIT_ENTRY: 34 | return { 35 | ...state, 36 | isEditing: true, 37 | selectedId: payload, 38 | }; 39 | 40 | case ActionTypes.CANCEL_EDIT: 41 | return { 42 | ...state, 43 | isEditing: false, 44 | }; 45 | 46 | case fulfilledOf(ActionTypes.SAVE_ENTRY): 47 | return { 48 | ...state, 49 | isEditing: false, 50 | selectedId: state.selectedId || payload.id, 51 | }; 52 | 53 | case fulfilledOf(ActionTypes.DELETE_ENTRY): 54 | return { 55 | ...state, 56 | isEditing: false, 57 | selectedId: null, 58 | }; 59 | 60 | default: 61 | return state; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /chapter6/part2/app/reducers/editor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file reducers for editor 3 | */ 4 | 5 | import * as ActionTypes from 'actions'; 6 | 7 | const initialState = { 8 | isEditing: false, 9 | selectedId: null, 10 | }; 11 | 12 | const { fulfilledOf } = ActionTypes; 13 | 14 | export default function (state = initialState, action) { 15 | const { type, payload } = action; 16 | 17 | switch (type) { 18 | 19 | case ActionTypes.SELECT_ENTRY: 20 | return { 21 | ...state, 22 | isEditing: false, 23 | selectedId: payload, 24 | }; 25 | 26 | case ActionTypes.CREATE_NEW_ENTRY: 27 | return { 28 | ...state, 29 | isEditing: true, 30 | selectedId: null, 31 | }; 32 | 33 | case ActionTypes.EDIT_ENTRY: 34 | return { 35 | ...state, 36 | isEditing: true, 37 | selectedId: payload, 38 | }; 39 | 40 | case ActionTypes.CANCEL_EDIT: 41 | return { 42 | ...state, 43 | isEditing: false, 44 | }; 45 | 46 | case fulfilledOf(ActionTypes.SAVE_ENTRY): 47 | return { 48 | ...state, 49 | isEditing: false, 50 | selectedId: state.selectedId || payload.id, 51 | }; 52 | 53 | case fulfilledOf(ActionTypes.DELETE_ENTRY): 54 | return { 55 | ...state, 56 | isEditing: false, 57 | selectedId: null, 58 | }; 59 | 60 | default: 61 | return state; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /chapter4/part1/app/components/ItemEditor/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file component ItemEditor 3 | */ 4 | 5 | /* eslint react/no-danger: 0 */ 6 | 7 | import './style.scss'; 8 | 9 | import React, { PropTypes } from 'react'; 10 | 11 | const propTypes = { 12 | item: PropTypes.object, 13 | onSave: PropTypes.func.isRequired, 14 | onCancel: PropTypes.func.isRequired, 15 | }; 16 | 17 | class ItemEditor extends React.Component { 18 | render() { 19 | const { onSave, onCancel } = this.props; 20 | 21 | const item = this.props.item || { 22 | title: '', 23 | content: '', 24 | }; 25 | 26 | let saveText = item.id ? '保存' : '创建'; 27 | 28 | let save = () => { 29 | onSave({ 30 | ...item, 31 | title: this.refs.title.value, 32 | content: this.refs.content.value, 33 | }); 34 | }; 35 | 36 | return ( 37 |
    38 |
    39 | 40 | 41 |
    42 |
    43 | 44 |