├── .gitignore ├── .jshintrc ├── README.md ├── common └── db.js ├── config.js ├── controllers └── todo.js ├── lib └── patch.js ├── package.json ├── public ├── images │ ├── doing.gif │ ├── finished.jpg │ └── webpy_ss.png └── styles │ ├── index │ ├── images │ │ ├── bg_1.png │ │ ├── email.png │ │ ├── home.png │ │ ├── msn.png │ │ ├── ok.gif │ │ ├── qq.png │ │ └── website.png │ └── style.css │ └── reset.css ├── server.js ├── todo.sql └── views ├── error.html ├── index.html ├── layout.html └── todo └── edit.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "phantom", 4 | "module", 5 | "require", 6 | "__dirname", 7 | "process", 8 | "console", 9 | "it", 10 | "describe", 11 | "before", 12 | "beforeEach", 13 | "after", 14 | "should", 15 | "rewire", 16 | "$" 17 | ], 18 | 19 | "browser": true, 20 | "node" : true, 21 | "es5": true, 22 | "bitwise": true, 23 | "curly": true, 24 | "eqeqeq": true, 25 | "forin": false, 26 | "immed": true, 27 | "latedef": true, 28 | "newcap": true, 29 | "noarg": true, 30 | "noempty": true, 31 | "nonew": true, 32 | "plusplus": false, 33 | "undef": true, 34 | "strict": false, 35 | "trailing": false, 36 | "globalstrict": true, 37 | "nonstandard": true, 38 | "white": true, 39 | "indent": 2, 40 | "expr": true, 41 | "multistr": true, 42 | "onevar": false 43 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple TODO 2 | 3 | ## Description 4 | 5 | 本应用修改自一个简单的python示例: [simple-todo: 一个简易的 todo 程序 - web.py 中文教程](http://simple-is-better.com/news/309) 6 | 7 | ## Install 8 | 9 | ```bash 10 | $ npm install 11 | ``` 12 | 13 | ## Run 14 | 15 | ```bash 16 | $ node server.js 17 | ``` 18 | 19 | ## Demo 20 | 21 | [http://hello.cnodejs.net/](http://hello.cnodejs.net/) 22 | 23 | ## Test 24 | 25 | ```bash 26 | $ make test 27 | ``` 28 | 29 | ## License 30 | 31 | (The MIT License) 32 | 33 | Copyright (c) 2011, 2012 fengmk2 . 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining 36 | a copy of this software and associated documentation files (the 37 | 'Software'), to deal in the Software without restriction, including 38 | without limitation the rights to use, copy, modify, merge, publish, 39 | distribute, sublicense, and/or sell copies of the Software, and to 40 | permit persons to whom the Software is furnished to do so, subject to 41 | the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be 44 | included in all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 47 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 48 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 49 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 50 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 51 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 52 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /common/db.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * todo - common/db.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var mongoskin = require('mongoskin'); 14 | var config = require('../config'); 15 | 16 | var noop = function () {}; 17 | 18 | var db = mongoskin.db(config.db); 19 | db.bind('todo'); 20 | db.todo.ensureIndex({ finished: 1 }, noop); 21 | 22 | module.exports = db; -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * todo - config.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | exports.debug = true; 10 | exports.port = 3000; 11 | exports.email = 'fengmk2@gmail.com'; 12 | exports.site_name = 'Node TODO'; 13 | exports.site_desc = 'Very simple todo, demo for connect web dev.'; 14 | exports.session_secret = 'todo session secret'; 15 | 16 | exports.db = 'mongodb://127.0.0.1/todo_dev'; -------------------------------------------------------------------------------- /controllers/todo.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * todo - controllers/todo.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var config = require('../config'); 14 | var db = require('../common/db'); 15 | 16 | exports.index = function (req, res, next) { 17 | db.todo.findItems({}, { sort: {_id: 1, finished: 1}}, function (err, rows) { 18 | if (err) { 19 | return next(err); 20 | } 21 | res.render('index.html', {todos: rows}); 22 | }); 23 | }; 24 | 25 | exports.new = function (req, res, next) { 26 | var title = req.body.title || ''; 27 | title = title.trim(); 28 | if (!title) { 29 | return res.render('error.html', {message: '标题是必须的'}); 30 | } 31 | db.todo.save({title: title, post_date: new Date()}, function (err, row) { 32 | if (err) { 33 | return next(err); 34 | } 35 | res.redirect('/'); 36 | }); 37 | }; 38 | 39 | exports.view = function (req, res, next) { 40 | res.redirect('/'); 41 | }; 42 | 43 | exports.edit = function (req, res, next) { 44 | var id = req.params.id; 45 | db.todo.findById(id, function (err, row) { 46 | if (err) { 47 | return next(err); 48 | } 49 | if (!row) { 50 | return next(); 51 | } 52 | res.render('todo/edit.html', {todo: row}); 53 | }); 54 | }; 55 | 56 | exports.save = function (req, res, next) { 57 | var id = req.params.id; 58 | var title = req.body.title || ''; 59 | title = title.trim(); 60 | if (!title) { 61 | return res.render('error.html', {message: '标题是必须的'}); 62 | } 63 | db.todo.updateById(id, {$set: {title: title}}, function (err, result) { 64 | if (err) { 65 | return next(err); 66 | } 67 | res.redirect('/'); 68 | }); 69 | }; 70 | 71 | exports.delete = function (req, res, next) { 72 | var id = req.params.id; 73 | db.todo.removeById(id, function (err) { 74 | if (err) { 75 | return next(err); 76 | } 77 | res.redirect('/'); 78 | }); 79 | }; 80 | 81 | exports.finish = function (req, res, next) { 82 | var finished = req.query.status === 'yes' ? 1 : 0; 83 | var id = req.params.id; 84 | db.todo.updateById(id, {$set: {finished: finished}}, function (err, result) { 85 | if (err) { 86 | return next(err); 87 | } 88 | res.redirect('/'); 89 | }); 90 | }; -------------------------------------------------------------------------------- /lib/patch.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * todo - lib/patch.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var http = require('http'); 14 | 15 | if (typeof http.ServerResponse.prototype.redirect === 'undefined') { 16 | http.ServerResponse.prototype.redirect = function (url, status) { 17 | this.writeHead(status || 302, { 18 | Location: url 19 | }); 20 | this.end(); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "author": "fengmk2 (http://fengmk2.github.com/)", 4 | "name": "todo", 5 | "description": "Very simple todo, demo for connect web dev.", 6 | "version": "0.2.0", 7 | "homepage": "http://github.com/fengmk2/todo", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/fengmk2/todo.git" 11 | }, 12 | "main": "server.js", 13 | "scripts": { 14 | "test": "make test" 15 | }, 16 | "dependencies": { 17 | "connect": "1.x || >= 2.2.0", 18 | "connect-render": ">=0.1.4", 19 | "urlrouter": ">=0.2.0", 20 | "mongoskin": ">=0.3.6" 21 | }, 22 | "devDependencies": { 23 | "should": "*", 24 | "mocha": "*" 25 | }, 26 | "optionalDependencies": {}, 27 | "engines": { 28 | "node": ">= 0.6" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /public/images/doing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/images/doing.gif -------------------------------------------------------------------------------- /public/images/finished.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/images/finished.jpg -------------------------------------------------------------------------------- /public/images/webpy_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/images/webpy_ss.png -------------------------------------------------------------------------------- /public/styles/index/images/bg_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/styles/index/images/bg_1.png -------------------------------------------------------------------------------- /public/styles/index/images/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/styles/index/images/email.png -------------------------------------------------------------------------------- /public/styles/index/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/styles/index/images/home.png -------------------------------------------------------------------------------- /public/styles/index/images/msn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/styles/index/images/msn.png -------------------------------------------------------------------------------- /public/styles/index/images/ok.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/styles/index/images/ok.gif -------------------------------------------------------------------------------- /public/styles/index/images/qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/styles/index/images/qq.png -------------------------------------------------------------------------------- /public/styles/index/images/website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengmk2/todo/7fe2e356da554b00222e6b82f7663665dc74940a/public/styles/index/images/website.png -------------------------------------------------------------------------------- /public/styles/index/style.css: -------------------------------------------------------------------------------- 1 | body { background:#EEE; } 2 | .box { margin-bottom:20px; } 3 | .clear { clear:both; } 4 | .float_left { float:left; } 5 | .float_right { float:right; } 6 | .left { text-align:left; } 7 | .right { text-align:right; } 8 | .url { background:url(images/website.png) no-repeat right; padding-right:20px; } 9 | .tips { color:#CCC; } 10 | .v_top { vertical-align:top; } 11 | .qq { background:url(images/qq.png) no-repeat right; padding-right:20px; } 12 | .msn { background:url(images/msn.png) no-repeat right; padding-right:20px; } 13 | .email { background:url(images/email.png) no-repeat right; padding-right:20px; } 14 | .tips { color:#666; } 15 | .long_txt { width:98%; } 16 | .must_be { color:red !important; } 17 | textarea { line-height:1.5em; } 18 | 19 | image { border:none; } 20 | p { line-height:1.5em; } 21 | 22 | a { color:#3366CC; text-decoration:underline; } 23 | a:hover { color:#FF3300; } 24 | 25 | .page { width:700px; margin:10px auto; background:#FFF; padding:10px 20px; border:1px solid #CCC; border-radius:5px; } 26 | 27 | .header { padding-top:10px; border-bottom:1px solid #CCC; } 28 | .header h1 { font-size:50px; } 29 | .header a { color:#333; text-decoration:none; } 30 | 31 | .main { } 32 | .main_content { } 33 | 34 | .box h2 { padding-bottom:5px; margin-bottom:10px; font-weight:400; color:green; font-size:28px;} 35 | 36 | .todos {} 37 | .todos ul li { list-style-type:none; margin-bottom:10px; padding-bottom:10px; border-bottom:1px dotted #CCC; } 38 | .todos ul li.finished { color:#666; } 39 | .todos ul li a { color:#666; } 40 | .todos ul li del { padding-left:15px; background:url(images/ok.gif) no-repeat left center; } 41 | 42 | .post { } 43 | .post p { margin-bottom:10px; } 44 | .post .submit { line-height:normal; -padding:0; } 45 | 46 | 47 | 48 | .foot { border-top:1px solid #CCC; color:#666; padding-top:10px; font-size:11px; } 49 | .foot img { vertical-align:top; } 50 | 51 | -------------------------------------------------------------------------------- /public/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | KISSY CSS Reset 3 | 理念:清除和重置是紧密不可分的 4 | 特色:1.适应中文 2.基于最新主流浏览器 5 | 维护:玉伯(lifesinger@gmail.com), 正淳(ragecarrier@gmail.com) 6 | */ 7 | 8 | /* 清除内外边距 */ 9 | body, h1, h2, h3, h4, h5, h6, p, blockquote, /* structural elements 结构元素 */ 10 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 11 | pre, /* text formatting elements 文本格式元素 */ 12 | fieldset, lengend, button, input, textarea, /* form elements 表单元素 */ 13 | th, td { /* table elements 表格元素 */ 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | /* 设置默认字体 */ 19 | body, 20 | button, input, select, textarea { /* for ie */ 21 | /*font: 12px/1 Tahoma, Helvetica, Arial, "宋体", sans-serif;*/ 22 | font: 14px/1.5 Arial, sans-serif; 23 | padding:3px; 24 | } 25 | input { line-height:normal; } 26 | h1 { font-size:24px; } 27 | h2 { font-size:20px; } 28 | h3 { font-size:18px; } 29 | h4 { font-size:18px; } 30 | h5 { font-size:14px; } 31 | h1, h2, h3, h4, h5 { font-weight:normal; } 32 | 33 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 34 | small { font-size: 11px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 35 | 36 | /* 重置文本格式元素 */ 37 | a { color:#03C; } 38 | a:hover { color:#F30; } 39 | 40 | abbr[title], acronym[title] { /* 注:1.ie6 不支持 abbr; 2.这里用了属性选择符,ie6 下无效果 */ 41 | border-bottom: 1px dotted; 42 | cursor: help; 43 | } 44 | 45 | q:before, q:after { content: ''; } 46 | 47 | /* 重置表单元素 */ 48 | legend { color: #000; } /* for ie6 */ 49 | fieldset, img { border: none; } /* img 搭车:让链接里的 img 无边框 */ 50 | /* 注:optgroup 无法扶正 */ 51 | button, input, select, textarea { 52 | font-size: 100%; /* 使得表单元素在 ie 下能继承字体大小 */ 53 | } 54 | 55 | /* 重置表格元素 */ 56 | table { 57 | border-collapse: collapse; 58 | border-spacing: 0; 59 | } 60 | 61 | /* 让非ie浏览器默认也显示垂直滚动条,防止因滚动条引起的闪烁 */ 62 | html { overflow-y: scroll; } 63 | 64 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * todo - app.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | require('./lib/patch'); 14 | var connect = require('connect'); 15 | var render = require('connect-render'); 16 | var urlrouter = require('urlrouter'); 17 | var config = require('./config'); 18 | var todo = require('./controllers/todo'); 19 | 20 | var app = connect(); 21 | 22 | app.use('/public', connect.static(__dirname + '/public', {maxAge: 3600000 * 24 * 30})); 23 | app.use(connect.cookieParser()); 24 | app.use(connect.query()); 25 | app.use(connect.bodyParser()); 26 | app.use(connect.session({secret: config.session_secret})); 27 | app.use(connect.csrf()); 28 | app.use(render({ 29 | root: __dirname + '/views', 30 | layout: 'layout.html', 31 | cache: config.debug, // `false` for debug 32 | helpers: { 33 | config: config, 34 | _csrf: function (req, res) { 35 | return req.session._csrf; 36 | } 37 | } 38 | })); 39 | 40 | /** 41 | * Routing 42 | */ 43 | var router = urlrouter(function (app) { 44 | app.get('/', todo.index); 45 | app.post('/todo/new', todo.new); 46 | app.get('/todo/:id', todo.view); 47 | app.get('/todo/:id/edit', todo.edit); 48 | app.post('/todo/:id/edit', todo.save); 49 | app.get('/todo/:id/delete', todo.delete); 50 | app.get('/todo/:id/finish', todo.finish); 51 | }); 52 | app.use(router); 53 | 54 | app.listen(config.port); 55 | console.log('Server start on ' + config.port); -------------------------------------------------------------------------------- /todo.sql: -------------------------------------------------------------------------------- 1 | /* 2 | MySQL Data Transfer 3 | Source Host: localhost 4 | Source Database: todo 5 | Target Host: localhost 6 | Target Database: todo 7 | Date: 2011/6/25 21:57:10 8 | */ 9 | 10 | SET FOREIGN_KEY_CHECKS=0; 11 | -- ---------------------------- 12 | -- Table structure for todo 13 | -- ---------------------------- 14 | DROP TABLE IF EXISTS `todo`; 15 | CREATE TABLE `todo` ( 16 | `id` int(11) NOT NULL auto_increment, 17 | `title` varchar(300) default NULL, 18 | `finished` int(11) default '0', 19 | `post_date` datetime default NULL, 20 | PRIMARY KEY (`id`) 21 | ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; 22 | 23 | -- ---------------------------- 24 | -- Records 25 | -- ---------------------------- 26 | INSERT INTO `todo` VALUES ('4', '增加摘要功能,除标题外还可以写描述', '0', '2011-06-03 06:08:10'); 27 | INSERT INTO `todo` VALUES ('5', '增加分类,不同的事项划分到不同的分类中去', '0', '2011-06-03 06:08:37'); 28 | INSERT INTO `todo` VALUES ('6', '增加协作功能,增加用户功能,可以指定转给某个人,且用邮件通知他', '0', '2011-06-03 06:09:18'); 29 | INSERT INTO `todo` VALUES ('7', '不直接删除,改为完成,标示为该条事项已完成显示在最下方', '0', '2011-06-03 06:09:41'); 30 | INSERT INTO `todo` VALUES ('8', '这是一条测试', '1', '2011-06-04 23:00:47'); 31 | INSERT INTO `todo` VALUES ('9', '这是一条测试2', '1', '2011-06-04 23:01:31'); 32 | 33 | 34 | CREATE TABLE `status` ( 35 | `id` int(11) NOT NULL auto_increment, 36 | `text` varchar(300) default NULL, 37 | `created_at` datetime default NULL, 38 | `user_name` varchar(200), 39 | `json` longtext, 40 | PRIMARY KEY (`id`) 41 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -------------------------------------------------------------------------------- /views/error.html: -------------------------------------------------------------------------------- 1 | 10 |
11 |

12 | <%= message %> 13 |

14 |

15 | 返回 | 16 | 首页 17 |

18 |
19 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

待办事项

5 |
    6 | <% for (var i = 0, len = todos.length; i < len; i++) { 7 | var todo = todos[i]; 8 | var status = todo.finished ? 'class="finished"' : ''; 9 | %> 10 |
  • > 11 | <% if (!todo.finished) { %> 12 | <%= todo.title %> 13 |   14 | 完成, 15 | <% } else { %> 16 | <%= todo.title %> 17 |   18 | 恢复, 19 | <% } %> 20 | 修改, 21 | 删除 22 |
  • 23 | <% } %> 24 |
25 |
26 | 27 |
28 |

新增

29 |
30 | 31 |

32 |

33 |
34 |
35 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /views/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= config.site_name %> 6 | 7 | 8 | 9 | 10 |
11 | 14 | 15 |
16 | <%- body %> 17 |
18 | 19 |
20 | Copyright© <%= config.site_name %> 21 | CNode 22 |
23 | 修改自 simple-todo: 一个简易的 todo 程序 - web.py 中文教程 24 | from Python.cn(news, jobs) 25 |
26 |
27 | 28 | -------------------------------------------------------------------------------- /views/todo/edit.html: -------------------------------------------------------------------------------- 1 |
2 |

修改

3 |
4 | 5 |

6 |

7 |
8 |
9 | 10 | --------------------------------------------------------------------------------