├── .gitignore ├── Procfile ├── README.md ├── app ├── controllers │ ├── apiController.js │ ├── homeController.js │ ├── todoController.js │ └── userController.js ├── index.js ├── lib │ ├── dal.js │ ├── logging.js │ ├── middleware.js │ ├── routes.js │ ├── server.js │ └── views.js ├── middleware │ ├── errorMiddleware.js │ └── passportMiddleware.js ├── models │ ├── Todo.js │ └── User.js ├── public │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ └── style.css │ ├── favicon.ico │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── controllers.js │ │ ├── directives.js │ │ ├── filters.js │ │ ├── lib │ │ │ ├── angular │ │ │ │ ├── angular-cookies.js │ │ │ │ ├── angular-cookies.min.js │ │ │ │ ├── angular-loader.js │ │ │ │ ├── angular-loader.min.js │ │ │ │ ├── angular-resource.js │ │ │ │ ├── angular-resource.min.js │ │ │ │ ├── angular-sanitize.js │ │ │ │ ├── angular-sanitize.min.js │ │ │ │ ├── angular.js │ │ │ │ ├── angular.min.js │ │ │ │ └── version.txt │ │ │ ├── bootstrap │ │ │ │ ├── bootstrap.js │ │ │ │ └── bootstrap.min.js │ │ │ ├── jquery │ │ │ │ └── jquery.js │ │ │ └── underscore.js │ │ └── services.js │ └── partials │ │ ├── 404.html │ │ ├── index.html │ │ ├── login.html │ │ └── todos.html └── views │ └── partials │ └── .git.me ├── config ├── default.json └── production.json ├── doc └── .gitme ├── logs └── .git.me ├── package.json └── test └── .gitme /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env.js 3 | .env.json 4 | .idea 5 | npm-debug.log 6 | .ini -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: train run -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Express Train Seed 2 | 3 | This is a starting point for making AngularJS web applications with a node.js back end. Here are some of the features: 4 | - organized directories for both server side and client side 5 | - basic user authentication using 401 responses from server 6 | - data persistence using mongoose 7 | 8 | Check out the demo site here: http://angular-express-train-seed.herokuapp.com/ 9 | 10 | Order you should learn this stuff, if you don't know it already: 11 | 12 | 1. AngularJS (http://angularjs.org/) 13 | 2. Node.js (http://nodejs.org/) 14 | 3. Express (http://expressjs.com/) 15 | 4. Express Train (https://npmjs.org/package/express-train) 16 | 17 | Since this app is using express train, start it by using the command 'train run'. 18 | You may need to install express train globally ('npm install -g express-train'). 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/controllers/apiController.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | var controller = {}; 3 | 4 | /* 5 | Generic CRUD functions for any model 6 | */ 7 | controller.search = [ 8 | /* 9 | route functions get 3 args - the request object, the response object, and next - a callback to move on 10 | to the next middleware. 11 | req.query = json object with query string arguments 12 | req.params = json object with values of routing params such as :model or :id 13 | req.body = json request body from post / put requests 14 | */ 15 | function (req, res, next) { 16 | console.log('starting api.search'); 17 | var query = req.query; 18 | //req.Model is a value I set in libs/params.js 19 | req.Model.find(query, function (err, docs) { 20 | if (err) return next(err); 21 | return res.json(docs); 22 | }); 23 | } 24 | ] 25 | controller.create = [ 26 | function (req, res, next) { 27 | console.log(req.body); 28 | var model = new req.Model(req.body); 29 | model.save(function (err, doc) { 30 | if (err) return next(err); 31 | return res.json(doc); 32 | }) 33 | } 34 | ] 35 | controller.read = [ 36 | function (req, res, next) { 37 | var id = req.params.id; 38 | req.Model.findById(id, function (err, doc) { 39 | if (err) return next(err); 40 | if (doc === null) return res.send(404); 41 | return res.json(doc); 42 | }); 43 | } 44 | ] 45 | controller.update = [ 46 | function (req, res, next) { 47 | var id = req.params.id; 48 | delete req.body._id; //removing the _id from the model to prevent mongo from thinking we are trying to change its type 49 | req.Model.findByIdAndUpdate(id, req.body, function (err, doc) { 50 | if (err) return next(err); 51 | if (doc === null) return res.send(404); 52 | return res.json(doc); 53 | }) 54 | } 55 | ] 56 | controller.destroy = [ 57 | function (req, res, next) { 58 | var id = req.params.id; 59 | req.Model.findByIdAndRemove(id, function (err, doc) { 60 | if (err) return next(err); 61 | if (doc === null) return res.send(404); 62 | return res.send(204); 63 | }) 64 | } 65 | ] 66 | 67 | return controller; 68 | } 69 | -------------------------------------------------------------------------------- /app/controllers/homeController.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | return { 3 | index:[ 4 | function (req, res, next) { 5 | //we just want to return an html file. Angular will take care of the templating. 6 | res.sendfile(app.set('public') + '/index.html'); 7 | }] 8 | }; 9 | }; -------------------------------------------------------------------------------- /app/controllers/todoController.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app, Todo) { 2 | var controller = {}; 3 | 4 | controller.preSearch = [ 5 | function (req, res, next) { 6 | console.log('this it?'); 7 | req.query = {userId: req.user.id}; 8 | req.Model = Todo; 9 | next(); 10 | } 11 | ] 12 | controller.preCreate = [ 13 | function (req, res, next) { 14 | req.body.userId = req.user.id; 15 | req.Model = Todo; 16 | next(); 17 | } 18 | ] 19 | controller.preUpdate = [ 20 | function (req, res, next) { 21 | //try to find a todo that matches the ID in the uri and belongs to the user who is logged i 22 | Todo.find({_id: req.params.id, userId: req.user.id}, function (err, results) { 23 | if (err) return next(err); 24 | if(!results) return res.send(401); //trying to update a todo that isn't yours?!?!?! 25 | req.Model = Todo; 26 | next(); 27 | }); 28 | } 29 | ] 30 | controller.preDestroy = [ 31 | function (req, res, next) { 32 | //try to find a todo that matches the ID in the uri and belongs to the user who is logged in 33 | Todo.find({_id: req.params.id, userId: req.user.id}, function (err, results) { 34 | if (err) return next(err); 35 | if(!results) return res.send(401); //trying to update a todo that isn't yours?!?!?! 36 | req.Model = Todo; 37 | next(); 38 | }); 39 | } 40 | ] 41 | 42 | return controller; 43 | } 44 | -------------------------------------------------------------------------------- /app/controllers/userController.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app, User, passportMiddleware) { 2 | return { 3 | getCurrent: [ 4 | function (req, res, next) { 5 | if (!req.user) { 6 | res.send(400); 7 | } 8 | else { 9 | User.findOne({_id: req.user.id}, function (err, results) { 10 | if (err) return next(err); 11 | if (results) { 12 | console.log({username: results.username}); 13 | res.send({username: results.username}); 14 | } 15 | else { 16 | res.send(400); 17 | } 18 | }); 19 | } 20 | }], 21 | 22 | authenticate: [ 23 | function (req, res, next) { 24 | passportMiddleware.authenticate('local', function (err, user, info) { 25 | if (err) return next(err); 26 | if (!user) { 27 | return res.send('Invalid username or password.', 400); 28 | } 29 | req.logIn(user, function (err) { 30 | if (err) return next(err); 31 | return res.send('Ok', 200); 32 | }); 33 | })(req, res, next); 34 | }], 35 | 36 | create: [ 37 | function (req, res, next) { 38 | var user = new User(req.body); 39 | User.findOne({username: user.username}, function (err, results) { 40 | if (err) return next(err); 41 | if (results) { 42 | res.send('A user with this username already exists.', 400); 43 | } 44 | else { 45 | user.save(function (err, results) { 46 | if (err) return next(err); 47 | req.logIn(user, function () { 48 | res.send('ok', 200) 49 | }); 50 | }); 51 | } 52 | }) 53 | }], 54 | //logout/kill session 55 | kill: [ 56 | function (req, res) { 57 | if (req.session) { 58 | req.session.destroy(function () { 59 | res.send('ok', 200) 60 | }); 61 | } 62 | else { 63 | res.send('ok', 200) 64 | } 65 | }] 66 | }; 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | var train = require('express-train'); 2 | 3 | module.exports = train(__dirname); -------------------------------------------------------------------------------- /app/lib/dal.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | module.exports = function (config) { 4 | //set up mongoose database connection 5 | if(!mongoose.connection.readyState){ 6 | mongoose.connect(config.mongodb.uri); 7 | } 8 | } -------------------------------------------------------------------------------- /app/lib/logging.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(app) { 3 | 4 | } -------------------------------------------------------------------------------- /app/lib/middleware.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | MongoStore = require('connect-mongo')(express), 3 | connect_timeout = require('connect-timeout'); 4 | 5 | 6 | // Middleware 7 | 8 | module.exports = function (app, config, passportMiddleware ) { 9 | 10 | // Sessions 11 | var mongoStore = new MongoStore({ 12 | url: config.mongodb.uri 13 | }); 14 | 15 | var session_middleware = express.session({ 16 | key:config.session.key, 17 | secret: config.session.secret, 18 | store:mongoStore 19 | }); 20 | 21 | // Error handler 22 | var error_middleware = express.errorHandler({ 23 | dumpExceptions:true, 24 | showStack:true 25 | }); 26 | 27 | // Middleware stack for all requests 28 | app.use(express['static'](app.set('public'))); // static files in /public 29 | app.use(connect_timeout({ time:config.request_timeout })); // request timeouts 30 | app.use(express.cookieParser()); // req.cookies 31 | app.use(session_middleware); // req.session 32 | app.use(express.bodyParser()); // req.body & req.files 33 | app.use(express.methodOverride()); // '_method' property in body (POST -> DELETE / PUT) 34 | app.use(passportMiddleware.initialize()); 35 | app.use(passportMiddleware.session()); 36 | app.use(passportMiddleware.setLocals); 37 | app.use(app.router); // routes in lib/routes.js 38 | 39 | // Handle errors thrown from middleware/routes 40 | app.use(error_middleware); 41 | 42 | app.configure('development', function(){ 43 | require('express-trace')(app); 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /app/lib/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app, homeController, userController, todoController, apiController) { 2 | 3 | // Home 4 | app.get('/', homeController.index); 5 | 6 | //User 7 | app.get('/user', userController.getCurrent); 8 | app.post('/user/login', userController.authenticate); 9 | app.post('/user/register', userController.create); 10 | app.post('/user/logout', userController.kill); 11 | 12 | /* 13 | Rather than go right to the API routes below, we need to do a few things first, like say what Users Todos 14 | to return and stamp the Todos they create with their ID. Once we've dne these things (in the "pre" functions), 15 | we end up invoking the generic api methods. Cool, right? 16 | */ 17 | app.get('/api/Todo', ensureAuthenticated, todoController.preSearch, apiController.search); 18 | app.post('/api/Todo', ensureAuthenticated, todoController.preCreate, apiController.create); 19 | app.post('/api/Todo/:id', ensureAuthenticated, todoController.preUpdate, apiController.update); 20 | app.del('/api/Todo/:id', ensureAuthenticated, todoController.preDestroy, apiController.destroy); 21 | 22 | //Generic restful api for all models - if previous routes are not matched, will fall back to these 23 | //See libs/params.js, which adds param middleware to load & set req.Model based on :model argument 24 | app.get('/api/:model', ensureAuthenticated, apiController.search); 25 | app.post('/api/:model', ensureAuthenticated, apiController.create); 26 | app.get('/api/:model/:id', ensureAuthenticated, apiController.read); 27 | app.post('/api/:model/:id', ensureAuthenticated, apiController.update); 28 | app.del('/api/:model/:id', ensureAuthenticated, apiController.destroy); 29 | 30 | /* 31 | default route if we haven't hit anything yet 32 | This will just return the index file and pass the url to our angular app. 33 | Angular will decide if it should display a 404 page. 34 | */ 35 | app.get('*', homeController.index); 36 | 37 | /* 38 | Route Helpers 39 | */ 40 | 41 | //whenever a router parameter :model is matched, this is run 42 | app.param('model', function (req, res, next, model) { 43 | console.log(app); 44 | var Model = app.models[model]; 45 | if (Model === undefined) { 46 | //if the request is for a model that does not exist, 404 47 | return res.send(404); 48 | } 49 | req.Model = Model; 50 | return next(); 51 | }); 52 | 53 | /* 54 | Make sure this person is logged in. 55 | If they aren't, return a 401. This let's angular know to go to a login page. 56 | */ 57 | function ensureAuthenticated(req, res, next) { 58 | if (req.isAuthenticated()) { 59 | return next(); 60 | } 61 | res.send(401) 62 | } 63 | }; -------------------------------------------------------------------------------- /app/lib/server.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app, config) { 2 | 3 | console.log('[express train application listening on %s]', config.port); 4 | return app.listen(config.port); 5 | } -------------------------------------------------------------------------------- /app/lib/views.js: -------------------------------------------------------------------------------- 1 | var hbs = require('express-hbs'), 2 | path = require('path'); 3 | 4 | module.exports = function (app) { 5 | 6 | //set up view engine 7 | app.set('view engine', 'hbs'); 8 | 9 | app.engine('hbs', hbs.express3({ 10 | partialsDir:path.join(__dirname, "../views/partials") 11 | })); 12 | 13 | // Static locals 14 | app.locals({ 15 | }); 16 | 17 | }; -------------------------------------------------------------------------------- /app/middleware/errorMiddleware.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | return { 3 | 4 | log: function(msg) { 5 | return function(req, res, next) { 6 | console.log(msg); 7 | return next(); 8 | }; 9 | }, 10 | 11 | apologize: function(err, req, res, next) { 12 | req.flash('Sorry, something went wrong'); 13 | res.redirect('/'); 14 | console.log('ERROR:', err); 15 | } 16 | }; 17 | }; -------------------------------------------------------------------------------- /app/middleware/passportMiddleware.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'), 2 | LocalStrategy = require('passport-local').Strategy; 3 | 4 | var BAD_LOGIN_STRING = 'Invalid username or password' 5 | 6 | module.exports = function(app, User) { 7 | var strategy = new LocalStrategy( 8 | { 9 | usernameField: 'username', 10 | passwordField: 'password' 11 | }, 12 | function (username, password, done) { 13 | console.log('starting local strategy'); 14 | User.findOne({username: username}, function (err, user) { 15 | console.log('user = ' + user); 16 | if (err) return done(err); 17 | if (!user) return done(null, false, { message:BAD_LOGIN_STRING }); 18 | if (user.authenticate(password)) return done(null, user); 19 | else return done(null, false, { message:BAD_LOGIN_STRING }); 20 | }); 21 | }); 22 | 23 | passport.serializeUser(function (user, done) { 24 | done(null, user.id); 25 | }); 26 | 27 | passport.deserializeUser(function (id, done) { 28 | User.findById(id, function(err, user){ 29 | done(err, user); 30 | }); 31 | }); 32 | 33 | passport.use(strategy); 34 | 35 | passport.setLocals = function(req, res, next) { 36 | if(req.isAuthenticated()) { 37 | res.locals.user = req.user; 38 | } 39 | return next(); 40 | } 41 | 42 | return passport; 43 | } 44 | -------------------------------------------------------------------------------- /app/models/Todo.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | module.exports = function (app) { 4 | 5 | var TodoSchema = new mongoose.Schema({ 6 | text: {type: String}, //text of the todo 7 | complete: {type: Boolean}, //whether the todo is complete or not 8 | userId: {type: mongoose.Schema.Types.ObjectId} //the user this todo belongs to 9 | }); 10 | 11 | return mongoose.model('Todo', TodoSchema); 12 | } 13 | -------------------------------------------------------------------------------- /app/models/User.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'), 2 | crypto = require('crypto'); 3 | 4 | 5 | module.exports = function (app) { 6 | 7 | var UserSchema = new mongoose.Schema({ 8 | username: { type:String, required:true , unique:true}, 9 | hashed_password: {type:String, required:true}, 10 | salt: {type:String, required:true} 11 | }); 12 | 13 | UserSchema.virtual('password') 14 | .set(function(password) { 15 | this._password = password; 16 | this.salt = this.makeSalt(); 17 | this.hashed_password = this.encryptPassword(password); 18 | }) 19 | .get(function() { return this._password; }); 20 | 21 | UserSchema.method('authenticate', function(plainText) { 22 | console.log('authenticate called:') 23 | console.log('plain text = ' + plainText) 24 | console.log('hashed = ' + this.encryptPassword(plainText)) 25 | console.log('db password= ' + this.hashed_password) 26 | return this.encryptPassword(plainText) === this.hashed_password; 27 | }); 28 | 29 | UserSchema.method('makeSalt', function() { 30 | return Math.round((new Date().valueOf() * Math.random())) + ''; 31 | }); 32 | 33 | UserSchema.method('encryptPassword', function(password) { 34 | return crypto.createHmac('sha1', this.salt).update(password).digest('hex'); 35 | }); 36 | 37 | UserSchema.method('generateToken', function() { 38 | return crypto.createHash('md5').update(this.username + Date().toString()).digest("hex"); 39 | }); 40 | 41 | UserSchema.pre('save', function(next) { 42 | 43 | this.token = this.generateToken(); 44 | 45 | if (!validatePresenceOf(this.password || this.hashed_password)) { 46 | next(new Error('Invalid password')); 47 | } else { 48 | next(); 49 | } 50 | }); 51 | 52 | return mongoose.model('User', UserSchema); 53 | 54 | } 55 | 56 | function validatePresenceOf(value) { 57 | return value && value.length; 58 | } -------------------------------------------------------------------------------- /app/public/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.1 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | 11 | .clearfix { 12 | *zoom: 1; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | line-height: 0; 19 | content: ""; 20 | } 21 | 22 | .clearfix:after { 23 | clear: both; 24 | } 25 | 26 | .hide-text { 27 | font: 0/0 a; 28 | color: transparent; 29 | text-shadow: none; 30 | background-color: transparent; 31 | border: 0; 32 | } 33 | 34 | .input-block-level { 35 | display: block; 36 | width: 100%; 37 | min-height: 30px; 38 | -webkit-box-sizing: border-box; 39 | -moz-box-sizing: border-box; 40 | box-sizing: border-box; 41 | } 42 | 43 | @-ms-viewport { 44 | width: device-width; 45 | } 46 | 47 | .hidden { 48 | display: none; 49 | visibility: hidden; 50 | } 51 | 52 | .visible-phone { 53 | display: none !important; 54 | } 55 | 56 | .visible-tablet { 57 | display: none !important; 58 | } 59 | 60 | .hidden-desktop { 61 | display: none !important; 62 | } 63 | 64 | .visible-desktop { 65 | display: inherit !important; 66 | } 67 | 68 | @media (min-width: 768px) and (max-width: 979px) { 69 | .hidden-desktop { 70 | display: inherit !important; 71 | } 72 | .visible-desktop { 73 | display: none !important ; 74 | } 75 | .visible-tablet { 76 | display: inherit !important; 77 | } 78 | .hidden-tablet { 79 | display: none !important; 80 | } 81 | } 82 | 83 | @media (max-width: 767px) { 84 | .hidden-desktop { 85 | display: inherit !important; 86 | } 87 | .visible-desktop { 88 | display: none !important; 89 | } 90 | .visible-phone { 91 | display: inherit !important; 92 | } 93 | .hidden-phone { 94 | display: none !important; 95 | } 96 | } 97 | 98 | .visible-print { 99 | display: none !important; 100 | } 101 | 102 | @media print { 103 | .visible-print { 104 | display: inherit !important; 105 | } 106 | .hidden-print { 107 | display: none !important; 108 | } 109 | } 110 | 111 | @media (min-width: 1200px) { 112 | .row { 113 | margin-left: -30px; 114 | *zoom: 1; 115 | } 116 | .row:before, 117 | .row:after { 118 | display: table; 119 | line-height: 0; 120 | content: ""; 121 | } 122 | .row:after { 123 | clear: both; 124 | } 125 | [class*="span"] { 126 | float: left; 127 | min-height: 1px; 128 | margin-left: 30px; 129 | } 130 | .container, 131 | .navbar-static-top .container, 132 | .navbar-fixed-top .container, 133 | .navbar-fixed-bottom .container { 134 | width: 1170px; 135 | } 136 | .span12 { 137 | width: 1170px; 138 | } 139 | .span11 { 140 | width: 1070px; 141 | } 142 | .span10 { 143 | width: 970px; 144 | } 145 | .span9 { 146 | width: 870px; 147 | } 148 | .span8 { 149 | width: 770px; 150 | } 151 | .span7 { 152 | width: 670px; 153 | } 154 | .span6 { 155 | width: 570px; 156 | } 157 | .span5 { 158 | width: 470px; 159 | } 160 | .span4 { 161 | width: 370px; 162 | } 163 | .span3 { 164 | width: 270px; 165 | } 166 | .span2 { 167 | width: 170px; 168 | } 169 | .span1 { 170 | width: 70px; 171 | } 172 | .offset12 { 173 | margin-left: 1230px; 174 | } 175 | .offset11 { 176 | margin-left: 1130px; 177 | } 178 | .offset10 { 179 | margin-left: 1030px; 180 | } 181 | .offset9 { 182 | margin-left: 930px; 183 | } 184 | .offset8 { 185 | margin-left: 830px; 186 | } 187 | .offset7 { 188 | margin-left: 730px; 189 | } 190 | .offset6 { 191 | margin-left: 630px; 192 | } 193 | .offset5 { 194 | margin-left: 530px; 195 | } 196 | .offset4 { 197 | margin-left: 430px; 198 | } 199 | .offset3 { 200 | margin-left: 330px; 201 | } 202 | .offset2 { 203 | margin-left: 230px; 204 | } 205 | .offset1 { 206 | margin-left: 130px; 207 | } 208 | .row-fluid { 209 | width: 100%; 210 | *zoom: 1; 211 | } 212 | .row-fluid:before, 213 | .row-fluid:after { 214 | display: table; 215 | line-height: 0; 216 | content: ""; 217 | } 218 | .row-fluid:after { 219 | clear: both; 220 | } 221 | .row-fluid [class*="span"] { 222 | display: block; 223 | float: left; 224 | width: 100%; 225 | min-height: 30px; 226 | margin-left: 2.564102564102564%; 227 | *margin-left: 2.5109110747408616%; 228 | -webkit-box-sizing: border-box; 229 | -moz-box-sizing: border-box; 230 | box-sizing: border-box; 231 | } 232 | .row-fluid [class*="span"]:first-child { 233 | margin-left: 0; 234 | } 235 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 236 | margin-left: 2.564102564102564%; 237 | } 238 | .row-fluid .span12 { 239 | width: 100%; 240 | *width: 99.94680851063829%; 241 | } 242 | .row-fluid .span11 { 243 | width: 91.45299145299145%; 244 | *width: 91.39979996362975%; 245 | } 246 | .row-fluid .span10 { 247 | width: 82.90598290598291%; 248 | *width: 82.8527914166212%; 249 | } 250 | .row-fluid .span9 { 251 | width: 74.35897435897436%; 252 | *width: 74.30578286961266%; 253 | } 254 | .row-fluid .span8 { 255 | width: 65.81196581196582%; 256 | *width: 65.75877432260411%; 257 | } 258 | .row-fluid .span7 { 259 | width: 57.26495726495726%; 260 | *width: 57.21176577559556%; 261 | } 262 | .row-fluid .span6 { 263 | width: 48.717948717948715%; 264 | *width: 48.664757228587014%; 265 | } 266 | .row-fluid .span5 { 267 | width: 40.17094017094017%; 268 | *width: 40.11774868157847%; 269 | } 270 | .row-fluid .span4 { 271 | width: 31.623931623931625%; 272 | *width: 31.570740134569924%; 273 | } 274 | .row-fluid .span3 { 275 | width: 23.076923076923077%; 276 | *width: 23.023731587561375%; 277 | } 278 | .row-fluid .span2 { 279 | width: 14.52991452991453%; 280 | *width: 14.476723040552828%; 281 | } 282 | .row-fluid .span1 { 283 | width: 5.982905982905983%; 284 | *width: 5.929714493544281%; 285 | } 286 | .row-fluid .offset12 { 287 | margin-left: 105.12820512820512%; 288 | *margin-left: 105.02182214948171%; 289 | } 290 | .row-fluid .offset12:first-child { 291 | margin-left: 102.56410256410257%; 292 | *margin-left: 102.45771958537915%; 293 | } 294 | .row-fluid .offset11 { 295 | margin-left: 96.58119658119658%; 296 | *margin-left: 96.47481360247316%; 297 | } 298 | .row-fluid .offset11:first-child { 299 | margin-left: 94.01709401709402%; 300 | *margin-left: 93.91071103837061%; 301 | } 302 | .row-fluid .offset10 { 303 | margin-left: 88.03418803418803%; 304 | *margin-left: 87.92780505546462%; 305 | } 306 | .row-fluid .offset10:first-child { 307 | margin-left: 85.47008547008548%; 308 | *margin-left: 85.36370249136206%; 309 | } 310 | .row-fluid .offset9 { 311 | margin-left: 79.48717948717949%; 312 | *margin-left: 79.38079650845607%; 313 | } 314 | .row-fluid .offset9:first-child { 315 | margin-left: 76.92307692307693%; 316 | *margin-left: 76.81669394435352%; 317 | } 318 | .row-fluid .offset8 { 319 | margin-left: 70.94017094017094%; 320 | *margin-left: 70.83378796144753%; 321 | } 322 | .row-fluid .offset8:first-child { 323 | margin-left: 68.37606837606839%; 324 | *margin-left: 68.26968539734497%; 325 | } 326 | .row-fluid .offset7 { 327 | margin-left: 62.393162393162385%; 328 | *margin-left: 62.28677941443899%; 329 | } 330 | .row-fluid .offset7:first-child { 331 | margin-left: 59.82905982905982%; 332 | *margin-left: 59.72267685033642%; 333 | } 334 | .row-fluid .offset6 { 335 | margin-left: 53.84615384615384%; 336 | *margin-left: 53.739770867430444%; 337 | } 338 | .row-fluid .offset6:first-child { 339 | margin-left: 51.28205128205128%; 340 | *margin-left: 51.175668303327875%; 341 | } 342 | .row-fluid .offset5 { 343 | margin-left: 45.299145299145295%; 344 | *margin-left: 45.1927623204219%; 345 | } 346 | .row-fluid .offset5:first-child { 347 | margin-left: 42.73504273504273%; 348 | *margin-left: 42.62865975631933%; 349 | } 350 | .row-fluid .offset4 { 351 | margin-left: 36.75213675213675%; 352 | *margin-left: 36.645753773413354%; 353 | } 354 | .row-fluid .offset4:first-child { 355 | margin-left: 34.18803418803419%; 356 | *margin-left: 34.081651209310785%; 357 | } 358 | .row-fluid .offset3 { 359 | margin-left: 28.205128205128204%; 360 | *margin-left: 28.0987452264048%; 361 | } 362 | .row-fluid .offset3:first-child { 363 | margin-left: 25.641025641025642%; 364 | *margin-left: 25.53464266230224%; 365 | } 366 | .row-fluid .offset2 { 367 | margin-left: 19.65811965811966%; 368 | *margin-left: 19.551736679396257%; 369 | } 370 | .row-fluid .offset2:first-child { 371 | margin-left: 17.094017094017094%; 372 | *margin-left: 16.98763411529369%; 373 | } 374 | .row-fluid .offset1 { 375 | margin-left: 11.11111111111111%; 376 | *margin-left: 11.004728132387708%; 377 | } 378 | .row-fluid .offset1:first-child { 379 | margin-left: 8.547008547008547%; 380 | *margin-left: 8.440625568285142%; 381 | } 382 | input, 383 | textarea, 384 | .uneditable-input { 385 | margin-left: 0; 386 | } 387 | .controls-row [class*="span"] + [class*="span"] { 388 | margin-left: 30px; 389 | } 390 | input.span12, 391 | textarea.span12, 392 | .uneditable-input.span12 { 393 | width: 1156px; 394 | } 395 | input.span11, 396 | textarea.span11, 397 | .uneditable-input.span11 { 398 | width: 1056px; 399 | } 400 | input.span10, 401 | textarea.span10, 402 | .uneditable-input.span10 { 403 | width: 956px; 404 | } 405 | input.span9, 406 | textarea.span9, 407 | .uneditable-input.span9 { 408 | width: 856px; 409 | } 410 | input.span8, 411 | textarea.span8, 412 | .uneditable-input.span8 { 413 | width: 756px; 414 | } 415 | input.span7, 416 | textarea.span7, 417 | .uneditable-input.span7 { 418 | width: 656px; 419 | } 420 | input.span6, 421 | textarea.span6, 422 | .uneditable-input.span6 { 423 | width: 556px; 424 | } 425 | input.span5, 426 | textarea.span5, 427 | .uneditable-input.span5 { 428 | width: 456px; 429 | } 430 | input.span4, 431 | textarea.span4, 432 | .uneditable-input.span4 { 433 | width: 356px; 434 | } 435 | input.span3, 436 | textarea.span3, 437 | .uneditable-input.span3 { 438 | width: 256px; 439 | } 440 | input.span2, 441 | textarea.span2, 442 | .uneditable-input.span2 { 443 | width: 156px; 444 | } 445 | input.span1, 446 | textarea.span1, 447 | .uneditable-input.span1 { 448 | width: 56px; 449 | } 450 | .thumbnails { 451 | margin-left: -30px; 452 | } 453 | .thumbnails > li { 454 | margin-left: 30px; 455 | } 456 | .row-fluid .thumbnails { 457 | margin-left: 0; 458 | } 459 | } 460 | 461 | @media (min-width: 768px) and (max-width: 979px) { 462 | .row { 463 | margin-left: -20px; 464 | *zoom: 1; 465 | } 466 | .row:before, 467 | .row:after { 468 | display: table; 469 | line-height: 0; 470 | content: ""; 471 | } 472 | .row:after { 473 | clear: both; 474 | } 475 | [class*="span"] { 476 | float: left; 477 | min-height: 1px; 478 | margin-left: 20px; 479 | } 480 | .container, 481 | .navbar-static-top .container, 482 | .navbar-fixed-top .container, 483 | .navbar-fixed-bottom .container { 484 | width: 724px; 485 | } 486 | .span12 { 487 | width: 724px; 488 | } 489 | .span11 { 490 | width: 662px; 491 | } 492 | .span10 { 493 | width: 600px; 494 | } 495 | .span9 { 496 | width: 538px; 497 | } 498 | .span8 { 499 | width: 476px; 500 | } 501 | .span7 { 502 | width: 414px; 503 | } 504 | .span6 { 505 | width: 352px; 506 | } 507 | .span5 { 508 | width: 290px; 509 | } 510 | .span4 { 511 | width: 228px; 512 | } 513 | .span3 { 514 | width: 166px; 515 | } 516 | .span2 { 517 | width: 104px; 518 | } 519 | .span1 { 520 | width: 42px; 521 | } 522 | .offset12 { 523 | margin-left: 764px; 524 | } 525 | .offset11 { 526 | margin-left: 702px; 527 | } 528 | .offset10 { 529 | margin-left: 640px; 530 | } 531 | .offset9 { 532 | margin-left: 578px; 533 | } 534 | .offset8 { 535 | margin-left: 516px; 536 | } 537 | .offset7 { 538 | margin-left: 454px; 539 | } 540 | .offset6 { 541 | margin-left: 392px; 542 | } 543 | .offset5 { 544 | margin-left: 330px; 545 | } 546 | .offset4 { 547 | margin-left: 268px; 548 | } 549 | .offset3 { 550 | margin-left: 206px; 551 | } 552 | .offset2 { 553 | margin-left: 144px; 554 | } 555 | .offset1 { 556 | margin-left: 82px; 557 | } 558 | .row-fluid { 559 | width: 100%; 560 | *zoom: 1; 561 | } 562 | .row-fluid:before, 563 | .row-fluid:after { 564 | display: table; 565 | line-height: 0; 566 | content: ""; 567 | } 568 | .row-fluid:after { 569 | clear: both; 570 | } 571 | .row-fluid [class*="span"] { 572 | display: block; 573 | float: left; 574 | width: 100%; 575 | min-height: 30px; 576 | margin-left: 2.7624309392265194%; 577 | *margin-left: 2.709239449864817%; 578 | -webkit-box-sizing: border-box; 579 | -moz-box-sizing: border-box; 580 | box-sizing: border-box; 581 | } 582 | .row-fluid [class*="span"]:first-child { 583 | margin-left: 0; 584 | } 585 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 586 | margin-left: 2.7624309392265194%; 587 | } 588 | .row-fluid .span12 { 589 | width: 100%; 590 | *width: 99.94680851063829%; 591 | } 592 | .row-fluid .span11 { 593 | width: 91.43646408839778%; 594 | *width: 91.38327259903608%; 595 | } 596 | .row-fluid .span10 { 597 | width: 82.87292817679558%; 598 | *width: 82.81973668743387%; 599 | } 600 | .row-fluid .span9 { 601 | width: 74.30939226519337%; 602 | *width: 74.25620077583166%; 603 | } 604 | .row-fluid .span8 { 605 | width: 65.74585635359117%; 606 | *width: 65.69266486422946%; 607 | } 608 | .row-fluid .span7 { 609 | width: 57.18232044198895%; 610 | *width: 57.12912895262725%; 611 | } 612 | .row-fluid .span6 { 613 | width: 48.61878453038674%; 614 | *width: 48.56559304102504%; 615 | } 616 | .row-fluid .span5 { 617 | width: 40.05524861878453%; 618 | *width: 40.00205712942283%; 619 | } 620 | .row-fluid .span4 { 621 | width: 31.491712707182323%; 622 | *width: 31.43852121782062%; 623 | } 624 | .row-fluid .span3 { 625 | width: 22.92817679558011%; 626 | *width: 22.87498530621841%; 627 | } 628 | .row-fluid .span2 { 629 | width: 14.3646408839779%; 630 | *width: 14.311449394616199%; 631 | } 632 | .row-fluid .span1 { 633 | width: 5.801104972375691%; 634 | *width: 5.747913483013988%; 635 | } 636 | .row-fluid .offset12 { 637 | margin-left: 105.52486187845304%; 638 | *margin-left: 105.41847889972962%; 639 | } 640 | .row-fluid .offset12:first-child { 641 | margin-left: 102.76243093922652%; 642 | *margin-left: 102.6560479605031%; 643 | } 644 | .row-fluid .offset11 { 645 | margin-left: 96.96132596685082%; 646 | *margin-left: 96.8549429881274%; 647 | } 648 | .row-fluid .offset11:first-child { 649 | margin-left: 94.1988950276243%; 650 | *margin-left: 94.09251204890089%; 651 | } 652 | .row-fluid .offset10 { 653 | margin-left: 88.39779005524862%; 654 | *margin-left: 88.2914070765252%; 655 | } 656 | .row-fluid .offset10:first-child { 657 | margin-left: 85.6353591160221%; 658 | *margin-left: 85.52897613729868%; 659 | } 660 | .row-fluid .offset9 { 661 | margin-left: 79.8342541436464%; 662 | *margin-left: 79.72787116492299%; 663 | } 664 | .row-fluid .offset9:first-child { 665 | margin-left: 77.07182320441989%; 666 | *margin-left: 76.96544022569647%; 667 | } 668 | .row-fluid .offset8 { 669 | margin-left: 71.2707182320442%; 670 | *margin-left: 71.16433525332079%; 671 | } 672 | .row-fluid .offset8:first-child { 673 | margin-left: 68.50828729281768%; 674 | *margin-left: 68.40190431409427%; 675 | } 676 | .row-fluid .offset7 { 677 | margin-left: 62.70718232044199%; 678 | *margin-left: 62.600799341718584%; 679 | } 680 | .row-fluid .offset7:first-child { 681 | margin-left: 59.94475138121547%; 682 | *margin-left: 59.838368402492065%; 683 | } 684 | .row-fluid .offset6 { 685 | margin-left: 54.14364640883978%; 686 | *margin-left: 54.037263430116376%; 687 | } 688 | .row-fluid .offset6:first-child { 689 | margin-left: 51.38121546961326%; 690 | *margin-left: 51.27483249088986%; 691 | } 692 | .row-fluid .offset5 { 693 | margin-left: 45.58011049723757%; 694 | *margin-left: 45.47372751851417%; 695 | } 696 | .row-fluid .offset5:first-child { 697 | margin-left: 42.81767955801105%; 698 | *margin-left: 42.71129657928765%; 699 | } 700 | .row-fluid .offset4 { 701 | margin-left: 37.01657458563536%; 702 | *margin-left: 36.91019160691196%; 703 | } 704 | .row-fluid .offset4:first-child { 705 | margin-left: 34.25414364640884%; 706 | *margin-left: 34.14776066768544%; 707 | } 708 | .row-fluid .offset3 { 709 | margin-left: 28.45303867403315%; 710 | *margin-left: 28.346655695309746%; 711 | } 712 | .row-fluid .offset3:first-child { 713 | margin-left: 25.69060773480663%; 714 | *margin-left: 25.584224756083227%; 715 | } 716 | .row-fluid .offset2 { 717 | margin-left: 19.88950276243094%; 718 | *margin-left: 19.783119783707537%; 719 | } 720 | .row-fluid .offset2:first-child { 721 | margin-left: 17.12707182320442%; 722 | *margin-left: 17.02068884448102%; 723 | } 724 | .row-fluid .offset1 { 725 | margin-left: 11.32596685082873%; 726 | *margin-left: 11.219583872105325%; 727 | } 728 | .row-fluid .offset1:first-child { 729 | margin-left: 8.56353591160221%; 730 | *margin-left: 8.457152932878806%; 731 | } 732 | input, 733 | textarea, 734 | .uneditable-input { 735 | margin-left: 0; 736 | } 737 | .controls-row [class*="span"] + [class*="span"] { 738 | margin-left: 20px; 739 | } 740 | input.span12, 741 | textarea.span12, 742 | .uneditable-input.span12 { 743 | width: 710px; 744 | } 745 | input.span11, 746 | textarea.span11, 747 | .uneditable-input.span11 { 748 | width: 648px; 749 | } 750 | input.span10, 751 | textarea.span10, 752 | .uneditable-input.span10 { 753 | width: 586px; 754 | } 755 | input.span9, 756 | textarea.span9, 757 | .uneditable-input.span9 { 758 | width: 524px; 759 | } 760 | input.span8, 761 | textarea.span8, 762 | .uneditable-input.span8 { 763 | width: 462px; 764 | } 765 | input.span7, 766 | textarea.span7, 767 | .uneditable-input.span7 { 768 | width: 400px; 769 | } 770 | input.span6, 771 | textarea.span6, 772 | .uneditable-input.span6 { 773 | width: 338px; 774 | } 775 | input.span5, 776 | textarea.span5, 777 | .uneditable-input.span5 { 778 | width: 276px; 779 | } 780 | input.span4, 781 | textarea.span4, 782 | .uneditable-input.span4 { 783 | width: 214px; 784 | } 785 | input.span3, 786 | textarea.span3, 787 | .uneditable-input.span3 { 788 | width: 152px; 789 | } 790 | input.span2, 791 | textarea.span2, 792 | .uneditable-input.span2 { 793 | width: 90px; 794 | } 795 | input.span1, 796 | textarea.span1, 797 | .uneditable-input.span1 { 798 | width: 28px; 799 | } 800 | } 801 | 802 | @media (max-width: 767px) { 803 | body { 804 | padding-right: 20px; 805 | padding-left: 20px; 806 | } 807 | .navbar-fixed-top, 808 | .navbar-fixed-bottom, 809 | .navbar-static-top { 810 | margin-right: -20px; 811 | margin-left: -20px; 812 | } 813 | .container-fluid { 814 | padding: 0; 815 | } 816 | .dl-horizontal dt { 817 | float: none; 818 | width: auto; 819 | clear: none; 820 | text-align: left; 821 | } 822 | .dl-horizontal dd { 823 | margin-left: 0; 824 | } 825 | .container { 826 | width: auto; 827 | } 828 | .row-fluid { 829 | width: 100%; 830 | } 831 | .row, 832 | .thumbnails { 833 | margin-left: 0; 834 | } 835 | .thumbnails > li { 836 | float: none; 837 | margin-left: 0; 838 | } 839 | [class*="span"], 840 | .uneditable-input[class*="span"], 841 | .row-fluid [class*="span"] { 842 | display: block; 843 | float: none; 844 | width: 100%; 845 | margin-left: 0; 846 | -webkit-box-sizing: border-box; 847 | -moz-box-sizing: border-box; 848 | box-sizing: border-box; 849 | } 850 | .span12, 851 | .row-fluid .span12 { 852 | width: 100%; 853 | -webkit-box-sizing: border-box; 854 | -moz-box-sizing: border-box; 855 | box-sizing: border-box; 856 | } 857 | .row-fluid [class*="offset"]:first-child { 858 | margin-left: 0; 859 | } 860 | .input-large, 861 | .input-xlarge, 862 | .input-xxlarge, 863 | input[class*="span"], 864 | select[class*="span"], 865 | textarea[class*="span"], 866 | .uneditable-input { 867 | display: block; 868 | width: 100%; 869 | min-height: 30px; 870 | -webkit-box-sizing: border-box; 871 | -moz-box-sizing: border-box; 872 | box-sizing: border-box; 873 | } 874 | .input-prepend input, 875 | .input-append input, 876 | .input-prepend input[class*="span"], 877 | .input-append input[class*="span"] { 878 | display: inline-block; 879 | width: auto; 880 | } 881 | .controls-row [class*="span"] + [class*="span"] { 882 | margin-left: 0; 883 | } 884 | .modal { 885 | position: fixed; 886 | top: 20px; 887 | right: 20px; 888 | left: 20px; 889 | width: auto; 890 | margin: 0; 891 | } 892 | .modal.fade { 893 | top: -100px; 894 | } 895 | .modal.fade.in { 896 | top: 20px; 897 | } 898 | } 899 | 900 | @media (max-width: 480px) { 901 | .nav-collapse { 902 | -webkit-transform: translate3d(0, 0, 0); 903 | } 904 | .page-header h1 small { 905 | display: block; 906 | line-height: 20px; 907 | } 908 | input[type="checkbox"], 909 | input[type="radio"] { 910 | border: 1px solid #ccc; 911 | } 912 | .form-horizontal .control-label { 913 | float: none; 914 | width: auto; 915 | padding-top: 0; 916 | text-align: left; 917 | } 918 | .form-horizontal .controls { 919 | margin-left: 0; 920 | } 921 | .form-horizontal .control-list { 922 | padding-top: 0; 923 | } 924 | .form-horizontal .form-actions { 925 | padding-right: 10px; 926 | padding-left: 10px; 927 | } 928 | .media .pull-left, 929 | .media .pull-right { 930 | display: block; 931 | float: none; 932 | margin-bottom: 10px; 933 | } 934 | .media-object { 935 | margin-right: 0; 936 | margin-left: 0; 937 | } 938 | .modal { 939 | top: 10px; 940 | right: 10px; 941 | left: 10px; 942 | } 943 | .modal-header .close { 944 | padding: 10px; 945 | margin: -10px; 946 | } 947 | .carousel-caption { 948 | position: static; 949 | } 950 | } 951 | 952 | @media (max-width: 979px) { 953 | body { 954 | padding-top: 0; 955 | } 956 | .navbar-fixed-top, 957 | .navbar-fixed-bottom { 958 | position: static; 959 | } 960 | .navbar-fixed-top { 961 | margin-bottom: 20px; 962 | } 963 | .navbar-fixed-bottom { 964 | margin-top: 20px; 965 | } 966 | .navbar-fixed-top .navbar-inner, 967 | .navbar-fixed-bottom .navbar-inner { 968 | padding: 5px; 969 | } 970 | .navbar .container { 971 | width: auto; 972 | padding: 0; 973 | } 974 | .navbar .brand { 975 | padding-right: 10px; 976 | padding-left: 10px; 977 | margin: 0 0 0 -5px; 978 | } 979 | .nav-collapse { 980 | clear: both; 981 | } 982 | .nav-collapse .nav { 983 | float: none; 984 | margin: 0 0 10px; 985 | } 986 | .nav-collapse .nav > li { 987 | float: none; 988 | } 989 | .nav-collapse .nav > li > a { 990 | margin-bottom: 2px; 991 | } 992 | .nav-collapse .nav > .divider-vertical { 993 | display: none; 994 | } 995 | .nav-collapse .nav .nav-header { 996 | color: #777777; 997 | text-shadow: none; 998 | } 999 | .nav-collapse .nav > li > a, 1000 | .nav-collapse .dropdown-menu a { 1001 | padding: 9px 15px; 1002 | font-weight: bold; 1003 | color: #777777; 1004 | -webkit-border-radius: 3px; 1005 | -moz-border-radius: 3px; 1006 | border-radius: 3px; 1007 | } 1008 | .nav-collapse .btn { 1009 | padding: 4px 10px 4px; 1010 | font-weight: normal; 1011 | -webkit-border-radius: 4px; 1012 | -moz-border-radius: 4px; 1013 | border-radius: 4px; 1014 | } 1015 | .nav-collapse .dropdown-menu li + li a { 1016 | margin-bottom: 2px; 1017 | } 1018 | .nav-collapse .nav > li > a:hover, 1019 | .nav-collapse .nav > li > a:focus, 1020 | .nav-collapse .dropdown-menu a:hover, 1021 | .nav-collapse .dropdown-menu a:focus { 1022 | background-color: #f2f2f2; 1023 | } 1024 | .navbar-inverse .nav-collapse .nav > li > a, 1025 | .navbar-inverse .nav-collapse .dropdown-menu a { 1026 | color: #999999; 1027 | } 1028 | .navbar-inverse .nav-collapse .nav > li > a:hover, 1029 | .navbar-inverse .nav-collapse .nav > li > a:focus, 1030 | .navbar-inverse .nav-collapse .dropdown-menu a:hover, 1031 | .navbar-inverse .nav-collapse .dropdown-menu a:focus { 1032 | background-color: #111111; 1033 | } 1034 | .nav-collapse.in .btn-group { 1035 | padding: 0; 1036 | margin-top: 5px; 1037 | } 1038 | .nav-collapse .dropdown-menu { 1039 | position: static; 1040 | top: auto; 1041 | left: auto; 1042 | display: none; 1043 | float: none; 1044 | max-width: none; 1045 | padding: 0; 1046 | margin: 0 15px; 1047 | background-color: transparent; 1048 | border: none; 1049 | -webkit-border-radius: 0; 1050 | -moz-border-radius: 0; 1051 | border-radius: 0; 1052 | -webkit-box-shadow: none; 1053 | -moz-box-shadow: none; 1054 | box-shadow: none; 1055 | } 1056 | .nav-collapse .open > .dropdown-menu { 1057 | display: block; 1058 | } 1059 | .nav-collapse .dropdown-menu:before, 1060 | .nav-collapse .dropdown-menu:after { 1061 | display: none; 1062 | } 1063 | .nav-collapse .dropdown-menu .divider { 1064 | display: none; 1065 | } 1066 | .nav-collapse .nav > li > .dropdown-menu:before, 1067 | .nav-collapse .nav > li > .dropdown-menu:after { 1068 | display: none; 1069 | } 1070 | .nav-collapse .navbar-form, 1071 | .nav-collapse .navbar-search { 1072 | float: none; 1073 | padding: 10px 15px; 1074 | margin: 10px 0; 1075 | border-top: 1px solid #f2f2f2; 1076 | border-bottom: 1px solid #f2f2f2; 1077 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1078 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1079 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1080 | } 1081 | .navbar-inverse .nav-collapse .navbar-form, 1082 | .navbar-inverse .nav-collapse .navbar-search { 1083 | border-top-color: #111111; 1084 | border-bottom-color: #111111; 1085 | } 1086 | .navbar .nav-collapse .nav.pull-right { 1087 | float: none; 1088 | margin-left: 0; 1089 | } 1090 | .nav-collapse, 1091 | .nav-collapse.collapse { 1092 | height: 0; 1093 | overflow: hidden; 1094 | } 1095 | .navbar .btn-navbar { 1096 | display: block; 1097 | } 1098 | .navbar-static .navbar-inner { 1099 | padding-right: 10px; 1100 | padding-left: 10px; 1101 | } 1102 | } 1103 | 1104 | @media (min-width: 980px) { 1105 | .nav-collapse.collapse { 1106 | height: auto !important; 1107 | overflow: visible !important; 1108 | } 1109 | } 1110 | -------------------------------------------------------------------------------- /app/public/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.1 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /app/public/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-langager/Angular-Express-Train-Seed/d67b9e8e63e95ad14be6eb31d7ea9a7d7a82714d/app/public/favicon.ico -------------------------------------------------------------------------------- /app/public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-langager/Angular-Express-Train-Seed/d67b9e8e63e95ad14be6eb31d7ea9a7d7a82714d/app/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /app/public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-langager/Angular-Express-Train-Seed/d67b9e8e63e95ad14be6eb31d7ea9a7d7a82714d/app/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular Express Train 5 | 6 | 7 | 8 | 9 |
10 | 29 |
30 |
31 | 32 | 33 |
34 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/public/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on filters, and services 4 | angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives']) 5 | .config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) { 6 | 7 | //The routes that our angular app will handle 8 | $routeProvider 9 | .when('/', { templateUrl: '/partials/index.html', controller: IndexCtrl }) 10 | .when('/login', { templateUrl: '/partials/login.html'}) 11 | .when('/todos', { templateUrl: '/partials/todos.html', controller: TodosCtrl }) 12 | .otherwise({ templateUrl: '/partials/404.html' }); 13 | 14 | //gets rid of the # in urls 15 | $locationProvider.html5Mode(true); 16 | 17 | /* 18 | Set up an interceptor to watch for 401 errors. 19 | The server, rather than redirect to a login page (or whatever), just returns a 401 error 20 | if it receives a request that should have a user session going. Angular catches the error below 21 | and says what happens - in this case, we just redirect to a login page. You can get a little more 22 | complex with this strategy, such as queueing up failed requests and re-trying them once the user logs in. 23 | Read all about it here: http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application 24 | */ 25 | var interceptor = ['$q', '$location', '$rootScope', function ($q, $location, $rootScope) { 26 | function success(response) { 27 | return response; 28 | } 29 | 30 | function error(response) { 31 | var status = response.status; 32 | if (status == 401) { 33 | $rootScope.redirect = $location.url(); // save the current url so we can redirect the user back 34 | $rootScope.user = {} 35 | $location.path('/login'); 36 | } 37 | return $q.reject(response); 38 | } 39 | 40 | return function (promise) { 41 | return promise.then(success, error); 42 | } 43 | }]; 44 | $httpProvider.responseInterceptors.push(interceptor); 45 | 46 | }]) 47 | .run(function ($rootScope, $http, $location) { 48 | 49 | //global object representing the user who is logged in 50 | $rootScope.user = {}; 51 | 52 | //as the app spins up, let's check to see if we have an active session with the server 53 | $http.get('/user') 54 | .success(function (data) { 55 | $rootScope.user.username = data.username; 56 | }) 57 | .error(function (data) { 58 | }); 59 | 60 | //global function for logging out a user 61 | $rootScope.logout = function () { 62 | $rootScope.user = {} 63 | $http.post('user/logout', {}); 64 | $location.path('/'); 65 | } 66 | 67 | }); -------------------------------------------------------------------------------- /app/public/js/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | function IndexCtrl($scope, $http) { 5 | } 6 | 7 | function LoginCtrl($scope, $http, $rootScope, $location) { 8 | $scope.user = {}; 9 | $scope.statusMessage = ''; 10 | 11 | //figure out where we should redirect to once the user has logged in. 12 | if (!$rootScope.redirect || $rootScope.redirect == '/login') { 13 | $rootScope.redirect = '/todos'; 14 | } 15 | 16 | $scope.submit = function (user) { 17 | $http.post('/user/login', $scope.user) 18 | .success(function (data) { 19 | $rootScope.user.username = $scope.user.username; 20 | $location.path($rootScope.redirect); 21 | }) 22 | .error(function (data, status, headers, config) { 23 | $scope.statusMessage = data; 24 | }); 25 | } 26 | } 27 | 28 | function RegisterCtrl($scope, $http, $rootScope, $location) { 29 | $scope.user = {}; 30 | $scope.statusMessage = ''; 31 | 32 | $scope.submit = function (user) { 33 | $http.post('/user/register', $scope.user) 34 | .success(function (data) { 35 | $rootScope.user.username = $scope.user.username; 36 | $location.path('/todos'); 37 | }) 38 | .error(function (data, status, headers, config) { 39 | $scope.statusMessage = data; 40 | }); 41 | } 42 | } 43 | 44 | function TodosCtrl($scope, $http, Todo) { 45 | 46 | //get the todos from server 47 | getTodosFromServer() 48 | 49 | $scope.newTodo = {}; 50 | 51 | //function to create a new Todo object 52 | $scope.createTodo = function (todo) { 53 | if ($scope.newTodoForm.$invalid) { 54 | return; 55 | } 56 | Todo.save({}, $scope.newTodo, 57 | function (data) { 58 | $scope.todos.push(data); 59 | $scope.statusMessage = ''; 60 | $scope.newTodo = {}; 61 | 62 | }, 63 | function (data, status, headers, config) { 64 | $scope.statusMessage = data; 65 | }); 66 | } 67 | 68 | //we'll call this function when the checkbox of a todo is checked 69 | $scope.markComplete = function (todo) { 70 | todo.$save({id: todo._id}); 71 | } 72 | 73 | //remove complete todos 74 | $scope.removeComplete = function () { 75 | $scope.todos.forEach(function (todo) { 76 | if (todo.complete) { 77 | todo.$delete({id: todo._id}, function(){ //delete on server 78 | $scope.todos.splice( $scope.todos.indexOf(todo), 1 ); //remove from client 79 | }); 80 | } 81 | }) 82 | } 83 | 84 | function getTodosFromServer() { 85 | Todo.query(function (data) { 86 | $scope.todos = data; 87 | }); 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /app/public/js/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | 5 | 6 | angular.module('myApp.directives', []). 7 | directive('appVersion', ['version', function(version) { 8 | return function(scope, elm, attrs) { 9 | elm.text(version); 10 | }; 11 | }]) 12 | .directive('dropdown', function () { 13 | return function (scope, elm, attrs) { 14 | $(elm).dropdown(); 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /app/public/js/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('myApp.filters', []). 6 | filter('interpolate', ['version', function(version) { 7 | return function(text) { 8 | return String(text).replace(/\%VERSION\%/mg, version); 9 | } 10 | }]); 11 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/angular-cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.0 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngCookies 12 | */ 13 | 14 | 15 | angular.module('ngCookies', ['ng']). 16 | /** 17 | * @ngdoc object 18 | * @name ngCookies.$cookies 19 | * @requires $browser 20 | * 21 | * @description 22 | * Provides read/write access to browser's cookies. 23 | * 24 | * Only a simple Object is exposed and by adding or removing properties to/from 25 | * this object, new cookies are created/deleted at the end of current $eval. 26 | * 27 | * @example 28 | */ 29 | factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { 30 | var cookies = {}, 31 | lastCookies = {}, 32 | lastBrowserCookies, 33 | runEval = false, 34 | copy = angular.copy, 35 | isUndefined = angular.isUndefined; 36 | 37 | //creates a poller fn that copies all cookies from the $browser to service & inits the service 38 | $browser.addPollFn(function() { 39 | var currentCookies = $browser.cookies(); 40 | if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl 41 | lastBrowserCookies = currentCookies; 42 | copy(currentCookies, lastCookies); 43 | copy(currentCookies, cookies); 44 | if (runEval) $rootScope.$apply(); 45 | } 46 | })(); 47 | 48 | runEval = true; 49 | 50 | //at the end of each eval, push cookies 51 | //TODO: this should happen before the "delayed" watches fire, because if some cookies are not 52 | // strings or browser refuses to store some cookies, we update the model in the push fn. 53 | $rootScope.$watch(push); 54 | 55 | return cookies; 56 | 57 | 58 | /** 59 | * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. 60 | */ 61 | function push() { 62 | var name, 63 | value, 64 | browserCookies, 65 | updated; 66 | 67 | //delete any cookies deleted in $cookies 68 | for (name in lastCookies) { 69 | if (isUndefined(cookies[name])) { 70 | $browser.cookies(name, undefined); 71 | } 72 | } 73 | 74 | //update all cookies updated in $cookies 75 | for(name in cookies) { 76 | value = cookies[name]; 77 | if (!angular.isString(value)) { 78 | if (angular.isDefined(lastCookies[name])) { 79 | cookies[name] = lastCookies[name]; 80 | } else { 81 | delete cookies[name]; 82 | } 83 | } else if (value !== lastCookies[name]) { 84 | $browser.cookies(name, value); 85 | updated = true; 86 | } 87 | } 88 | 89 | //verify what was actually stored 90 | if (updated){ 91 | updated = false; 92 | browserCookies = $browser.cookies(); 93 | 94 | for (name in cookies) { 95 | if (cookies[name] !== browserCookies[name]) { 96 | //delete or reset all cookies that the browser dropped from $cookies 97 | if (isUndefined(browserCookies[name])) { 98 | delete cookies[name]; 99 | } else { 100 | cookies[name] = browserCookies[name]; 101 | } 102 | updated = true; 103 | } 104 | } 105 | } 106 | } 107 | }]). 108 | 109 | 110 | /** 111 | * @ngdoc object 112 | * @name ngCookies.$cookieStore 113 | * @requires $cookies 114 | * 115 | * @description 116 | * Provides a key-value (string-object) storage, that is backed by session cookies. 117 | * Objects put or retrieved from this storage are automatically serialized or 118 | * deserialized by angular's toJson/fromJson. 119 | * @example 120 | */ 121 | factory('$cookieStore', ['$cookies', function($cookies) { 122 | 123 | return { 124 | /** 125 | * @ngdoc method 126 | * @name ngCookies.$cookieStore#get 127 | * @methodOf ngCookies.$cookieStore 128 | * 129 | * @description 130 | * Returns the value of given cookie key 131 | * 132 | * @param {string} key Id to use for lookup. 133 | * @returns {Object} Deserialized cookie value. 134 | */ 135 | get: function(key) { 136 | return angular.fromJson($cookies[key]); 137 | }, 138 | 139 | /** 140 | * @ngdoc method 141 | * @name ngCookies.$cookieStore#put 142 | * @methodOf ngCookies.$cookieStore 143 | * 144 | * @description 145 | * Sets a value for given cookie key 146 | * 147 | * @param {string} key Id for the `value`. 148 | * @param {Object} value Value to be stored. 149 | */ 150 | put: function(key, value) { 151 | $cookies[key] = angular.toJson(value); 152 | }, 153 | 154 | /** 155 | * @ngdoc method 156 | * @name ngCookies.$cookieStore#remove 157 | * @methodOf ngCookies.$cookieStore 158 | * 159 | * @description 160 | * Remove given cookie 161 | * 162 | * @param {string} key Id of the key-value pair to delete. 163 | */ 164 | remove: function(key) { 165 | delete $cookies[key]; 166 | } 167 | }; 168 | 169 | }]); 170 | 171 | })(window, window.angular); 172 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.0 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,c){var b={},g={},h,i=!1,j=f.copy,k=f.isUndefined;c.addPollFn(function(){var a=c.cookies();h!=a&&(h=a,j(a,g),j(a,b),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(b[a])&&c.cookies(a,l);for(a in b)e=b[a],f.isString(e)?e!==g[a]&&(c.cookies(a,e),d=!0):f.isDefined(g[a])?b[a]=g[a]:delete b[a];if(d)for(a in e=c.cookies(),b)b[a]!==e[a]&&(k(e[a])?delete b[a]:b[a]=e[a])});return b}]).factory("$cookieStore", 7 | ["$cookies",function(d){return{get:function(c){return f.fromJson(d[c])},put:function(c,b){d[c]=f.toJson(b)},remove:function(c){delete d[c]}}}])})(window,window.angular); 8 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/angular-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.0 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | 7 | ( 8 | 9 | /** 10 | * @ngdoc interface 11 | * @name angular.Module 12 | * @description 13 | * 14 | * Interface for configuring angular {@link angular.module modules}. 15 | */ 16 | 17 | function setupModuleLoader(window) { 18 | 19 | function ensure(obj, name, factory) { 20 | return obj[name] || (obj[name] = factory()); 21 | } 22 | 23 | return ensure(ensure(window, 'angular', Object), 'module', function() { 24 | /** @type {Object.} */ 25 | var modules = {}; 26 | 27 | /** 28 | * @ngdoc function 29 | * @name angular.module 30 | * @description 31 | * 32 | * The `angular.module` is a global place for creating and registering Angular modules. All 33 | * modules (angular core or 3rd party) that should be available to an application must be 34 | * registered using this mechanism. 35 | * 36 | * 37 | * # Module 38 | * 39 | * A module is a collocation of services, directives, filters, and configure information. Module 40 | * is used to configure the {@link AUTO.$injector $injector}. 41 | * 42 | *
 43 |      * // Create a new module
 44 |      * var myModule = angular.module('myModule', []);
 45 |      *
 46 |      * // register a new service
 47 |      * myModule.value('appName', 'MyCoolApp');
 48 |      *
 49 |      * // configure existing services inside initialization blocks.
 50 |      * myModule.config(function($locationProvider) {
 51 | 'use strict';
 52 |      *   // Configure existing providers
 53 |      *   $locationProvider.hashPrefix('!');
 54 |      * });
 55 |      * 
56 | * 57 | * Then you can create an injector and load your modules like this: 58 | * 59 | *
 60 |      * var injector = angular.injector(['ng', 'MyModule'])
 61 |      * 
62 | * 63 | * However it's more likely that you'll just use 64 | * {@link ng.directive:ngApp ngApp} or 65 | * {@link angular.bootstrap} to simplify this process for you. 66 | * 67 | * @param {!string} name The name of the module to create or retrieve. 68 | * @param {Array.=} requires If specified then new module is being created. If unspecified then the 69 | * the module is being retrieved for further configuration. 70 | * @param {Function} configFn Option configuration function for the module. Same as 71 | * {@link angular.Module#config Module#config()}. 72 | * @returns {module} new module with the {@link angular.Module} api. 73 | */ 74 | return function module(name, requires, configFn) { 75 | if (requires && modules.hasOwnProperty(name)) { 76 | modules[name] = null; 77 | } 78 | return ensure(modules, name, function() { 79 | if (!requires) { 80 | throw Error('No module: ' + name); 81 | } 82 | 83 | /** @type {!Array.>} */ 84 | var invokeQueue = []; 85 | 86 | /** @type {!Array.} */ 87 | var runBlocks = []; 88 | 89 | var config = invokeLater('$injector', 'invoke'); 90 | 91 | /** @type {angular.Module} */ 92 | var moduleInstance = { 93 | // Private state 94 | _invokeQueue: invokeQueue, 95 | _runBlocks: runBlocks, 96 | 97 | /** 98 | * @ngdoc property 99 | * @name angular.Module#requires 100 | * @propertyOf angular.Module 101 | * @returns {Array.} List of module names which must be loaded before this module. 102 | * @description 103 | * Holds the list of modules which the injector will load before the current module is loaded. 104 | */ 105 | requires: requires, 106 | 107 | /** 108 | * @ngdoc property 109 | * @name angular.Module#name 110 | * @propertyOf angular.Module 111 | * @returns {string} Name of the module. 112 | * @description 113 | */ 114 | name: name, 115 | 116 | 117 | /** 118 | * @ngdoc method 119 | * @name angular.Module#provider 120 | * @methodOf angular.Module 121 | * @param {string} name service name 122 | * @param {Function} providerType Construction function for creating new instance of the service. 123 | * @description 124 | * See {@link AUTO.$provide#provider $provide.provider()}. 125 | */ 126 | provider: invokeLater('$provide', 'provider'), 127 | 128 | /** 129 | * @ngdoc method 130 | * @name angular.Module#factory 131 | * @methodOf angular.Module 132 | * @param {string} name service name 133 | * @param {Function} providerFunction Function for creating new instance of the service. 134 | * @description 135 | * See {@link AUTO.$provide#factory $provide.factory()}. 136 | */ 137 | factory: invokeLater('$provide', 'factory'), 138 | 139 | /** 140 | * @ngdoc method 141 | * @name angular.Module#service 142 | * @methodOf angular.Module 143 | * @param {string} name service name 144 | * @param {Function} constructor A constructor function that will be instantiated. 145 | * @description 146 | * See {@link AUTO.$provide#service $provide.service()}. 147 | */ 148 | service: invokeLater('$provide', 'service'), 149 | 150 | /** 151 | * @ngdoc method 152 | * @name angular.Module#value 153 | * @methodOf angular.Module 154 | * @param {string} name service name 155 | * @param {*} object Service instance object. 156 | * @description 157 | * See {@link AUTO.$provide#value $provide.value()}. 158 | */ 159 | value: invokeLater('$provide', 'value'), 160 | 161 | /** 162 | * @ngdoc method 163 | * @name angular.Module#constant 164 | * @methodOf angular.Module 165 | * @param {string} name constant name 166 | * @param {*} object Constant value. 167 | * @description 168 | * Because the constant are fixed, they get applied before other provide methods. 169 | * See {@link AUTO.$provide#constant $provide.constant()}. 170 | */ 171 | constant: invokeLater('$provide', 'constant', 'unshift'), 172 | 173 | /** 174 | * @ngdoc method 175 | * @name angular.Module#filter 176 | * @methodOf angular.Module 177 | * @param {string} name Filter name. 178 | * @param {Function} filterFactory Factory function for creating new instance of filter. 179 | * @description 180 | * See {@link ng.$filterProvider#register $filterProvider.register()}. 181 | */ 182 | filter: invokeLater('$filterProvider', 'register'), 183 | 184 | /** 185 | * @ngdoc method 186 | * @name angular.Module#controller 187 | * @methodOf angular.Module 188 | * @param {string} name Controller name. 189 | * @param {Function} constructor Controller constructor function. 190 | * @description 191 | * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 192 | */ 193 | controller: invokeLater('$controllerProvider', 'register'), 194 | 195 | /** 196 | * @ngdoc method 197 | * @name angular.Module#directive 198 | * @methodOf angular.Module 199 | * @param {string} name directive name 200 | * @param {Function} directiveFactory Factory function for creating new instance of 201 | * directives. 202 | * @description 203 | * See {@link ng.$compileProvider.directive $compileProvider.directive()}. 204 | */ 205 | directive: invokeLater('$compileProvider', 'directive'), 206 | 207 | /** 208 | * @ngdoc method 209 | * @name angular.Module#config 210 | * @methodOf angular.Module 211 | * @param {Function} configFn Execute this function on module load. Useful for service 212 | * configuration. 213 | * @description 214 | * Use this method to register work which needs to be performed on module loading. 215 | */ 216 | config: config, 217 | 218 | /** 219 | * @ngdoc method 220 | * @name angular.Module#run 221 | * @methodOf angular.Module 222 | * @param {Function} initializationFn Execute this function after injector creation. 223 | * Useful for application initialization. 224 | * @description 225 | * Use this method to register work which needs to be performed when the injector with 226 | * with the current module is finished loading. 227 | */ 228 | run: function(block) { 229 | runBlocks.push(block); 230 | return this; 231 | } 232 | }; 233 | 234 | if (configFn) { 235 | config(configFn); 236 | } 237 | 238 | return moduleInstance; 239 | 240 | /** 241 | * @param {string} provider 242 | * @param {string} method 243 | * @param {String=} insertMethod 244 | * @returns {angular.Module} 245 | */ 246 | function invokeLater(provider, method, insertMethod) { 247 | return function() { 248 | invokeQueue[insertMethod || 'push']([provider, method, arguments]); 249 | return moduleInstance; 250 | } 251 | } 252 | }); 253 | }; 254 | }); 255 | 256 | } 257 | )(window); 258 | 259 | /** 260 | * Closure compiler type information 261 | * 262 | * @typedef { { 263 | * requires: !Array., 264 | * invokeQueue: !Array.>, 265 | * 266 | * service: function(string, Function):angular.Module, 267 | * factory: function(string, Function):angular.Module, 268 | * value: function(string, *):angular.Module, 269 | * 270 | * filter: function(string, Function):angular.Module, 271 | * 272 | * init: function(Function):angular.Module 273 | * } } 274 | */ 275 | angular.Module; 276 | 277 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/angular-loader.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.0 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(i){'use strict';function d(c,b,e){return c[b]||(c[b]=e())}return d(d(i,"angular",Object),"module",function(){var c={};return function(b,e,f){e&&c.hasOwnProperty(b)&&(c[b]=null);return d(c,b,function(){function a(a,b,d){return function(){c[d||"push"]([a,b,arguments]);return g}}if(!e)throw Error("No module: "+b);var c=[],d=[],h=a("$injector","invoke"),g={_invokeQueue:c,_runBlocks:d,requires:e,name:b,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"), 7 | value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:h,run:function(a){d.push(a);return this}};f&&h(f);return g})}})})(window); 8 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/angular-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.0 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngResource 12 | * @description 13 | */ 14 | 15 | /** 16 | * @ngdoc object 17 | * @name ngResource.$resource 18 | * @requires $http 19 | * 20 | * @description 21 | * A factory which creates a resource object that lets you interact with 22 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 23 | * 24 | * The returned resource object has action methods which provide high-level behaviors without 25 | * the need to interact with the low level {@link ng.$http $http} service. 26 | * 27 | * @param {string} url A parameterized URL template with parameters prefixed by `:` as in 28 | * `/user/:username`. 29 | * 30 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 31 | * `actions` methods. 32 | * 33 | * Each key value in the parameter object is first bound to url template if present and then any 34 | * excess keys are appended to the url search query after the `?`. 35 | * 36 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 37 | * URL `/path/greet?salutation=Hello`. 38 | * 39 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 40 | * the data object (useful for non-GET operations). 41 | * 42 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 43 | * default set of resource actions. The declaration should be created in the following format: 44 | * 45 | * {action1: {method:?, params:?, isArray:?}, 46 | * action2: {method:?, params:?, isArray:?}, 47 | * ...} 48 | * 49 | * Where: 50 | * 51 | * - `action` – {string} – The name of action. This name becomes the name of the method on your 52 | * resource object. 53 | * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 54 | * and `JSONP` 55 | * - `params` – {object=} – Optional set of pre-bound parameters for this action. 56 | * - isArray – {boolean=} – If true then the returned object for this action is an array, see 57 | * `returns` section. 58 | * 59 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 60 | * optionally extended with custom `actions`. The default set contains these actions: 61 | * 62 | * { 'get': {method:'GET'}, 63 | * 'save': {method:'POST'}, 64 | * 'query': {method:'GET', isArray:true}, 65 | * 'remove': {method:'DELETE'}, 66 | * 'delete': {method:'DELETE'} }; 67 | * 68 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 69 | * destination and parameters. When the data is returned from the server then the object is an 70 | * instance of the resource class `save`, `remove` and `delete` actions are available on it as 71 | * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read, 72 | * update, delete) on server-side data like this: 73 | *
 74 |         var User = $resource('/user/:userId', {userId:'@id'});
 75 |         var user = User.get({userId:123}, function() {
 76 |           user.abc = true;
 77 |           user.$save();
 78 |         });
 79 |      
80 | * 81 | * It is important to realize that invoking a $resource object method immediately returns an 82 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 83 | * server the existing reference is populated with the actual data. This is a useful trick since 84 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 85 | * object results in no rendering, once the data arrives from the server then the object is 86 | * populated with the data and the view automatically re-renders itself showing the new data. This 87 | * means that in most case one never has to write a callback function for the action methods. 88 | * 89 | * The action methods on the class object or instance object can be invoked with the following 90 | * parameters: 91 | * 92 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 93 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 94 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 95 | * 96 | * 97 | * @example 98 | * 99 | * # Credit card resource 100 | * 101 | *
102 |      // Define CreditCard class
103 |      var CreditCard = $resource('/user/:userId/card/:cardId',
104 |       {userId:123, cardId:'@id'}, {
105 |        charge: {method:'POST', params:{charge:true}}
106 |       });
107 | 
108 |      // We can retrieve a collection from the server
109 |      var cards = CreditCard.query(function() {
110 |        // GET: /user/123/card
111 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
112 | 
113 |        var card = cards[0];
114 |        // each item is an instance of CreditCard
115 |        expect(card instanceof CreditCard).toEqual(true);
116 |        card.name = "J. Smith";
117 |        // non GET methods are mapped onto the instances
118 |        card.$save();
119 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
120 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
121 | 
122 |        // our custom method is mapped as well.
123 |        card.$charge({amount:9.99});
124 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
125 |      });
126 | 
127 |      // we can create an instance as well
128 |      var newCard = new CreditCard({number:'0123'});
129 |      newCard.name = "Mike Smith";
130 |      newCard.$save();
131 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
132 |      // server returns: {id:789, number:'01234', name: 'Mike Smith'};
133 |      expect(newCard.id).toEqual(789);
134 |  * 
135 | * 136 | * The object returned from this function execution is a resource "class" which has "static" method 137 | * for each action in the definition. 138 | * 139 | * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`. 140 | * When the data is returned from the server then the object is an instance of the resource type and 141 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 142 | * operations (create, read, update, delete) on server-side data. 143 | 144 |
145 |      var User = $resource('/user/:userId', {userId:'@id'});
146 |      var user = User.get({userId:123}, function() {
147 |        user.abc = true;
148 |        user.$save();
149 |      });
150 |    
151 | * 152 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 153 | * in the response that came from the server as well as $http header getter function, so one 154 | * could rewrite the above example and get access to http headers as: 155 | * 156 |
157 |      var User = $resource('/user/:userId', {userId:'@id'});
158 |      User.get({userId:123}, function(u, getResponseHeaders){
159 |        u.abc = true;
160 |        u.$save(function(u, putResponseHeaders) {
161 |          //u => saved user object
162 |          //putResponseHeaders => $http header getter
163 |        });
164 |      });
165 |    
166 | 167 | * # Buzz client 168 | 169 | Let's look at what a buzz client created with the `$resource` service looks like: 170 | 171 | 172 | 192 | 193 |
194 | 195 | 196 |
197 |
198 |

199 | 200 | {{item.actor.name}} 201 | Expand replies: {{item.links.replies[0].count}} 202 |

203 | {{item.object.content | html}} 204 |
205 | 206 | {{reply.actor.name}}: {{reply.content | html}} 207 |
208 |
209 |
210 |
211 | 212 | 213 |
214 | */ 215 | angular.module('ngResource', ['ng']). 216 | factory('$resource', ['$http', '$parse', function($http, $parse) { 217 | var DEFAULT_ACTIONS = { 218 | 'get': {method:'GET'}, 219 | 'save': {method:'POST'}, 220 | 'query': {method:'GET', isArray:true}, 221 | 'remove': {method:'DELETE'}, 222 | 'delete': {method:'DELETE'} 223 | }; 224 | var noop = angular.noop, 225 | forEach = angular.forEach, 226 | extend = angular.extend, 227 | copy = angular.copy, 228 | isFunction = angular.isFunction, 229 | getter = function(obj, path) { 230 | return $parse(path)(obj); 231 | }; 232 | 233 | /** 234 | * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow 235 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 236 | * segments: 237 | * segment = *pchar 238 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 239 | * pct-encoded = "%" HEXDIG HEXDIG 240 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 241 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 242 | * / "*" / "+" / "," / ";" / "=" 243 | */ 244 | function encodeUriSegment(val) { 245 | return encodeUriQuery(val, true). 246 | replace(/%26/gi, '&'). 247 | replace(/%3D/gi, '='). 248 | replace(/%2B/gi, '+'); 249 | } 250 | 251 | 252 | /** 253 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 254 | * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be 255 | * encoded per http://tools.ietf.org/html/rfc3986: 256 | * query = *( pchar / "/" / "?" ) 257 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 258 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 259 | * pct-encoded = "%" HEXDIG HEXDIG 260 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 261 | * / "*" / "+" / "," / ";" / "=" 262 | */ 263 | function encodeUriQuery(val, pctEncodeSpaces) { 264 | return encodeURIComponent(val). 265 | replace(/%40/gi, '@'). 266 | replace(/%3A/gi, ':'). 267 | replace(/%24/g, '$'). 268 | replace(/%2C/gi, ','). 269 | replace((pctEncodeSpaces ? null : /%20/g), '+'); 270 | } 271 | 272 | function Route(template, defaults) { 273 | this.template = template = template + '#'; 274 | this.defaults = defaults || {}; 275 | var urlParams = this.urlParams = {}; 276 | forEach(template.split(/\W/), function(param){ 277 | if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) { 278 | urlParams[param] = true; 279 | } 280 | }); 281 | this.template = template.replace(/\\:/g, ':'); 282 | } 283 | 284 | Route.prototype = { 285 | url: function(params) { 286 | var self = this, 287 | url = this.template, 288 | encodedVal; 289 | 290 | params = params || {}; 291 | forEach(this.urlParams, function(_, urlParam){ 292 | encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || ""); 293 | url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1"); 294 | }); 295 | url = url.replace(/\/?#$/, ''); 296 | var query = []; 297 | forEach(params, function(value, key){ 298 | if (!self.urlParams[key]) { 299 | query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); 300 | } 301 | }); 302 | query.sort(); 303 | url = url.replace(/\/*$/, ''); 304 | return url + (query.length ? '?' + query.join('&') : ''); 305 | } 306 | }; 307 | 308 | 309 | function ResourceFactory(url, paramDefaults, actions) { 310 | var route = new Route(url); 311 | 312 | actions = extend({}, DEFAULT_ACTIONS, actions); 313 | 314 | function extractParams(data){ 315 | var ids = {}; 316 | forEach(paramDefaults || {}, function(value, key){ 317 | ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 318 | }); 319 | return ids; 320 | } 321 | 322 | function Resource(value){ 323 | copy(value || {}, this); 324 | } 325 | 326 | forEach(actions, function(action, name) { 327 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 328 | Resource[name] = function(a1, a2, a3, a4) { 329 | var params = {}; 330 | var data; 331 | var success = noop; 332 | var error = null; 333 | switch(arguments.length) { 334 | case 4: 335 | error = a4; 336 | success = a3; 337 | //fallthrough 338 | case 3: 339 | case 2: 340 | if (isFunction(a2)) { 341 | if (isFunction(a1)) { 342 | success = a1; 343 | error = a2; 344 | break; 345 | } 346 | 347 | success = a2; 348 | error = a3; 349 | //fallthrough 350 | } else { 351 | params = a1; 352 | data = a2; 353 | success = a3; 354 | break; 355 | } 356 | case 1: 357 | if (isFunction(a1)) success = a1; 358 | else if (hasBody) data = a1; 359 | else params = a1; 360 | break; 361 | case 0: break; 362 | default: 363 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 364 | arguments.length + " arguments."; 365 | } 366 | 367 | var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); 368 | $http({ 369 | method: action.method, 370 | url: route.url(extend({}, extractParams(data), action.params || {}, params)), 371 | data: data 372 | }).then(function(response) { 373 | var data = response.data; 374 | 375 | if (data) { 376 | if (action.isArray) { 377 | value.length = 0; 378 | forEach(data, function(item) { 379 | value.push(new Resource(item)); 380 | }); 381 | } else { 382 | copy(data, value); 383 | } 384 | } 385 | (success||noop)(value, response.headers); 386 | }, error); 387 | 388 | return value; 389 | }; 390 | 391 | 392 | Resource.bind = function(additionalParamDefaults){ 393 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 394 | }; 395 | 396 | 397 | Resource.prototype['$' + name] = function(a1, a2, a3) { 398 | var params = extractParams(this), 399 | success = noop, 400 | error; 401 | 402 | switch(arguments.length) { 403 | case 3: params = a1; success = a2; error = a3; break; 404 | case 2: 405 | case 1: 406 | if (isFunction(a1)) { 407 | success = a1; 408 | error = a2; 409 | } else { 410 | params = a1; 411 | success = a2 || noop; 412 | } 413 | case 0: break; 414 | default: 415 | throw "Expected between 1-3 arguments [params, success, error], got " + 416 | arguments.length + " arguments."; 417 | } 418 | var data = hasBody ? this : undefined; 419 | Resource[name].call(this, params, data, success, error); 420 | }; 421 | }); 422 | return Resource; 423 | } 424 | 425 | return ResourceFactory; 426 | }]); 427 | 428 | })(window, window.angular); 429 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.0 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(A,f,u){'use strict';f.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(v,w){function g(b,c){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(c?null:/%20/g,"+")}function l(b,c){this.template=b+="#";this.defaults=c||{};var a=this.urlParams={};j(b.split(/\W/),function(c){c&&b.match(RegExp("[^\\\\]:"+c+"\\W"))&&(a[c]=!0)});this.template=b.replace(/\\:/g,":")}function s(b,c,a){function f(d){var b= 7 | {};j(c||{},function(a,x){var m;a.charAt&&a.charAt(0)=="@"?(m=a.substr(1),m=w(m)(d)):m=a;b[x]=m});return b}function e(a){t(a||{},this)}var y=new l(b),a=r({},z,a);j(a,function(d,g){var l=d.method=="POST"||d.method=="PUT"||d.method=="PATCH";e[g]=function(a,b,c,g){var i={},h,k=o,p=null;switch(arguments.length){case 4:p=g,k=c;case 3:case 2:if(q(b)){if(q(a)){k=a;p=b;break}k=b;p=c}else{i=a;h=b;k=c;break}case 1:q(a)?k=a:l?h=a:i=a;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+ 8 | arguments.length+" arguments.";}var n=this instanceof e?this:d.isArray?[]:new e(h);v({method:d.method,url:y.url(r({},f(h),d.params||{},i)),data:h}).then(function(a){var b=a.data;if(b)d.isArray?(n.length=0,j(b,function(a){n.push(new e(a))})):t(b,n);(k||o)(n,a.headers)},p);return n};e.bind=function(d){return s(b,r({},c,d),a)};e.prototype["$"+g]=function(a,b,d){var c=f(this),i=o,h;switch(arguments.length){case 3:c=a;i=b;h=d;break;case 2:case 1:q(a)?(i=a,h=b):(c=a,i=b||o);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+ 9 | arguments.length+" arguments.";}e[g].call(this,c,l?this:u,i,h)}});return e}var z={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},o=f.noop,j=f.forEach,r=f.extend,t=f.copy,q=f.isFunction;l.prototype={url:function(b){var c=this,a=this.template,f,b=b||{};j(this.urlParams,function(e,d){f=g(b[d]||c.defaults[d]||"",!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+");a=a.replace(RegExp(":"+d+"(\\W)"),f+"$1")});var a= 10 | a.replace(/\/?#$/,""),e=[];j(b,function(a,b){c.urlParams[b]||e.push(g(b)+"="+g(a))});e.sort();a=a.replace(/\/*$/,"");return a+(e.length?"?"+e.join("&"):"")}};return s}])})(window,window.angular); 11 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/angular-sanitize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.0 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngSanitize 12 | * @description 13 | */ 14 | 15 | /* 16 | * HTML Parser By Misko Hevery (misko@hevery.com) 17 | * based on: HTML Parser By John Resig (ejohn.org) 18 | * Original code by Erik Arvidsson, Mozilla Public License 19 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 20 | * 21 | * // Use like so: 22 | * htmlParser(htmlString, { 23 | * start: function(tag, attrs, unary) {}, 24 | * end: function(tag) {}, 25 | * chars: function(text) {}, 26 | * comment: function(text) {} 27 | * }); 28 | * 29 | */ 30 | 31 | 32 | /** 33 | * @ngdoc service 34 | * @name ngSanitize.$sanitize 35 | * @function 36 | * 37 | * @description 38 | * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are 39 | * then serialized back to properly escaped html string. This means that no unsafe input can make 40 | * it into the returned string, however, since our parser is more strict than a typical browser 41 | * parser, it's possible that some obscure input, which would be recognized as valid HTML by a 42 | * browser, won't make it through the sanitizer. 43 | * 44 | * @param {string} html Html input. 45 | * @returns {string} Sanitized html. 46 | * 47 | * @example 48 | 49 | 50 | 58 |
59 | Snippet: 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
FilterSourceRendered
html filter 69 |
<div ng-bind-html="snippet">
</div>
70 |
72 |
73 |
no filter
<div ng-bind="snippet">
</div>
unsafe html filter
<div ng-bind-html-unsafe="snippet">
</div>
86 |
87 |
88 | 89 | it('should sanitize the html snippet ', function() { 90 | expect(using('#html-filter').element('div').html()). 91 | toBe('

an html\nclick here\nsnippet

'); 92 | }); 93 | 94 | it('should escape snippet without any filter', function() { 95 | expect(using('#escaped-html').element('div').html()). 96 | toBe("<p style=\"color:blue\">an html\n" + 97 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 98 | "snippet</p>"); 99 | }); 100 | 101 | it('should inline raw snippet if filtered as unsafe', function() { 102 | expect(using('#html-unsafe-filter').element("div").html()). 103 | toBe("

an html\n" + 104 | "click here\n" + 105 | "snippet

"); 106 | }); 107 | 108 | it('should update', function() { 109 | input('snippet').enter('new text'); 110 | expect(using('#html-filter').binding('snippet')).toBe('new text'); 111 | expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>"); 112 | expect(using('#html-unsafe-filter').binding("snippet")).toBe('new text'); 113 | }); 114 |
115 |
116 | */ 117 | var $sanitize = function(html) { 118 | var buf = []; 119 | htmlParser(html, htmlSanitizeWriter(buf)); 120 | return buf.join(''); 121 | }; 122 | 123 | 124 | // Regular Expressions for parsing tags and attributes 125 | var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, 126 | END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, 127 | ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 128 | BEGIN_TAG_REGEXP = /^/g, 131 | CDATA_REGEXP = //g, 132 | URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/, 133 | NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character) 134 | 135 | 136 | // Good source of info about elements and attributes 137 | // http://dev.w3.org/html5/spec/Overview.html#semantics 138 | // http://simon.html5.org/html-elements 139 | 140 | // Safe Void Elements - HTML5 141 | // http://dev.w3.org/html5/spec/Overview.html#void-elements 142 | var voidElements = makeMap("area,br,col,hr,img,wbr"); 143 | 144 | // Elements that you can, intentionally, leave open (and which close themselves) 145 | // http://dev.w3.org/html5/spec/Overview.html#optional-tags 146 | var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), 147 | optionalEndTagInlineElements = makeMap("rp,rt"), 148 | optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements); 149 | 150 | // Safe Block Elements - HTML5 151 | var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," + 152 | "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," + 153 | "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); 154 | 155 | // Inline Elements - HTML5 156 | var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," + 157 | "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," + 158 | "span,strike,strong,sub,sup,time,tt,u,var")); 159 | 160 | 161 | // Special Elements (can contain anything) 162 | var specialElements = makeMap("script,style"); 163 | 164 | var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements); 165 | 166 | //Attributes that have href and hence need to be sanitized 167 | var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); 168 | var validAttrs = angular.extend({}, uriAttrs, makeMap( 169 | 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ 170 | 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ 171 | 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ 172 | 'scope,scrolling,shape,span,start,summary,target,title,type,'+ 173 | 'valign,value,vspace,width')); 174 | 175 | function makeMap(str) { 176 | var obj = {}, items = str.split(','), i; 177 | for (i = 0; i < items.length; i++) obj[items[i]] = true; 178 | return obj; 179 | } 180 | 181 | 182 | /** 183 | * @example 184 | * htmlParser(htmlString, { 185 | * start: function(tag, attrs, unary) {}, 186 | * end: function(tag) {}, 187 | * chars: function(text) {}, 188 | * comment: function(text) {} 189 | * }); 190 | * 191 | * @param {string} html string 192 | * @param {object} handler 193 | */ 194 | function htmlParser( html, handler ) { 195 | var index, chars, match, stack = [], last = html; 196 | stack.last = function() { return stack[ stack.length - 1 ]; }; 197 | 198 | while ( html ) { 199 | chars = true; 200 | 201 | // Make sure we're not in a script or style element 202 | if ( !stack.last() || !specialElements[ stack.last() ] ) { 203 | 204 | // Comment 205 | if ( html.indexOf(""); 207 | 208 | if ( index >= 0 ) { 209 | if (handler.comment) handler.comment( html.substring( 4, index ) ); 210 | html = html.substring( index + 3 ); 211 | chars = false; 212 | } 213 | 214 | // end tag 215 | } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { 216 | match = html.match( END_TAG_REGEXP ); 217 | 218 | if ( match ) { 219 | html = html.substring( match[0].length ); 220 | match[0].replace( END_TAG_REGEXP, parseEndTag ); 221 | chars = false; 222 | } 223 | 224 | // start tag 225 | } else if ( BEGIN_TAG_REGEXP.test(html) ) { 226 | match = html.match( START_TAG_REGEXP ); 227 | 228 | if ( match ) { 229 | html = html.substring( match[0].length ); 230 | match[0].replace( START_TAG_REGEXP, parseStartTag ); 231 | chars = false; 232 | } 233 | } 234 | 235 | if ( chars ) { 236 | index = html.indexOf("<"); 237 | 238 | var text = index < 0 ? html : html.substring( 0, index ); 239 | html = index < 0 ? "" : html.substring( index ); 240 | 241 | if (handler.chars) handler.chars( decodeEntities(text) ); 242 | } 243 | 244 | } else { 245 | html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){ 246 | text = text. 247 | replace(COMMENT_REGEXP, "$1"). 248 | replace(CDATA_REGEXP, "$1"); 249 | 250 | if (handler.chars) handler.chars( decodeEntities(text) ); 251 | 252 | return ""; 253 | }); 254 | 255 | parseEndTag( "", stack.last() ); 256 | } 257 | 258 | if ( html == last ) { 259 | throw "Parse Error: " + html; 260 | } 261 | last = html; 262 | } 263 | 264 | // Clean up any remaining tags 265 | parseEndTag(); 266 | 267 | function parseStartTag( tag, tagName, rest, unary ) { 268 | tagName = angular.lowercase(tagName); 269 | if ( blockElements[ tagName ] ) { 270 | while ( stack.last() && inlineElements[ stack.last() ] ) { 271 | parseEndTag( "", stack.last() ); 272 | } 273 | } 274 | 275 | if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { 276 | parseEndTag( "", tagName ); 277 | } 278 | 279 | unary = voidElements[ tagName ] || !!unary; 280 | 281 | if ( !unary ) 282 | stack.push( tagName ); 283 | 284 | var attrs = {}; 285 | 286 | rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) { 287 | var value = doubleQuotedValue 288 | || singleQoutedValue 289 | || unqoutedValue 290 | || ''; 291 | 292 | attrs[name] = decodeEntities(value); 293 | }); 294 | if (handler.start) handler.start( tagName, attrs, unary ); 295 | } 296 | 297 | function parseEndTag( tag, tagName ) { 298 | var pos = 0, i; 299 | tagName = angular.lowercase(tagName); 300 | if ( tagName ) 301 | // Find the closest opened tag of the same type 302 | for ( pos = stack.length - 1; pos >= 0; pos-- ) 303 | if ( stack[ pos ] == tagName ) 304 | break; 305 | 306 | if ( pos >= 0 ) { 307 | // Close all the open elements, up the stack 308 | for ( i = stack.length - 1; i >= pos; i-- ) 309 | if (handler.end) handler.end( stack[ i ] ); 310 | 311 | // Remove the open elements from the stack 312 | stack.length = pos; 313 | } 314 | } 315 | } 316 | 317 | /** 318 | * decodes all entities into regular string 319 | * @param value 320 | * @returns {string} A string with decoded entities. 321 | */ 322 | var hiddenPre=document.createElement("pre"); 323 | function decodeEntities(value) { 324 | hiddenPre.innerHTML=value.replace(//g, '>'); 343 | } 344 | 345 | /** 346 | * create an HTML/XML writer which writes to buffer 347 | * @param {Array} buf use buf.jain('') to get out sanitized html string 348 | * @returns {object} in the form of { 349 | * start: function(tag, attrs, unary) {}, 350 | * end: function(tag) {}, 351 | * chars: function(text) {}, 352 | * comment: function(text) {} 353 | * } 354 | */ 355 | function htmlSanitizeWriter(buf){ 356 | var ignore = false; 357 | var out = angular.bind(buf, buf.push); 358 | return { 359 | start: function(tag, attrs, unary){ 360 | tag = angular.lowercase(tag); 361 | if (!ignore && specialElements[tag]) { 362 | ignore = tag; 363 | } 364 | if (!ignore && validElements[tag] == true) { 365 | out('<'); 366 | out(tag); 367 | angular.forEach(attrs, function(value, key){ 368 | var lkey=angular.lowercase(key); 369 | if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { 370 | out(' '); 371 | out(key); 372 | out('="'); 373 | out(encodeEntities(value)); 374 | out('"'); 375 | } 376 | }); 377 | out(unary ? '/>' : '>'); 378 | } 379 | }, 380 | end: function(tag){ 381 | tag = angular.lowercase(tag); 382 | if (!ignore && validElements[tag] == true) { 383 | out(''); 386 | } 387 | if (tag == ignore) { 388 | ignore = false; 389 | } 390 | }, 391 | chars: function(chars){ 392 | if (!ignore) { 393 | out(encodeEntities(chars)); 394 | } 395 | } 396 | }; 397 | } 398 | 399 | 400 | // define ngSanitize module and register $sanitize service 401 | angular.module('ngSanitize', []).value('$sanitize', $sanitize); 402 | 403 | /** 404 | * @ngdoc directive 405 | * @name ngSanitize.directive:ngBindHtml 406 | * 407 | * @description 408 | * Creates a binding that will sanitize the result of evaluating the `expression` with the 409 | * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element. 410 | * 411 | * See {@link ngSanitize.$sanitize $sanitize} docs for examples. 412 | * 413 | * @element ANY 414 | * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 415 | */ 416 | angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) { 417 | return function(scope, element, attr) { 418 | element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 419 | scope.$watch(attr.ngBindHtml, function(value) { 420 | value = $sanitize(value); 421 | element.html(value || ''); 422 | }); 423 | }; 424 | }]); 425 | /** 426 | * @ngdoc filter 427 | * @name ngSanitize.filter:linky 428 | * @function 429 | * 430 | * @description 431 | * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 432 | * plain email address links. 433 | * 434 | * @param {string} text Input text. 435 | * @returns {string} Html-linkified text. 436 | * 437 | * @example 438 | 439 | 440 | 450 |
451 | Snippet: 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 463 | 466 | 467 | 468 | 469 | 470 | 471 | 472 |
FilterSourceRendered
linky filter 461 |
<div ng-bind-html="snippet | linky">
</div>
462 |
464 |
465 |
no filter
<div ng-bind="snippet">
</div>
473 | 474 | 475 | it('should linkify the snippet with urls', function() { 476 | expect(using('#linky-filter').binding('snippet | linky')). 477 | toBe('Pretty text with some links: ' + 478 | 'http://angularjs.org/, ' + 479 | 'us@somewhere.org, ' + 480 | 'another@somewhere.org, ' + 481 | 'and one more: ftp://127.0.0.1/.'); 482 | }); 483 | 484 | it ('should not linkify snippet without the linky filter', function() { 485 | expect(using('#escaped-html').binding('snippet')). 486 | toBe("Pretty text with some links:\n" + 487 | "http://angularjs.org/,\n" + 488 | "mailto:us@somewhere.org,\n" + 489 | "another@somewhere.org,\n" + 490 | "and one more: ftp://127.0.0.1/."); 491 | }); 492 | 493 | it('should update', function() { 494 | input('snippet').enter('new http://link.'); 495 | expect(using('#linky-filter').binding('snippet | linky')). 496 | toBe('new http://link.'); 497 | expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); 498 | }); 499 | 500 | 501 | */ 502 | angular.module('ngSanitize').filter('linky', function() { 503 | var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/, 504 | MAILTO_REGEXP = /^mailto:/; 505 | 506 | return function(text) { 507 | if (!text) return text; 508 | var match; 509 | var raw = text; 510 | var html = []; 511 | // TODO(vojta): use $sanitize instead 512 | var writer = htmlSanitizeWriter(html); 513 | var url; 514 | var i; 515 | while ((match = raw.match(LINKY_URL_REGEXP))) { 516 | // We can not end in these as they are sometimes found at the end of the sentence 517 | url = match[0]; 518 | // if we did not match ftp/http/mailto then assume mailto 519 | if (match[2] == match[3]) url = 'mailto:' + url; 520 | i = match.index; 521 | writer.chars(raw.substr(0, i)); 522 | writer.start('a', {href:url}); 523 | writer.chars(match[0].replace(MAILTO_REGEXP, '')); 524 | writer.end('a'); 525 | raw = raw.substring(i + match[0].length); 526 | } 527 | writer.chars(raw); 528 | return html.join(''); 529 | }; 530 | }); 531 | 532 | })(window, window.angular); 533 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.0 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length= 7 | e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+ 8 | f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(//g,">")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]== 9 | !0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b(""));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^/g, 10 | E=//g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), 11 | q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[]; 12 | z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index, 13 | h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular); 14 | -------------------------------------------------------------------------------- /app/public/js/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /app/public/js/lib/bootstrap/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'

'}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery); -------------------------------------------------------------------------------- /app/public/js/lib/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.3 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | 9 | (function() { 10 | 11 | // Baseline setup 12 | // -------------- 13 | 14 | // Establish the root object, `window` in the browser, or `global` on the server. 15 | var root = this; 16 | 17 | // Save the previous value of the `_` variable. 18 | var previousUnderscore = root._; 19 | 20 | // Establish the object that gets returned to break out of a loop iteration. 21 | var breaker = {}; 22 | 23 | // Save bytes in the minified (but not gzipped) version: 24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 25 | 26 | // Create quick reference variables for speed access to core prototypes. 27 | var slice = ArrayProto.slice, 28 | unshift = ArrayProto.unshift, 29 | toString = ObjProto.toString, 30 | hasOwnProperty = ObjProto.hasOwnProperty; 31 | 32 | // All **ECMAScript 5** native function implementations that we hope to use 33 | // are declared here. 34 | var 35 | nativeForEach = ArrayProto.forEach, 36 | nativeMap = ArrayProto.map, 37 | nativeReduce = ArrayProto.reduce, 38 | nativeReduceRight = ArrayProto.reduceRight, 39 | nativeFilter = ArrayProto.filter, 40 | nativeEvery = ArrayProto.every, 41 | nativeSome = ArrayProto.some, 42 | nativeIndexOf = ArrayProto.indexOf, 43 | nativeLastIndexOf = ArrayProto.lastIndexOf, 44 | nativeIsArray = Array.isArray, 45 | nativeKeys = Object.keys, 46 | nativeBind = FuncProto.bind; 47 | 48 | // Create a safe reference to the Underscore object for use below. 49 | var _ = function(obj) { return new wrapper(obj); }; 50 | 51 | // Export the Underscore object for **Node.js**, with 52 | // backwards-compatibility for the old `require()` API. If we're in 53 | // the browser, add `_` as a global object via a string identifier, 54 | // for Closure Compiler "advanced" mode. 55 | if (typeof exports !== 'undefined') { 56 | if (typeof module !== 'undefined' && module.exports) { 57 | exports = module.exports = _; 58 | } 59 | exports._ = _; 60 | } else { 61 | root['_'] = _; 62 | } 63 | 64 | // Current version. 65 | _.VERSION = '1.3.3'; 66 | 67 | // Collection Functions 68 | // -------------------- 69 | 70 | // The cornerstone, an `each` implementation, aka `forEach`. 71 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 72 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 73 | var each = _.each = _.forEach = function(obj, iterator, context) { 74 | if (obj == null) return; 75 | if (nativeForEach && obj.forEach === nativeForEach) { 76 | obj.forEach(iterator, context); 77 | } else if (obj.length === +obj.length) { 78 | for (var i = 0, l = obj.length; i < l; i++) { 79 | if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; 80 | } 81 | } else { 82 | for (var key in obj) { 83 | if (_.has(obj, key)) { 84 | if (iterator.call(context, obj[key], key, obj) === breaker) return; 85 | } 86 | } 87 | } 88 | }; 89 | 90 | // Return the results of applying the iterator to each element. 91 | // Delegates to **ECMAScript 5**'s native `map` if available. 92 | _.map = _.collect = function(obj, iterator, context) { 93 | var results = []; 94 | if (obj == null) return results; 95 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 96 | each(obj, function(value, index, list) { 97 | results[results.length] = iterator.call(context, value, index, list); 98 | }); 99 | if (obj.length === +obj.length) results.length = obj.length; 100 | return results; 101 | }; 102 | 103 | // **Reduce** builds up a single result from a list of values, aka `inject`, 104 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 105 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 106 | var initial = arguments.length > 2; 107 | if (obj == null) obj = []; 108 | if (nativeReduce && obj.reduce === nativeReduce) { 109 | if (context) iterator = _.bind(iterator, context); 110 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 111 | } 112 | each(obj, function(value, index, list) { 113 | if (!initial) { 114 | memo = value; 115 | initial = true; 116 | } else { 117 | memo = iterator.call(context, memo, value, index, list); 118 | } 119 | }); 120 | if (!initial) throw new TypeError('Reduce of empty array with no initial value'); 121 | return memo; 122 | }; 123 | 124 | // The right-associative version of reduce, also known as `foldr`. 125 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 126 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 127 | var initial = arguments.length > 2; 128 | if (obj == null) obj = []; 129 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 130 | if (context) iterator = _.bind(iterator, context); 131 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 132 | } 133 | var reversed = _.toArray(obj).reverse(); 134 | if (context && !initial) iterator = _.bind(iterator, context); 135 | return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); 136 | }; 137 | 138 | // Return the first value which passes a truth test. Aliased as `detect`. 139 | _.find = _.detect = function(obj, iterator, context) { 140 | var result; 141 | any(obj, function(value, index, list) { 142 | if (iterator.call(context, value, index, list)) { 143 | result = value; 144 | return true; 145 | } 146 | }); 147 | return result; 148 | }; 149 | 150 | // Return all the elements that pass a truth test. 151 | // Delegates to **ECMAScript 5**'s native `filter` if available. 152 | // Aliased as `select`. 153 | _.filter = _.select = function(obj, iterator, context) { 154 | var results = []; 155 | if (obj == null) return results; 156 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 157 | each(obj, function(value, index, list) { 158 | if (iterator.call(context, value, index, list)) results[results.length] = value; 159 | }); 160 | return results; 161 | }; 162 | 163 | // Return all the elements for which a truth test fails. 164 | _.reject = function(obj, iterator, context) { 165 | var results = []; 166 | if (obj == null) return results; 167 | each(obj, function(value, index, list) { 168 | if (!iterator.call(context, value, index, list)) results[results.length] = value; 169 | }); 170 | return results; 171 | }; 172 | 173 | // Determine whether all of the elements match a truth test. 174 | // Delegates to **ECMAScript 5**'s native `every` if available. 175 | // Aliased as `all`. 176 | _.every = _.all = function(obj, iterator, context) { 177 | var result = true; 178 | if (obj == null) return result; 179 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 180 | each(obj, function(value, index, list) { 181 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 182 | }); 183 | return !!result; 184 | }; 185 | 186 | // Determine if at least one element in the object matches a truth test. 187 | // Delegates to **ECMAScript 5**'s native `some` if available. 188 | // Aliased as `any`. 189 | var any = _.some = _.any = function(obj, iterator, context) { 190 | iterator || (iterator = _.identity); 191 | var result = false; 192 | if (obj == null) return result; 193 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 194 | each(obj, function(value, index, list) { 195 | if (result || (result = iterator.call(context, value, index, list))) return breaker; 196 | }); 197 | return !!result; 198 | }; 199 | 200 | // Determine if a given value is included in the array or object using `===`. 201 | // Aliased as `contains`. 202 | _.include = _.contains = function(obj, target) { 203 | var found = false; 204 | if (obj == null) return found; 205 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 206 | found = any(obj, function(value) { 207 | return value === target; 208 | }); 209 | return found; 210 | }; 211 | 212 | // Invoke a method (with arguments) on every item in a collection. 213 | _.invoke = function(obj, method) { 214 | var args = slice.call(arguments, 2); 215 | return _.map(obj, function(value) { 216 | return (_.isFunction(method) ? method || value : value[method]).apply(value, args); 217 | }); 218 | }; 219 | 220 | // Convenience version of a common use case of `map`: fetching a property. 221 | _.pluck = function(obj, key) { 222 | return _.map(obj, function(value){ return value[key]; }); 223 | }; 224 | 225 | // Return the maximum element or (element-based computation). 226 | _.max = function(obj, iterator, context) { 227 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.max.apply(Math, obj); 228 | if (!iterator && _.isEmpty(obj)) return -Infinity; 229 | var result = {computed : -Infinity}; 230 | each(obj, function(value, index, list) { 231 | var computed = iterator ? iterator.call(context, value, index, list) : value; 232 | computed >= result.computed && (result = {value : value, computed : computed}); 233 | }); 234 | return result.value; 235 | }; 236 | 237 | // Return the minimum element (or element-based computation). 238 | _.min = function(obj, iterator, context) { 239 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0]) return Math.min.apply(Math, obj); 240 | if (!iterator && _.isEmpty(obj)) return Infinity; 241 | var result = {computed : Infinity}; 242 | each(obj, function(value, index, list) { 243 | var computed = iterator ? iterator.call(context, value, index, list) : value; 244 | computed < result.computed && (result = {value : value, computed : computed}); 245 | }); 246 | return result.value; 247 | }; 248 | 249 | // Shuffle an array. 250 | _.shuffle = function(obj) { 251 | var shuffled = [], rand; 252 | each(obj, function(value, index, list) { 253 | rand = Math.floor(Math.random() * (index + 1)); 254 | shuffled[index] = shuffled[rand]; 255 | shuffled[rand] = value; 256 | }); 257 | return shuffled; 258 | }; 259 | 260 | // Sort the object's values by a criterion produced by an iterator. 261 | _.sortBy = function(obj, val, context) { 262 | var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; 263 | return _.pluck(_.map(obj, function(value, index, list) { 264 | return { 265 | value : value, 266 | criteria : iterator.call(context, value, index, list) 267 | }; 268 | }).sort(function(left, right) { 269 | var a = left.criteria, b = right.criteria; 270 | if (a === void 0) return 1; 271 | if (b === void 0) return -1; 272 | return a < b ? -1 : a > b ? 1 : 0; 273 | }), 'value'); 274 | }; 275 | 276 | // Groups the object's values by a criterion. Pass either a string attribute 277 | // to group by, or a function that returns the criterion. 278 | _.groupBy = function(obj, val) { 279 | var result = {}; 280 | var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; 281 | each(obj, function(value, index) { 282 | var key = iterator(value, index); 283 | (result[key] || (result[key] = [])).push(value); 284 | }); 285 | return result; 286 | }; 287 | 288 | // Use a comparator function to figure out at what index an object should 289 | // be inserted so as to maintain order. Uses binary search. 290 | _.sortedIndex = function(array, obj, iterator) { 291 | iterator || (iterator = _.identity); 292 | var low = 0, high = array.length; 293 | while (low < high) { 294 | var mid = (low + high) >> 1; 295 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; 296 | } 297 | return low; 298 | }; 299 | 300 | // Safely convert anything iterable into a real, live array. 301 | _.toArray = function(obj) { 302 | if (!obj) return []; 303 | if (_.isArray(obj)) return slice.call(obj); 304 | if (_.isArguments(obj)) return slice.call(obj); 305 | if (obj.toArray && _.isFunction(obj.toArray)) return obj.toArray(); 306 | return _.values(obj); 307 | }; 308 | 309 | // Return the number of elements in an object. 310 | _.size = function(obj) { 311 | return _.isArray(obj) ? obj.length : _.keys(obj).length; 312 | }; 313 | 314 | // Array Functions 315 | // --------------- 316 | 317 | // Get the first element of an array. Passing **n** will return the first N 318 | // values in the array. Aliased as `head` and `take`. The **guard** check 319 | // allows it to work with `_.map`. 320 | _.first = _.head = _.take = function(array, n, guard) { 321 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 322 | }; 323 | 324 | // Returns everything but the last entry of the array. Especcialy useful on 325 | // the arguments object. Passing **n** will return all the values in 326 | // the array, excluding the last N. The **guard** check allows it to work with 327 | // `_.map`. 328 | _.initial = function(array, n, guard) { 329 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 330 | }; 331 | 332 | // Get the last element of an array. Passing **n** will return the last N 333 | // values in the array. The **guard** check allows it to work with `_.map`. 334 | _.last = function(array, n, guard) { 335 | if ((n != null) && !guard) { 336 | return slice.call(array, Math.max(array.length - n, 0)); 337 | } else { 338 | return array[array.length - 1]; 339 | } 340 | }; 341 | 342 | // Returns everything but the first entry of the array. Aliased as `tail`. 343 | // Especially useful on the arguments object. Passing an **index** will return 344 | // the rest of the values in the array from that index onward. The **guard** 345 | // check allows it to work with `_.map`. 346 | _.rest = _.tail = function(array, index, guard) { 347 | return slice.call(array, (index == null) || guard ? 1 : index); 348 | }; 349 | 350 | // Trim out all falsy values from an array. 351 | _.compact = function(array) { 352 | return _.filter(array, function(value){ return !!value; }); 353 | }; 354 | 355 | // Return a completely flattened version of an array. 356 | _.flatten = function(array, shallow) { 357 | return _.reduce(array, function(memo, value) { 358 | if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); 359 | memo[memo.length] = value; 360 | return memo; 361 | }, []); 362 | }; 363 | 364 | // Return a version of the array that does not contain the specified value(s). 365 | _.without = function(array) { 366 | return _.difference(array, slice.call(arguments, 1)); 367 | }; 368 | 369 | // Produce a duplicate-free version of the array. If the array has already 370 | // been sorted, you have the option of using a faster algorithm. 371 | // Aliased as `unique`. 372 | _.uniq = _.unique = function(array, isSorted, iterator) { 373 | var initial = iterator ? _.map(array, iterator) : array; 374 | var results = []; 375 | // The `isSorted` flag is irrelevant if the array only contains two elements. 376 | if (array.length < 3) isSorted = true; 377 | _.reduce(initial, function (memo, value, index) { 378 | if (isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) { 379 | memo.push(value); 380 | results.push(array[index]); 381 | } 382 | return memo; 383 | }, []); 384 | return results; 385 | }; 386 | 387 | // Produce an array that contains the union: each distinct element from all of 388 | // the passed-in arrays. 389 | _.union = function() { 390 | return _.uniq(_.flatten(arguments, true)); 391 | }; 392 | 393 | // Produce an array that contains every item shared between all the 394 | // passed-in arrays. (Aliased as "intersect" for back-compat.) 395 | _.intersection = _.intersect = function(array) { 396 | var rest = slice.call(arguments, 1); 397 | return _.filter(_.uniq(array), function(item) { 398 | return _.every(rest, function(other) { 399 | return _.indexOf(other, item) >= 0; 400 | }); 401 | }); 402 | }; 403 | 404 | // Take the difference between one array and a number of other arrays. 405 | // Only the elements present in just the first array will remain. 406 | _.difference = function(array) { 407 | var rest = _.flatten(slice.call(arguments, 1), true); 408 | return _.filter(array, function(value){ return !_.include(rest, value); }); 409 | }; 410 | 411 | // Zip together multiple lists into a single array -- elements that share 412 | // an index go together. 413 | _.zip = function() { 414 | var args = slice.call(arguments); 415 | var length = _.max(_.pluck(args, 'length')); 416 | var results = new Array(length); 417 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); 418 | return results; 419 | }; 420 | 421 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 422 | // we need this function. Return the position of the first occurrence of an 423 | // item in an array, or -1 if the item is not included in the array. 424 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 425 | // If the array is large and already in sort order, pass `true` 426 | // for **isSorted** to use binary search. 427 | _.indexOf = function(array, item, isSorted) { 428 | if (array == null) return -1; 429 | var i, l; 430 | if (isSorted) { 431 | i = _.sortedIndex(array, item); 432 | return array[i] === item ? i : -1; 433 | } 434 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); 435 | for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; 436 | return -1; 437 | }; 438 | 439 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 440 | _.lastIndexOf = function(array, item) { 441 | if (array == null) return -1; 442 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); 443 | var i = array.length; 444 | while (i--) if (i in array && array[i] === item) return i; 445 | return -1; 446 | }; 447 | 448 | // Generate an integer Array containing an arithmetic progression. A port of 449 | // the native Python `range()` function. See 450 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 451 | _.range = function(start, stop, step) { 452 | if (arguments.length <= 1) { 453 | stop = start || 0; 454 | start = 0; 455 | } 456 | step = arguments[2] || 1; 457 | 458 | var len = Math.max(Math.ceil((stop - start) / step), 0); 459 | var idx = 0; 460 | var range = new Array(len); 461 | 462 | while(idx < len) { 463 | range[idx++] = start; 464 | start += step; 465 | } 466 | 467 | return range; 468 | }; 469 | 470 | // Function (ahem) Functions 471 | // ------------------ 472 | 473 | // Reusable constructor function for prototype setting. 474 | var ctor = function(){}; 475 | 476 | // Create a function bound to a given object (assigning `this`, and arguments, 477 | // optionally). Binding with arguments is also known as `curry`. 478 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 479 | // We check for `func.bind` first, to fail fast when `func` is undefined. 480 | _.bind = function bind(func, context) { 481 | var bound, args; 482 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 483 | if (!_.isFunction(func)) throw new TypeError; 484 | args = slice.call(arguments, 2); 485 | return bound = function() { 486 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 487 | ctor.prototype = func.prototype; 488 | var self = new ctor; 489 | var result = func.apply(self, args.concat(slice.call(arguments))); 490 | if (Object(result) === result) return result; 491 | return self; 492 | }; 493 | }; 494 | 495 | // Bind all of an object's methods to that object. Useful for ensuring that 496 | // all callbacks defined on an object belong to it. 497 | _.bindAll = function(obj) { 498 | var funcs = slice.call(arguments, 1); 499 | if (funcs.length == 0) funcs = _.functions(obj); 500 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 501 | return obj; 502 | }; 503 | 504 | // Memoize an expensive function by storing its results. 505 | _.memoize = function(func, hasher) { 506 | var memo = {}; 507 | hasher || (hasher = _.identity); 508 | return function() { 509 | var key = hasher.apply(this, arguments); 510 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 511 | }; 512 | }; 513 | 514 | // Delays a function for the given number of milliseconds, and then calls 515 | // it with the arguments supplied. 516 | _.delay = function(func, wait) { 517 | var args = slice.call(arguments, 2); 518 | return setTimeout(function(){ return func.apply(null, args); }, wait); 519 | }; 520 | 521 | // Defers a function, scheduling it to run after the current call stack has 522 | // cleared. 523 | _.defer = function(func) { 524 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 525 | }; 526 | 527 | // Returns a function, that, when invoked, will only be triggered at most once 528 | // during a given window of time. 529 | _.throttle = function(func, wait) { 530 | var context, args, timeout, throttling, more, result; 531 | var whenDone = _.debounce(function(){ more = throttling = false; }, wait); 532 | return function() { 533 | context = this; args = arguments; 534 | var later = function() { 535 | timeout = null; 536 | if (more) func.apply(context, args); 537 | whenDone(); 538 | }; 539 | if (!timeout) timeout = setTimeout(later, wait); 540 | if (throttling) { 541 | more = true; 542 | } else { 543 | result = func.apply(context, args); 544 | } 545 | whenDone(); 546 | throttling = true; 547 | return result; 548 | }; 549 | }; 550 | 551 | // Returns a function, that, as long as it continues to be invoked, will not 552 | // be triggered. The function will be called after it stops being called for 553 | // N milliseconds. If `immediate` is passed, trigger the function on the 554 | // leading edge, instead of the trailing. 555 | _.debounce = function(func, wait, immediate) { 556 | var timeout; 557 | return function() { 558 | var context = this, args = arguments; 559 | var later = function() { 560 | timeout = null; 561 | if (!immediate) func.apply(context, args); 562 | }; 563 | if (immediate && !timeout) func.apply(context, args); 564 | clearTimeout(timeout); 565 | timeout = setTimeout(later, wait); 566 | }; 567 | }; 568 | 569 | // Returns a function that will be executed at most one time, no matter how 570 | // often you call it. Useful for lazy initialization. 571 | _.once = function(func) { 572 | var ran = false, memo; 573 | return function() { 574 | if (ran) return memo; 575 | ran = true; 576 | return memo = func.apply(this, arguments); 577 | }; 578 | }; 579 | 580 | // Returns the first function passed as an argument to the second, 581 | // allowing you to adjust arguments, run code before and after, and 582 | // conditionally execute the original function. 583 | _.wrap = function(func, wrapper) { 584 | return function() { 585 | var args = [func].concat(slice.call(arguments, 0)); 586 | return wrapper.apply(this, args); 587 | }; 588 | }; 589 | 590 | // Returns a function that is the composition of a list of functions, each 591 | // consuming the return value of the function that follows. 592 | _.compose = function() { 593 | var funcs = arguments; 594 | return function() { 595 | var args = arguments; 596 | for (var i = funcs.length - 1; i >= 0; i--) { 597 | args = [funcs[i].apply(this, args)]; 598 | } 599 | return args[0]; 600 | }; 601 | }; 602 | 603 | // Returns a function that will only be executed after being called N times. 604 | _.after = function(times, func) { 605 | if (times <= 0) return func(); 606 | return function() { 607 | if (--times < 1) { return func.apply(this, arguments); } 608 | }; 609 | }; 610 | 611 | // Object Functions 612 | // ---------------- 613 | 614 | // Retrieve the names of an object's properties. 615 | // Delegates to **ECMAScript 5**'s native `Object.keys` 616 | _.keys = nativeKeys || function(obj) { 617 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 618 | var keys = []; 619 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; 620 | return keys; 621 | }; 622 | 623 | // Retrieve the values of an object's properties. 624 | _.values = function(obj) { 625 | return _.map(obj, _.identity); 626 | }; 627 | 628 | // Return a sorted list of the function names available on the object. 629 | // Aliased as `methods` 630 | _.functions = _.methods = function(obj) { 631 | var names = []; 632 | for (var key in obj) { 633 | if (_.isFunction(obj[key])) names.push(key); 634 | } 635 | return names.sort(); 636 | }; 637 | 638 | // Extend a given object with all the properties in passed-in object(s). 639 | _.extend = function(obj) { 640 | each(slice.call(arguments, 1), function(source) { 641 | for (var prop in source) { 642 | obj[prop] = source[prop]; 643 | } 644 | }); 645 | return obj; 646 | }; 647 | 648 | // Return a copy of the object only containing the whitelisted properties. 649 | _.pick = function(obj) { 650 | var result = {}; 651 | each(_.flatten(slice.call(arguments, 1)), function(key) { 652 | if (key in obj) result[key] = obj[key]; 653 | }); 654 | return result; 655 | }; 656 | 657 | // Fill in a given object with default properties. 658 | _.defaults = function(obj) { 659 | each(slice.call(arguments, 1), function(source) { 660 | for (var prop in source) { 661 | if (obj[prop] == null) obj[prop] = source[prop]; 662 | } 663 | }); 664 | return obj; 665 | }; 666 | 667 | // Create a (shallow-cloned) duplicate of an object. 668 | _.clone = function(obj) { 669 | if (!_.isObject(obj)) return obj; 670 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 671 | }; 672 | 673 | // Invokes interceptor with the obj, and then returns obj. 674 | // The primary purpose of this method is to "tap into" a method chain, in 675 | // order to perform operations on intermediate results within the chain. 676 | _.tap = function(obj, interceptor) { 677 | interceptor(obj); 678 | return obj; 679 | }; 680 | 681 | // Internal recursive comparison function. 682 | function eq(a, b, stack) { 683 | // Identical objects are equal. `0 === -0`, but they aren't identical. 684 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. 685 | if (a === b) return a !== 0 || 1 / a == 1 / b; 686 | // A strict comparison is necessary because `null == undefined`. 687 | if (a == null || b == null) return a === b; 688 | // Unwrap any wrapped objects. 689 | if (a._chain) a = a._wrapped; 690 | if (b._chain) b = b._wrapped; 691 | // Invoke a custom `isEqual` method if one is provided. 692 | if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); 693 | if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); 694 | // Compare `[[Class]]` names. 695 | var className = toString.call(a); 696 | if (className != toString.call(b)) return false; 697 | switch (className) { 698 | // Strings, numbers, dates, and booleans are compared by value. 699 | case '[object String]': 700 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 701 | // equivalent to `new String("5")`. 702 | return a == String(b); 703 | case '[object Number]': 704 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 705 | // other numeric values. 706 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 707 | case '[object Date]': 708 | case '[object Boolean]': 709 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 710 | // millisecond representations. Note that invalid dates with millisecond representations 711 | // of `NaN` are not equivalent. 712 | return +a == +b; 713 | // RegExps are compared by their source patterns and flags. 714 | case '[object RegExp]': 715 | return a.source == b.source && 716 | a.global == b.global && 717 | a.multiline == b.multiline && 718 | a.ignoreCase == b.ignoreCase; 719 | } 720 | if (typeof a != 'object' || typeof b != 'object') return false; 721 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 722 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 723 | var length = stack.length; 724 | while (length--) { 725 | // Linear search. Performance is inversely proportional to the number of 726 | // unique nested structures. 727 | if (stack[length] == a) return true; 728 | } 729 | // Add the first object to the stack of traversed objects. 730 | stack.push(a); 731 | var size = 0, result = true; 732 | // Recursively compare objects and arrays. 733 | if (className == '[object Array]') { 734 | // Compare array lengths to determine if a deep comparison is necessary. 735 | size = a.length; 736 | result = size == b.length; 737 | if (result) { 738 | // Deep compare the contents, ignoring non-numeric properties. 739 | while (size--) { 740 | // Ensure commutative equality for sparse arrays. 741 | if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; 742 | } 743 | } 744 | } else { 745 | // Objects with different constructors are not equivalent. 746 | if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; 747 | // Deep compare objects. 748 | for (var key in a) { 749 | if (_.has(a, key)) { 750 | // Count the expected number of properties. 751 | size++; 752 | // Deep compare each member. 753 | if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break; 754 | } 755 | } 756 | // Ensure that both objects contain the same number of properties. 757 | if (result) { 758 | for (key in b) { 759 | if (_.has(b, key) && !(size--)) break; 760 | } 761 | result = !size; 762 | } 763 | } 764 | // Remove the first object from the stack of traversed objects. 765 | stack.pop(); 766 | return result; 767 | } 768 | 769 | // Perform a deep comparison to check if two objects are equal. 770 | _.isEqual = function(a, b) { 771 | return eq(a, b, []); 772 | }; 773 | 774 | // Is a given array, string, or object empty? 775 | // An "empty" object has no enumerable own-properties. 776 | _.isEmpty = function(obj) { 777 | if (obj == null) return true; 778 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 779 | for (var key in obj) if (_.has(obj, key)) return false; 780 | return true; 781 | }; 782 | 783 | // Is a given value a DOM element? 784 | _.isElement = function(obj) { 785 | return !!(obj && obj.nodeType == 1); 786 | }; 787 | 788 | // Is a given value an array? 789 | // Delegates to ECMA5's native Array.isArray 790 | _.isArray = nativeIsArray || function(obj) { 791 | return toString.call(obj) == '[object Array]'; 792 | }; 793 | 794 | // Is a given variable an object? 795 | _.isObject = function(obj) { 796 | return obj === Object(obj); 797 | }; 798 | 799 | // Is a given variable an arguments object? 800 | _.isArguments = function(obj) { 801 | return toString.call(obj) == '[object Arguments]'; 802 | }; 803 | if (!_.isArguments(arguments)) { 804 | _.isArguments = function(obj) { 805 | return !!(obj && _.has(obj, 'callee')); 806 | }; 807 | } 808 | 809 | // Is a given value a function? 810 | _.isFunction = function(obj) { 811 | return toString.call(obj) == '[object Function]'; 812 | }; 813 | 814 | // Is a given value a string? 815 | _.isString = function(obj) { 816 | return toString.call(obj) == '[object String]'; 817 | }; 818 | 819 | // Is a given value a number? 820 | _.isNumber = function(obj) { 821 | return toString.call(obj) == '[object Number]'; 822 | }; 823 | 824 | // Is a given object a finite number? 825 | _.isFinite = function(obj) { 826 | return _.isNumber(obj) && isFinite(obj); 827 | }; 828 | 829 | // Is the given value `NaN`? 830 | _.isNaN = function(obj) { 831 | // `NaN` is the only value for which `===` is not reflexive. 832 | return obj !== obj; 833 | }; 834 | 835 | // Is a given value a boolean? 836 | _.isBoolean = function(obj) { 837 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 838 | }; 839 | 840 | // Is a given value a date? 841 | _.isDate = function(obj) { 842 | return toString.call(obj) == '[object Date]'; 843 | }; 844 | 845 | // Is the given value a regular expression? 846 | _.isRegExp = function(obj) { 847 | return toString.call(obj) == '[object RegExp]'; 848 | }; 849 | 850 | // Is a given value equal to null? 851 | _.isNull = function(obj) { 852 | return obj === null; 853 | }; 854 | 855 | // Is a given variable undefined? 856 | _.isUndefined = function(obj) { 857 | return obj === void 0; 858 | }; 859 | 860 | // Has own property? 861 | _.has = function(obj, key) { 862 | return hasOwnProperty.call(obj, key); 863 | }; 864 | 865 | // Utility Functions 866 | // ----------------- 867 | 868 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 869 | // previous owner. Returns a reference to the Underscore object. 870 | _.noConflict = function() { 871 | root._ = previousUnderscore; 872 | return this; 873 | }; 874 | 875 | // Keep the identity function around for default iterators. 876 | _.identity = function(value) { 877 | return value; 878 | }; 879 | 880 | // Run a function **n** times. 881 | _.times = function (n, iterator, context) { 882 | for (var i = 0; i < n; i++) iterator.call(context, i); 883 | }; 884 | 885 | // Escape a string for HTML interpolation. 886 | _.escape = function(string) { 887 | return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); 888 | }; 889 | 890 | // If the value of the named property is a function then invoke it; 891 | // otherwise, return it. 892 | _.result = function(object, property) { 893 | if (object == null) return null; 894 | var value = object[property]; 895 | return _.isFunction(value) ? value.call(object) : value; 896 | }; 897 | 898 | // Add your own custom functions to the Underscore object, ensuring that 899 | // they're correctly added to the OOP wrapper as well. 900 | _.mixin = function(obj) { 901 | each(_.functions(obj), function(name){ 902 | addToWrapper(name, _[name] = obj[name]); 903 | }); 904 | }; 905 | 906 | // Generate a unique integer id (unique within the entire client session). 907 | // Useful for temporary DOM ids. 908 | var idCounter = 0; 909 | _.uniqueId = function(prefix) { 910 | var id = idCounter++; 911 | return prefix ? prefix + id : id; 912 | }; 913 | 914 | // By default, Underscore uses ERB-style template delimiters, change the 915 | // following template settings to use alternative delimiters. 916 | _.templateSettings = { 917 | evaluate : /<%([\s\S]+?)%>/g, 918 | interpolate : /<%=([\s\S]+?)%>/g, 919 | escape : /<%-([\s\S]+?)%>/g 920 | }; 921 | 922 | // When customizing `templateSettings`, if you don't want to define an 923 | // interpolation, evaluation or escaping regex, we need one that is 924 | // guaranteed not to match. 925 | var noMatch = /.^/; 926 | 927 | // Certain characters need to be escaped so that they can be put into a 928 | // string literal. 929 | var escapes = { 930 | '\\': '\\', 931 | "'": "'", 932 | 'r': '\r', 933 | 'n': '\n', 934 | 't': '\t', 935 | 'u2028': '\u2028', 936 | 'u2029': '\u2029' 937 | }; 938 | 939 | for (var p in escapes) escapes[escapes[p]] = p; 940 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 941 | var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; 942 | 943 | // Within an interpolation, evaluation, or escaping, remove HTML escaping 944 | // that had been previously added. 945 | var unescape = function(code) { 946 | return code.replace(unescaper, function(match, escape) { 947 | return escapes[escape]; 948 | }); 949 | }; 950 | 951 | // JavaScript micro-templating, similar to John Resig's implementation. 952 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 953 | // and correctly escapes quotes within interpolated code. 954 | _.template = function(text, data, settings) { 955 | settings = _.defaults(settings || {}, _.templateSettings); 956 | 957 | // Compile the template source, taking care to escape characters that 958 | // cannot be included in a string literal and then unescape them in code 959 | // blocks. 960 | var source = "__p+='" + text 961 | .replace(escaper, function(match) { 962 | return '\\' + escapes[match]; 963 | }) 964 | .replace(settings.escape || noMatch, function(match, code) { 965 | return "'+\n_.escape(" + unescape(code) + ")+\n'"; 966 | }) 967 | .replace(settings.interpolate || noMatch, function(match, code) { 968 | return "'+\n(" + unescape(code) + ")+\n'"; 969 | }) 970 | .replace(settings.evaluate || noMatch, function(match, code) { 971 | return "';\n" + unescape(code) + "\n;__p+='"; 972 | }) + "';\n"; 973 | 974 | // If a variable is not specified, place data values in local scope. 975 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 976 | 977 | source = "var __p='';" + 978 | "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + 979 | source + "return __p;\n"; 980 | 981 | var render = new Function(settings.variable || 'obj', '_', source); 982 | if (data) return render(data, _); 983 | var template = function(data) { 984 | return render.call(this, data, _); 985 | }; 986 | 987 | // Provide the compiled function source as a convenience for build time 988 | // precompilation. 989 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + 990 | source + '}'; 991 | 992 | return template; 993 | }; 994 | 995 | // Add a "chain" function, which will delegate to the wrapper. 996 | _.chain = function(obj) { 997 | return _(obj).chain(); 998 | }; 999 | 1000 | // The OOP Wrapper 1001 | // --------------- 1002 | 1003 | // If Underscore is called as a function, it returns a wrapped object that 1004 | // can be used OO-style. This wrapper holds altered versions of all the 1005 | // underscore functions. Wrapped objects may be chained. 1006 | var wrapper = function(obj) { this._wrapped = obj; }; 1007 | 1008 | // Expose `wrapper.prototype` as `_.prototype` 1009 | _.prototype = wrapper.prototype; 1010 | 1011 | // Helper function to continue chaining intermediate results. 1012 | var result = function(obj, chain) { 1013 | return chain ? _(obj).chain() : obj; 1014 | }; 1015 | 1016 | // A method to easily add functions to the OOP wrapper. 1017 | var addToWrapper = function(name, func) { 1018 | wrapper.prototype[name] = function() { 1019 | var args = slice.call(arguments); 1020 | unshift.call(args, this._wrapped); 1021 | return result(func.apply(_, args), this._chain); 1022 | }; 1023 | }; 1024 | 1025 | // Add all of the Underscore functions to the wrapper object. 1026 | _.mixin(_); 1027 | 1028 | // Add all mutator Array functions to the wrapper. 1029 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1030 | var method = ArrayProto[name]; 1031 | wrapper.prototype[name] = function() { 1032 | var wrapped = this._wrapped; 1033 | method.apply(wrapped, arguments); 1034 | var length = wrapped.length; 1035 | if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0]; 1036 | return result(wrapped, this._chain); 1037 | }; 1038 | }); 1039 | 1040 | // Add all accessor Array functions to the wrapper. 1041 | each(['concat', 'join', 'slice'], function(name) { 1042 | var method = ArrayProto[name]; 1043 | wrapper.prototype[name] = function() { 1044 | return result(method.apply(this._wrapped, arguments), this._chain); 1045 | }; 1046 | }); 1047 | 1048 | // Start chaining a wrapped Underscore object. 1049 | wrapper.prototype.chain = function() { 1050 | this._chain = true; 1051 | return this; 1052 | }; 1053 | 1054 | // Extracts the result from a wrapped and chained object. 1055 | wrapper.prototype.value = function() { 1056 | return this._wrapped; 1057 | }; 1058 | 1059 | }).call(this); 1060 | -------------------------------------------------------------------------------- /app/public/js/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | angular.module('myApp.services', ['ngResource']) 5 | .factory('Todo', function ($resource) { 6 | return $resource('api/Todo/:id', {}, { 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /app/public/partials/404.html: -------------------------------------------------------------------------------- 1 |
    You should put one of those really cool and unique 404 pages here!
    2 | -------------------------------------------------------------------------------- /app/public/partials/index.html: -------------------------------------------------------------------------------- 1 |

    Welcome!

    2 |

    This is a standard AngularJS "todo app" with user authentication added in.

    3 |

    The full source for this site is available here: https://github.com/Khelldar/Angular-Express-Train-Seed 5 |

    6 | 7 |

    Stuff that was used to make this: 8 |

      9 |
    • AngularJS
    • 10 |
    • Node.js using the Express Train framework
    • 11 |
    • MongoDB using Mongoose
    • 12 |
    • Passport (a node module for user authentication)
    • 13 |
    14 | 15 |

    16 | -------------------------------------------------------------------------------- /app/public/partials/login.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 |

    Login

    6 | 7 |
    8 |
    9 |
    10 | {{statusMessage}} 11 |
    12 |
    13 | - or - 14 |
    15 |

    Register

    16 | 17 |
    18 |
    19 |
    20 |
    21 | {{statusMessage}} 22 |
    23 |
    24 |
    -------------------------------------------------------------------------------- /app/public/partials/todos.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 |

    My Todos

    10 |
    11 |
    12 | 13 |
    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    {{todo.text}}
    28 | 29 |
    30 | New Todo 31 | 32 | 33 |
    34 | {{statusMessage}} 35 |
    36 |
    -------------------------------------------------------------------------------- /app/views/partials/.git.me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-langager/Angular-Express-Train-Seed/d67b9e8e63e95ad14be6eb31d7ea9a7d7a82714d/app/views/partials/.git.me -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "cookie_secret":"boggle at the situation", 3 | "session":{ 4 | "key":"boggle at the situation", 5 | "secret": "boggle at the situation" 6 | }, 7 | "request_timeout":1000, 8 | "port":3000, 9 | "mongodb":{ 10 | "uri":"mongodb://test:test@ds045507.mongolab.com:45507/service-health-train" 11 | }, 12 | "character_encoding":"utf-8" 13 | } -------------------------------------------------------------------------------- /config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "cookie_secret": "boggle at the situation", 3 | "session": { 4 | "key": "boggle at the situation", 5 | "secret": "boggle at the situation" 6 | }, 7 | "request_timeout": 1000, 8 | "port": "{{PORT}}", 9 | "mongodb": { 10 | "uri": "{{MONGOLAB_URI}}" 11 | }, 12 | "character_encoding": "utf-8" 13 | } -------------------------------------------------------------------------------- /doc/.gitme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-langager/Angular-Express-Train-Seed/d67b9e8e63e95ad14be6eb31d7ea9a7d7a82714d/doc/.gitme -------------------------------------------------------------------------------- /logs/.git.me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-langager/Angular-Express-Train-Seed/d67b9e8e63e95ad14be6eb31d7ea9a7d7a82714d/logs/.git.me -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Angular-Express-Train-Seed", 3 | "version": "0.0.2", 4 | "directories": {}, 5 | "engines": { 6 | "node": ">=0.6.6", 7 | "npm": ">=1.1" 8 | }, 9 | "dependencies": { 10 | "express": "~3.2.4", 11 | "express-train": "~1.0.2", 12 | "connect-timeout": "0.0.1", 13 | "connect-mongo": "~0.3.2", 14 | "mongoose": "~3.6.11", 15 | "async": "~0.2.8", 16 | "express-hbs": "~0.2.0", 17 | "passport": "~0.1.17", 18 | "passport-local": "~0.1.6", 19 | "winston": "~0.7.1", 20 | "express-trace": "0.0.3" 21 | }, 22 | "devDependencies": { 23 | "mocha": "latest" 24 | }, 25 | "main": "./app/index.js", 26 | "scripts": { 27 | "start": "./app/index.js" 28 | }, 29 | "constants": { 30 | "title": "structure app", 31 | "session_length": 1209600000, 32 | "request_timeout": 10000 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/.gitme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-langager/Angular-Express-Train-Seed/d67b9e8e63e95ad14be6eb31d7ea9a7d7a82714d/test/.gitme --------------------------------------------------------------------------------