├── 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 | [](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 |
24 | );
25 | }
26 |
27 | const content = marked(item.content);
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
{item.title}
36 |
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 |
24 | );
25 | }
26 |
27 | const content = marked(item.content);
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
{item.title}
36 |
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 |
24 | );
25 | }
26 |
27 | const content = marked(item.content);
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
{item.title}
36 |
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 |
25 | );
26 | }
27 |
28 | const content = marked(item.content);
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
{item.title}
37 |
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 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | ItemEditor.propTypes = propTypes;
52 |
53 | export default ItemEditor;
54 |
--------------------------------------------------------------------------------
/chapter6/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 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | ItemEditor.propTypes = propTypes;
52 |
53 | export default ItemEditor;
54 |
--------------------------------------------------------------------------------
/chapter6/part2/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 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | ItemEditor.propTypes = propTypes;
52 |
53 | export default ItemEditor;
54 |
--------------------------------------------------------------------------------
/online/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 from 'react';
10 | import PropTypes from 'prop-types';
11 |
12 | const propTypes = {
13 | item: PropTypes.object,
14 | onSave: PropTypes.func.isRequired,
15 | onCancel: PropTypes.func.isRequired,
16 | };
17 |
18 | class ItemEditor extends React.Component {
19 | render() {
20 | const { onSave, onCancel } = this.props;
21 |
22 | const item = this.props.item || {
23 | title: '',
24 | content: '',
25 | };
26 |
27 | let saveText = item.objectId ? '保存' : '创建';
28 |
29 | let save = () => {
30 | onSave({
31 | ...item,
32 | title: this.refs.title.value,
33 | content: this.refs.content.value,
34 | });
35 | };
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | ItemEditor.propTypes = propTypes;
53 |
54 | export default ItemEditor;
55 |
--------------------------------------------------------------------------------
/chapter3/part4/app/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import Hobby from './Hobby';
3 |
4 | const propTypes = {
5 | name: PropTypes.string.isRequired,
6 | age: PropTypes.number.isRequired
7 | };
8 |
9 | class Profile extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | liked: 0,
14 | hobbies: ['skateboarding', 'rock music']
15 | };
16 | this.likedCallback = this.likedCallback.bind(this);
17 | this.addHobbyCallback = this.addHobbyCallback.bind(this);
18 | }
19 |
20 |
21 | componentDidMount() {
22 | setTimeout(() => {
23 | this.likedCallback();
24 | }, 1000);
25 | }
26 |
27 | likedCallback() {
28 | let liked = this.state.liked;
29 | liked++;
30 | this.setState({
31 | liked
32 | });
33 | }
34 |
35 | addHobbyCallback() {
36 | const hobbyInput = this.refs.hobby;
37 | const val = hobbyInput.value;
38 | if (val) {
39 | let hobbies = this.state.hobbies;
40 | hobbies = [...hobbies, val];
41 | this.setState({
42 | hobbies
43 | }, () => {
44 | hobbyInput.value = '';
45 | });
46 | }
47 | }
48 |
49 | render() {
50 | return (
51 |
52 |
我的名字叫 {this.props.name}
53 |
我今年 {this.props.age} 岁
54 |
55 |
总点赞数: {this.state.liked}
56 |
我的爱好:
57 |
58 | {this.state.hobbies.map((hobby, i) => )}
59 |
60 |
61 |
62 |
63 | );
64 | }
65 | }
66 |
67 | Profile.propTypes = propTypes;
68 |
69 | export default Profile;
70 |
--------------------------------------------------------------------------------
/online/app/utils/firebaseStorage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file storage based on firebase
3 | */
4 |
5 | import uuid from 'uuid';
6 | import { fetch, save } from './firebase';
7 |
8 | export function getAll() {
9 | return fetch('list').then(list => list || []);
10 | }
11 |
12 | export function saveAll(list) {
13 | return save('list', list);
14 | }
15 |
16 | function updateAll(update) {
17 | return getAll()
18 | .then(update)
19 | .then(saveAll);
20 | }
21 |
22 | export function getEntry(id) {
23 | return fetch(`detail/${id}`);
24 | }
25 |
26 | export function insertEntry(title, content) {
27 | const summary = {
28 | title,
29 | id: uuid.v4(),
30 | time: new Date().getTime(),
31 | };
32 |
33 | const entry = {
34 | ...summary,
35 | content,
36 | };
37 |
38 | return Promise.all([
39 | updateAll(list => [...list, summary]),
40 | save(`detail/${entry.id}`, entry),
41 | ]).then(() => entry);
42 | }
43 |
44 | export function deleteEntry(id) {
45 | return Promise.all([
46 | updateAll(
47 | list => list.filter(
48 | summary => summary.id !== id
49 | )
50 | ),
51 | save(`detail/${id}`, null),
52 | ]);
53 | }
54 |
55 | export function updateEntry(id, title, content) {
56 | const name = `detail/${id}`;
57 | let entry;
58 |
59 | return Promise.all([
60 | updateAll(
61 | list => list.map(
62 | summary => (
63 | summary.id === id
64 | ? {
65 | ...summary,
66 | title,
67 | }
68 | : summary
69 | )
70 | )
71 | ),
72 | fetch(name).then(
73 | saved => {
74 | entry = {
75 | ...saved,
76 | title,
77 | content,
78 | };
79 | return save(name, entry);
80 | }
81 | ),
82 | ]).then(() => entry);
83 | }
84 |
--------------------------------------------------------------------------------
/chapter6/part2/app/utils/firebaseStorage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file storage based on firebase
3 | */
4 |
5 | import uuid from 'uuid';
6 | import { fetch, save } from './firebase';
7 |
8 | export function getAll() {
9 | return fetch('list').then(list => list || []);
10 | }
11 |
12 | export function saveAll(list) {
13 | return save('list', list);
14 | }
15 |
16 | function updateAll(update) {
17 | return getAll()
18 | .then(update)
19 | .then(saveAll);
20 | }
21 |
22 | export function getEntry(id) {
23 | return fetch(`detail/${id}`);
24 | }
25 |
26 | export function insertEntry(title, content) {
27 | const summary = {
28 | title,
29 | id: uuid.v4(),
30 | time: new Date().getTime(),
31 | };
32 |
33 | const entry = {
34 | ...summary,
35 | content,
36 | };
37 |
38 | return Promise.all([
39 | updateAll(list => [...list, summary]),
40 | save(`detail/${entry.id}`, entry),
41 | ]).then(() => entry);
42 | }
43 |
44 | export function deleteEntry(id) {
45 | return Promise.all([
46 | updateAll(
47 | list => list.filter(
48 | summary => summary.id !== id
49 | )
50 | ),
51 | save(`detail/${id}`, null),
52 | ]);
53 | }
54 |
55 | export function updateEntry(id, title, content) {
56 | const name = `detail/${id}`;
57 | let entry;
58 |
59 | return Promise.all([
60 | updateAll(
61 | list => list.map(
62 | summary => (
63 | summary.id === id
64 | ? {
65 | ...summary,
66 | title,
67 | }
68 | : summary
69 | )
70 | )
71 | ),
72 | fetch(name).then(
73 | saved => {
74 | entry = {
75 | ...saved,
76 | title,
77 | content,
78 | };
79 | return save(name, entry);
80 | }
81 | ),
82 | ]).then(() => entry);
83 | }
84 |
--------------------------------------------------------------------------------
/chapter6/part2/app/reducers/entries/detail.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file reducers for entries' detail
3 | */
4 |
5 | import * as ActionTypes from 'actions';
6 |
7 | const initialState = {};
8 |
9 | const { pendingOf, fulfilledOf } = ActionTypes;
10 |
11 | export default function (state = initialState, action) {
12 | const { type, payload } = action;
13 | let id;
14 | let entry;
15 |
16 | switch (type) {
17 |
18 | case pendingOf(ActionTypes.FETCH_ENTRY):
19 | id = payload;
20 | return {
21 | ...state,
22 | [id]: {
23 | ...state[id],
24 | isFetching: true,
25 | },
26 | };
27 |
28 | case fulfilledOf(ActionTypes.FETCH_ENTRY):
29 | entry = payload;
30 | return {
31 | ...state,
32 | [entry.id]: {
33 | data: entry,
34 | isFetching: false,
35 | },
36 | };
37 |
38 | case pendingOf(ActionTypes.SAVE_ENTRY):
39 | id = payload.id;
40 | return {
41 | ...state,
42 | [id]: {
43 | ...state[id],
44 | isFetching: true,
45 | },
46 | };
47 |
48 | case fulfilledOf(ActionTypes.SAVE_ENTRY):
49 | entry = payload;
50 | return {
51 | ...state,
52 | [entry.id]: {
53 | data: entry,
54 | isFetching: false,
55 | },
56 | };
57 |
58 | case pendingOf(ActionTypes.DELETE_ENTRY):
59 | id = payload;
60 | return {
61 | ...state,
62 | [id]: {
63 | ...state[id],
64 | isFetching: true,
65 | },
66 | };
67 |
68 | case fulfilledOf(ActionTypes.DELETE_ENTRY):
69 | id = payload;
70 | return {
71 | ...state,
72 | [id]: {
73 | data: null,
74 | isFetching: false,
75 | },
76 | };
77 |
78 | default:
79 | return state;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/chapter6/part1/app/utils/storage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file storage based on localStorage
3 | */
4 |
5 | import uuid from 'uuid';
6 |
7 | const STORAGE = window.localStorage;
8 | const STORAGE_KEY = 'deskmark';
9 |
10 | export function getAll() {
11 | return new Promise((resolve) => {
12 | const results = STORAGE.getItem(STORAGE_KEY);
13 |
14 | try {
15 | resolve(
16 | results
17 | ? JSON.parse(results)
18 | : []
19 | );
20 | } catch (e) {
21 | resolve([]);
22 | }
23 | });
24 | }
25 |
26 | export function saveAll(results) {
27 | return new Promise((resolve) => {
28 | STORAGE.setItem(
29 | STORAGE_KEY,
30 | JSON.stringify(results)
31 | );
32 |
33 | resolve();
34 | });
35 | }
36 |
37 | export function getEntry(id) {
38 | return getAll()
39 | .then(
40 | results => results.find(
41 | result => result.id === id
42 | )
43 | );
44 | }
45 |
46 | export function insertEntry(title, content) {
47 | const entry = {
48 | title,
49 | content,
50 | id: uuid.v4(),
51 | time: new Date().getTime(),
52 | };
53 |
54 | return getAll()
55 | .then(results => [...results, entry])
56 | .then(saveAll)
57 | .then(() => entry);
58 | }
59 |
60 | export function deleteEntry(id) {
61 | return getAll()
62 | .then(
63 | results => results.filter(
64 | result => result.id !== id
65 | )
66 | )
67 | .then(saveAll);
68 | }
69 |
70 | export function updateEntry(id, title, content) {
71 | let entry;
72 | return getAll()
73 | .then(
74 | results => results.map(
75 | result => (
76 | result.id === id
77 | ? (entry = {
78 | ...result,
79 | title,
80 | content,
81 | })
82 | : result
83 | )
84 | )
85 | )
86 | .then(saveAll)
87 | .then(() => entry);
88 | }
89 |
--------------------------------------------------------------------------------
/online/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskmark",
3 | "version": "1.0.0",
4 | "description": "an web markdown writer an docs manager based on indexeddb and build on React.",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack -p --config webpack.prod.config.js",
8 | "dev": "webpack-dev-server --hot --host 0.0.0.0"
9 | },
10 | "directories": {
11 | "test": "test"
12 | },
13 | "keywords": [
14 | "javascript",
15 | "react",
16 | "markdown",
17 | "webpack",
18 | "redux"
19 | ],
20 | "author": "vikingmute, nighca",
21 | "license": "MIT",
22 | "homepage": "https://github.com/vikingmute/deskmark#readme",
23 | "engines": {
24 | "node": ">=0.12.0"
25 | },
26 | "devDependencies": {
27 | "babel-core": "^6.3.15",
28 | "babel-eslint": "^6.0.2",
29 | "babel-loader": "^6.2.0",
30 | "babel-plugin-transform-object-rest-spread": "^6.5.0",
31 | "babel-preset-es2015": "^6.3.13",
32 | "babel-preset-react": "^6.3.13",
33 | "babel-preset-stage-2": "^6.24.1",
34 | "css-loader": "^0.23.1",
35 | "eslint": "^2.7.0",
36 | "eslint-config-airbnb": "^6.2.0",
37 | "eslint-loader": "^1.3.0",
38 | "eslint-plugin-react": "^4.3.0",
39 | "extract-text-webpack-plugin": "^3.0.0",
40 | "html-webpack-plugin": "^2.21.0",
41 | "node-sass": "^3.4.2",
42 | "sass-loader": "^3.1.2",
43 | "style-loader": "^0.13.0",
44 | "uuid": "^2.0.1",
45 | "webpack": "^3.5.2",
46 | "webpack-dev-server": "^2.7.1"
47 | },
48 | "dependencies": {
49 | "bootstrap": "^4.0.0-beta",
50 | "classnames": "^2.2.5",
51 | "isomorphic-fetch": "^2.2.1",
52 | "leancloud-storage": "^3.1.0",
53 | "marked": "^0.3.5",
54 | "prop-types": "^15.5.10",
55 | "react": "^15.6.1",
56 | "react-dom": "^15.6.1",
57 | "react-redux": "^4.4.2",
58 | "redux": "^3.2.1",
59 | "redux-promise-middleware": "^3.0.0",
60 | "redux-thunk": "^1.0.3"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/chapter4/part1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskmark",
3 | "version": "1.0.0",
4 | "description": "an web markdown writer an docs manager based on indexeddb and build on React.",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack",
8 | "dev": "webpack-dev-server --hot --host 0.0.0.0",
9 | "test": "NODE_ENV=production mocha --compilers js:babel-core/register --require ignore-styles"
10 | },
11 | "directories": {
12 | "test": "test"
13 | },
14 | "keywords": [
15 | "javascript",
16 | "react",
17 | "markdown",
18 | "indexeddb",
19 | "webpack",
20 | "karma"
21 | ],
22 | "author": "vikingmute, island205",
23 | "license": "MIT",
24 | "homepage": "https://github.com/island205/deskmark#readme",
25 | "engines": {
26 | "node": ">=0.12.0"
27 | },
28 | "devDependencies": {
29 | "babel-core": "^6.3.15",
30 | "babel-eslint": "^6.0.2",
31 | "babel-loader": "^6.2.0",
32 | "babel-plugin-transform-object-rest-spread": "^6.5.0",
33 | "babel-preset-es2015": "^6.3.13",
34 | "babel-preset-react": "^6.3.13",
35 | "babel-preset-react-hmre": "^1.1.1",
36 | "bootstrap": "^4.0.0-alpha.2",
37 | "chai": "^3.5.0",
38 | "css-loader": "^0.23.1",
39 | "enzyme": "^2.3.0",
40 | "eslint": "^2.7.0",
41 | "eslint-config-airbnb": "^6.2.0",
42 | "eslint-loader": "^1.3.0",
43 | "eslint-plugin-react": "^4.3.0",
44 | "expect": "^1.13.4",
45 | "html-webpack-plugin": "^2.21.0",
46 | "ignore-styles": "^2.0.0",
47 | "jsdom": "^9.8.3",
48 | "mocha": "^2.3.4",
49 | "node-sass": "^3.4.2",
50 | "react-addons-test-utils": "^15.0.1",
51 | "sass-loader": "^3.1.2",
52 | "style-loader": "^0.13.0",
53 | "uuid": "^2.0.1",
54 | "webpack": "^1.12.9",
55 | "webpack-dev-server": "^1.14.1"
56 | },
57 | "dependencies": {
58 | "babel-polyfill": "^6.6.1",
59 | "marked": "^0.3.5",
60 | "react": "^15.0.1",
61 | "react-dom": "^15.0.1"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/chapter6/part1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskmark",
3 | "version": "1.0.0",
4 | "description": "an web markdown writer an docs manager build on React&Redux.",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack",
8 | "dev": "webpack-dev-server --hot --host 0.0.0.0",
9 | "test": "karma start"
10 | },
11 | "directories": {
12 | "test": "test"
13 | },
14 | "keywords": [
15 | "javascript",
16 | "react",
17 | "markdown",
18 | "indexeddb",
19 | "webpack",
20 | "karma"
21 | ],
22 | "author": "vikingmute, island205, nighca",
23 | "license": "MIT",
24 | "homepage": "https://github.com/vikingmute/deskmark#readme",
25 | "engines": {
26 | "node": ">=0.12.0"
27 | },
28 | "devDependencies": {
29 | "babel-core": "^6.3.15",
30 | "babel-eslint": "^6.0.2",
31 | "babel-loader": "^6.2.0",
32 | "babel-plugin-transform-object-rest-spread": "^6.5.0",
33 | "babel-preset-es2015": "^6.3.13",
34 | "babel-preset-react": "^6.3.13",
35 | "babel-preset-react-hmre": "^1.1.1",
36 | "bootstrap": "^4.0.0-alpha.2",
37 | "css-loader": "^0.23.1",
38 | "eslint": "^2.7.0",
39 | "eslint-config-airbnb": "^6.2.0",
40 | "eslint-loader": "^1.3.0",
41 | "eslint-plugin-react": "^4.3.0",
42 | "expect": "^1.13.4",
43 | "html-webpack-plugin": "^2.21.0",
44 | "karma": "^0.13.18",
45 | "karma-chrome-launcher": "^0.2.2",
46 | "karma-cli": "^0.1.2",
47 | "karma-mocha": "^0.2.1",
48 | "karma-webpack": "^1.7.0",
49 | "mocha": "^2.3.4",
50 | "node-sass": "^3.4.2",
51 | "react-addons-test-utils": "^15.0.1",
52 | "sass-loader": "^3.1.2",
53 | "style-loader": "^0.13.0",
54 | "uuid": "^2.0.1",
55 | "webpack": "^1.12.9",
56 | "webpack-dev-server": "^1.14.1"
57 | },
58 | "dependencies": {
59 | "marked": "^0.3.5",
60 | "react": "^15.0.1",
61 | "react-dom": "^15.0.1",
62 | "react-redux": "^4.4.2",
63 | "redux": "^3.2.1",
64 | "redux-thunk": "^2.0.1"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/chapter6/part1/app/actions/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file main file for actions
3 | */
4 |
5 | import * as storage from 'utils/storage';
6 |
7 | export const SELECT_ENTRY = 'SELECT_ENTRY';
8 | export const CREATE_NEW_ENTRY = 'CREATE_NEW_ENTRY';
9 | export const EDIT_ENTRY = 'EDIT_ENTRY';
10 | export const CANCEL_EDIT = 'CANCEL_EDIT';
11 |
12 | export function selectEntry(id) {
13 | return { type: SELECT_ENTRY, id };
14 | }
15 |
16 | export function createNewEntry() {
17 | return { type: CREATE_NEW_ENTRY };
18 | }
19 |
20 | export function editEntry(id) {
21 | return { type: EDIT_ENTRY, id };
22 | }
23 |
24 | export function cancelEdit() {
25 | return { type: CANCEL_EDIT };
26 | }
27 |
28 | export const UPDATE_ENTRY_LIST = 'UPDATE_ENTRY_LIST';
29 |
30 | function updateEntryList(items) {
31 | return { type: UPDATE_ENTRY_LIST, items };
32 | }
33 |
34 | export function deleteEntry(id) {
35 | return dispatch => {
36 | storage.deleteEntry(id)
37 | .then(() => storage.getAll())
38 | .then((items) => dispatch(updateEntryList(items)));
39 | };
40 | }
41 |
42 | export function fetchEntryList() {
43 | return dispatch => {
44 | storage.getAll()
45 | .then(items => dispatch(updateEntryList(items)));
46 | };
47 | }
48 |
49 | export const UPDATE_SAVED_ENTRY = 'UPDATE_SAVED_ENTRY';
50 |
51 | function updateSavedEntry(id) {
52 | return { type: UPDATE_SAVED_ENTRY, id };
53 | }
54 |
55 | export function saveEntry(item) {
56 | const { title, content, id } = item;
57 | return dispatch => {
58 | if (id) {
59 | // 更新流程
60 | storage.updateEntry(id, title, content)
61 | .then(() => dispatch(updateSavedEntry(id)))
62 | .then(() => storage.getAll())
63 | .then(items => dispatch(updateEntryList(items)));
64 | } else {
65 | // 创建流程
66 | storage.insertEntry(title, content)
67 | .then(inserted => dispatch(updateSavedEntry(inserted.id)))
68 | .then(() => storage.getAll())
69 | .then(items => dispatch(updateEntryList(items)));
70 | }
71 | };
72 | }
73 |
--------------------------------------------------------------------------------
/chapter6/part1/app/components/Deskmark/index.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @file component deskmark
3 | */
4 |
5 | import React, { PropTypes } from 'react';
6 | import CreateBar from 'components/CreateBar';
7 | import List from 'components/List';
8 | import ItemShowLayer from 'components/ItemShowLayer';
9 | import ItemEditor from 'components/ItemEditor';
10 |
11 | import './style.scss';
12 |
13 | const propTypes = {
14 | state: PropTypes.object.isRequired,
15 | actions: PropTypes.object.isRequired,
16 | };
17 |
18 | class Deskmark extends React.Component {
19 |
20 | componentDidMount() {
21 | this.props.actions.fetchEntryList();
22 | }
23 |
24 | render() {
25 | const { state, actions } = this.props;
26 | const { isEditing, selectedId } = state.editor;
27 | const items = state.items;
28 | const item = items.find(
29 | ({ id }) => id === selectedId
30 | );
31 |
32 | const mainPart = isEditing
33 | ? (
34 |
39 | )
40 | : (
41 |
46 | );
47 |
48 | return (
49 |
50 |
53 |
54 |
55 |
56 |
57 |
61 |
62 | {mainPart}
63 |
64 |
65 |
66 | );
67 | }
68 | }
69 |
70 | Deskmark.propTypes = propTypes;
71 |
72 | export default Deskmark;
73 |
--------------------------------------------------------------------------------
/online/app/utils/storage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file storage based on ajax
3 | */
4 |
5 | import uuid from 'uuid';
6 | import fetch from 'isomorphic-fetch';
7 |
8 | const ENTRIES_PRIFIX = 'http://localhost:8080/api/entries';
9 |
10 | function defaultHeaders() {
11 | const headers = new Headers();
12 | headers.append('accept', 'application/json');
13 | headers.append('content-type', 'application/json');
14 | return headers;
15 | }
16 |
17 | function getJSON(url, opts = {}) {
18 | const headers = defaultHeaders();
19 | const options = Object.assign({}, {method: 'GET', headers}, opts);
20 | return fetch(url, options).then(res => res.json());
21 | }
22 |
23 | function postJSON(url, data = null, opts = {}) {
24 | const headers = defaultHeaders();
25 | const defaultOpts = {method: 'POST', headers};
26 | if (data) {
27 | defaultOpts.body = JSON.stringify(data);
28 | }
29 | const options = Object.assign(defaultOpts, opts);
30 | return fetch(url, defaultOpts).then(res => res.json());
31 | }
32 |
33 | function putJSON(url, data = null, opts = {}) {
34 | return postJSON(url, data, Object.assign({ method: 'PUT'}, opts));
35 | }
36 |
37 | function deleteJSON(url, opts = {}) {
38 | return getJSON(url, Object.assign({method: 'DELETE'}, opts));
39 | }
40 |
41 | let storage = {
42 | getAll() {
43 | return getJSON(`${ENTRIES_PRIFIX}`);
44 | },
45 | saveAll(results) {
46 | window.localStorage.setItem('deskmark', JSON.stringify(results));
47 | },
48 | getEntry(id) {
49 | return getJSON(`${ENTRIES_PRIFIX}/${id}`);
50 | },
51 | insertEntry(title, content) {
52 | let id = uuid.v4();
53 | let entry = {id, title, content, 'time': new Date().getTime()};
54 | return postJSON(`${ENTRIES_PRIFIX}`, entry);
55 | },
56 | deleteEntry(id) {
57 | return deleteJSON(`${ENTRIES_PRIFIX}/${id}`);
58 | },
59 | updateEntry(id, title, content) {
60 | let entry = {title, content};
61 | entry.time = new Date().getTime();
62 | return putJSON(`${ENTRIES_PRIFIX}/${id}`, entry);
63 | }
64 | };
65 |
66 | export default storage;
67 |
--------------------------------------------------------------------------------
/chapter6/part2/app/utils/storage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file storage based on ajax
3 | */
4 |
5 | import uuid from 'uuid';
6 | import fetch from 'isomorphic-fetch';
7 |
8 | const ENTRIES_PRIFIX = 'http://localhost:8080/api/entries';
9 |
10 | function defaultHeaders() {
11 | const headers = new Headers();
12 | headers.append('accept', 'application/json');
13 | headers.append('content-type', 'application/json');
14 | return headers;
15 | }
16 |
17 | function getJSON(url, opts = {}) {
18 | const headers = defaultHeaders();
19 | const options = Object.assign({}, {method: 'GET', headers}, opts);
20 | return fetch(url, options).then(res => res.json());
21 | }
22 |
23 | function postJSON(url, data = null, opts = {}) {
24 | const headers = defaultHeaders();
25 | const defaultOpts = {method: 'POST', headers};
26 | if (data) {
27 | defaultOpts.body = JSON.stringify(data);
28 | }
29 | const options = Object.assign(defaultOpts, opts);
30 | return fetch(url, defaultOpts).then(res => res.json());
31 | }
32 |
33 | function putJSON(url, data = null, opts = {}) {
34 | return postJSON(url, data, Object.assign({ method: 'PUT'}, opts));
35 | }
36 |
37 | function deleteJSON(url, opts = {}) {
38 | return getJSON(url, Object.assign({method: 'DELETE'}, opts));
39 | }
40 |
41 | let storage = {
42 | getAll() {
43 | return getJSON(`${ENTRIES_PRIFIX}`);
44 | },
45 | saveAll(results) {
46 | window.localStorage.setItem('deskmark', JSON.stringify(results));
47 | },
48 | getEntry(id) {
49 | return getJSON(`${ENTRIES_PRIFIX}/${id}`);
50 | },
51 | insertEntry(title, content) {
52 | let id = uuid.v4();
53 | let entry = {id, title, content, 'time': new Date().getTime()};
54 | return postJSON(`${ENTRIES_PRIFIX}`, entry);
55 | },
56 | deleteEntry(id) {
57 | return deleteJSON(`${ENTRIES_PRIFIX}/${id}`);
58 | },
59 | updateEntry(id, title, content) {
60 | let entry = {title, content};
61 | entry.time = new Date().getTime();
62 | return putJSON(`${ENTRIES_PRIFIX}/${id}`, entry);
63 | }
64 | };
65 |
66 | export default storage;
67 |
--------------------------------------------------------------------------------
/online/app/reducers/entries/detail.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file reducers for entries' detail
3 | */
4 |
5 | import * as ActionTypes from 'actions';
6 |
7 | const initialState = {};
8 |
9 | const { pendingOf, fulfilledOf } = ActionTypes;
10 |
11 | export default function (state = initialState, action) {
12 | const { type, payload } = action;
13 | let id;
14 | let entry;
15 | switch (type) {
16 | case pendingOf(ActionTypes.FETCH_ENTRY):
17 | id = payload;
18 | return {
19 | ...state,
20 | [id]: {
21 | ...state[id],
22 | isFetching: true,
23 | },
24 | };
25 |
26 | case fulfilledOf(ActionTypes.FETCH_ENTRY):
27 | entry = payload;
28 | return {
29 | ...state,
30 | [entry.objectId]: {
31 | data: entry,
32 | isFetching: false,
33 | },
34 | };
35 |
36 | case pendingOf(ActionTypes.SAVE_ENTRY):
37 | id = payload.objectId;
38 | // newly create entry has no objectId
39 | if (!id) {
40 | id = 'temp';
41 | }
42 | return {
43 | ...state,
44 | [id]: {
45 | ...state[id],
46 | isFetching: true,
47 | },
48 | };
49 |
50 | case fulfilledOf(ActionTypes.SAVE_ENTRY):
51 | entry = payload;
52 | return {
53 | ...state,
54 | [entry.objectId]: {
55 | data: entry,
56 | isFetching: false,
57 | },
58 | temp: {
59 | data: null,
60 | isFetching: false,
61 | },
62 | };
63 |
64 | case pendingOf(ActionTypes.DELETE_ENTRY):
65 | id = payload;
66 | return {
67 | ...state,
68 | [id]: {
69 | ...state[id],
70 | isFetching: true,
71 | },
72 | };
73 |
74 | case fulfilledOf(ActionTypes.DELETE_ENTRY):
75 | id = payload;
76 | return {
77 | ...state,
78 | [id]: {
79 | data: null,
80 | isFetching: false,
81 | },
82 | };
83 |
84 | default:
85 | return state;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/chapter6/part2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskmark",
3 | "version": "1.0.0",
4 | "description": "an web markdown writer an docs manager based on indexeddb and build on React.",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack",
8 | "dev": "webpack-dev-server --hot --host 0.0.0.0",
9 | "test": "karma start"
10 | },
11 | "directories": {
12 | "test": "test"
13 | },
14 | "keywords": [
15 | "javascript",
16 | "react",
17 | "markdown",
18 | "indexeddb",
19 | "webpack",
20 | "karma"
21 | ],
22 | "author": "vikingmute, island205",
23 | "license": "MIT",
24 | "homepage": "https://github.com/island205/deskmark#readme",
25 | "engines": {
26 | "node": ">=0.12.0"
27 | },
28 | "devDependencies": {
29 | "babel-core": "^6.3.15",
30 | "babel-eslint": "^6.0.2",
31 | "babel-loader": "^6.2.0",
32 | "babel-plugin-transform-object-rest-spread": "^6.5.0",
33 | "babel-preset-es2015": "^6.3.13",
34 | "babel-preset-react": "^6.3.13",
35 | "bootstrap": "^4.0.0-alpha.2",
36 | "css-loader": "^0.23.1",
37 | "eslint": "^2.7.0",
38 | "eslint-config-airbnb": "^6.2.0",
39 | "eslint-loader": "^1.3.0",
40 | "eslint-plugin-react": "^4.3.0",
41 | "expect": "^1.13.4",
42 | "html-webpack-plugin": "^2.21.0",
43 | "karma": "^0.13.18",
44 | "karma-chrome-launcher": "^0.2.2",
45 | "karma-cli": "^0.1.2",
46 | "karma-mocha": "^0.2.1",
47 | "karma-webpack": "^1.7.0",
48 | "mocha": "^2.3.4",
49 | "node-sass": "^3.4.2",
50 | "react-addons-test-utils": "^0.14.6",
51 | "react-hot-loader": "^1.3.0",
52 | "sass-loader": "^3.1.2",
53 | "style-loader": "^0.13.0",
54 | "uuid": "^2.0.1",
55 | "webpack": "^1.12.9",
56 | "webpack-dev-server": "^1.14.1"
57 | },
58 | "dependencies": {
59 | "isomorphic-fetch": "^2.2.1",
60 | "marked": "^0.3.5",
61 | "react": "^15.0.1",
62 | "react-dom": "^15.0.1",
63 | "react-redux": "^4.4.2",
64 | "redux": "^3.2.1",
65 | "redux-promise-middleware": "^3.0.0",
66 | "redux-thunk": "^1.0.3"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/chapter6/part2/app/components/Deskmark/index.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @file component deskmark
3 | */
4 |
5 | import React, { PropTypes } from 'react';
6 | import CreateBar from 'components/CreateBar';
7 | import List from 'components/List';
8 | import ItemShowLayer from 'components/ItemShowLayer';
9 | import ItemEditor from 'components/ItemEditor';
10 |
11 | import './style.scss';
12 |
13 | const propTypes = {
14 | state: PropTypes.object.isRequired,
15 | actions: PropTypes.object.isRequired,
16 | };
17 |
18 | class Deskmark extends React.Component {
19 |
20 | componentDidMount() {
21 | this.props.actions.fetchEntryList();
22 | }
23 |
24 | render() {
25 | const { state, actions } = this.props;
26 | const { isEditing, selectedId } = state.editor;
27 | const detailedEntries = state.entries.detail;
28 |
29 | const entryList = state.entries.list.data;
30 |
31 | const entry = (
32 | selectedId
33 | && detailedEntries[selectedId]
34 | && detailedEntries[selectedId].data
35 | ) || null;
36 |
37 | const mainPart = isEditing
38 | ? (
39 |
44 | )
45 | : (
46 |
51 | );
52 |
53 | return (
54 |
55 |
58 |
59 |
60 |
61 |
62 |
66 |
67 | {mainPart}
68 |
69 |
70 |
71 | );
72 | }
73 | }
74 |
75 | Deskmark.propTypes = propTypes;
76 |
77 | export default Deskmark;
78 |
--------------------------------------------------------------------------------
/chapter4/part1/test/SFC.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { expect } from 'chai';
3 | import List from '../app/components/List';
4 | import ListItem from '../app/components/ListItem';
5 | import ItemShowLayer from '../app/components/ItemShowLayer';
6 | import TestUtils from 'react-addons-test-utils';
7 |
8 |
9 | describe('testing all stateless component', function () {
10 | const testData = [
11 | {
12 | "id": "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
13 | "title": "Hello",
14 | "content": "# testing markdown",
15 | "time": 1458030208359
16 | }, {
17 | "id": "6c84fb90-12c4-11e1-840d-7b25c5ee775b",
18 | "title": "Hello2",
19 | "content": "# Hello world",
20 | "time": 1458030208359
21 | }
22 | ];
23 | function createRender(Component, props) {
24 | const renderer = TestUtils.createRenderer();
25 |
26 | renderer.render();
27 | return renderer.getRenderOutput();
28 | }
29 | it('test List component', function() {
30 | const props = {
31 | items: testData
32 | }
33 | const list = createRender(List, props);
34 | const len = list.props.children.length;
35 | expect(len).to.equal(2);
36 | })
37 | it('test ListItem component', function() {
38 | const props = {
39 | item: testData[0]
40 | };
41 | const listItem = createRender(ListItem, props);
42 | expect(listItem.props.children[1].props.children).to.equal(testData[0].title);
43 | expect(listItem.props.children[0].type).to.equal('span');
44 | })
45 | it('test ItemShowLayer component with no data', function() {
46 | const nullProps = {
47 | item: null
48 | }
49 | const itemShowLayer = createRender(ItemShowLayer, nullProps);
50 | const textTag = itemShowLayer.props.children.props;
51 | expect(textTag.className).equal('no-select');
52 | })
53 | it('test ItemShowLayer component with provided data', function() {
54 | const props = {
55 | item: testData[0]
56 | }
57 | const itemShowLayer = createRender(ItemShowLayer, props);
58 | const title = itemShowLayer.props.children[1];
59 | expect(title.type).equal('h2');
60 | expect(title.props.children).equal(testData[0].title);
61 | })
62 | })
63 |
--------------------------------------------------------------------------------
/online/app/components/Deskmark/index.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @file component deskmark
3 | */
4 |
5 | import React from 'react';
6 | import PropTypes from 'prop-types';
7 | import CreateBar from 'components/CreateBar';
8 | import List from 'components/List';
9 | import ItemShowLayer from 'components/ItemShowLayer';
10 | import ItemEditor from 'components/ItemEditor';
11 | import Loader from 'components/Loader';
12 | import './style.scss';
13 |
14 | const propTypes = {
15 | state: PropTypes.object.isRequired,
16 | actions: PropTypes.object.isRequired,
17 | };
18 |
19 | class Deskmark extends React.Component {
20 |
21 | componentDidMount() {
22 | this.props.actions.fetchEntryList();
23 | }
24 | checkIsLoading = (state) => {
25 | // check if the list is fetching
26 | const listIsFetching = state.entries.list.isFetching;
27 | // check if one of the details is fetch
28 | const details = state.entries.detail;
29 | const oneOfDetailIsFetching = Object.keys(details).some(keyId => details[keyId].isFetching);
30 | return listIsFetching || oneOfDetailIsFetching;
31 | }
32 | render() {
33 | const { state, actions } = this.props;
34 | const { isEditing, selectedId } = state.editor;
35 | const detailedEntries = state.entries.detail;
36 | const loading = this.checkIsLoading(state);
37 | const entryList = state.entries.list.data;
38 | const entry = (
39 | selectedId
40 | && detailedEntries[selectedId]
41 | && detailedEntries[selectedId].data
42 | ) || null;
43 | const loadingIcon = loading ? : '';
44 | const mainPart = isEditing
45 | ? (
46 |
51 | )
52 | : (
53 |
58 | );
59 |
60 | return (
61 |
62 |
65 | {loadingIcon}
66 |
67 |
68 |
69 |
70 |
75 |
76 | {mainPart}
77 |
78 |
79 |
80 | );
81 | }
82 | }
83 |
84 | Deskmark.propTypes = propTypes;
85 |
86 | export default Deskmark;
87 |
--------------------------------------------------------------------------------
/chapter6/part2/app/actions/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file actions
3 | */
4 |
5 | import * as storage from 'utils/firebaseStorage';
6 |
7 | // sync actions
8 | export const SELECT_ENTRY = 'SELECT_ENTRY';
9 | export const CREATE_NEW_ENTRY = 'CREATE_NEW_ENTRY';
10 | export const EDIT_ENTRY = 'EDIT_ENTRY';
11 | export const CANCEL_EDIT = 'CANCEL_EDIT';
12 |
13 | export function selectEntry(id) {
14 | return dispatch => {
15 | dispatch({
16 | type: SELECT_ENTRY,
17 | payload: id,
18 | });
19 |
20 | dispatch(fetchEntry(id));
21 | };
22 | }
23 |
24 | export function createNewEntry() {
25 | return { type: CREATE_NEW_ENTRY };
26 | }
27 |
28 | export function editEntry(id) {
29 | return {
30 | type: EDIT_ENTRY,
31 | payload: id,
32 | };
33 | }
34 |
35 | export function cancelEdit() {
36 | return { type: CANCEL_EDIT };
37 | }
38 |
39 | // default promiseTypeSuffixes of redux-promise-middleware:
40 | // ['PENDING', 'FULFILLED', 'REJECTED']
41 | export const pendingOf = actionType => `${actionType}_PENDING`;
42 | export const fulfilledOf = actionType => `${actionType}_FULFILLED`;
43 | export const rejectedOf = actionType => `${actionType}_REJECTED`;
44 |
45 | // async actions generated with redux-promise-middleware:
46 | export const FETCH_ENTRY = 'FETCH_ENTRY';
47 | export const FETCH_ENTRY_LIST = 'FETCH_ENTRY_LIST';
48 | export const SAVE_ENTRY = 'SAVE_ENTRY';
49 | export const DELETE_ENTRY = 'DELETE_ENTRY';
50 |
51 | export function fetchEntry(id) {
52 | return {
53 | type: FETCH_ENTRY,
54 | payload: {
55 | promise: storage.getEntry(id),
56 | data: id,
57 | },
58 | };
59 | }
60 |
61 | export function fetchEntryList() {
62 | return {
63 | type: FETCH_ENTRY_LIST,
64 | payload: storage.getAll(),
65 | };
66 | }
67 |
68 | export function saveEntry(entry) {
69 | const promise = entry.id
70 | ? storage.updateEntry(
71 | entry.id,
72 | entry.title,
73 | entry.content
74 | )
75 | : storage.insertEntry(
76 | entry.title,
77 | entry.content
78 | );
79 |
80 | return dispatch => {
81 | dispatch({
82 | type: SAVE_ENTRY,
83 | payload: {
84 | promise,
85 | data: entry,
86 | },
87 | });
88 |
89 | promise.then(
90 | () => dispatch(fetchEntryList())
91 | );
92 | };
93 | }
94 |
95 | export function deleteEntry(id) {
96 | const promise = storage.deleteEntry(id).then(() => id);
97 |
98 | return dispatch => {
99 | dispatch({
100 | type: DELETE_ENTRY,
101 | payload: {
102 | promise,
103 | data: id,
104 | },
105 | });
106 |
107 | promise.then(
108 | () => dispatch(fetchEntryList())
109 | );
110 | };
111 | }
112 |
--------------------------------------------------------------------------------
/online/app/actions/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file actions
3 | */
4 |
5 | // import * as storage from 'utils/firebaseStorage';
6 | import * as storage from 'utils/leancloud';
7 | // sync actions
8 | export const SELECT_ENTRY = 'SELECT_ENTRY';
9 | export const CREATE_NEW_ENTRY = 'CREATE_NEW_ENTRY';
10 | export const EDIT_ENTRY = 'EDIT_ENTRY';
11 | export const CANCEL_EDIT = 'CANCEL_EDIT';
12 |
13 | export function selectEntry(id) {
14 | return dispatch => {
15 | dispatch({
16 | type: SELECT_ENTRY,
17 | payload: id,
18 | });
19 |
20 | dispatch(fetchEntry(id));
21 | };
22 | }
23 |
24 | export function createNewEntry() {
25 | return { type: CREATE_NEW_ENTRY };
26 | }
27 |
28 | export function editEntry(id) {
29 | return {
30 | type: EDIT_ENTRY,
31 | payload: id,
32 | };
33 | }
34 |
35 | export function cancelEdit() {
36 | return { type: CANCEL_EDIT };
37 | }
38 |
39 | // default promiseTypeSuffixes of redux-promise-middleware:
40 | // ['PENDING', 'FULFILLED', 'REJECTED']
41 | export const pendingOf = actionType => `${actionType}_PENDING`;
42 | export const fulfilledOf = actionType => `${actionType}_FULFILLED`;
43 | export const rejectedOf = actionType => `${actionType}_REJECTED`;
44 |
45 | // async actions generated with redux-promise-middleware:
46 | export const FETCH_ENTRY = 'FETCH_ENTRY';
47 | export const FETCH_ENTRY_LIST = 'FETCH_ENTRY_LIST';
48 | export const SAVE_ENTRY = 'SAVE_ENTRY';
49 | export const DELETE_ENTRY = 'DELETE_ENTRY';
50 |
51 | export function fetchEntry(id) {
52 | return {
53 | type: FETCH_ENTRY,
54 | payload: {
55 | promise: storage.getEntry(id),
56 | data: id,
57 | },
58 | };
59 | }
60 |
61 | export function fetchEntryList() {
62 | return {
63 | type: FETCH_ENTRY_LIST,
64 | payload: storage.getAll(),
65 | };
66 | }
67 |
68 | export function saveEntry(entry) {
69 | const promise = entry.objectId
70 | ? storage.updateEntry(
71 | entry.objectId,
72 | entry.title,
73 | entry.content
74 | )
75 | : storage.insertEntry(
76 | entry.title,
77 | entry.content
78 | );
79 |
80 | return dispatch => {
81 | dispatch({
82 | type: SAVE_ENTRY,
83 | payload: {
84 | promise,
85 | data: entry,
86 | },
87 | });
88 |
89 | promise.then(
90 | () => dispatch(fetchEntryList())
91 | );
92 | };
93 | }
94 |
95 | export function deleteEntry(id) {
96 | const promise = storage.deleteEntry(id).then(() => id);
97 |
98 | return dispatch => {
99 | dispatch({
100 | type: DELETE_ENTRY,
101 | payload: {
102 | promise,
103 | data: id,
104 | },
105 | });
106 |
107 | promise.then(
108 | () => dispatch(fetchEntryList())
109 | );
110 | };
111 | }
112 |
--------------------------------------------------------------------------------
/corrigendum.md:
--------------------------------------------------------------------------------
1 | 以下为本书 2016 年 10 月第 1 版的勘误列表,欢迎提交 PR 进行补充,帮助其他读者。
2 |
3 | ### [5.1.1 单向数据流] 错误别字
4 | 第118页 Flux整个流程图 VIEW文字描述 一旦change事件被处罚 应该是一旦change事件被触发
5 |
6 |
7 | ### [1.1.1 语言特性] const、let 关键字
8 |
9 | 第一段示例代码:单引号错误,另 `console.log` 的参数应该是 `a`。勘正后为:
10 |
11 | ```javascript
12 | if (true) {
13 | let a = 'name';
14 | }
15 | console.log(a);
16 | // ReferenceError: a is not defined
17 | ```
18 |
19 |
20 | ### [1.1.1 语言特性] 函数 this在箭头函数中
21 |
22 | 第一段代码示例:结果错误 这里的 this.age 在setTimeout中应该是 undefined。
23 |
24 | ```javascript
25 | let age = 2;
26 | let kitty = {
27 | age: 1,
28 | grow: function() {
29 | setTimeout(function() {
30 | console.log(++this.age);
31 | }, 100);
32 | }
33 | };
34 |
35 | kitty.grow();
36 | // 3
37 |
38 | ```
39 | 应修改为 var age =2;
40 | 因为:
41 | `At the top level of programs and functions, let, unlike var, does not create a property on the global object.`
42 | 但是这段代码在 babel 编译后可以正常运行,所以笔者出现了这个错误。原因是 Babel 编译后并没有对这个特性没有特殊的处理。
43 |
44 | 附代码 Babel 编译后的结果:
45 |
46 | ```javascript
47 | "use strict";
48 |
49 | var age = 2;
50 | var kitty = {
51 | age: 1,
52 | grow: function grow() {
53 | setTimeout(function () {
54 | console.log(++this.age);
55 | }, 100);
56 | }
57 | };
58 |
59 | kitty.grow();
60 | ```
61 | ### [1.1.1 语言特性] 模板字符串
62 |
63 | 代码示例中的变量名 `name` 错误书写成 `viking`,即,
64 |
65 | ```
66 | var a = 'My name is ' + viking + '!';
67 | ```
68 |
69 | 应为
70 |
71 | ```
72 | var a = 'My name is ' + name + '!';
73 | ```
74 |
75 | ### [1.1.1 语言特性] 类
76 | 在类Dog中 注释 super.写成 super()., 即:
77 | ```
78 | //但是可以采用super(). +方法调用父类方法
79 | ```
80 | 应为
81 | ```
82 | //但是可以采用super. +方法调用父类方法
83 | ```
84 |
85 |
86 | ### [1.3.2]
87 | 在Gruntfile.js中 loadNpmTasks写成了loadnpmTasks, 即:
88 | ```
89 | //将两个任务插件导入
90 | grunt.loadnpmTasks('grunt-contrib-uglify');
91 | grunt.loadnpmTasks('grunt-contrib-jshint');
92 | ```
93 | 应为
94 | ```
95 | //将两个任务插件导入
96 | grunt.loadNpmTasks('grunt-contrib-uglify');
97 | grunt.loadNpmTasks('grunt-contrib-jshint');
98 | ```
99 | ### [3.4.1 props属性] 代码段export default Class 中的Class应为 class关键字
100 |
101 |
102 | ### [4.2.3 配置测试环境]
103 |
104 | `来简单介绍一下这个命令, mocha也可以使用编译器编译 javascript 代码,这里使用 babel 这个编译器,因为现在代码的格式都是ES6的,而由于 Webpack 允许直接在代码中 import 样式文件,例如 import './style.scss' 这种格式的代码,如果直接用 mocha 来运行这类的代码,会直接报错,因为 babel 无法解析这类 CSS 代码,所以需要一个插件来忽略掉这类型的代码。这插件就是 ignore-styles 。简单来说运行 mocha 的时候可以接受这类型的参数:--compilers 指的是指定文件格式的预编译器,而 --require 指的是运行测试之前需要引入的一些辅助插件。`
105 |
106 | 在该段后面应该添加:
107 |
108 | **特别注意,在windows环境下该命令应该为:"test": "set NODE_ENV=production && mocha --compilers js:babel-core/register --require ignore-styles"**
109 |
110 |
111 | ### [3.3.3 配置 webpack]
112 |
113 | `在项目根目录新建一个 *webpack.config.js* 的文件。` 应该修改为: **在项目根目录新建app文件夹,同时再新建一个 *webpack.config.js* 的文件。**
114 |
115 | 代码中 `app: path.resolve(APP_PATH, 'index.jsx')` 应该修改为: **app: path.resolve(APP_PATH, 'app.jsx')**
116 |
117 | `复习一下上一章的知识,把 app 文件夹中的 index.jsx 作为入口,` 应该修改为: **复习一下上一章的知识,把 app 文件夹中的 app.jsx 作为入口,**
118 |
119 | ### [6.2.3 使用 middleware]
120 |
121 | `2. redux-promise-middleware` 部分,第二段示例代码,首行注释中的文件位置 `chapter5/part2/app/reducers/entries/list.js`,应该修改为: **`chapter6/part2/app/reducers/entries/list.js`**
122 |
--------------------------------------------------------------------------------
/chapter5/part2/app/app.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux';
2 |
3 | import Handlebars from 'handlebars';
4 |
5 | const source = ` 文章列表: 总数 {{posts.length}}
6 | {{#if posts}}
7 |
8 | {{#each posts}}
9 | - {{this.id}} --- {{this.title}}
10 | {{/each}}
11 |
12 | {{/if}}
13 |
用户信息: 是否登录:{{user.isLogin}}
14 | {{#if user.isLogin}}
15 | 用户邮箱:{{user.userData.email}} 用户名:{{user.userData.name}}
16 | {{/if}}
`;
17 |
18 | const template = Handlebars.compile(source);
19 |
20 | function displayPage(data) {
21 | const html = template(data);
22 | document.body.innerHTML += html;
23 | console.log(data);
24 | }
25 |
26 | // inital states
27 | const initalPostsState = [];
28 |
29 | const initalUserState = {
30 | isLogin: false,
31 | userData: {
32 |
33 | }
34 | };
35 |
36 |
37 | // action names
38 | const CREATE_POST = 'CREATE_POST';
39 | const DELETE_POST = 'DELETE_POST';
40 | const USER_LOGIN = 'USER_LOGIN';
41 |
42 | // action creators
43 | function createPost(data) {
44 | return {
45 | type: CREATE_POST,
46 | data
47 | };
48 | }
49 | function deletePost(id) {
50 | return {
51 | type: DELETE_POST,
52 | id
53 | };
54 | }
55 | function userLogin(data) {
56 | return {
57 | type: USER_LOGIN,
58 | data
59 | };
60 | }
61 | function posts(state = initalPostsState, action) {
62 | switch (action.type) {
63 | case CREATE_POST:
64 | return [...state, action.data];
65 | case DELETE_POST:
66 | return state.filter(post => post.id !== action.id);
67 | default:
68 | return state;
69 | }
70 | }
71 |
72 | function user(state = initalUserState, action) {
73 | switch (action.type) {
74 | case USER_LOGIN:
75 | return Object.assign({}, state, {
76 | isLogin: true,
77 | userData: action.data
78 | });
79 | default:
80 | return state;
81 | }
82 | }
83 |
84 | const rootReducer = combineReducers({
85 | posts,
86 | user
87 | });
88 |
89 | const store = createStore(rootReducer);
90 |
91 | document.body.innerHTML += '初始化状态
';
92 |
93 | displayPage(store.getState());
94 |
95 | store.subscribe(() => {
96 | displayPage(store.getState());
97 | });
98 |
99 | // create two posts
100 | document.body.innerHTML += '创建两篇文章
';
101 | store.dispatch(createPost({ id: 1, title: 'new title' }));
102 | // store.dispatch({type: CREATE_POST, data: {id: 1, title: 'new title'}});
103 | store.dispatch(createPost({ id: 2, title: 'the second title' }));
104 | // store.dispatch({type: CREATE_POST, data: {id: 2, title: 'the second title'}});
105 |
106 | // delete one post
107 | document.body.innerHTML += '删除一篇文章
';
108 | store.dispatch(deletePost(1));
109 | // store.dispatch({type: DELETE_POST, id: 1});
110 |
111 | // User login
112 | document.body.innerHTML += '用户登录
';
113 | store.dispatch(userLogin({ name: 'viking', email: 'viking@v.me' }));
114 | // store.dispatch({type: USER_LOGIN, data: {name: 'viking', email: 'viking@v.me'}});
115 |
116 |
--------------------------------------------------------------------------------
/chapter3/part3/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "part3",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "asap": {
6 | "version": "2.0.5",
7 | "from": "asap@>=2.0.3 <2.1.0",
8 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz"
9 | },
10 | "core-js": {
11 | "version": "1.2.7",
12 | "from": "core-js@>=1.0.0 <2.0.0",
13 | "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-1.2.7.tgz"
14 | },
15 | "encoding": {
16 | "version": "0.1.12",
17 | "from": "encoding@>=0.1.11 <0.2.0",
18 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz"
19 | },
20 | "fbjs": {
21 | "version": "0.8.6",
22 | "from": "fbjs@>=0.8.4 <0.9.0",
23 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.8.6.tgz"
24 | },
25 | "iconv-lite": {
26 | "version": "0.4.13",
27 | "from": "iconv-lite@>=0.4.13 <0.5.0",
28 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
29 | },
30 | "is-stream": {
31 | "version": "1.1.0",
32 | "from": "is-stream@>=1.0.1 <2.0.0",
33 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
34 | },
35 | "isomorphic-fetch": {
36 | "version": "2.2.1",
37 | "from": "isomorphic-fetch@>=2.1.1 <3.0.0",
38 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz"
39 | },
40 | "js-tokens": {
41 | "version": "2.0.0",
42 | "from": "js-tokens@>=2.0.0 <3.0.0",
43 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
44 | },
45 | "loose-envify": {
46 | "version": "1.3.0",
47 | "from": "loose-envify@>=1.1.0 <2.0.0",
48 | "resolved": "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.0.tgz"
49 | },
50 | "node-fetch": {
51 | "version": "1.6.3",
52 | "from": "node-fetch@>=1.0.1 <2.0.0",
53 | "resolved": "https://registry.npm.taobao.org/node-fetch/download/node-fetch-1.6.3.tgz"
54 | },
55 | "object-assign": {
56 | "version": "4.1.0",
57 | "from": "object-assign@>=4.1.0 <5.0.0",
58 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
59 | },
60 | "promise": {
61 | "version": "7.1.1",
62 | "from": "promise@>=7.1.1 <8.0.0",
63 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz"
64 | },
65 | "react": {
66 | "version": "15.4.0",
67 | "from": "react@>=15.1.0 <16.0.0",
68 | "resolved": "https://registry.npm.taobao.org/react/download/react-15.4.0.tgz"
69 | },
70 | "react-dom": {
71 | "version": "15.4.0",
72 | "from": "react-dom@>=15.1.0 <16.0.0",
73 | "resolved": "https://registry.npm.taobao.org/react-dom/download/react-dom-15.4.0.tgz"
74 | },
75 | "ua-parser-js": {
76 | "version": "0.7.12",
77 | "from": "ua-parser-js@>=0.7.9 <0.8.0",
78 | "resolved": "https://registry.npm.taobao.org/ua-parser-js/download/ua-parser-js-0.7.12.tgz"
79 | },
80 | "whatwg-fetch": {
81 | "version": "2.0.1",
82 | "from": "whatwg-fetch@>=0.10.0",
83 | "resolved": "https://registry.npm.taobao.org/whatwg-fetch/download/whatwg-fetch-2.0.1.tgz"
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/chapter4/part1/app/components/Deskmark/index.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @file component Deskmark
3 | */
4 |
5 | import React from 'react';
6 | import uuid from 'uuid';
7 |
8 | import CreateBar from '../CreateBar';
9 | import List from '../List';
10 | import ItemEditor from '../ItemEditor';
11 | import ItemShowLayer from '../ItemShowLayer';
12 |
13 | import './style.scss';
14 |
15 | export default class App extends React.Component {
16 |
17 | constructor(props) {
18 | super(props);
19 |
20 | this.state = {
21 | items: [],
22 | selectedId: null,
23 | editing: false,
24 | };
25 |
26 | this.selectItem = this.selectItem.bind(this);
27 | this.saveItem = this.saveItem.bind(this);
28 | this.deleteItem = this.deleteItem.bind(this);
29 | this.createItem = this.createItem.bind(this);
30 | this.editItem = this.editItem.bind(this);
31 | this.cancelEdit = this.cancelEdit.bind(this);
32 | }
33 |
34 | selectItem(id) {
35 | if (id === this.state.selectedId) {
36 | return;
37 | }
38 |
39 | this.setState({
40 | selectedId: id,
41 | editing: false,
42 | });
43 | }
44 |
45 | saveItem(item) {
46 | let items = this.state.items;
47 |
48 | // new item
49 | if (!item.id) {
50 | items = [...items, {
51 | ...item,
52 | id: uuid.v4(),
53 | time: new Date().getTime(),
54 | }];
55 | // existed item
56 | } else {
57 | items = items.map(
58 | exist => (
59 | exist.id === item.id
60 | ? {
61 | ...exist,
62 | ...item,
63 | }
64 | : exist
65 | )
66 | );
67 | }
68 |
69 | this.setState({
70 | items,
71 | selectedId: item.id,
72 | editing: false,
73 | });
74 | }
75 |
76 | deleteItem(id) {
77 | if (!id) {
78 | return;
79 | }
80 |
81 | this.setState({
82 | items: this.state.items.filter(
83 | result => result.id !== id
84 | ),
85 | });
86 | }
87 |
88 | createItem() {
89 | this.setState({
90 | selectedId: null,
91 | editing: true,
92 | });
93 | }
94 |
95 | editItem(id) {
96 | this.setState({
97 | selectedId: id,
98 | editing: true,
99 | });
100 | }
101 |
102 | cancelEdit() {
103 | this.setState({ editing: false });
104 | }
105 |
106 | render() {
107 | const { items, selectedId, editing } = this.state;
108 | const selected = selectedId && items.find(item => item.id === selectedId);
109 | const mainPart = editing
110 | ? (
111 |
116 | )
117 | : (
118 |
123 | );
124 |
125 | return (
126 |
127 |
130 |
131 |
132 |
133 |
134 |
138 |
139 | {mainPart}
140 |
141 |
142 |
143 | );
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/chapter3/part4/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "profile",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "asap": {
6 | "version": "2.0.5",
7 | "from": "asap@>=2.0.3 <2.1.0",
8 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz"
9 | },
10 | "babel-polyfill": {
11 | "version": "6.16.0",
12 | "from": "babel-polyfill@>=6.6.1 <7.0.0",
13 | "resolved": "https://registry.npm.taobao.org/babel-polyfill/download/babel-polyfill-6.16.0.tgz"
14 | },
15 | "babel-runtime": {
16 | "version": "6.18.0",
17 | "from": "babel-runtime@>=6.9.1 <7.0.0",
18 | "resolved": "https://registry.npm.taobao.org/babel-runtime/download/babel-runtime-6.18.0.tgz"
19 | },
20 | "core-js": {
21 | "version": "2.4.1",
22 | "from": "core-js@>=2.4.0 <3.0.0",
23 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz"
24 | },
25 | "encoding": {
26 | "version": "0.1.12",
27 | "from": "encoding@>=0.1.11 <0.2.0",
28 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz"
29 | },
30 | "fbjs": {
31 | "version": "0.8.6",
32 | "from": "fbjs@>=0.8.4 <0.9.0",
33 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.8.6.tgz",
34 | "dependencies": {
35 | "core-js": {
36 | "version": "1.2.7",
37 | "from": "core-js@>=1.0.0 <2.0.0",
38 | "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-1.2.7.tgz"
39 | }
40 | }
41 | },
42 | "iconv-lite": {
43 | "version": "0.4.13",
44 | "from": "iconv-lite@>=0.4.13 <0.5.0",
45 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
46 | },
47 | "is-stream": {
48 | "version": "1.1.0",
49 | "from": "is-stream@>=1.0.1 <2.0.0",
50 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
51 | },
52 | "isomorphic-fetch": {
53 | "version": "2.2.1",
54 | "from": "isomorphic-fetch@>=2.1.1 <3.0.0",
55 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz"
56 | },
57 | "js-tokens": {
58 | "version": "2.0.0",
59 | "from": "js-tokens@>=2.0.0 <3.0.0",
60 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
61 | },
62 | "loose-envify": {
63 | "version": "1.3.0",
64 | "from": "loose-envify@>=1.1.0 <2.0.0",
65 | "resolved": "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.0.tgz"
66 | },
67 | "node-fetch": {
68 | "version": "1.6.3",
69 | "from": "node-fetch@>=1.0.1 <2.0.0",
70 | "resolved": "https://registry.npm.taobao.org/node-fetch/download/node-fetch-1.6.3.tgz"
71 | },
72 | "object-assign": {
73 | "version": "4.1.0",
74 | "from": "object-assign@>=4.1.0 <5.0.0",
75 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
76 | },
77 | "promise": {
78 | "version": "7.1.1",
79 | "from": "promise@>=7.1.1 <8.0.0",
80 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz"
81 | },
82 | "react": {
83 | "version": "15.4.0",
84 | "from": "react@>=15.0.1 <16.0.0",
85 | "resolved": "https://registry.npm.taobao.org/react/download/react-15.4.0.tgz"
86 | },
87 | "react-dom": {
88 | "version": "15.4.0",
89 | "from": "react-dom@>=15.0.1 <16.0.0",
90 | "resolved": "https://registry.npm.taobao.org/react-dom/download/react-dom-15.4.0.tgz"
91 | },
92 | "regenerator-runtime": {
93 | "version": "0.9.6",
94 | "from": "regenerator-runtime@>=0.9.5 <0.10.0",
95 | "resolved": "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.9.6.tgz"
96 | },
97 | "ua-parser-js": {
98 | "version": "0.7.12",
99 | "from": "ua-parser-js@>=0.7.9 <0.8.0",
100 | "resolved": "https://registry.npm.taobao.org/ua-parser-js/download/ua-parser-js-0.7.12.tgz"
101 | },
102 | "whatwg-fetch": {
103 | "version": "2.0.1",
104 | "from": "whatwg-fetch@>=0.10.0",
105 | "resolved": "https://registry.npm.taobao.org/whatwg-fetch/download/whatwg-fetch-2.0.1.tgz"
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/chapter4/part1/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskmark",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "asap": {
6 | "version": "2.0.5",
7 | "from": "asap@>=2.0.3 <2.1.0",
8 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz"
9 | },
10 | "babel-polyfill": {
11 | "version": "6.16.0",
12 | "from": "babel-polyfill@>=6.6.1 <7.0.0",
13 | "resolved": "https://registry.npm.taobao.org/babel-polyfill/download/babel-polyfill-6.16.0.tgz"
14 | },
15 | "babel-runtime": {
16 | "version": "6.18.0",
17 | "from": "babel-runtime@>=6.9.1 <7.0.0",
18 | "resolved": "https://registry.npm.taobao.org/babel-runtime/download/babel-runtime-6.18.0.tgz"
19 | },
20 | "core-js": {
21 | "version": "2.4.1",
22 | "from": "core-js@>=2.4.0 <3.0.0",
23 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz"
24 | },
25 | "encoding": {
26 | "version": "0.1.12",
27 | "from": "encoding@>=0.1.11 <0.2.0",
28 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz"
29 | },
30 | "fbjs": {
31 | "version": "0.8.6",
32 | "from": "fbjs@>=0.8.4 <0.9.0",
33 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.8.6.tgz",
34 | "dependencies": {
35 | "core-js": {
36 | "version": "1.2.7",
37 | "from": "core-js@>=1.0.0 <2.0.0",
38 | "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-1.2.7.tgz"
39 | }
40 | }
41 | },
42 | "iconv-lite": {
43 | "version": "0.4.13",
44 | "from": "iconv-lite@>=0.4.13 <0.5.0",
45 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
46 | },
47 | "is-stream": {
48 | "version": "1.1.0",
49 | "from": "is-stream@>=1.0.1 <2.0.0",
50 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
51 | },
52 | "isomorphic-fetch": {
53 | "version": "2.2.1",
54 | "from": "isomorphic-fetch@>=2.1.1 <3.0.0",
55 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz"
56 | },
57 | "js-tokens": {
58 | "version": "2.0.0",
59 | "from": "js-tokens@>=2.0.0 <3.0.0",
60 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
61 | },
62 | "loose-envify": {
63 | "version": "1.3.0",
64 | "from": "loose-envify@>=1.1.0 <2.0.0",
65 | "resolved": "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.0.tgz"
66 | },
67 | "marked": {
68 | "version": "0.3.6",
69 | "from": "marked@>=0.3.5 <0.4.0",
70 | "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz"
71 | },
72 | "node-fetch": {
73 | "version": "1.6.3",
74 | "from": "node-fetch@>=1.0.1 <2.0.0",
75 | "resolved": "https://registry.npm.taobao.org/node-fetch/download/node-fetch-1.6.3.tgz"
76 | },
77 | "object-assign": {
78 | "version": "4.1.0",
79 | "from": "object-assign@>=4.1.0 <5.0.0",
80 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
81 | },
82 | "promise": {
83 | "version": "7.1.1",
84 | "from": "promise@>=7.1.1 <8.0.0",
85 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz"
86 | },
87 | "react": {
88 | "version": "15.4.0",
89 | "from": "react@>=15.0.1 <16.0.0",
90 | "resolved": "https://registry.npm.taobao.org/react/download/react-15.4.0.tgz"
91 | },
92 | "react-dom": {
93 | "version": "15.4.0",
94 | "from": "react-dom@>=15.0.1 <16.0.0",
95 | "resolved": "https://registry.npm.taobao.org/react-dom/download/react-dom-15.4.0.tgz"
96 | },
97 | "regenerator-runtime": {
98 | "version": "0.9.6",
99 | "from": "regenerator-runtime@>=0.9.5 <0.10.0",
100 | "resolved": "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.9.6.tgz"
101 | },
102 | "ua-parser-js": {
103 | "version": "0.7.12",
104 | "from": "ua-parser-js@>=0.7.9 <0.8.0",
105 | "resolved": "https://registry.npm.taobao.org/ua-parser-js/download/ua-parser-js-0.7.12.tgz"
106 | },
107 | "whatwg-fetch": {
108 | "version": "2.0.1",
109 | "from": "whatwg-fetch@>=0.10.0",
110 | "resolved": "https://registry.npm.taobao.org/whatwg-fetch/download/whatwg-fetch-2.0.1.tgz"
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/chapter6/part1/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskmark",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "asap": {
6 | "version": "2.0.5",
7 | "from": "asap@>=2.0.3 <2.1.0",
8 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz"
9 | },
10 | "core-js": {
11 | "version": "1.2.7",
12 | "from": "core-js@>=1.0.0 <2.0.0",
13 | "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-1.2.7.tgz"
14 | },
15 | "encoding": {
16 | "version": "0.1.12",
17 | "from": "encoding@>=0.1.11 <0.2.0",
18 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz"
19 | },
20 | "fbjs": {
21 | "version": "0.8.6",
22 | "from": "fbjs@>=0.8.4 <0.9.0",
23 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.8.6.tgz"
24 | },
25 | "hoist-non-react-statics": {
26 | "version": "1.2.0",
27 | "from": "hoist-non-react-statics@>=1.0.3 <2.0.0",
28 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz"
29 | },
30 | "iconv-lite": {
31 | "version": "0.4.13",
32 | "from": "iconv-lite@>=0.4.13 <0.5.0",
33 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
34 | },
35 | "invariant": {
36 | "version": "2.2.2",
37 | "from": "invariant@>=2.0.0 <3.0.0",
38 | "resolved": "https://registry.npm.taobao.org/invariant/download/invariant-2.2.2.tgz"
39 | },
40 | "is-stream": {
41 | "version": "1.1.0",
42 | "from": "is-stream@>=1.0.1 <2.0.0",
43 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
44 | },
45 | "isomorphic-fetch": {
46 | "version": "2.2.1",
47 | "from": "isomorphic-fetch@>=2.1.1 <3.0.0",
48 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz"
49 | },
50 | "js-tokens": {
51 | "version": "2.0.0",
52 | "from": "js-tokens@>=2.0.0 <3.0.0",
53 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
54 | },
55 | "lodash": {
56 | "version": "4.17.2",
57 | "from": "lodash@>=4.2.0 <5.0.0",
58 | "resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.2.tgz"
59 | },
60 | "lodash-es": {
61 | "version": "4.17.2",
62 | "from": "lodash-es@>=4.2.1 <5.0.0",
63 | "resolved": "https://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.2.tgz"
64 | },
65 | "loose-envify": {
66 | "version": "1.3.0",
67 | "from": "loose-envify@>=1.1.0 <2.0.0",
68 | "resolved": "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.0.tgz"
69 | },
70 | "marked": {
71 | "version": "0.3.6",
72 | "from": "marked@>=0.3.5 <0.4.0",
73 | "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz"
74 | },
75 | "node-fetch": {
76 | "version": "1.6.3",
77 | "from": "node-fetch@>=1.0.1 <2.0.0",
78 | "resolved": "https://registry.npm.taobao.org/node-fetch/download/node-fetch-1.6.3.tgz"
79 | },
80 | "object-assign": {
81 | "version": "4.1.0",
82 | "from": "object-assign@>=4.1.0 <5.0.0",
83 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
84 | },
85 | "promise": {
86 | "version": "7.1.1",
87 | "from": "promise@>=7.1.1 <8.0.0",
88 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz"
89 | },
90 | "react": {
91 | "version": "15.4.0",
92 | "from": "react@>=15.0.1 <16.0.0",
93 | "resolved": "https://registry.npm.taobao.org/react/download/react-15.4.0.tgz"
94 | },
95 | "react-dom": {
96 | "version": "15.4.0",
97 | "from": "react-dom@>=15.0.1 <16.0.0",
98 | "resolved": "https://registry.npm.taobao.org/react-dom/download/react-dom-15.4.0.tgz"
99 | },
100 | "react-redux": {
101 | "version": "4.4.6",
102 | "from": "react-redux@>=4.4.2 <5.0.0",
103 | "resolved": "https://registry.npm.taobao.org/react-redux/download/react-redux-4.4.6.tgz"
104 | },
105 | "redux": {
106 | "version": "3.6.0",
107 | "from": "redux@>=3.2.1 <4.0.0",
108 | "resolved": "https://registry.npm.taobao.org/redux/download/redux-3.6.0.tgz"
109 | },
110 | "redux-thunk": {
111 | "version": "2.1.0",
112 | "from": "redux-thunk@>=2.0.1 <3.0.0",
113 | "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.1.0.tgz"
114 | },
115 | "symbol-observable": {
116 | "version": "1.0.4",
117 | "from": "symbol-observable@>=1.0.2 <2.0.0",
118 | "resolved": "https://registry.npm.taobao.org/symbol-observable/download/symbol-observable-1.0.4.tgz"
119 | },
120 | "ua-parser-js": {
121 | "version": "0.7.12",
122 | "from": "ua-parser-js@>=0.7.9 <0.8.0",
123 | "resolved": "https://registry.npm.taobao.org/ua-parser-js/download/ua-parser-js-0.7.12.tgz"
124 | },
125 | "whatwg-fetch": {
126 | "version": "2.0.1",
127 | "from": "whatwg-fetch@>=0.10.0",
128 | "resolved": "https://registry.npm.taobao.org/whatwg-fetch/download/whatwg-fetch-2.0.1.tgz"
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/chapter5/part1/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "part1",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "asap": {
6 | "version": "2.0.5",
7 | "from": "asap@>=2.0.3 <2.1.0",
8 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz"
9 | },
10 | "core-js": {
11 | "version": "1.2.7",
12 | "from": "core-js@>=1.0.0 <2.0.0",
13 | "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-1.2.7.tgz"
14 | },
15 | "encoding": {
16 | "version": "0.1.12",
17 | "from": "encoding@>=0.1.11 <0.2.0",
18 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz"
19 | },
20 | "events": {
21 | "version": "1.1.1",
22 | "from": "events@>=1.1.0 <2.0.0",
23 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz"
24 | },
25 | "fbemitter": {
26 | "version": "2.1.1",
27 | "from": "fbemitter@>=2.0.0 <3.0.0",
28 | "resolved": "https://registry.npm.taobao.org/fbemitter/download/fbemitter-2.1.1.tgz",
29 | "dependencies": {
30 | "fbjs": {
31 | "version": "0.8.6",
32 | "from": "fbjs@>=0.8.4 <0.9.0",
33 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.8.6.tgz"
34 | }
35 | }
36 | },
37 | "fbjs": {
38 | "version": "0.1.0-alpha.7",
39 | "from": "fbjs@0.1.0-alpha.7",
40 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.1.0-alpha.7.tgz",
41 | "dependencies": {
42 | "whatwg-fetch": {
43 | "version": "0.9.0",
44 | "from": "whatwg-fetch@>=0.9.0 <0.10.0",
45 | "resolved": "https://registry.npm.taobao.org/whatwg-fetch/download/whatwg-fetch-0.9.0.tgz"
46 | }
47 | }
48 | },
49 | "flux": {
50 | "version": "2.1.1",
51 | "from": "flux@>=2.1.1 <3.0.0",
52 | "resolved": "https://registry.npm.taobao.org/flux/download/flux-2.1.1.tgz"
53 | },
54 | "iconv-lite": {
55 | "version": "0.4.13",
56 | "from": "iconv-lite@>=0.4.13 <0.5.0",
57 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
58 | },
59 | "immutable": {
60 | "version": "3.8.1",
61 | "from": "immutable@>=3.7.4 <4.0.0",
62 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.1.tgz"
63 | },
64 | "is-stream": {
65 | "version": "1.1.0",
66 | "from": "is-stream@>=1.0.1 <2.0.0",
67 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
68 | },
69 | "isomorphic-fetch": {
70 | "version": "2.2.1",
71 | "from": "isomorphic-fetch@>=2.1.1 <3.0.0",
72 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz"
73 | },
74 | "js-tokens": {
75 | "version": "2.0.0",
76 | "from": "js-tokens@>=2.0.0 <3.0.0",
77 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
78 | },
79 | "loose-envify": {
80 | "version": "1.3.0",
81 | "from": "loose-envify@>=1.0.0 <2.0.0",
82 | "resolved": "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.0.tgz"
83 | },
84 | "node-fetch": {
85 | "version": "1.6.3",
86 | "from": "node-fetch@>=1.0.1 <2.0.0",
87 | "resolved": "https://registry.npm.taobao.org/node-fetch/download/node-fetch-1.6.3.tgz"
88 | },
89 | "object-assign": {
90 | "version": "4.1.0",
91 | "from": "object-assign@>=4.1.0 <5.0.0",
92 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
93 | },
94 | "promise": {
95 | "version": "7.1.1",
96 | "from": "promise@>=7.1.1 <8.0.0",
97 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz"
98 | },
99 | "react": {
100 | "version": "15.4.0",
101 | "from": "react@>=15.1.0 <16.0.0",
102 | "resolved": "https://registry.npm.taobao.org/react/download/react-15.4.0.tgz",
103 | "dependencies": {
104 | "fbjs": {
105 | "version": "0.8.6",
106 | "from": "fbjs@>=0.8.4 <0.9.0",
107 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.8.6.tgz"
108 | }
109 | }
110 | },
111 | "react-dom": {
112 | "version": "15.4.0",
113 | "from": "react-dom@>=15.1.0 <16.0.0",
114 | "resolved": "https://registry.npm.taobao.org/react-dom/download/react-dom-15.4.0.tgz",
115 | "dependencies": {
116 | "fbjs": {
117 | "version": "0.8.6",
118 | "from": "fbjs@>=0.8.1 <0.9.0",
119 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.8.6.tgz"
120 | }
121 | }
122 | },
123 | "ua-parser-js": {
124 | "version": "0.7.12",
125 | "from": "ua-parser-js@>=0.7.9 <0.8.0",
126 | "resolved": "https://registry.npm.taobao.org/ua-parser-js/download/ua-parser-js-0.7.12.tgz"
127 | },
128 | "uuid": {
129 | "version": "2.0.3",
130 | "from": "uuid@>=2.0.2 <3.0.0",
131 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz"
132 | },
133 | "whatwg-fetch": {
134 | "version": "2.0.1",
135 | "from": "whatwg-fetch@>=0.10.0",
136 | "resolved": "https://registry.npm.taobao.org/whatwg-fetch/download/whatwg-fetch-2.0.1.tgz"
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/chapter4/part1/test/SFC-enzyme.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { expect } from 'chai';
3 | import List from '../app/components/List';
4 | import ListItem from '../app/components/ListItem';
5 | import ItemShowLayer from '../app/components/ItemShowLayer';
6 | import ItemEditor from '../app/components/ItemEditor';
7 | import CreateBar from '../app/components/CreateBar';
8 | import Deskmark from '../app/components/Deskmark';
9 | import { shallow, mount } from 'enzyme';
10 |
11 | describe("Testing all the SFC using enzyme", function() {
12 | const testData = [
13 | {
14 | "id": "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
15 | "title": "Hello",
16 | "content": "# testing markdown",
17 | "time": 1458030208359
18 | }, {
19 | "id": "6c84fb90-12c4-11e1-840d-7b25c5ee775b",
20 | "title": "Hello2",
21 | "content": "# Hello world",
22 | "time": 1458030208359
23 | }
24 | ];
25 |
26 | it('test List component using Enzyme', () => {
27 | let list = shallow(
);
28 | expect(list.find(ListItem).length).to.equal(testData.length);
29 | })
30 | it('test ListItem component using Enzyme', () => {
31 | let listItem = shallow();
32 | expect(listItem.childAt(1).text()).to.equal(testData[0].title);
33 | expect(listItem.hasClass('list-group-item')).to.be.true;
34 | })
35 | it('test ItemShowLayer with no data using Enzyme', () => {
36 | let itemShowLayer = shallow();
37 | expect(itemShowLayer.find('.no-select').length).to.equal(1);
38 | expect(itemShowLayer.hasClass('item-show-layer-component'));
39 | })
40 | it('test ItemShowLayer with data using Enzyme', () => {
41 | let itemShowLayer = shallow();
42 | expect(itemShowLayer.find('h2').text()).to.equal(testData[0].title);
43 | expect(itemShowLayer.hasClass('item-show-layer-component'));
44 | })
45 | it('test ItemEditor with no data using Enzyme', () => {
46 | let itemEditor = shallow();
47 | expect(itemEditor.find('.btn-success').text()).to.equal('创建');
48 | expect(itemEditor.find('input').props().defaultValue).to.equal('');
49 | expect(itemEditor.find('textarea').props().defaultValue).to.equal('');
50 | })
51 | it('test ItemEditor with data using Enzyme', () => {
52 | let itemEditor = shallow();
53 | expect(itemEditor.find('.btn-success').text()).to.equal('保存');
54 | expect(itemEditor.find('input').props().defaultValue).to.equal(testData[0].title);
55 | expect(itemEditor.find('textarea').props().defaultValue).to.equal(testData[0].content);
56 | })
57 | it('test Deskmark inital load with Enzyme', () => {
58 | let deskmark = shallow();
59 | expect(deskmark.find(CreateBar).length).to.equal(1);
60 | expect(deskmark.find(ItemShowLayer).length).to.equal(1);
61 | expect(deskmark.find(ItemEditor).length).to.equal(0);
62 | expect(deskmark.find(List).length).to.equal(1);
63 | })
64 | it('test Deskmark create one post and delete a post', () => {
65 | // I think it is hard to do the UI stuff with shallow render,
66 | // the dom-render is more reasonable and more clear
67 | /*let deskmark = shallow();
68 | deskmark.find(CreateBar).simulate('click');
69 | expect(deskmark.find(ItemEditor).length).to.equal(1);
70 | expect(deskmark.find(ItemShowLayer).length).to.equal(0);
71 | console.log(deskmark.find(ItemEditor).props());
72 | deskmark.find(ItemEditor).props().onSave({'title': 'test', 'content': '#testing markdown'});
73 | expect(deskmark.find(ItemShowLayer).length).to.equal(1);*/
74 | let deskmark = mount();
75 | //click the create button
76 | deskmark.find('.create-bar-component').simulate('click');
77 | expect(deskmark.find('.item-editor-component').length).to.equal(1);
78 | expect(deskmark.find('.item-show-layer-component').length).to.equal(0);
79 | expect(deskmark.find('.item-component').length).to.equal(0);
80 | //set input and textarea
81 | let input = deskmark.find('input');
82 | input.node.value = 'new title';
83 | input.simulate('change', input);
84 | let textarea = deskmark.find('textarea');
85 | textarea.node.value = '#looks good';
86 | textarea.simulate('change', textarea);
87 | //click save button
88 | deskmark.find('.btn-success').simulate('click');
89 | //editor should gone, showLayer should show
90 | expect(deskmark.find('.item-editor-component').length).to.equal(0);
91 | expect(deskmark.find('.item-show-layer-component').length).to.equal(1);
92 | //list should have one item
93 | expect(deskmark.find('.item-component').length).to.equal(1);
94 | //the first item should have the same title
95 | expect(deskmark.find('.item-component').first().find('.item-title').text()).to.equal('new title');
96 | //click the first item
97 | deskmark.find('.item-component').first().simulate('click');
98 | //the showLayer editor should have the new title
99 | expect(deskmark.find('.item-show-layer-component h2').text()).to.equal('new title');
100 | //click the delete button
101 | deskmark.find('.btn-danger').simulate('click');
102 | //the itemlist should be empty
103 | expect(deskmark.find('.item-component').length).to.equal(0);
104 | })
105 | });
106 |
--------------------------------------------------------------------------------
/chapter6/part2/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deskmark",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "asap": {
6 | "version": "2.0.5",
7 | "from": "asap@>=2.0.3 <2.1.0",
8 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz"
9 | },
10 | "core-js": {
11 | "version": "1.2.7",
12 | "from": "core-js@>=1.0.0 <2.0.0",
13 | "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-1.2.7.tgz"
14 | },
15 | "encoding": {
16 | "version": "0.1.12",
17 | "from": "encoding@>=0.1.11 <0.2.0",
18 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz"
19 | },
20 | "fbjs": {
21 | "version": "0.8.6",
22 | "from": "fbjs@>=0.8.4 <0.9.0",
23 | "resolved": "https://registry.npm.taobao.org/fbjs/download/fbjs-0.8.6.tgz"
24 | },
25 | "hoist-non-react-statics": {
26 | "version": "1.2.0",
27 | "from": "hoist-non-react-statics@>=1.0.3 <2.0.0",
28 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz"
29 | },
30 | "iconv-lite": {
31 | "version": "0.4.13",
32 | "from": "iconv-lite@>=0.4.13 <0.5.0",
33 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
34 | },
35 | "invariant": {
36 | "version": "2.2.2",
37 | "from": "invariant@>=2.0.0 <3.0.0",
38 | "resolved": "https://registry.npm.taobao.org/invariant/download/invariant-2.2.2.tgz"
39 | },
40 | "is-stream": {
41 | "version": "1.1.0",
42 | "from": "is-stream@>=1.0.1 <2.0.0",
43 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
44 | },
45 | "isomorphic-fetch": {
46 | "version": "2.2.1",
47 | "from": "isomorphic-fetch@>=2.2.1 <3.0.0",
48 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz"
49 | },
50 | "js-tokens": {
51 | "version": "2.0.0",
52 | "from": "js-tokens@>=2.0.0 <3.0.0",
53 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
54 | },
55 | "lodash": {
56 | "version": "4.17.2",
57 | "from": "lodash@>=4.2.0 <5.0.0",
58 | "resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.2.tgz"
59 | },
60 | "lodash-es": {
61 | "version": "4.17.2",
62 | "from": "lodash-es@>=4.2.1 <5.0.0",
63 | "resolved": "https://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.2.tgz"
64 | },
65 | "loose-envify": {
66 | "version": "1.3.0",
67 | "from": "loose-envify@>=1.1.0 <2.0.0",
68 | "resolved": "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.0.tgz"
69 | },
70 | "marked": {
71 | "version": "0.3.6",
72 | "from": "marked@>=0.3.5 <0.4.0",
73 | "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz"
74 | },
75 | "node-fetch": {
76 | "version": "1.6.3",
77 | "from": "node-fetch@>=1.0.1 <2.0.0",
78 | "resolved": "https://registry.npm.taobao.org/node-fetch/download/node-fetch-1.6.3.tgz"
79 | },
80 | "object-assign": {
81 | "version": "4.1.0",
82 | "from": "object-assign@>=4.1.0 <5.0.0",
83 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
84 | },
85 | "promise": {
86 | "version": "7.1.1",
87 | "from": "promise@>=7.1.1 <8.0.0",
88 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz"
89 | },
90 | "react": {
91 | "version": "15.0.1",
92 | "from": "react@15.0.1",
93 | "resolved": "https://registry.npm.taobao.org/react/download/react-15.0.1.tgz"
94 | },
95 | "react-dom": {
96 | "version": "15.0.1",
97 | "from": "react-dom@15.0.1",
98 | "resolved": "https://registry.npm.taobao.org/react-dom/download/react-dom-15.0.1.tgz"
99 | },
100 | "react-redux": {
101 | "version": "4.4.6",
102 | "from": "react-redux@>=4.4.2 <5.0.0",
103 | "resolved": "https://registry.npm.taobao.org/react-redux/download/react-redux-4.4.6.tgz"
104 | },
105 | "redux": {
106 | "version": "3.6.0",
107 | "from": "redux@>=3.2.1 <4.0.0",
108 | "resolved": "https://registry.npm.taobao.org/redux/download/redux-3.6.0.tgz"
109 | },
110 | "redux-promise-middleware": {
111 | "version": "3.3.2",
112 | "from": "redux-promise-middleware@>=3.0.0 <4.0.0",
113 | "resolved": "https://registry.npmjs.org/redux-promise-middleware/-/redux-promise-middleware-3.3.2.tgz"
114 | },
115 | "redux-thunk": {
116 | "version": "1.0.3",
117 | "from": "redux-thunk@>=1.0.3 <2.0.0",
118 | "resolved": "https://registry.npm.taobao.org/redux-thunk/download/redux-thunk-1.0.3.tgz"
119 | },
120 | "symbol-observable": {
121 | "version": "1.0.4",
122 | "from": "symbol-observable@>=1.0.2 <2.0.0",
123 | "resolved": "https://registry.npm.taobao.org/symbol-observable/download/symbol-observable-1.0.4.tgz"
124 | },
125 | "ua-parser-js": {
126 | "version": "0.7.12",
127 | "from": "ua-parser-js@>=0.7.9 <0.8.0",
128 | "resolved": "https://registry.npm.taobao.org/ua-parser-js/download/ua-parser-js-0.7.12.tgz"
129 | },
130 | "whatwg-fetch": {
131 | "version": "2.0.1",
132 | "from": "whatwg-fetch@>=0.10.0",
133 | "resolved": "https://registry.npm.taobao.org/whatwg-fetch/download/whatwg-fetch-2.0.1.tgz"
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/chapter5/part2/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "part2",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "align-text": {
6 | "version": "0.1.4",
7 | "from": "align-text@>=0.1.3 <0.2.0",
8 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz"
9 | },
10 | "amdefine": {
11 | "version": "1.0.1",
12 | "from": "amdefine@>=0.0.4",
13 | "resolved": "https://registry.npm.taobao.org/amdefine/download/amdefine-1.0.1.tgz"
14 | },
15 | "async": {
16 | "version": "1.5.2",
17 | "from": "async@>=1.4.0 <2.0.0",
18 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
19 | },
20 | "camelcase": {
21 | "version": "1.2.1",
22 | "from": "camelcase@>=1.0.2 <2.0.0",
23 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
24 | },
25 | "center-align": {
26 | "version": "0.1.3",
27 | "from": "center-align@>=0.1.1 <0.2.0",
28 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz"
29 | },
30 | "cliui": {
31 | "version": "2.1.0",
32 | "from": "cliui@>=2.1.0 <3.0.0",
33 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
34 | "dependencies": {
35 | "wordwrap": {
36 | "version": "0.0.2",
37 | "from": "wordwrap@0.0.2",
38 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
39 | }
40 | }
41 | },
42 | "decamelize": {
43 | "version": "1.2.0",
44 | "from": "decamelize@>=1.0.0 <2.0.0",
45 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
46 | },
47 | "handlebars": {
48 | "version": "4.0.6",
49 | "from": "handlebars@>=4.0.5 <5.0.0",
50 | "resolved": "https://registry.npm.taobao.org/handlebars/download/handlebars-4.0.6.tgz"
51 | },
52 | "is-buffer": {
53 | "version": "1.1.4",
54 | "from": "is-buffer@>=1.0.2 <2.0.0",
55 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz"
56 | },
57 | "js-tokens": {
58 | "version": "2.0.0",
59 | "from": "js-tokens@>=2.0.0 <3.0.0",
60 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
61 | },
62 | "kind-of": {
63 | "version": "3.0.4",
64 | "from": "kind-of@>=3.0.2 <4.0.0",
65 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.4.tgz"
66 | },
67 | "lazy-cache": {
68 | "version": "1.0.4",
69 | "from": "lazy-cache@>=1.0.3 <2.0.0",
70 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz"
71 | },
72 | "lodash": {
73 | "version": "4.17.2",
74 | "from": "lodash@>=4.2.1 <5.0.0",
75 | "resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.2.tgz"
76 | },
77 | "lodash-es": {
78 | "version": "4.17.2",
79 | "from": "lodash-es@>=4.2.1 <5.0.0",
80 | "resolved": "https://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.2.tgz"
81 | },
82 | "longest": {
83 | "version": "1.0.1",
84 | "from": "longest@>=1.0.1 <2.0.0",
85 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz"
86 | },
87 | "loose-envify": {
88 | "version": "1.3.0",
89 | "from": "loose-envify@>=1.1.0 <2.0.0",
90 | "resolved": "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.3.0.tgz"
91 | },
92 | "minimist": {
93 | "version": "0.0.10",
94 | "from": "minimist@>=0.0.1 <0.1.0",
95 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
96 | },
97 | "optimist": {
98 | "version": "0.6.1",
99 | "from": "optimist@>=0.6.1 <0.7.0",
100 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz"
101 | },
102 | "redux": {
103 | "version": "3.6.0",
104 | "from": "redux@>=3.5.2 <4.0.0",
105 | "resolved": "https://registry.npm.taobao.org/redux/download/redux-3.6.0.tgz"
106 | },
107 | "repeat-string": {
108 | "version": "1.6.1",
109 | "from": "repeat-string@>=1.5.2 <2.0.0",
110 | "resolved": "https://registry.npm.taobao.org/repeat-string/download/repeat-string-1.6.1.tgz"
111 | },
112 | "right-align": {
113 | "version": "0.1.3",
114 | "from": "right-align@>=0.1.1 <0.2.0",
115 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz"
116 | },
117 | "source-map": {
118 | "version": "0.4.4",
119 | "from": "source-map@>=0.4.4 <0.5.0",
120 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz"
121 | },
122 | "symbol-observable": {
123 | "version": "1.0.4",
124 | "from": "symbol-observable@>=1.0.2 <2.0.0",
125 | "resolved": "https://registry.npm.taobao.org/symbol-observable/download/symbol-observable-1.0.4.tgz"
126 | },
127 | "uglify-js": {
128 | "version": "2.7.4",
129 | "from": "uglify-js@>=2.6.0 <3.0.0",
130 | "resolved": "https://registry.npm.taobao.org/uglify-js/download/uglify-js-2.7.4.tgz",
131 | "dependencies": {
132 | "async": {
133 | "version": "0.2.10",
134 | "from": "async@>=0.2.6 <0.3.0",
135 | "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
136 | },
137 | "source-map": {
138 | "version": "0.5.6",
139 | "from": "source-map@>=0.5.1 <0.6.0",
140 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz"
141 | }
142 | }
143 | },
144 | "uglify-to-browserify": {
145 | "version": "1.0.2",
146 | "from": "uglify-to-browserify@>=1.0.0 <1.1.0",
147 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz"
148 | },
149 | "window-size": {
150 | "version": "0.1.0",
151 | "from": "window-size@0.1.0",
152 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz"
153 | },
154 | "wordwrap": {
155 | "version": "0.0.3",
156 | "from": "wordwrap@>=0.0.2 <0.1.0",
157 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
158 | },
159 | "yargs": {
160 | "version": "3.10.0",
161 | "from": "yargs@>=3.10.0 <3.11.0",
162 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz"
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/chapter2/part3/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "part2",
3 | "version": "0.0.1",
4 | "dependencies": {
5 | "alphanum-sort": {
6 | "version": "1.0.2",
7 | "from": "alphanum-sort@>=1.0.1 <2.0.0",
8 | "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz"
9 | },
10 | "ansi-regex": {
11 | "version": "2.0.0",
12 | "from": "ansi-regex@>=2.0.0 <3.0.0",
13 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
14 | },
15 | "ansi-styles": {
16 | "version": "2.2.1",
17 | "from": "ansi-styles@>=2.2.1 <3.0.0",
18 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz"
19 | },
20 | "argparse": {
21 | "version": "1.0.9",
22 | "from": "argparse@>=1.0.7 <2.0.0",
23 | "resolved": "https://registry.npm.taobao.org/argparse/download/argparse-1.0.9.tgz"
24 | },
25 | "autoprefixer": {
26 | "version": "6.5.3",
27 | "from": "autoprefixer@>=6.3.1 <7.0.0",
28 | "resolved": "https://registry.npm.taobao.org/autoprefixer/download/autoprefixer-6.5.3.tgz"
29 | },
30 | "balanced-match": {
31 | "version": "0.4.2",
32 | "from": "balanced-match@>=0.4.2 <0.5.0",
33 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz"
34 | },
35 | "big.js": {
36 | "version": "3.1.3",
37 | "from": "big.js@>=3.1.3 <4.0.0",
38 | "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz"
39 | },
40 | "browserslist": {
41 | "version": "1.4.0",
42 | "from": "browserslist@>=1.4.0 <1.5.0",
43 | "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-1.4.0.tgz"
44 | },
45 | "caniuse-db": {
46 | "version": "1.0.30000584",
47 | "from": "caniuse-db@>=1.0.30000578 <2.0.0",
48 | "resolved": "https://registry.npm.taobao.org/caniuse-db/download/caniuse-db-1.0.30000584.tgz"
49 | },
50 | "chalk": {
51 | "version": "1.1.3",
52 | "from": "chalk@>=1.1.3 <2.0.0",
53 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
54 | "dependencies": {
55 | "supports-color": {
56 | "version": "2.0.0",
57 | "from": "supports-color@>=2.0.0 <3.0.0",
58 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
59 | }
60 | }
61 | },
62 | "clap": {
63 | "version": "1.1.1",
64 | "from": "clap@>=1.0.9 <2.0.0",
65 | "resolved": "https://registry.npmjs.org/clap/-/clap-1.1.1.tgz"
66 | },
67 | "clone": {
68 | "version": "1.0.2",
69 | "from": "clone@>=1.0.2 <2.0.0",
70 | "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz"
71 | },
72 | "coa": {
73 | "version": "1.0.1",
74 | "from": "coa@>=1.0.1 <1.1.0",
75 | "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.1.tgz"
76 | },
77 | "color": {
78 | "version": "0.11.4",
79 | "from": "color@>=0.11.0 <0.12.0",
80 | "resolved": "https://registry.npm.taobao.org/color/download/color-0.11.4.tgz"
81 | },
82 | "color-convert": {
83 | "version": "1.8.2",
84 | "from": "color-convert@>=1.3.0 <2.0.0",
85 | "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-1.8.2.tgz"
86 | },
87 | "color-name": {
88 | "version": "1.1.1",
89 | "from": "color-name@>=1.1.1 <2.0.0",
90 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz"
91 | },
92 | "color-string": {
93 | "version": "0.3.0",
94 | "from": "color-string@>=0.3.0 <0.4.0",
95 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz"
96 | },
97 | "colormin": {
98 | "version": "1.1.2",
99 | "from": "colormin@>=1.0.5 <2.0.0",
100 | "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz"
101 | },
102 | "colors": {
103 | "version": "1.1.2",
104 | "from": "colors@>=1.1.2 <1.2.0",
105 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz"
106 | },
107 | "css-color-names": {
108 | "version": "0.0.4",
109 | "from": "css-color-names@0.0.4",
110 | "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz"
111 | },
112 | "css-selector-tokenizer": {
113 | "version": "0.5.4",
114 | "from": "css-selector-tokenizer@>=0.5.1 <0.6.0",
115 | "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.5.4.tgz"
116 | },
117 | "cssesc": {
118 | "version": "0.1.0",
119 | "from": "cssesc@>=0.1.0 <0.2.0",
120 | "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz"
121 | },
122 | "cssnano": {
123 | "version": "3.8.0",
124 | "from": "cssnano@>=2.6.1 <4.0.0",
125 | "resolved": "https://registry.npm.taobao.org/cssnano/download/cssnano-3.8.0.tgz"
126 | },
127 | "csso": {
128 | "version": "2.2.1",
129 | "from": "csso@>=2.2.1 <2.3.0",
130 | "resolved": "https://registry.npmjs.org/csso/-/csso-2.2.1.tgz"
131 | },
132 | "decamelize": {
133 | "version": "1.2.0",
134 | "from": "decamelize@>=1.1.2 <2.0.0",
135 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
136 | },
137 | "defined": {
138 | "version": "1.0.0",
139 | "from": "defined@>=1.0.0 <2.0.0",
140 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz"
141 | },
142 | "emojis-list": {
143 | "version": "2.1.0",
144 | "from": "emojis-list@>=2.0.0 <3.0.0",
145 | "resolved": "https://registry.npm.taobao.org/emojis-list/download/emojis-list-2.1.0.tgz"
146 | },
147 | "escape-string-regexp": {
148 | "version": "1.0.5",
149 | "from": "escape-string-regexp@>=1.0.2 <2.0.0",
150 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
151 | },
152 | "esprima": {
153 | "version": "2.7.3",
154 | "from": "esprima@>=2.6.0 <3.0.0",
155 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz"
156 | },
157 | "fastparse": {
158 | "version": "1.1.1",
159 | "from": "fastparse@>=1.1.1 <2.0.0",
160 | "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz"
161 | },
162 | "flatten": {
163 | "version": "1.0.2",
164 | "from": "flatten@>=1.0.2 <2.0.0",
165 | "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz"
166 | },
167 | "function-bind": {
168 | "version": "1.1.0",
169 | "from": "function-bind@>=1.0.2 <2.0.0",
170 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz"
171 | },
172 | "has": {
173 | "version": "1.0.1",
174 | "from": "has@>=1.0.1 <2.0.0",
175 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz"
176 | },
177 | "has-ansi": {
178 | "version": "2.0.0",
179 | "from": "has-ansi@>=2.0.0 <3.0.0",
180 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
181 | },
182 | "has-flag": {
183 | "version": "1.0.0",
184 | "from": "has-flag@>=1.0.0 <2.0.0",
185 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz"
186 | },
187 | "html-comment-regex": {
188 | "version": "1.1.1",
189 | "from": "html-comment-regex@>=1.1.0 <2.0.0",
190 | "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz"
191 | },
192 | "icss-replace-symbols": {
193 | "version": "1.0.2",
194 | "from": "icss-replace-symbols@>=1.0.2 <2.0.0",
195 | "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz"
196 | },
197 | "indexes-of": {
198 | "version": "1.0.1",
199 | "from": "indexes-of@>=1.0.1 <2.0.0",
200 | "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz"
201 | },
202 | "is-absolute-url": {
203 | "version": "2.0.0",
204 | "from": "is-absolute-url@>=2.0.0 <3.0.0",
205 | "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.0.0.tgz"
206 | },
207 | "is-plain-obj": {
208 | "version": "1.1.0",
209 | "from": "is-plain-obj@>=1.0.0 <2.0.0",
210 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz"
211 | },
212 | "is-svg": {
213 | "version": "2.1.0",
214 | "from": "is-svg@>=2.0.0 <3.0.0",
215 | "resolved": "https://registry.npm.taobao.org/is-svg/download/is-svg-2.1.0.tgz"
216 | },
217 | "js-base64": {
218 | "version": "2.1.9",
219 | "from": "js-base64@>=2.1.9 <3.0.0",
220 | "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz"
221 | },
222 | "js-yaml": {
223 | "version": "3.6.1",
224 | "from": "js-yaml@>=3.6.1 <3.7.0",
225 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz"
226 | },
227 | "jsesc": {
228 | "version": "0.5.0",
229 | "from": "jsesc@>=0.5.0 <0.6.0",
230 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz"
231 | },
232 | "json5": {
233 | "version": "0.5.0",
234 | "from": "json5@>=0.5.0 <0.6.0",
235 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.0.tgz"
236 | },
237 | "loader-utils": {
238 | "version": "0.2.16",
239 | "from": "loader-utils@>=0.2.2 <0.3.0",
240 | "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.16.tgz"
241 | },
242 | "lodash._createcompounder": {
243 | "version": "3.0.0",
244 | "from": "lodash._createcompounder@>=3.0.0 <4.0.0",
245 | "resolved": "https://registry.npmjs.org/lodash._createcompounder/-/lodash._createcompounder-3.0.0.tgz"
246 | },
247 | "lodash._root": {
248 | "version": "3.0.1",
249 | "from": "lodash._root@>=3.0.0 <4.0.0",
250 | "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz"
251 | },
252 | "lodash.camelcase": {
253 | "version": "3.0.1",
254 | "from": "lodash.camelcase@>=3.0.1 <4.0.0",
255 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-3.0.1.tgz"
256 | },
257 | "lodash.deburr": {
258 | "version": "3.2.0",
259 | "from": "lodash.deburr@>=3.0.0 <4.0.0",
260 | "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-3.2.0.tgz"
261 | },
262 | "lodash.indexof": {
263 | "version": "4.0.5",
264 | "from": "lodash.indexof@>=4.0.5 <5.0.0",
265 | "resolved": "https://registry.npmjs.org/lodash.indexof/-/lodash.indexof-4.0.5.tgz"
266 | },
267 | "lodash.words": {
268 | "version": "3.2.0",
269 | "from": "lodash.words@>=3.0.0 <4.0.0",
270 | "resolved": "https://registry.npmjs.org/lodash.words/-/lodash.words-3.2.0.tgz"
271 | },
272 | "macaddress": {
273 | "version": "0.2.8",
274 | "from": "macaddress@>=0.2.8 <0.3.0",
275 | "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz"
276 | },
277 | "math-expression-evaluator": {
278 | "version": "1.2.14",
279 | "from": "math-expression-evaluator@>=1.2.14 <2.0.0",
280 | "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.14.tgz"
281 | },
282 | "minimist": {
283 | "version": "0.0.8",
284 | "from": "minimist@0.0.8",
285 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
286 | },
287 | "mkdirp": {
288 | "version": "0.5.1",
289 | "from": "mkdirp@>=0.5.1 <0.6.0",
290 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
291 | },
292 | "normalize-range": {
293 | "version": "0.1.2",
294 | "from": "normalize-range@>=0.1.2 <0.2.0",
295 | "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz"
296 | },
297 | "normalize-url": {
298 | "version": "1.8.0",
299 | "from": "normalize-url@>=1.4.0 <2.0.0",
300 | "resolved": "https://registry.npm.taobao.org/normalize-url/download/normalize-url-1.8.0.tgz"
301 | },
302 | "num2fraction": {
303 | "version": "1.2.2",
304 | "from": "num2fraction@>=1.2.2 <2.0.0",
305 | "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz"
306 | },
307 | "object-assign": {
308 | "version": "4.1.0",
309 | "from": "object-assign@>=4.0.1 <5.0.0",
310 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
311 | },
312 | "postcss": {
313 | "version": "5.2.5",
314 | "from": "postcss@>=5.0.6 <6.0.0",
315 | "resolved": "https://registry.npm.taobao.org/postcss/download/postcss-5.2.5.tgz"
316 | },
317 | "postcss-calc": {
318 | "version": "5.3.1",
319 | "from": "postcss-calc@>=5.2.0 <6.0.0",
320 | "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz"
321 | },
322 | "postcss-colormin": {
323 | "version": "2.2.1",
324 | "from": "postcss-colormin@>=2.1.8 <3.0.0",
325 | "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.1.tgz"
326 | },
327 | "postcss-convert-values": {
328 | "version": "2.4.1",
329 | "from": "postcss-convert-values@>=2.3.4 <3.0.0",
330 | "resolved": "https://registry.npm.taobao.org/postcss-convert-values/download/postcss-convert-values-2.4.1.tgz"
331 | },
332 | "postcss-discard-comments": {
333 | "version": "2.0.4",
334 | "from": "postcss-discard-comments@>=2.0.4 <3.0.0",
335 | "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz"
336 | },
337 | "postcss-discard-duplicates": {
338 | "version": "2.0.2",
339 | "from": "postcss-discard-duplicates@>=2.0.1 <3.0.0",
340 | "resolved": "https://registry.npm.taobao.org/postcss-discard-duplicates/download/postcss-discard-duplicates-2.0.2.tgz"
341 | },
342 | "postcss-discard-empty": {
343 | "version": "2.1.0",
344 | "from": "postcss-discard-empty@>=2.0.1 <3.0.0",
345 | "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz"
346 | },
347 | "postcss-discard-overridden": {
348 | "version": "0.1.1",
349 | "from": "postcss-discard-overridden@>=0.1.1 <0.2.0",
350 | "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz"
351 | },
352 | "postcss-discard-unused": {
353 | "version": "2.2.2",
354 | "from": "postcss-discard-unused@>=2.2.1 <3.0.0",
355 | "resolved": "https://registry.npm.taobao.org/postcss-discard-unused/download/postcss-discard-unused-2.2.2.tgz"
356 | },
357 | "postcss-filter-plugins": {
358 | "version": "2.0.2",
359 | "from": "postcss-filter-plugins@>=2.0.0 <3.0.0",
360 | "resolved": "https://registry.npm.taobao.org/postcss-filter-plugins/download/postcss-filter-plugins-2.0.2.tgz"
361 | },
362 | "postcss-merge-idents": {
363 | "version": "2.1.7",
364 | "from": "postcss-merge-idents@>=2.1.5 <3.0.0",
365 | "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz"
366 | },
367 | "postcss-merge-longhand": {
368 | "version": "2.0.1",
369 | "from": "postcss-merge-longhand@>=2.0.1 <3.0.0",
370 | "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.1.tgz"
371 | },
372 | "postcss-merge-rules": {
373 | "version": "2.0.10",
374 | "from": "postcss-merge-rules@>=2.0.3 <3.0.0",
375 | "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.0.10.tgz"
376 | },
377 | "postcss-message-helpers": {
378 | "version": "2.0.0",
379 | "from": "postcss-message-helpers@>=2.0.0 <3.0.0",
380 | "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz"
381 | },
382 | "postcss-minify-font-values": {
383 | "version": "1.0.5",
384 | "from": "postcss-minify-font-values@>=1.0.2 <2.0.0",
385 | "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz"
386 | },
387 | "postcss-minify-gradients": {
388 | "version": "1.0.5",
389 | "from": "postcss-minify-gradients@>=1.0.1 <2.0.0",
390 | "resolved": "https://registry.npm.taobao.org/postcss-minify-gradients/download/postcss-minify-gradients-1.0.5.tgz"
391 | },
392 | "postcss-minify-params": {
393 | "version": "1.0.5",
394 | "from": "postcss-minify-params@>=1.0.4 <2.0.0",
395 | "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.0.5.tgz"
396 | },
397 | "postcss-minify-selectors": {
398 | "version": "2.0.7",
399 | "from": "postcss-minify-selectors@>=2.0.4 <3.0.0",
400 | "resolved": "https://registry.npm.taobao.org/postcss-minify-selectors/download/postcss-minify-selectors-2.0.7.tgz"
401 | },
402 | "postcss-modules-extract-imports": {
403 | "version": "1.0.1",
404 | "from": "postcss-modules-extract-imports@>=1.0.0 <2.0.0",
405 | "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz"
406 | },
407 | "postcss-modules-local-by-default": {
408 | "version": "1.1.1",
409 | "from": "postcss-modules-local-by-default@>=1.0.1 <2.0.0",
410 | "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.1.1.tgz",
411 | "dependencies": {
412 | "css-selector-tokenizer": {
413 | "version": "0.6.0",
414 | "from": "css-selector-tokenizer@>=0.6.0 <0.7.0",
415 | "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.6.0.tgz"
416 | }
417 | }
418 | },
419 | "postcss-modules-scope": {
420 | "version": "1.0.2",
421 | "from": "postcss-modules-scope@>=1.0.0 <2.0.0",
422 | "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.0.2.tgz",
423 | "dependencies": {
424 | "css-selector-tokenizer": {
425 | "version": "0.6.0",
426 | "from": "css-selector-tokenizer@>=0.6.0 <0.7.0",
427 | "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.6.0.tgz"
428 | }
429 | }
430 | },
431 | "postcss-modules-values": {
432 | "version": "1.2.2",
433 | "from": "postcss-modules-values@>=1.1.0 <2.0.0",
434 | "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.2.2.tgz"
435 | },
436 | "postcss-normalize-charset": {
437 | "version": "1.1.1",
438 | "from": "postcss-normalize-charset@>=1.1.0 <2.0.0",
439 | "resolved": "https://registry.npm.taobao.org/postcss-normalize-charset/download/postcss-normalize-charset-1.1.1.tgz"
440 | },
441 | "postcss-normalize-url": {
442 | "version": "3.0.7",
443 | "from": "postcss-normalize-url@>=3.0.7 <4.0.0",
444 | "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.7.tgz"
445 | },
446 | "postcss-ordered-values": {
447 | "version": "2.2.2",
448 | "from": "postcss-ordered-values@>=2.1.0 <3.0.0",
449 | "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.2.tgz"
450 | },
451 | "postcss-reduce-idents": {
452 | "version": "2.3.1",
453 | "from": "postcss-reduce-idents@>=2.2.2 <3.0.0",
454 | "resolved": "https://registry.npm.taobao.org/postcss-reduce-idents/download/postcss-reduce-idents-2.3.1.tgz"
455 | },
456 | "postcss-reduce-initial": {
457 | "version": "1.0.0",
458 | "from": "postcss-reduce-initial@>=1.0.0 <2.0.0",
459 | "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.0.tgz"
460 | },
461 | "postcss-reduce-transforms": {
462 | "version": "1.0.4",
463 | "from": "postcss-reduce-transforms@>=1.0.3 <2.0.0",
464 | "resolved": "https://registry.npm.taobao.org/postcss-reduce-transforms/download/postcss-reduce-transforms-1.0.4.tgz"
465 | },
466 | "postcss-selector-parser": {
467 | "version": "2.2.2",
468 | "from": "postcss-selector-parser@>=2.0.0 <3.0.0",
469 | "resolved": "https://registry.npm.taobao.org/postcss-selector-parser/download/postcss-selector-parser-2.2.2.tgz"
470 | },
471 | "postcss-svgo": {
472 | "version": "2.1.5",
473 | "from": "postcss-svgo@>=2.1.1 <3.0.0",
474 | "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.5.tgz"
475 | },
476 | "postcss-unique-selectors": {
477 | "version": "2.0.2",
478 | "from": "postcss-unique-selectors@>=2.0.2 <3.0.0",
479 | "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz"
480 | },
481 | "postcss-value-parser": {
482 | "version": "3.3.0",
483 | "from": "postcss-value-parser@>=3.2.3 <4.0.0",
484 | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz"
485 | },
486 | "postcss-zindex": {
487 | "version": "2.1.1",
488 | "from": "postcss-zindex@>=2.0.1 <3.0.0",
489 | "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.1.1.tgz"
490 | },
491 | "prepend-http": {
492 | "version": "1.0.4",
493 | "from": "prepend-http@>=1.0.0 <2.0.0",
494 | "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz"
495 | },
496 | "q": {
497 | "version": "1.4.1",
498 | "from": "q@>=1.1.2 <2.0.0",
499 | "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz"
500 | },
501 | "query-string": {
502 | "version": "4.2.3",
503 | "from": "query-string@>=4.1.0 <5.0.0",
504 | "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.2.3.tgz"
505 | },
506 | "reduce-css-calc": {
507 | "version": "1.3.0",
508 | "from": "reduce-css-calc@>=1.2.6 <2.0.0",
509 | "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz"
510 | },
511 | "reduce-function-call": {
512 | "version": "1.0.1",
513 | "from": "reduce-function-call@>=1.0.1 <2.0.0",
514 | "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.1.tgz",
515 | "dependencies": {
516 | "balanced-match": {
517 | "version": "0.1.0",
518 | "from": "balanced-match@>=0.1.0 <0.2.0",
519 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.1.0.tgz"
520 | }
521 | }
522 | },
523 | "regenerate": {
524 | "version": "1.3.2",
525 | "from": "regenerate@>=1.2.1 <2.0.0",
526 | "resolved": "https://registry.npm.taobao.org/regenerate/download/regenerate-1.3.2.tgz"
527 | },
528 | "regexpu-core": {
529 | "version": "1.0.0",
530 | "from": "regexpu-core@>=1.0.0 <2.0.0",
531 | "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz"
532 | },
533 | "regjsgen": {
534 | "version": "0.2.0",
535 | "from": "regjsgen@>=0.2.0 <0.3.0",
536 | "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz"
537 | },
538 | "regjsparser": {
539 | "version": "0.1.5",
540 | "from": "regjsparser@>=0.1.4 <0.2.0",
541 | "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz"
542 | },
543 | "sax": {
544 | "version": "1.2.1",
545 | "from": "sax@>=1.2.1 <1.3.0",
546 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz"
547 | },
548 | "sort-keys": {
549 | "version": "1.1.2",
550 | "from": "sort-keys@>=1.0.0 <2.0.0",
551 | "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz"
552 | },
553 | "source-list-map": {
554 | "version": "0.1.6",
555 | "from": "source-list-map@>=0.1.4 <0.2.0",
556 | "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.6.tgz"
557 | },
558 | "source-map": {
559 | "version": "0.5.6",
560 | "from": "source-map@>=0.5.6 <0.6.0",
561 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz"
562 | },
563 | "sprintf-js": {
564 | "version": "1.0.3",
565 | "from": "sprintf-js@>=1.0.2 <1.1.0",
566 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
567 | },
568 | "strict-uri-encode": {
569 | "version": "1.1.0",
570 | "from": "strict-uri-encode@>=1.0.0 <2.0.0",
571 | "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz"
572 | },
573 | "strip-ansi": {
574 | "version": "3.0.1",
575 | "from": "strip-ansi@>=3.0.0 <4.0.0",
576 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
577 | },
578 | "supports-color": {
579 | "version": "3.1.2",
580 | "from": "supports-color@>=3.1.2 <4.0.0",
581 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz"
582 | },
583 | "svgo": {
584 | "version": "0.7.1",
585 | "from": "svgo@>=0.7.0 <0.8.0",
586 | "resolved": "https://registry.npm.taobao.org/svgo/download/svgo-0.7.1.tgz"
587 | },
588 | "uniq": {
589 | "version": "1.0.1",
590 | "from": "uniq@>=1.0.1 <2.0.0",
591 | "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz"
592 | },
593 | "uniqid": {
594 | "version": "4.1.0",
595 | "from": "uniqid@>=4.0.0 <5.0.0",
596 | "resolved": "https://registry.npm.taobao.org/uniqid/download/uniqid-4.1.0.tgz"
597 | },
598 | "uniqs": {
599 | "version": "2.0.0",
600 | "from": "uniqs@>=2.0.0 <3.0.0",
601 | "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz"
602 | },
603 | "vendors": {
604 | "version": "1.0.1",
605 | "from": "vendors@>=1.0.0 <2.0.0",
606 | "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz"
607 | },
608 | "whet.extend": {
609 | "version": "0.9.9",
610 | "from": "whet.extend@>=0.9.9 <0.10.0",
611 | "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz"
612 | }
613 | }
614 | }
615 |
--------------------------------------------------------------------------------