├── .gitignore ├── .meteor ├── .finished-upgraders ├── .gitignore ├── .id ├── packages ├── platforms ├── release └── versions ├── LICENSE ├── README.md ├── client ├── main.css ├── main.html └── main.js ├── imports ├── api │ ├── tasks.js │ └── tasks.test.js ├── components │ └── todosList │ │ ├── client │ │ └── todosList.tests.js │ │ ├── todosList.html │ │ └── todosList.js └── startup │ └── accounts-config.js ├── package.json ├── screenshot.png └── server └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1vxxaq5ipkkmy1fjan5v 8 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base # Packages every Meteor app needs to have 8 | mobile-experience # Packages for a great mobile UX 9 | mongo # The database Meteor supports right now 10 | reactive-var # Reactive variable for tracker 11 | jquery # Helpful client-side library 12 | tracker # Meteor's client-side reactive programming library 13 | 14 | standard-minifier-css # CSS minifier run for production mode 15 | standard-minifier-js # JS minifier run for production mode 16 | es5-shim # ECMAScript 5 compatibility for older browsers. 17 | ecmascript # Enable ECMAScript2015+ syntax in app code 18 | 19 | angular-templates 20 | accounts-password 21 | dotansimha:accounts-ui-angular 22 | practicalmeteor:mocha 23 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | none 2 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.4-rc.8 2 | accounts-password@1.1.6-rc.8 3 | accounts-ui@1.1.7-rc.8 4 | accounts-ui-unstyled@1.1.10-rc.8 5 | allow-deny@1.0.2-rc.8 6 | angular-templates@1.0.0 7 | autoupdate@1.2.6-rc.8 8 | babel-compiler@6.5.2-rc.8 9 | babel-runtime@0.1.6-rc.8 10 | base64@1.0.6-rc.8 11 | binary-heap@1.0.6-rc.8 12 | blaze@2.1.5-rc.8 13 | blaze-html-templates@1.0.2-rc.8 14 | blaze-tools@1.0.6-rc.8 15 | boilerplate-generator@1.0.6-rc.8 16 | caching-compiler@1.0.2-rc.8 17 | caching-html-compiler@1.0.4-rc.8 18 | callback-hook@1.0.6-rc.8 19 | check@1.1.2-rc.8 20 | coffeescript@1.0.15-rc.8 21 | ddp@1.2.3-rc.8 22 | ddp-client@1.2.3-rc.8 23 | ddp-common@1.2.3-rc.8 24 | ddp-rate-limiter@1.0.2-rc.8 25 | ddp-server@1.2.4-rc.8 26 | deps@1.0.10-rc.8 27 | diff-sequence@1.0.3-rc.8 28 | dotansimha:accounts-ui-angular@0.0.4 29 | ecmascript@0.4.1-rc.8 30 | ecmascript-runtime@0.2.8-rc.8 31 | ejson@1.0.9-rc.8 32 | email@1.0.10-rc.8 33 | es5-shim@4.5.8-rc.8 34 | fastclick@1.0.9-rc.8 35 | geojson-utils@1.0.6-rc.8 36 | hot-code-push@1.0.2-rc.8 37 | html-tools@1.0.7-rc.8 38 | htmljs@1.0.7-rc.8 39 | http@1.1.3-rc.8 40 | id-map@1.0.5-rc.8 41 | jquery@1.11.6-rc.8 42 | launch-screen@1.0.8-rc.8 43 | less@2.5.6-rc.8 44 | livedata@1.0.16-rc.8 45 | localstorage@1.0.7-rc.8 46 | logging@1.0.10-rc.8 47 | meteor@1.1.12-rc.8 48 | meteor-base@1.0.2-rc.8 49 | minifier-css@1.1.9-rc.8 50 | minifier-js@1.1.9-rc.8 51 | minimongo@1.0.12-rc.8 52 | mobile-experience@1.0.2-rc.8 53 | mobile-status-bar@1.0.10-rc.8 54 | modules@0.5.1-rc.8 55 | modules-runtime@0.6.1-rc.8 56 | mongo@1.1.5-rc.8 57 | mongo-id@1.0.2-rc.8 58 | npm-bcrypt@0.7.8_2 59 | npm-mongo@1.4.41-rc.8 60 | observe-sequence@1.0.9-rc.8 61 | ordered-dict@1.0.5-rc.8 62 | practicalmeteor:chai@2.1.0_1 63 | practicalmeteor:loglevel@1.2.0_2 64 | practicalmeteor:mocha@2.1.0_7 65 | practicalmeteor:mocha-core@0.1.4 66 | practicalmeteor:sinon@1.14.1_2 67 | promise@0.6.4-rc.8 68 | random@1.0.7-rc.8 69 | rate-limit@1.0.2-rc.8 70 | reactive-dict@1.1.5-rc.8 71 | reactive-var@1.0.7-rc.8 72 | reload@1.1.6-rc.8 73 | retry@1.0.5-rc.8 74 | routepolicy@1.0.8-rc.8 75 | service-configuration@1.0.7-rc.8 76 | session@1.1.3-rc.8 77 | sha@1.0.5-rc.8 78 | spacebars@1.0.9-rc.8 79 | spacebars-compiler@1.0.9-rc.8 80 | srp@1.0.6-rc.8 81 | standard-minifier-css@1.0.4-rc.8 82 | standard-minifier-js@1.0.4-rc.8 83 | templating@1.1.7-rc.8 84 | templating-tools@1.0.2-rc.8 85 | tmeasday:check-npm-versions@0.1.1 86 | tracker@1.0.11-rc.8 87 | ui@1.0.9-rc.8 88 | underscore@1.0.6-rc.8 89 | url@1.0.7-rc.8 90 | webapp@1.2.6-rc.8 91 | webapp-hashing@1.0.7-rc.8 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ======================================== 2 | Meteor is licensed under the MIT License 3 | ======================================== 4 | 5 | Copyright (C) 2011--2016 Meteor Development Group 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Todo List 2 | 3 | The Meteor Tutorial app. 4 | 5 | Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them. 6 | 7 | Learn how to build this app by following the [Meteor Tutorial](http://www.meteor.com/install). 8 | 9 | Read more about building apps with Meteor in the [Meteor Guide](http://guide.meteor.com). 10 | 11 | ![screenshot](screenshot.png) 12 | -------------------------------------------------------------------------------- /client/main.css: -------------------------------------------------------------------------------- 1 | /* CSS declarations go here */ 2 | body { 3 | font-family: sans-serif; 4 | background-color: #315481; 5 | background-image: linear-gradient(to bottom, #315481, #918e82 100%); 6 | background-attachment: fixed; 7 | 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | left: 0; 12 | right: 0; 13 | 14 | padding: 0; 15 | margin: 0; 16 | 17 | font-size: 14px; 18 | } 19 | 20 | .container { 21 | max-width: 600px; 22 | margin: 0 auto; 23 | min-height: 100%; 24 | background: white; 25 | } 26 | 27 | header { 28 | background: #d2edf4; 29 | background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); 30 | padding: 20px 15px 15px 15px; 31 | position: relative; 32 | } 33 | 34 | #login-buttons { 35 | display: block; 36 | } 37 | 38 | h1 { 39 | font-size: 1.5em; 40 | margin: 0; 41 | margin-bottom: 10px; 42 | display: inline-block; 43 | margin-right: 1em; 44 | } 45 | 46 | form { 47 | margin-top: 10px; 48 | margin-bottom: -10px; 49 | position: relative; 50 | } 51 | 52 | .new-task input { 53 | box-sizing: border-box; 54 | padding: 10px 0; 55 | background: transparent; 56 | border: none; 57 | width: 100%; 58 | padding-right: 80px; 59 | font-size: 1em; 60 | } 61 | 62 | .new-task input:focus{ 63 | outline: 0; 64 | } 65 | 66 | ul { 67 | margin: 0; 68 | padding: 0; 69 | background: white; 70 | } 71 | 72 | .delete { 73 | float: right; 74 | font-weight: bold; 75 | background: none; 76 | font-size: 1em; 77 | border: none; 78 | position: relative; 79 | } 80 | 81 | li { 82 | position: relative; 83 | list-style: none; 84 | padding: 15px; 85 | border-bottom: #eee solid 1px; 86 | } 87 | 88 | li .text { 89 | margin-left: 10px; 90 | } 91 | 92 | li.checked { 93 | color: #888; 94 | } 95 | 96 | li.checked .text { 97 | text-decoration: line-through; 98 | } 99 | 100 | li.private { 101 | background: #eee; 102 | border-color: #ddd; 103 | } 104 | 105 | header .hide-completed { 106 | float: right; 107 | } 108 | 109 | .toggle-private { 110 | margin-left: 5px; 111 | } 112 | 113 | @media (max-width: 600px) { 114 | li { 115 | padding: 12px 15px; 116 | } 117 | 118 | .search { 119 | width: 150px; 120 | clear: both; 121 | } 122 | 123 | .new-task input { 124 | padding-bottom: 5px; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /client/main.html: -------------------------------------------------------------------------------- 1 | 2 | Todo List 3 | 4 | 5 | 6 |
7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /client/main.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import angularMeteor from 'angular-meteor'; 3 | import todosList from '../imports/components/todosList/todosList'; 4 | import '../imports/startup/accounts-config.js'; 5 | 6 | angular.module('simple-todos', [ 7 | angularMeteor, 8 | todosList.name, 9 | 'accounts.ui' 10 | ]); 11 | 12 | function onReady() { 13 | angular.bootstrap(document, ['simple-todos']); 14 | } 15 | 16 | if (Meteor.isCordova) { 17 | angular.element(document).on('deviceready', onReady); 18 | } else { 19 | angular.element(document).ready(onReady); 20 | } 21 | -------------------------------------------------------------------------------- /imports/api/tasks.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor'; 2 | import { Mongo } from 'meteor/mongo'; 3 | import { check } from 'meteor/check'; 4 | 5 | export const Tasks = new Mongo.Collection('tasks'); 6 | 7 | if (Meteor.isServer) { 8 | // This code only runs on the server 9 | // Only publish tasks that are public or belong to the current user 10 | Meteor.publish('tasks', function tasksPublication() { 11 | return Tasks.find({ 12 | $or: [{ 13 | private: { 14 | $ne: true 15 | } 16 | }, { 17 | owner: this.userId 18 | }, ], 19 | }); 20 | }); 21 | } 22 | 23 | Meteor.methods({ 24 | 'tasks.insert' (text) { 25 | check(text, String); 26 | 27 | // Make sure the user is logged in before inserting a task 28 | if (!Meteor.userId()) { 29 | throw new Meteor.Error('not-authorized'); 30 | } 31 | 32 | Tasks.insert({ 33 | text, 34 | createdAt: new Date(), 35 | owner: Meteor.userId(), 36 | username: Meteor.user().username, 37 | }); 38 | }, 39 | 'tasks.remove' (taskId) { 40 | check(taskId, String); 41 | 42 | const task = Tasks.findOne(taskId); 43 | if (task.private && task.owner !== Meteor.userId()) { 44 | // If the task is private, make sure only the owner can delete it 45 | throw new Meteor.Error('not-authorized'); 46 | } 47 | 48 | Tasks.remove(taskId); 49 | }, 50 | 'tasks.setChecked' (taskId, setChecked) { 51 | check(taskId, String); 52 | check(setChecked, Boolean); 53 | 54 | const task = Tasks.findOne(taskId); 55 | if (task.private && task.owner !== Meteor.userId()) { 56 | // If the task is private, make sure only the owner can check it off 57 | throw new Meteor.Error('not-authorized'); 58 | } 59 | 60 | Tasks.update(taskId, { 61 | $set: { 62 | checked: setChecked 63 | } 64 | }); 65 | }, 66 | 'tasks.setPrivate' (taskId, setToPrivate) { 67 | check(taskId, String); 68 | check(setToPrivate, Boolean); 69 | 70 | const task = Tasks.findOne(taskId); 71 | 72 | // Make sure only the task owner can make a task private 73 | if (task.owner !== Meteor.userId()) { 74 | throw new Meteor.Error('not-authorized'); 75 | } 76 | 77 | Tasks.update(taskId, { 78 | $set: { 79 | private: setToPrivate 80 | } 81 | }); 82 | }, 83 | }); 84 | -------------------------------------------------------------------------------- /imports/api/tasks.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { Meteor } from 'meteor/meteor'; 4 | import { Random } from 'meteor/random'; 5 | import { assert } from 'meteor/practicalmeteor:chai'; 6 | 7 | import { Tasks } from './tasks.js'; 8 | 9 | if (Meteor.isServer) { 10 | describe('Tasks', () => { 11 | describe('methods', () => { 12 | const userId = Random.id(); 13 | let taskId; 14 | 15 | beforeEach(() => { 16 | Tasks.remove({}); 17 | taskId = Tasks.insert({ 18 | text: 'test task', 19 | createdAt: new Date(), 20 | owner: userId, 21 | username: 'tmeasday', 22 | }); 23 | }); 24 | 25 | it('can delete owned task', () => { 26 | // Find the internal implementation of the task method so we can 27 | // test it in isolation 28 | const deleteTask = Meteor.server.method_handlers['tasks.remove']; 29 | 30 | // Set up a fake method invocation that looks like what the method expects 31 | const invocation = { 32 | userId 33 | }; 34 | 35 | // Run the method with `this` set to the fake invocation 36 | deleteTask.apply(invocation, [taskId]); 37 | 38 | // Verify that the method does what we expected 39 | assert.equal(Tasks.find().count(), 0); 40 | }); 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /imports/components/todosList/client/todosList.tests.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import 'angular-mocks'; 4 | import { Meteor } from 'meteor/meteor'; 5 | import { assert } from 'meteor/practicalmeteor:chai'; 6 | import { sinon } from 'meteor/practicalmeteor:sinon'; 7 | 8 | import todosList from '../todosList'; 9 | 10 | describe('todosList', function() { 11 | var element; 12 | 13 | beforeEach(function() { 14 | var $compile; 15 | var $rootScope; 16 | 17 | window.module(todosList.name); 18 | 19 | inject(function(_$compile_, _$rootScope_){ 20 | $compile = _$compile_; 21 | $rootScope = _$rootScope_; 22 | }); 23 | 24 | element = $compile('')($rootScope.$new(true)); 25 | $rootScope.$digest(); 26 | }); 27 | 28 | describe('component', function() { 29 | it('should be showing incomplete tasks count', function() { 30 | assert.include(element[0].querySelector('h1').innerHTML, '0'); 31 | }); 32 | }); 33 | 34 | describe('controller', function() { 35 | describe('addTask', function() { 36 | var controller; 37 | var newTask = 'Be more fabolous'; 38 | 39 | beforeEach(() => { 40 | sinon.stub(Meteor, 'call'); 41 | controller = element.controller('todosList'); 42 | controller.newTask = 'Be fabolous'; 43 | controller.addTask(newTask); 44 | }); 45 | 46 | afterEach(() => { 47 | Meteor.call.restore(); 48 | }); 49 | 50 | it('should call tasks.insert method', function() { 51 | sinon.assert.calledOnce(Meteor.call); 52 | sinon.assert.calledWith(Meteor.call, 'tasks.insert', newTask); 53 | }); 54 | 55 | it('should reset newTask', function() { 56 | assert.equal(controller.newTask, ''); 57 | }); 58 | }); 59 | }); 60 | }) 61 | -------------------------------------------------------------------------------- /imports/components/todosList/todosList.html: -------------------------------------------------------------------------------- 1 |
2 |

Todo List ( {{$ctrl.incompleteCount}} )

3 | 4 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 | 17 | 32 | -------------------------------------------------------------------------------- /imports/components/todosList/todosList.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import angularMeteor from 'angular-meteor'; 3 | import { Meteor } from 'meteor/meteor'; 4 | import { Tasks } from '../../api/tasks.js'; 5 | 6 | import template from './todosList.html'; 7 | 8 | class TodosListCtrl { 9 | constructor($scope) { 10 | $scope.viewModel(this); 11 | 12 | this.subscribe('tasks'); 13 | 14 | this.hideCompleted = false; 15 | 16 | this.helpers({ 17 | tasks() { 18 | const selector = {}; 19 | 20 | // If hide completed is checked, filter tasks 21 | if (this.getReactively('hideCompleted')) { 22 | selector.checked = { 23 | $ne: true 24 | }; 25 | } 26 | 27 | // Show newest tasks at the top 28 | return Tasks.find(selector, { 29 | sort: { 30 | createdAt: -1 31 | } 32 | }); 33 | }, 34 | incompleteCount() { 35 | return Tasks.find({ 36 | checked: { 37 | $ne: true 38 | } 39 | }).count(); 40 | }, 41 | currentUser() { 42 | return Meteor.user(); 43 | } 44 | }) 45 | } 46 | 47 | addTask(newTask) { 48 | // Insert a task into the collection 49 | Meteor.call('tasks.insert', newTask); 50 | 51 | // Clear form 52 | this.newTask = ''; 53 | } 54 | 55 | setChecked(task) { 56 | // Set the checked property to the opposite of its current value 57 | Meteor.call('tasks.setChecked', task._id, !task.checked); 58 | } 59 | 60 | removeTask(task) { 61 | Meteor.call('tasks.remove', task._id); 62 | } 63 | 64 | setPrivate(task) { 65 | Meteor.call('tasks.setPrivate', task._id, !task.private); 66 | } 67 | } 68 | 69 | export default angular.module('todosList', [ 70 | angularMeteor 71 | ]) 72 | .component('todosList', { 73 | templateUrl: 'imports/components/todosList/todosList.html', 74 | controller: ['$scope', TodosListCtrl] 75 | }); 76 | -------------------------------------------------------------------------------- /imports/startup/accounts-config.js: -------------------------------------------------------------------------------- 1 | import { Accounts } from 'meteor/accounts-base'; 2 | 3 | Accounts.ui.config({ 4 | passwordSignupFields: 'USERNAME_ONLY', 5 | }); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-todos-angular", 3 | "private": true, 4 | "scripts": { 5 | "start": "meteor run" 6 | }, 7 | "dependencies": { 8 | "angular": "^1.5.2", 9 | "angular-meteor": "^1.3.7", 10 | "meteor-node-stubs": "~0.2.0" 11 | }, 12 | "devDependencies": { 13 | "angular-mocks": "^1.5.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteor/simple-todos-angular/83c53904e21e0aa21f236193532bb81075929d00/screenshot.png -------------------------------------------------------------------------------- /server/main.js: -------------------------------------------------------------------------------- 1 | import '../imports/api/tasks.js'; 2 | --------------------------------------------------------------------------------