diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/README.md b/README.md index 7eb72f5..d51d223 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,25 @@ Meteor TodoMVC A todo app built using [Meteor](http://meteor.com), inspired by [TodoMVC](https://github.com/addyosmani/todomvc). -You can view the site at: [todomvc.meteor.com](http://todomvc.meteor.com) +Setup +======= + +* Install Meteor ```$ curl install.meteor.com | /bin/sh``` +* $ cd meteor +* $ meteor + +The app should now be running on http://localhost:3000/ + +To deploy to meteor.com simply do this: -![Screen](http://f.cl.ly/items/15022c3Q0u260c3e1Y1G/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7%202012-04-14%20%E4%B8%8B%E5%8D%8812.13.12.png) +```$ meteor deploy myapp.meteor.com``` Credits ======= - Stylesheet from [TodoMVC](https://github.com/addyosmani/todomvc) - Meteor from [Meteor](http://meteor.com) +- This app by [siuying](https://github.com/siuying) License ======= diff --git a/css/todo.css b/css/todo.css new file mode 100644 index 0000000..3fc8a2c --- /dev/null +++ b/css/todo.css @@ -0,0 +1,389 @@ +html, +body { + margin: 0; + padding: 0; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + color: inherit; + -webkit-appearance: none; + /*-moz-appearance: none;*/ + -ms-appearance: none; + -o-appearance: none; + appearance: none; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #eeeeee url('bg.png'); + color: #4d4d4d; + width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#todoapp { + background: #fff; + background: rgba(255, 255, 255, 0.9); + margin: 130px 0 40px 0; + border: 1px solid #ccc; + position: relative; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.15); +} + +#todoapp:before { + content: ''; + border-left: 1px solid #f5d6d6; + border-right: 1px solid #f5d6d6; + width: 2px; + position: absolute; + top: 0; + left: 40px; + height: 100%; +} + +#todoapp h1 { + position: absolute; + top: -120px; + width: 100%; + font-size: 70px; + font-weight: bold; + text-align: center; + color: #b3b3b3; + color: rgba(255, 255, 255, 0.3); + text-shadow: -1px -1px rgba(0, 0, 0, 0.2); + -webkit-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + -ms-text-rendering: optimizeLegibility; + -o-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} + +#header { + padding-top: 15px; + border-radius: inherit; +} + +#todoapp header:before { + content: ''; + position: absolute; + top: 0; + right: 0; + left: 0; + height: 15px; + z-index: 2; + border-bottom: 1px solid #6c615c; + background: #8d7d77; + background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); + background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); + border-radius: inherit; +} + +#todoapp input::-webkit-input-placeholder { + font-style: italic; +} + +#todoapp input:-moz-placeholder { + font-style: italic; + color: #a9a9a9; +} + +#new-todo, +.edit { + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + line-height: 1.4em; + border: 0; + outline: none; + color: inherit; + padding: 6px; + border: 1px solid #999; + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.02); + position: relative; + z-index: 2; + box-shadow: none; +} + +#main { + position: relative; + z-index: 2; + border-top: 1px dotted #adadad; +} + +label[for='toggle-all'] { + display: none; +} + +#toggle-all { + position: absolute; + top: -42px; + left: 12px; + text-align: center; + -webkit-appearance: none; + /*-moz-appearance: none;*/ + -ms-appearance: none; + -o-appearance: none; + appearance: none; + -webkit-transform: rotate(90deg); + /*-moz-transform: rotate(90deg);*/ + -ms-transform: rotate(90deg); + /*-o-transform: rotate(90deg);*/ + transform: rotate(90deg); +} + +#toggle-all:before { + content: '»'; + font-size: 28px; + color: #d9d9d9; + padding: 0 25px 7px; +} + +#toggle-all:checked:before { + color: #737373; +} + +/* Need this ugly hack, since only +WebKit supports styling of inputs */ +@media screen and (-webkit-min-device-pixel-ratio:0) { + #toggle-all { + top: -52px; + left: -11px; + } +} + +#todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +#todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px dotted #ccc; +} + +#todo-list li:last-child { + border-bottom: none; +} + +#todo-list li.editing { + border-bottom: none; + padding: 0; +} + +#todo-list li.editing .edit { + display: block; + width: 506px; + padding: 13px 17px 12px 17px; + margin: 0 0 0 43px; +} + +#todo-list li.editing .view { + display: none; +} + +#todo-list li .toggle { + text-align: center; + width: 35px; + -webkit-appearance: none; + /*-moz-appearance: none;*/ + -ms-appearance: none; + -o-appearance: none; + appearance: none; +} + +#todo-list li .toggle:after { + font-size: 18px; + content: '✔'; + line-height: 40px; + font-size: 20px; + color: #d9d9d9; + text-shadow: 0 -1px 0 #bfbfbf; +} + +#todo-list li .toggle:checked:after { + color: #85ada7; + text-shadow: 0 1px 0 #669991; + bottom: 1px; + position: relative; +} + +#todo-list li label { + word-break: break-word; + margin: 20px 15px; + display: inline-block; + -webkit-transition: color 0.4s; + -moz-transition: color 0.4s; + -ms-transition: color 0.4s; + -o-transition: color 0.4s; + transition: color 0.4s; +} + +#todo-list li.completed label { + color: #a9a9a9; + text-decoration: line-through; +} + +#todo-list li .destroy { + display: none; + position: absolute; + top: 10px; + right: 10px; + width: 40px; + height: 40px; + font-size: 22px; + color: #a88a8a; + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; + -ms-transition: all 0.2s; + -o-transition: all 0.2s; + transition: all 0.2s; +} + +#todo-list li .destroy:hover { + text-shadow: 0 0 1px #000, + 0 0 10px rgba(199, 107, 107, 0.8); + -webkit-transform: scale(1.3); + -moz-transform: scale(1.3); + -ms-transform: scale(1.3); + -o-transform: scale(1.3); + transform: scale(1.3); +} + +#todo-list li .destroy:after { + content: '✖'; +} + +#todo-list li:hover .destroy { + display: block; +} + +#todo-list li .edit { + display: none; +} + +#todo-list li.editing:last-child { + margin-bottom: -1px; +} + +#footer { + color: #777; + padding: 0 15px; + position: absolute; + right: 0; + bottom: -31px; + left: 0; + z-index: 1; + text-align: center; +} + +#footer:before { + content: ''; + position: absolute; + right: 0; + bottom: 31px; + left: 0; + height: 100px; + z-index: -1; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), + 0 6px 0 -3px rgba(255, 255, 255, 0.8), + 0 7px 1px -3px rgba(0, 0, 0, 0.3), + 0 42px 0 -6px rgba(255, 255, 255, 0.8), + 0 43px 2px -6px rgba(0, 0, 0, 0.2); +} + +#todo-count { + float: left; + text-align: left; +} + +#filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +#filters li { + display: inline; +} + +#filters li a { + color: #83756f; + margin: 2px; + text-decoration: none; +} + +#filters li a.selected { + font-weight: bold; +} + +#clear-completed { + float: right; + line-height: 20px; + text-decoration: none; + background: rgba(0, 0, 0, 0.1); + font-size: 11px; + padding: 0 10px; + position: relative; + border-radius: 3px; + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); +} + +#clear-completed:hover { + background: rgba(0, 0, 0, 0.15); + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); +} + +#info { + margin: 65px auto 0; + color: #a6a6a6; + font-size: 12px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); + text-align: center; +} + +#info a { + color: inherit; +} \ No newline at end of file diff --git a/images/bg.png b/images/bg.png new file mode 100644 index 0000000..b2a7600 Binary files /dev/null and b/images/bg.png differ diff --git a/todo.coffee b/todo.coffee index bd70030..affc79f 100644 --- a/todo.coffee +++ b/todo.coffee @@ -1,65 +1,71 @@ -Tasks = new Meteor.Collection("tasks") +Tasks = new Meteor.Collection('tasks') +ENTER_KEY = 13 if Meteor.is_client - # Export Tasks model to client - window.Tasks = Tasks + Template.todo.tasks = -> + Tasks.find {}, {sort: {completed: 1}} - Template.todo.tasks = -> - Tasks.find {}, {sort: {completed: 1, updated: -1, created: -1}} + Template.todo.hasTodo = -> + Tasks.find({}).count() > 0 - Template.todo.remainingTodos = -> - Tasks.find({completed: false}).count() + Template.todo.incompleted = -> + Tasks.find({completed: false}).count() - Template.todo.hasCompleted = -> - Tasks.find({completed: true}).count() > 0 + Template.todo.incompleted_text = -> + count = Tasks.find({completed: false}).count() + if count == 1 + ' item left' + else + ' items left' - Template.todo.events = - 'keyup #new-todo' : (evt) -> - if evt.type == "keyup" && evt.which == 13 - textbox = $("#new-todo") + Template.todo.completed = -> + Tasks.find({completed: true}).count() - Tasks.insert {text: textbox.val(), created: new Date(), updated: new Date(), completed: false} - textbox.val("") - textbox.focus() - return false + Template.todo.events = + 'keyup #new-todo' : (evt) -> + if evt.type == 'keyup' && evt.which == ENTER_KEY + textbox = $('#new-todo') + Tasks.insert {title: textbox.val(), completed: false} + textbox.val('') + textbox.focus() + return false - 'click #clear-completed': -> - Tasks.remove {completed: true} - return false + 'click #clear-completed': -> + Tasks.remove {completed: true} + return false - 'click #mark-all-checked': (evt) -> - Tasks.update {}, {$set: {completed: true}}, {multi: true} - $(evt.target).removeAttr("checked") + 'click #mark-all-checked': (evt) -> + Tasks.update {}, {$set: {completed: true}}, {multi: true} + $(evt.target).removeAttr('checked') - Template.item.events = - 'click #done': (evt) -> - task = Tasks.findOne this._id - task.completed = $(evt.target).attr("checked") == "checked" - Tasks.update {_id: this._id}, task + Template.item.events = + 'click .toggle': (evt) -> + task = Tasks.findOne this._id + task.completed = $(evt.target).attr('checked') == 'checked' + Tasks.update {_id: this._id}, task - 'dblclick .text': (evt) -> - task = Tasks.findOne this._id - task.editing = true - selector = "#i-#{this._id} input.edit" - Tasks.update {_id: this._id}, task, (err) -> - $(selector).focus().select() unless err + 'click .destroy': (evt) -> + Tasks.remove {_id: this._id} - 'blur input.edit': (evt) -> - task = Tasks.findOne this._id - task.editing = false - Tasks.update {_id: this._id}, task + 'dblclick label': (evt) -> + task = Tasks.findOne this._id + task.editing = true + edit = $(evt.target).parent().parent().find('.edit') + Tasks.update {_id: this._id}, task, (err) -> + edit.focus().select() unless err - 'keyup input.edit': (evt) -> - if evt.type == "keyup" && evt.which == 13 - task = Tasks.findOne this._id - task.editing = false - task.updated = new Date() - task.text = $(evt.target).val() - Tasks.update {_id: this._id}, task, (err) => - alert("Sorry, an error prevent the changes to be saved") if err + 'blur input.edit': (evt) -> + task = Tasks.findOne this._id + task.editing = false + Tasks.update {_id: this._id}, task - return false + 'keyup input.edit': (evt) -> + if evt.type == 'keyup' && evt.which == ENTER_KEY + task = Tasks.findOne this._id + task.editing = false + task.updated = new Date() + task.title = $(evt.target).val() -if Meteor.is_server - Meteor.startup -> - console.log "server startup" + Tasks.update {_id: this._id}, task, (err) => + alert('Sorry, an error prevent the changes to be saved') if err + return false diff --git a/todo.css b/todo.css deleted file mode 100644 index fe9c189..0000000 --- a/todo.css +++ /dev/null @@ -1,178 +0,0 @@ -html, body { - margin: 0; - padding: 0; -} - -body { - font-family: "Helvetica Neue", helvetica, arial, sans-serif; - font-size: 14px; - line-height: 1.4em; - background: #eeeeee; - color: #333333; -} - -#views { - width: 520px; - margin: 0 auto 40px auto; - background: white; - - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - - -moz-border-radius: 0 0 5px 5px; - -o-border-radius: 0 0 5px 5px; - -webkit-border-radius: 0 0 5px 5px; - border-radius: 0 0 5px 5px; -} - -#tasks { - padding: 20px; -} - -#tasks h1 { - font-size: 36px; - font-weight: bold; - text-align: center; - padding: 0 0 10px 0; -} - -#tasks input[type="text"] { - width: 466px; - font-size: 24px; - font-family: inherit; - line-height: 1.4em; - border: 0; - outline: none; - padding: 6px; - border: 1px solid #999999; - - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; -} - -#tasks input::-webkit-input-placeholder { - font-style: italic; -} - -#tasks .items { - margin: 10px 0; - list-style: none; -} - -#tasks .item { - padding: 15px 20px 15px 0; - position: relative; - font-size: 24px; - border-bottom: 1px solid #cccccc; -} - -#tasks .item.done span { - color: #777777; - text-decoration: line-through; -} - -#tasks .item .destroy { - position: absolute; - right: 10px; - top: 16px; - display: none; - cursor: pointer; - width: 20px; - height: 20px; - background: url(../images/destroy.png) no-repeat center center; -} - -#tasks .item:hover .destroy { - display: block; -} - -#tasks .item .edit { display: none; } -#tasks .item.editing .edit { display: block; } -#tasks .item.editing .view { display: none; } - -#tasks footer { - display: block; - margin: 20px -20px -20px -20px; - overflow: hidden; - - color: #555555; - background: #f4fce8; - border-top: 1px solid #ededed; - padding: 0 20px; - line-height: 36px; - - -moz-border-radius: 0 0 5px 5px; - -o-border-radius: 0 0 5px 5px; - -webkit-border-radius: 0 0 5px 5px; - border-radius: 0 0 5px 5px; -} - -#tasks .clear { - display: block; - float: right; - line-height: 20px; - text-decoration: none; - - background: rgba(0, 0, 0, 0.1); - color: #555555; - font-size: 11px; - margin-top: 8px; - margin-bottom:8px; - padding: 0 10px 1px; - - -moz-border-radius: 12px; - -webkit-border-radius: 12px; - -o-border-radius: 12px; - border-radius: 12px; - - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - - cursor: pointer; -} - -/* focus is actually pointless here as it's not - possible to tab to the anchor for some reason */ -#tasks .clear:hover, #tasks .clear:focus { - background: rgba(0, 0, 0, 0.15); - -moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - -o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; -} - -#tasks .clear:active { - position: relative; - top: 1px; -} - -#tasks .count span { - font-weight: bold; -} - -#credits { - width: 520px; - margin: 30px auto; - color: #999; - text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0; - text-align: center; -} - -#credits a { - color: #888; -} - -#todo-list li { - list-style-type: none; -} - -#todo-list { - margin: 0; - padding: 0; -} diff --git a/todo.html b/todo.html index 8969678..1a7a3bb 100644 --- a/todo.html +++ b/todo.html @@ -1,57 +1,53 @@ - - Meteor - TodoMVC + + Meteor • TodoMVC -
-
- {{> todo}} -
-
- - Fork me on GitHub +
+ {{> todo}} +
+ + -