├── .client ├── .babelrc ├── .gitignore ├── actions │ └── TodoActions.js ├── components │ ├── Footer.js │ ├── Header.js │ ├── MainSection.js │ ├── TodoApp.js │ ├── TodoItem.js │ └── TodoTextInput.js ├── constants │ ├── ActionTypes.js │ └── TodoFilters.js ├── containers │ ├── App.js │ └── Root.js ├── index.js ├── package.json ├── reducers │ ├── index.js │ └── todos.js ├── server.js ├── stores │ ├── index.js │ └── todos.js └── webpack.config.js ├── .gitignore ├── .meteor ├── .finished-upgraders ├── .gitignore ├── .id ├── packages ├── platforms ├── release └── versions ├── README.md ├── client └── .git-keep ├── index.html ├── lib └── models │ └── todo.js └── makefile /.client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } 4 | -------------------------------------------------------------------------------- /.client/.gitignore: -------------------------------------------------------------------------------- 1 | .meteor/local 2 | .meteor/meteorite 3 | 4 | 5 | .DS_Store 6 | .AppleDouble 7 | ._* 8 | .Trashes 9 | .Spotlight-V100 10 | 11 | 12 | .idea 13 | 14 | 15 | Thumb.db 16 | ehthumb.db 17 | Desktop.ini 18 | $RECYCLE.BIN/ 19 | 20 | *.cab 21 | *.msi 22 | *.msm 23 | *.msp 24 | 25 | 26 | *~ 27 | .directory 28 | node_modules 29 | client/bundle.js -------------------------------------------------------------------------------- /.client/actions/TodoActions.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes'; 2 | 3 | export function addTodo(text) { 4 | return { 5 | type: types.ADD_TODO, 6 | text 7 | }; 8 | } 9 | 10 | export function deleteTodo(id) { 11 | return { 12 | type: types.DELETE_TODO, 13 | id 14 | }; 15 | } 16 | 17 | export function editTodo(id, text) { 18 | return { 19 | type: types.EDIT_TODO, 20 | id, 21 | text 22 | }; 23 | } 24 | 25 | export function markTodo(id) { 26 | return { 27 | type: types.MARK_TODO, 28 | id 29 | }; 30 | } 31 | 32 | export function markAll() { 33 | return { 34 | type: types.MARK_ALL 35 | }; 36 | } 37 | 38 | export function clearMarked() { 39 | return { 40 | type: types.CLEAR_MARKED 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /.client/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import classnames from 'classnames'; 3 | import { SHOW_ALL, SHOW_MARKED, SHOW_UNMARKED } from '../constants/TodoFilters'; 4 | 5 | const FILTER_TITLES = { 6 | [SHOW_ALL]: 'All', 7 | [SHOW_UNMARKED]: 'Active', 8 | [SHOW_MARKED]: 'Completed' 9 | }; 10 | 11 | export default class Footer extends Component { 12 | static propTypes = { 13 | markedCount: PropTypes.number.isRequired, 14 | unmarkedCount: PropTypes.number.isRequired, 15 | filter: PropTypes.string.isRequired, 16 | onClearMarked: PropTypes.func.isRequired, 17 | onShow: PropTypes.func.isRequired 18 | } 19 | 20 | render() { 21 | return ( 22 | 33 | ); 34 | } 35 | 36 | renderTodoCount() { 37 | const { unmarkedCount } = this.props; 38 | const itemWord = unmarkedCount === 1 ? 'item' : 'items'; 39 | 40 | return ( 41 | 42 | {unmarkedCount || 'No'} {itemWord} left 43 | 44 | ); 45 | } 46 | 47 | renderFilterLink(filter) { 48 | const title = FILTER_TITLES[filter]; 49 | const { filter: selectedFilter, onShow } = this.props; 50 | 51 | return ( 52 | onShow(filter)}> 55 | {title} 56 | 57 | ); 58 | } 59 | 60 | renderClearButton() { 61 | const { markedCount, onClearMarked } = this.props; 62 | if (markedCount > 0) { 63 | return ( 64 | 68 | ); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.client/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import TodoTextInput from './TodoTextInput'; 3 | 4 | export default class Header extends Component { 5 | static propTypes = { 6 | addTodo: PropTypes.func.isRequired 7 | }; 8 | 9 | handleSave(text) { 10 | if (text.length !== 0) { 11 | this.props.addTodo(text); 12 | } 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |

todos

19 | 22 |
23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.client/components/MainSection.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import TodoItem from './TodoItem'; 3 | import Footer from './Footer'; 4 | import { SHOW_ALL, SHOW_MARKED, SHOW_UNMARKED } from '../constants/TodoFilters'; 5 | 6 | const TODO_FILTERS = { 7 | [SHOW_ALL]: () => true, 8 | [SHOW_UNMARKED]: todo => !todo.marked, 9 | [SHOW_MARKED]: todo => todo.marked 10 | }; 11 | 12 | export default class MainSection extends Component { 13 | static propTypes = { 14 | todos: PropTypes.array.isRequired, 15 | actions: PropTypes.object.isRequired 16 | }; 17 | 18 | constructor(props, context) { 19 | super(props, context); 20 | this.state = { filter: SHOW_ALL }; 21 | } 22 | 23 | handleClearMarked() { 24 | const atLeastOneMarked = this.props.todos.some(todo => todo.marked); 25 | if (atLeastOneMarked) { 26 | this.props.actions.clearMarked(); 27 | } 28 | } 29 | 30 | handleShow(filter) { 31 | this.setState({ filter }); 32 | } 33 | 34 | render() { 35 | const { todos, actions } = this.props; 36 | const { filter } = this.state; 37 | 38 | const filteredTodos = todos.filter(TODO_FILTERS[filter]); 39 | const markedCount = todos.reduce((count, todo) => 40 | todo.marked ? count + 1 : count, 41 | 0 42 | ); 43 | 44 | return ( 45 |
46 | {this.renderToggleAll(markedCount)} 47 | 52 | {this.renderFooter(markedCount)} 53 |
54 | ); 55 | } 56 | 57 | renderToggleAll(markedCount) { 58 | const { todos, actions } = this.props; 59 | if (todos.length > 0) { 60 | return ( 61 | 65 | ); 66 | } 67 | } 68 | 69 | renderFooter(markedCount) { 70 | const { todos } = this.props; 71 | const { filter } = this.state; 72 | const unmarkedCount = todos.length - markedCount; 73 | 74 | if (todos.length) { 75 | return ( 76 |