├── public
├── favicon.ico
├── manifest.json
└── index.html
├── README.md
├── config
├── jest
│ ├── cssTransform.js
│ └── fileTransform.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
└── webpack.config.js
├── .gitignore
├── src
├── index.js
├── todo
│ ├── TodoCount.js
│ ├── TodoLists.js
│ ├── TodoItem.js
│ ├── TodoPanel.js
│ └── app.js
├── css
│ └── todo.scss
└── serviceWorker.js
├── scripts
├── test.js
├── start.js
└── build.js
└── package.json
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/funlee/todo/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | todo
2 | ===============
3 | 预览地址:[todo](http://show.funlee.cn/react-todo/index.html "todo")
4 |
5 | 安装
6 | ----
7 | ```bash
8 | git clone https://github.com/funlee/todo.git
9 | cd todo
10 | npm install
11 | npm run start
12 | ```
13 | 然后在浏览器里输入:http:localhost:8080 即可访问
14 |
15 | 主要功能
16 | -------
17 | * Task 增加、删除
18 | * 增加 Task 支持回车(Enter)
19 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './todo/app';
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
10 | // Learn more about service workers: http://bit.ly/CRA-PWA
11 | serviceWorker.unregister();
12 |
--------------------------------------------------------------------------------
/src/todo/TodoCount.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: funlee
3 | * @Email: i@funlee.cn
4 | * @Date: 2018-01-18 23:43:45
5 | * @Last Modified time: 2018-01-18 23:43:45
6 | * @Description: todoCount component
7 | */
8 | import React, { Component } from 'react';
9 |
10 | class TodoCount extends Component {
11 | render() {
12 | return (
13 |
14 | {this.props.totalCompleteCount}已完成 / {this.props.totalCount}总数
15 |
16 | )
17 | }
18 | }
19 | export default TodoCount
--------------------------------------------------------------------------------
/src/todo/TodoLists.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: funlee
3 | * @Email: i@funlee.cn
4 | * @Date: 2018-01-18 22:07:29
5 | * @Last Modified time: 2018-01-18 22:07:29
6 | * @Description: TodoLists component
7 | */
8 | import React, { Component } from 'react';
9 | import TodoItem from './TodoItem';
10 |
11 | class TodoLists extends Component {
12 | render() {
13 | const listItems = this.props.data.map(item => {
14 | return (
15 |
22 | )
23 | })
24 | return (
25 |
28 | )
29 | }
30 | }
31 | export default TodoLists
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/en/webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | const assetFilename = JSON.stringify(path.basename(filename));
11 |
12 | if (filename.match(/\.svg$/)) {
13 | return `module.exports = {
14 | __esModule: true,
15 | default: ${assetFilename},
16 | ReactComponent: (props) => ({
17 | $$typeof: Symbol.for('react.element'),
18 | type: 'svg',
19 | ref: null,
20 | key: null,
21 | props: Object.assign({}, props, {
22 | children: ${assetFilename}
23 | })
24 | }),
25 | };`;
26 | }
27 |
28 | return `module.exports = ${assetFilename};`;
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/src/todo/TodoItem.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: funlee
3 | * @Email: i@funlee.cn
4 | * @Date: 2018-01-18 22:04:01
5 | * @Last Modified time: 2018-01-18 22:04:01
6 | * @Description: todo item component
7 | */
8 | import React, { Component } from 'react';
9 |
10 | class TodoItem extends Component {
11 | constructor(props) {
12 | super(props)
13 | this.toggleComplete = this.toggleComplete.bind(this)
14 | this.deleteTask = this.deleteTask.bind(this)
15 | }
16 | toggleComplete() {
17 | this.props.toggleComplete(this.props.taskId)
18 | }
19 | deleteTask() {
20 | this.props.deleteTask(this.props.taskId)
21 | }
22 | render() {
23 | return (
24 |
25 |
26 | {this.props.task}
27 | 删除
28 |
29 | )
30 | }
31 | }
32 | export default TodoItem
--------------------------------------------------------------------------------
/src/todo/TodoPanel.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: funlee
3 | * @Email: i@funlee.cn
4 | * @Date: 2018-01-19 00:02:08
5 | * @Last Modified time: 2018-01-19 00:02:08
6 | * @Description: TodoPanel component
7 | */
8 | import React, { Component } from 'react';
9 |
10 | class TodoPanel extends Component {
11 | constructor(props) {
12 | super(props)
13 | this.state = {
14 | value:''
15 | }
16 | this.handleClick = this.handleClick.bind(this)
17 | this.handleChange = this.handleChange.bind(this)
18 | this.handleKeyPress = this.handleKeyPress.bind(this)
19 | }
20 | handleClick() {
21 | if (this.state.value.trim() === '') {
22 | alert('请输入Task!')
23 | return
24 | }
25 | this.props.addTask(this.state.value.trim())
26 | this.setState({
27 | value: ''
28 | })
29 | }
30 | handleChange(event) {
31 | this.setState({
32 | value: event.target.value
33 | })
34 | }
35 | handleKeyPress(event){
36 | if(event.keyCode === 13) {
37 | this.handleClick()
38 | }
39 | }
40 | render() {
41 | return (
42 |
43 |
44 | Task
45 |
46 |
47 |
48 |
49 |
50 |
51 | )
52 | }
53 | }
54 |
55 | export default TodoPanel
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
15 |
16 |
25 | React App
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 |
19 | const jest = require('jest');
20 | const execSync = require('child_process').execSync;
21 | let argv = process.argv.slice(2);
22 |
23 | function isInGitRepository() {
24 | try {
25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
26 | return true;
27 | } catch (e) {
28 | return false;
29 | }
30 | }
31 |
32 | function isInMercurialRepository() {
33 | try {
34 | execSync('hg --cwd . root', { stdio: 'ignore' });
35 | return true;
36 | } catch (e) {
37 | return false;
38 | }
39 | }
40 |
41 | // Watch unless on CI, in coverage mode, explicitly adding `--no-watch`,
42 | // or explicitly running all tests
43 | if (
44 | !process.env.CI &&
45 | argv.indexOf('--coverage') === -1 &&
46 | argv.indexOf('--no-watch') === -1 &&
47 | argv.indexOf('--watchAll') === -1
48 | ) {
49 | // https://github.com/facebook/create-react-app/issues/5210
50 | const hasSourceControl = isInGitRepository() || isInMercurialRepository();
51 | argv.push(hasSourceControl ? '--watch' : '--watchAll');
52 | }
53 |
54 | // Jest doesn't have this option so we'll remove it
55 | if (argv.indexOf('--no-watch') !== -1) {
56 | argv = argv.filter(arg => arg !== '--no-watch');
57 | }
58 |
59 |
60 | jest.run(argv);
61 |
--------------------------------------------------------------------------------
/src/todo/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: funlee
3 | * @Email: i@funlee.cn
4 | * @Date: 2018-01-19 00:36:17
5 | * @Last Modified time: 2018-01-19 00:36:17
6 | * @Description: todo
7 | */
8 | import React, { Component } from 'react';
9 | import '../css/todo.scss'
10 |
11 | import TodoLists from './TodoLists'
12 | import TodoCount from './TodoCount'
13 | import TodoPanel from './TodoPanel'
14 |
15 | import playTitle from 'play-title'
16 |
17 | class App extends Component {
18 | constructor(props) {
19 | super(props);
20 | playTitle()
21 | this.state = {
22 | tasks:[
23 | {'id':'001' ,'task': '打开 github 官网', 'complete': true },
24 | {'id':'002' ,'task': '搜索关键词 funlee', 'complete': false },
25 | {'id':'003' ,'task': 'And Then Follow him', 'complete': false }
26 | ]
27 | }
28 | this.handleToggleComplete = this.handleToggleComplete.bind(this)
29 | this.addTask = this.addTask.bind(this)
30 | this.deleteTask = this.deleteTask.bind(this)
31 | }
32 | // 切换一项任务的状态
33 | handleToggleComplete(taskId) {
34 | const { tasks } = this.state
35 | for(let i in tasks) {
36 | if(tasks[i].id === taskId) {
37 | tasks[i].complete = !tasks[i].complete
38 | break;
39 | }
40 | }
41 | this.setState({ tasks: tasks})
42 | }
43 | // 增加一项任务
44 | addTask(task) {
45 | const id = new Date().valueOf()
46 | const { tasks } = this.state
47 | tasks.unshift({
48 | id:id,
49 | task:task,
50 | complete:false
51 | })
52 | this.setState({tasks:tasks})
53 | }
54 | // 删除一条任务
55 | deleteTask(taskId) {
56 | const { tasks } = this.state
57 | const data = tasks.filter(item => {
58 | return item.id !== taskId
59 | })
60 | this.setState({ tasks: data })
61 | }
62 | render() {
63 | const totalCount = this.state.tasks.length
64 | const totalCompleteCount = this.state.tasks.filter(item=> {
65 | return item.complete === true
66 | }).length
67 | return (
68 |
69 |
Todo List
70 |
71 |
72 |
73 |
74 | )
75 | }
76 | }
77 |
78 | export default App
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebook/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(inputPath, needsSlash) {
15 | const hasSlash = inputPath.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return inputPath.substr(0, inputPath.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${inputPath}/`;
20 | } else {
21 | return inputPath;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right