├── 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