├── .gitignore ├── Capfile ├── Gemfile ├── Gemfile.lock ├── Gruntfile.js ├── LICENSE ├── README.md ├── app.js ├── app ├── middleware │ ├── errors.js │ ├── passport.js │ └── users.js ├── models │ ├── index.js │ ├── orgs.js │ ├── project.js │ ├── requesters.js │ ├── requests.js │ ├── support.js │ └── user.js ├── routes │ ├── index.js │ ├── maintainers.js │ ├── organizations.js │ ├── projects.js │ ├── service.js │ ├── supporters.js │ └── users.js ├── utils │ ├── ensure-auth.js │ ├── git-request.js │ ├── logger.js │ ├── mailer.js │ ├── requests-converter.js │ ├── requests-notifications.js │ ├── socket.io.js │ ├── title-builder.js │ └── updater │ │ ├── index.js │ │ ├── organizations.js │ │ └── projects.js └── views │ ├── 404.ejs │ ├── 500.ejs │ ├── account.ejs │ ├── emails │ ├── comment-for-author.ejs │ ├── donate-request-maintainer.ejs │ ├── donate-request-notify-maintainer.ejs │ ├── donate-request-satisfied.ejs │ └── donate-request-support.ejs │ ├── index.ejs │ ├── maintainer-editor.ejs │ ├── modals │ ├── email-to-author.ejs │ ├── index.ejs │ ├── need-contribution-ways.ejs │ └── note-from-author.ejs │ ├── organization.ejs │ ├── parts │ ├── contributions.ejs │ ├── foot.ejs │ ├── head.ejs │ ├── header.ejs │ ├── project-row.ejs │ └── welcome.ejs │ ├── project.ejs │ ├── registration.ejs │ ├── repo-editor.ejs │ ├── service │ └── donation-requests.ejs │ └── settings.ejs ├── bower.json ├── config ├── deploy.rb ├── deploy │ ├── production.rb │ └── staging.rb ├── example.config.js └── index.js ├── less ├── base │ └── body.less ├── main.less ├── pages │ ├── account.less │ ├── donation-requests.less │ ├── index.less │ ├── maintainers.less │ ├── project.less │ └── repo-selector.less ├── parts │ ├── font.less │ ├── forms.less │ ├── help-overlay.less │ ├── profile-header.less │ └── repos-list.less └── vendors │ ├── bootstrap │ ├── alerts.less │ ├── badges.less │ ├── bootstrap.less │ ├── breadcrumbs.less │ ├── button-groups.less │ ├── buttons.less │ ├── carousel.less │ ├── close.less │ ├── code.less │ ├── component-animations.less │ ├── dropdowns.less │ ├── forms.less │ ├── glyphicons.less │ ├── grid.less │ ├── input-groups.less │ ├── jumbotron.less │ ├── labels.less │ ├── list-group.less │ ├── media.less │ ├── mixins.less │ ├── modals.less │ ├── navbar.less │ ├── navs.less │ ├── normalize.less │ ├── pager.less │ ├── pagination.less │ ├── panels.less │ ├── popovers.less │ ├── print.less │ ├── progress-bars.less │ ├── responsive-utilities.less │ ├── scaffolding.less │ ├── tables.less │ ├── theme.less │ ├── thumbnails.less │ ├── tooltip.less │ ├── type.less │ ├── utilities.less │ ├── variables.less │ └── wells.less │ └── font-awesome │ ├── bootstrap.less │ ├── core.less │ ├── extras.less │ ├── font-awesome-ie7.less │ ├── font-awesome.less │ ├── icons.less │ ├── mixins.less │ ├── path.less │ └── variables.less ├── package.json └── public ├── favicon.ico ├── fonts ├── FontAwesome.otf ├── fontawesome-webfont.eot ├── fontawesome-webfont.svg ├── fontawesome-webfont.ttf ├── fontawesome-webfont.woff ├── quicksand-bold-webfont.svg ├── quicksand-bold-webfont.ttf ├── quicksand-bold-webfont.woff ├── quicksand-light-webfont.eot ├── quicksand-light-webfont.svg ├── quicksand-light-webfont.ttf ├── quicksand-light-webfont.woff ├── quicksand-regular-webfont.eot ├── quicksand-regular-webfont.svg ├── quicksand-regular-webfont.ttf └── quicksand-regular-webfont.woff ├── images ├── bg-highlight.jpg ├── bg.png ├── codio-icon.png ├── digitalocean.png ├── flattr.png ├── github-ribbon.png ├── gittip.png ├── help │ └── supporters │ │ ├── donate.png │ │ ├── dropdown.jpg │ │ ├── preview.png │ │ ├── project.png │ │ └── supporting-icons.png ├── logo-darkbg.png ├── logo-lightbg.png └── paypal.gif └── js ├── bootstrap.js ├── common ├── templates │ └── donate-methods.html └── views │ └── donate-methods.js ├── modules ├── account │ └── main.js ├── donate-requests │ ├── collections │ │ ├── requests.js │ │ └── supporters.js │ ├── main.js │ ├── models │ │ └── request.js │ ├── templates │ │ ├── list.html │ │ ├── request.html │ │ ├── search.html │ │ ├── supporter.html │ │ └── supporters.html │ └── views │ │ ├── list.js │ │ ├── request.js │ │ ├── search.js │ │ ├── supporter.js │ │ └── supporters.js ├── maintainers │ ├── collections │ │ ├── project-groups.js │ │ └── projects.js │ ├── main.js │ ├── models │ │ └── project.js │ ├── templates │ │ ├── group.html │ │ ├── help.html │ │ ├── list.html │ │ ├── project-settings.html │ │ └── project.html │ └── views │ │ ├── group.js │ │ ├── list.js │ │ ├── project-settings.js │ │ └── project.js ├── project │ └── main.js ├── repo-selector │ ├── collections │ │ ├── project-groups.js │ │ ├── projects.js │ │ ├── repos.js │ │ └── search.js │ ├── main.js │ ├── models │ │ ├── project.js │ │ ├── repo.js │ │ └── support.js │ ├── router.js │ ├── templates │ │ ├── group-selector.html │ │ ├── help.html │ │ ├── layout.html │ │ ├── repo-list.html │ │ ├── repo-row.html │ │ ├── repo-search.html │ │ ├── selected.html │ │ └── share.html │ └── views │ │ ├── group-selector.js │ │ ├── layout.js │ │ ├── project-row.js │ │ ├── repo-list.js │ │ ├── repo-row.js │ │ ├── search.js │ │ └── selected.js └── user-settings │ └── main.js ├── plugins ├── activate-plugins.js ├── project.updater.js └── store.js └── require.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | bower_components 14 | node_modules 15 | public/js/vendors 16 | public/js/build 17 | public/css/main.css 18 | config/config.*.js 19 | 20 | npm-debug.log 21 | *.sublime* 22 | .idea/ -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | load 'deploy' 2 | load 'config/deploy' -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'capistrano' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | capistrano (2.15.5) 5 | highline 6 | net-scp (>= 1.0.0) 7 | net-sftp (>= 2.0.0) 8 | net-ssh (>= 2.0.14) 9 | net-ssh-gateway (>= 1.1.0) 10 | highline (1.6.19) 11 | net-scp (1.1.2) 12 | net-ssh (>= 2.6.5) 13 | net-sftp (2.1.2) 14 | net-ssh (>= 2.6.5) 15 | net-ssh (2.7.0) 16 | net-ssh-gateway (1.2.0) 17 | net-ssh (>= 2.6.5) 18 | 19 | PLATFORMS 20 | ruby 21 | 22 | DEPENDENCIES 23 | capistrano 24 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.initConfig({ 4 | bower: { 5 | install: { 6 | options: { 7 | targetDir: './public/js/vendors/', 8 | layout: 'byComponent', 9 | install: true, 10 | verbose: true, 11 | cleanTargetDir: true, 12 | cleanBowerDir: true 13 | } 14 | } 15 | }, 16 | less: { 17 | default: { 18 | files: { 19 | 'public/css/main.css': 'less/main.less' 20 | }, 21 | options: { 22 | paths: [ 'less/basics', 'less/pages', 'less/parts' ] 23 | } 24 | } 25 | }, 26 | 27 | requirejs: { 28 | compile: { 29 | options: { 30 | appDir: './public/js', 31 | dir: './public/js/build', 32 | baseUrl: './', 33 | preserveLicenseComments: false, 34 | optimizeCss: 'none', 35 | useStrict: true, 36 | generateSourceMaps: true, 37 | optimize: 'uglify2', 38 | mainConfigFile: "./public/js/require.config.js", 39 | paths: { 40 | 'socket.io': 'empty:' 41 | }, 42 | modules: grunt.file.expand('./public/js/modules/*').map(function (entry) { 43 | return { 44 | name: entry.replace('./public/js/', '') + '/main' 45 | } 46 | }) 47 | } 48 | } 49 | }, 50 | 51 | autoprefixer: { 52 | options: { 53 | browsers: ['last 5 versions', '> 1%', 'ie 8', 'ie 7'] 54 | }, 55 | dist: { 56 | files: { 57 | 'public/css/main.css': 'public/css/main.css' 58 | } 59 | } 60 | }, 61 | 62 | cssmin: { 63 | default: { 64 | options: { 65 | report: 'min' 66 | }, 67 | files: { 68 | 'public/css/main.css': ['public/css/main.css'] 69 | } 70 | } 71 | }, 72 | 73 | watch: { 74 | files: 'less/**/*.less', 75 | tasks: ['build'] 76 | } 77 | }) 78 | 79 | grunt.loadNpmTasks('grunt-autoprefixer'); 80 | grunt.loadNpmTasks('grunt-bower-task'); 81 | grunt.loadNpmTasks('grunt-contrib-requirejs'); 82 | grunt.loadNpmTasks('grunt-contrib-less'); 83 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 84 | grunt.loadNpmTasks('grunt-contrib-watch'); 85 | 86 | // Default task(s). 87 | grunt.registerTask('default', ['build']); 88 | grunt.registerTask('build', ['less', 'autoprefixer']); 89 | grunt.registerTask('build:prod', ['less', 'autoprefixer', 'cssmin', 'requirejs']); 90 | grunt.registerTask('update', ['bower', 'build:prod']); 91 | grunt.registerTask('heroku:production', ['update']); 92 | 93 | }; 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Codio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 7/29/13 4 | * Time: 9:14 PM 5 | */ 6 | var http = require('http'), 7 | path = require('path'), 8 | express = require('express'), 9 | cfg = require('./config'), 10 | logger = require('./app/utils/logger'), 11 | MongoStore = require('connect-mongo')(express), 12 | mongoose = require('mongoose'), 13 | app = express(); 14 | 15 | mongoose.connect(cfg.mongodbUri) 16 | mongoose.connection.on('error', function (err) { 17 | logger.error('Mongo connection error', err.message); 18 | }); 19 | mongoose.connection.once('open', function callback () { 20 | logger.info("Connected to MongoDB"); 21 | }); 22 | 23 | // Bootstrap models 24 | require('./app/models') 25 | 26 | var passport = require('./app/middleware/passport') 27 | var sessionStore = new MongoStore({ 28 | url: cfg.mongodbUri 29 | }) 30 | 31 | app.locals._ = require('lodash'); 32 | app.locals.titleBuilder = require('./app/utils/title-builder'); 33 | app.locals.jsPath = cfg.jsPath; 34 | app.locals.siteUrl = cfg.fullUrl(); 35 | 36 | // all environments 37 | app.set('port', cfg.port); 38 | app.set('views', __dirname + '/app/views'); 39 | app.set('view engine', 'ejs'); 40 | 41 | 42 | app.use(express.static(path.join(__dirname, 'public'))); 43 | app.use(express.logger('dev')); 44 | app.use(express.favicon(path.join(__dirname, 'public/favicon.ico'))); 45 | app.use(express.bodyParser()); 46 | app.use(express.methodOverride()); 47 | app.use(express.cookieParser(cfg.sessionSecret)); 48 | app.use(express.session({ 49 | secret: cfg.sessionSecret, 50 | store: sessionStore 51 | })); 52 | app.use(passport.initialize()); 53 | app.use(passport.session()); 54 | require('./app/middleware/users')(app) 55 | app.use(app.router); 56 | require('./app/routes')(app) 57 | if (cfg.isDev) { 58 | app.use(express.errorHandler()); 59 | } else { 60 | require('./app/middleware/errors')(app) 61 | } 62 | 63 | var server = http.createServer(app) 64 | 65 | require('./app/utils/socket.io')(server, sessionStore) 66 | 67 | require('./app/utils/mailer').fillTemplates(path.join(__dirname, 'app/views/emails/'), function (error) { 68 | if (error) throw error; 69 | 70 | server.listen(app.get('port'), function () { 71 | logger.info('Express server listening on port ' + app.get('port')); 72 | }); 73 | }) 74 | 75 | -------------------------------------------------------------------------------- /app/middleware/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/18/13 4 | * Time: 7:35 PM 5 | */ 6 | var logger = require('winston') 7 | 8 | module.exports = function (app) { 9 | //handling request errors 10 | app.use(function (req, res, next) { 11 | res.status(404); 12 | logger.warn('404 not found: %s', req.originalUrl) 13 | 14 | if (req.accepts('html')) { 15 | res.render('404'); 16 | return; 17 | } 18 | 19 | if (req.accepts('json')) { 20 | res.send({ error: 'Not found' }); 21 | return; 22 | } 23 | 24 | res.type('txt').send('Not found'); 25 | }); 26 | 27 | app.use(function (err, req, res, next) { 28 | res.status(err.status || 500); 29 | logger.warn('Route Error: %s', req.originalUrl, err) 30 | 31 | if (req.accepts('html')) { 32 | return res.render('500', { error: err }); 33 | } 34 | 35 | if (req.accepts('json')) { 36 | return res.send({ error: err }); 37 | } 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /app/middleware/passport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/4/13 4 | * Time: 6:15 PM 5 | */ 6 | var passport = require('passport'), 7 | cfg = require('../../config'), 8 | GitHubStrategy = require('passport-github').Strategy, 9 | mongoose = require('mongoose'), 10 | logger = require('winston'), 11 | User = mongoose.model('User') 12 | 13 | passport.serializeUser(function (user, done) { 14 | done(null, user); 15 | }); 16 | 17 | passport.deserializeUser(function (obj, done) { 18 | User.findOne({_id: obj._id}, function (err, user) { 19 | done(err, user); 20 | }); 21 | }); 22 | 23 | var callbackUrl = cfg.fullUrl() + '/auth/github/callback' 24 | logger.info('GitHub callback url is', callbackUrl) 25 | 26 | passport.use(new GitHubStrategy({ 27 | clientID: cfg.github.clientId, 28 | clientSecret: cfg.github.clientSecret, 29 | callbackURL: callbackUrl 30 | }, 31 | function (accessToken, refreshToken, profile, done) { 32 | User.findOneAndUpdate({ 'github.id': profile.id }, { $set: {authToken: accessToken}}, function (err, user) { 33 | if (user) return done(err, user) 34 | 35 | user = new User({ 36 | name: profile.displayName, 37 | email: profile._json.email, 38 | username: profile.username, 39 | displayName: profile.username, 40 | provider: 'github', 41 | github: profile._json, 42 | type: profile._json.type, 43 | authToken: accessToken 44 | }) 45 | 46 | user.register(function (err) { 47 | if (err) console.error(err) 48 | return done(err, user, true) 49 | }) 50 | }) 51 | } 52 | )); 53 | 54 | module.exports = passport 55 | -------------------------------------------------------------------------------- /app/middleware/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/18/13 4 | * Time: 7:34 PM 5 | */ 6 | module.exports = function (app) { 7 | //exposing current user 8 | app.use(function (req, res, next) { 9 | req.realIP = req.headers['x-real-ip'] || req.connection.remoteAddress 10 | res.locals.isLoggedIn = req.isAuthenticated() 11 | res.locals.loggedUser = req.user 12 | if (req.session.isNewUser) { 13 | res.locals.isNewUser = true 14 | req.session.isNewUser = null 15 | } 16 | next(); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /app/models/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 7:32 AM 5 | */ 6 | require('./user') 7 | require('./orgs') 8 | require('./project') 9 | require('./support') 10 | require('./requesters') 11 | require('./requests') -------------------------------------------------------------------------------- /app/models/orgs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/18/13 4 | * Time: 14:21 AM 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | gravatarRegexp = /gravatar.com\/avatar\/([0-9a-z]+)/i 9 | 10 | var OrganizationSchema = new Schema({ 11 | githubId: {type: Number, default: 0, index: {unique: true}}, 12 | name: String, 13 | gravatar: String, 14 | admins: [ 15 | {type: Schema.ObjectId, ref: 'User', index: true} 16 | ], 17 | publicAdmins: [ 18 | {type: Schema.ObjectId, ref: 'User', index: true} 19 | ] 20 | }) 21 | 22 | OrganizationSchema.statics.parseGitHubData = function (entry) { 23 | var gravatar = entry.avatar_url.match(gravatarRegexp) 24 | return { 25 | githubId: entry.id, 26 | name: entry.login, 27 | gravatar: gravatar ? gravatar[1] : '', 28 | admins: [], 29 | publicAdmins: [] 30 | } 31 | } 32 | 33 | mongoose.model('Organization', OrganizationSchema) -------------------------------------------------------------------------------- /app/models/requesters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 10/7/13 4 | * Time: 3:21 PM 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | emailRegExp = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i 9 | 10 | var RequesterSchema = new Schema({ 11 | request: {type: Schema.ObjectId, ref: 'Request'}, 12 | ref: {type: Schema.ObjectId, ref: 'User'}, 13 | username: {type: String, trim: true }, 14 | email: {type: String, trim: true }, 15 | isAnon: {type: Boolean, default: false}, 16 | ip: {type: String, trim: true } 17 | }) 18 | 19 | RequesterSchema.path('email').validate(function (email) { 20 | if (email) return emailRegExp.test(email); 21 | return true 22 | }, 'Email field contain not an email') 23 | 24 | mongoose.model('Requester', RequesterSchema) 25 | -------------------------------------------------------------------------------- /app/models/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/4/13 4 | * Time: 3:45 PM 5 | */ 6 | 7 | var mongoose = require('mongoose'), 8 | Schema = mongoose.Schema, 9 | emailRegExp = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i 10 | 11 | var UserSchema = new Schema({ 12 | username: { type: String, required: true, index: { unique: true }, trim: true }, 13 | email: { type: String, trim: true }, 14 | avatar: String, 15 | admin: {type: Boolean, default: false}, 16 | 17 | displayName: {type: String, trim: true}, 18 | twitterName: {type: String, trim: true}, 19 | github: {}, 20 | provider: { type: String, default: '' }, 21 | authToken: { type: String, default: '' }, 22 | projectsUpdater: { 23 | updated: {type: Boolean}, 24 | updating: {type: Boolean}, 25 | status: {type: String, enum: ['error', 'success']}, 26 | updatedAt: { type: Date } 27 | }, 28 | noMaintainerNotifications: {type: Boolean, default: false}, 29 | statistics: { 30 | becameSupporterAt: Date, 31 | becameMaintainerAt: Date 32 | } 33 | }); 34 | 35 | UserSchema.path('email').validate(function (email) { 36 | if (email) return emailRegExp.test(email); 37 | return true 38 | }, 'Email field contain not an email') 39 | 40 | UserSchema.methods.register = function (callback) { 41 | this.save(function (error, user) { 42 | if (error) return callback(error) 43 | callback(error, user) 44 | require('../utils/updater/index.js')(user) 45 | }) 46 | } 47 | 48 | UserSchema.post('save', function (doc) { 49 | var Project = mongoose.model('Project') 50 | Project.updateOwner(doc) 51 | }) 52 | 53 | mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport') 2 | 3 | module.exports = function (app) { 4 | app.get('/auth/github', passport.authenticate('github')); 5 | 6 | app.get('/auth/github/callback', function (req, res, next) { 7 | passport.authenticate('github', function (err, user, isNew) { 8 | if (err) return next(err); 9 | if (!user) return res.redirect('/'); 10 | if (isNew) req.session.isNewUser = true; 11 | 12 | req.logIn(user, function (err) { 13 | if (err) return next(err); 14 | res.redirect(getRedirectUrl(req.session)); 15 | }); 16 | })(req, res, next); 17 | }); 18 | 19 | app.get('/logout', function (req, res) { 20 | req.logout(); 21 | res.redirect('/'); 22 | }); 23 | 24 | app.get('/', function (req, res) { 25 | res.render('index', { user: req.user }); 26 | }); 27 | 28 | require('./users')(app) 29 | require('./organizations')(app) 30 | require('./projects')(app) 31 | require('./supporters')(app) 32 | require('./maintainers')(app) 33 | require('./service')(app) 34 | }; 35 | 36 | function getRedirectUrl(session) { 37 | var redirectUrl = '/supporter/'; 38 | if (session.redirectUrl) { 39 | redirectUrl = session.redirectUrl; 40 | session.redirectUrl = null; 41 | } 42 | 43 | return redirectUrl 44 | } -------------------------------------------------------------------------------- /app/routes/organizations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/23/13 4 | * Time: 1:41 PM 5 | */ 6 | var mongoose = require('mongoose'), 7 | async = require('async'), 8 | Organization = mongoose.model('Organization'), 9 | Project = mongoose.model('Project'), 10 | Support = mongoose.model('Support'), 11 | User = mongoose.model('User') 12 | 13 | module.exports = function (app) { 14 | app.get('/orgs/:id', function (req, res) { 15 | Organization.findById(req.param('id')).populate('admins').exec(function (error, org) { 16 | if (error || !org) return res.send('400', 'There is no organization you looking for') 17 | 18 | 19 | async.parallel({ 20 | support: function (callback) { 21 | Support.find({ byOrganization: org._id }).populate('project').exec(callback) 22 | }, 23 | projects: function (callback) { 24 | Project.find({'owner.org': org._id}, callback) 25 | } 26 | }, function (error, results) { 27 | if (error) return res.send(500, 'Server error, please try later') 28 | 29 | res.render('organization', { 30 | org: org, 31 | projects: results.projects, 32 | support: results.support 33 | }); 34 | }) 35 | }) 36 | }); 37 | }; -------------------------------------------------------------------------------- /app/routes/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/17/13 4 | * Time: 4:44 PM 5 | */ 6 | var _ = require('lodash'), 7 | mongoose = require('mongoose'), 8 | async = require('async'), 9 | ensureAuthenticated = require('../utils/ensure-auth'), 10 | 11 | Project = mongoose.model('Project'), 12 | Organization = mongoose.model('Organization'), 13 | Support = mongoose.model('Support'), 14 | User = mongoose.model('User') 15 | 16 | module.exports = function (app) { 17 | 18 | app.get('/users/:username', function (req, res) { 19 | async.waterfall([ 20 | function (callback) { 21 | User.findOne({ 'username': req.param('username') }).exec(callback) 22 | }, 23 | function (user, callback) { 24 | if (!user) return callback(true); 25 | Organization.find({publicAdmins: user._id}) 26 | .exec(function(error, orgs) { 27 | if (error) return callback(error) 28 | 29 | user.orgs = orgs 30 | callback(null, user) 31 | }) 32 | } 33 | ], function (err, user) { 34 | if (err) return res.send(404) 35 | 36 | async.parallel({ 37 | personalSupport: function (callback) { 38 | Support.find({byUser: user._id}).populate('project').exec(callback) 39 | }, 40 | ownProjects: function (callback) { 41 | Project.find({'owner.user': user._id}, callback) 42 | }, 43 | adminProjects: function (callback) { 44 | Project.find({ 45 | admins: user._id, 46 | 'owner.org': {$in: _.pluck(user.orgs, '_id')} 47 | }, callback) 48 | } 49 | }, function (error, results) { 50 | if (error) return res.send(500, 'Server error, please try later') 51 | 52 | res.render('account', { 53 | user: user, 54 | projects: { 55 | personal: results.ownProjects, 56 | admin: results.adminProjects 57 | }, 58 | orgs: user.orgs, 59 | support: { 60 | personal: results.personalSupport 61 | } 62 | }); 63 | }) 64 | }) 65 | 66 | }) 67 | 68 | app.get('/settings', ensureAuthenticated, function (req, res) { 69 | res.render('settings', { user: req.user, error: ''}); 70 | }); 71 | 72 | app.post('/settings/:field', ensureAuthenticated, function (req, res) { 73 | var field = req.param('field') 74 | 75 | if (_.indexOf(['email', 'twitterName'], field) == -1) { 76 | return res.send(400, 'Wrong field'); 77 | } 78 | 79 | var data = {} 80 | data[field] = req.body.value 81 | 82 | User.findById(req.user._id, function (err, user) { 83 | if (err) return res.send(400, 'Failed to find user'); 84 | 85 | user[field] = req.body.value 86 | 87 | user.save(function (error) { 88 | if (error) return res.send(400, 'Failed to update field'); 89 | res.send(200, 'Field saved'); 90 | }) 91 | }); 92 | }); 93 | } 94 | ; -------------------------------------------------------------------------------- /app/utils/ensure-auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/18/13 4 | * Time: 8:12 PM 5 | */ 6 | module.exports = function (req, res, next) { 7 | if (req.isAuthenticated()) { 8 | return next(); 9 | } 10 | 11 | req.session.redirectUrl = req.url; 12 | res.redirect('/'); 13 | } 14 | -------------------------------------------------------------------------------- /app/utils/git-request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/19/13 4 | * Time: 11:27 AM 5 | */ 6 | 7 | var _ = require('lodash'), 8 | mongoose = require('mongoose'), 9 | https = require('https'), 10 | qs = require('querystring'), 11 | Project = mongoose.model('Project') 12 | 13 | module.exports.request = function (path, params, callback) { 14 | var options = { 15 | hostname: 'api.github.com', 16 | port: 443, 17 | path: '/' + path + '?' + qs.stringify(params), 18 | method: 'GET', 19 | headers: { 20 | 'User-Agent': 'ilos', 21 | 'Agent': 'ilos', 22 | 'Accept': 'application/vnd.github.preview' 23 | } 24 | }; 25 | 26 | var request = https.request(options, function (response) { 27 | var bodyParts = [], bytes = 0 28 | 29 | if (Math.floor(response.statusCode / 100) === 5) { 30 | return callback('GitHub is currently not available. Please try later'); 31 | } 32 | 33 | response.on("data", function (c) { 34 | bodyParts.push(c) 35 | bytes += c.length 36 | }) 37 | 38 | response.on("end", function () { 39 | var body = new Buffer(bytes), copied = 0, status = response.statusCode 40 | 41 | bodyParts.forEach(function (b) { 42 | b.copy(body, copied, 0) 43 | copied += b.length 44 | }) 45 | 46 | try { 47 | body = JSON.parse(body.toString() || '{}'); 48 | } catch (err) { 49 | return callback('Failed to parse github response'); 50 | } 51 | 52 | var errorStatuses = [422, 400, 401, 404, 403] 53 | if (body.message && _.indexOf(errorStatuses, status) != -1) { 54 | return callback('GitHub error: ' + body.message); 55 | } 56 | 57 | callback(null, body, response.headers, status) 58 | }) 59 | }) 60 | 61 | request.end(); 62 | 63 | request.on('error', function (e) { 64 | callback('GitHub is currently not available. Please try later') 65 | }); 66 | } 67 | 68 | module.exports.requestRepos = function (path, params, callback) { 69 | module.exports.request(path, params, function (error, body, headers) { 70 | if (error) return callback(error) 71 | 72 | var linkHeader = headers['Link'] || headers['link'], 73 | links = {} 74 | 75 | if (linkHeader) { 76 | var parts = linkHeader.split(','); 77 | 78 | // Parse each part into a named link 79 | _.each(parts, function (p) { 80 | var section = p.split(';'); 81 | if (section.length != 2) return 82 | 83 | var url = section[0].replace(/<(.*)>/, '$1').trim().replace('https://api.github.com/', ''); 84 | var name = section[1].replace(/rel="(.*)"/, '$1').trim().toLowerCase(); 85 | links[name] = url; 86 | }); 87 | } 88 | 89 | var items = body.items || body 90 | if (!_.isArray(items)) items = [items] 91 | 92 | callback(null, _.map(items, function (repo) { 93 | repo = Project.parseGitHubData(repo) 94 | return repo 95 | }), links) 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /app/utils/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 10/17/13 4 | * Time: 3:16 PM 5 | */ 6 | var winston = require('winston'), 7 | cfg = require('../../config') 8 | 9 | if (!cfg.isDev) { 10 | winston.remove(winston.transports.Console) 11 | winston.add(winston.transports.Console, { 12 | json: false, 13 | timestamp: true, 14 | handleExceptions: true 15 | }) 16 | winston.add(winston.transports.DailyRotateFile, { 17 | filename: cfg.logsDir + '/iloveopensource.log', 18 | datePattern: '.yyyy-MM-dd', 19 | handleExceptions: true 20 | }) 21 | } 22 | 23 | 24 | module.exports = winston; -------------------------------------------------------------------------------- /app/utils/mailer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 10/3/13 4 | * Time: 5:40 PM 5 | */ 6 | var cfg = require('../../config'), 7 | async = require('async'), 8 | _ = require('lodash'), 9 | ejs = require('ejs'), 10 | fs = require('fs'), 11 | logger = require('winston'), 12 | templates = {} 13 | 14 | //TODO: add mail queue 15 | module.exports.fillTemplates = function (dir, callback) { 16 | if (!_.isEmpty(templates)) return 17 | logger.info('Reading email templates from', dir) 18 | 19 | fs.readdir(dir, function (err, files) { 20 | if (err) return callback(err); 21 | 22 | async.each(files, function (file, cb) { 23 | fs.readFile(dir + file, 'utf-8', function (err, data) { 24 | templates[file.replace('.ejs', '')] = data 25 | cb.apply(this, arguments) 26 | }); 27 | }, callback); 28 | }); 29 | } 30 | 31 | module.exports.send = function (templateName, subject, to, data, callback) { 32 | var template = templates[templateName] 33 | if (!template) return callback('No such template') 34 | 35 | cfg.emails.transport.sendMail({ 36 | from: cfg.emails.from, 37 | subject: subject, 38 | to: to, 39 | html: ejs.render(template, _.merge(data, {siteUrl: cfg.fullUrl(), _: _})) 40 | }, function (error) { 41 | error && console.error(error) 42 | callback(error ? 'Failed to send email' : null) 43 | }) 44 | } -------------------------------------------------------------------------------- /app/utils/socket.io.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/19/13 4 | * Time: 5:09 PM 5 | */ 6 | var instance, 7 | passportSocketIo = require('passport.socketio'), 8 | express = require('express'), 9 | cfg = require('../../config') 10 | 11 | module.exports = function (server, sessionStore) { 12 | if (instance) return instance 13 | 14 | var io = require('socket.io').listen(server); 15 | 16 | io.configure('production', function () { 17 | io.enable('browser client minification'); 18 | io.enable('browser client etag'); 19 | io.enable('browser client gzip'); 20 | io.set('transports', [ 21 | 'websocket', 22 | 'flashsocket', 23 | 'htmlfile', 24 | 'xhr-polling', 25 | 'jsonp-polling' 26 | ]); 27 | io.set('polling duration', 10); 28 | }); 29 | 30 | io.configure('development', function () { 31 | io.set('transports', ['websocket']); 32 | }); 33 | 34 | io.set('authorization', passportSocketIo.authorize({ 35 | cookieParser: express.cookieParser, 36 | key: 'connect.sid', 37 | secret: cfg.sessionSecret, 38 | store: sessionStore 39 | })); 40 | 41 | io.of('/projects-update/status').on('connection', function (socket) { 42 | if (!socket.handshake.user) return socket.emit('error', 'unauthorized!') 43 | 44 | socket.join(socket.handshake.user._id) 45 | 46 | socket.on('start', function() { 47 | var state = socket.handshake.user.projectsUpdater 48 | !state.updating && require('./updater')(socket.handshake.user) 49 | }) 50 | }); 51 | 52 | instance = io 53 | } -------------------------------------------------------------------------------- /app/utils/title-builder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/14/13 4 | * Time: 3:51 AM 5 | */ 6 | module.exports = function () { 7 | var manager = {}, 8 | result = [], 9 | title = 'Open Source Supporters', 10 | sep = ' / ' 11 | 12 | manager.add = function (string) { 13 | result.push(string) 14 | } 15 | 16 | manager.render = function () { 17 | var string = result.reverse().join(sep) 18 | result = [title] 19 | return string 20 | } 21 | 22 | manager.add(title) 23 | return manager 24 | }() 25 | -------------------------------------------------------------------------------- /app/utils/updater/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/19/13 4 | * Time: 11:20 AM 5 | */ 6 | var mongoose = require('mongoose'), 7 | async = require('async'), 8 | logger = require('winston'), 9 | io = require('../socket.io.js'), 10 | OrgsUpdater = require('./organizations.js'), 11 | ProjectsUpdater = require('./projects.js'), 12 | Q = require('q'), 13 | _ = require('lodash'), 14 | ioNamespace = '/projects-update/status' 15 | 16 | function UserUpdater(user) { 17 | if (!(this instanceof UserUpdater)) 18 | return new UserUpdater(user); 19 | 20 | this.user = user 21 | this.deferred = Q.defer(); 22 | this.reqestParams = { 23 | access_token: this.user.authToken 24 | } 25 | 26 | this.user.projectsUpdater.updated = false 27 | this.user.projectsUpdater.updating = true 28 | this.user.save(_.bind(function () { 29 | this.init() 30 | }, this)) 31 | 32 | return this.deferred.promise 33 | } 34 | 35 | UserUpdater.prototype.progress = function (desc) { 36 | this.deferred.notify(desc); 37 | io().of(ioNamespace).in(this.user._id).emit('progress', desc) 38 | } 39 | 40 | UserUpdater.prototype.finish = function () { 41 | var self = this 42 | this.updateUser('success', function () { 43 | io().of(ioNamespace).in(self.user._id).emit('done') 44 | self.deferred.resolve(); 45 | }) 46 | } 47 | 48 | UserUpdater.prototype.error = function (error) { 49 | var self = this 50 | logger.error(error.msg, error.error) 51 | this.updateUser('error', function () { 52 | io().of(ioNamespace).in(self.user._id).emit('error', error.msg) 53 | self.deferred.reject(error); 54 | }) 55 | } 56 | 57 | UserUpdater.prototype.updateUser = function (status, callback) { 58 | this.user.projectsUpdater.updated = true 59 | this.user.projectsUpdater.updating = false 60 | this.user.projectsUpdater.updatedAt = new Date() 61 | this.user.projectsUpdater.status = status 62 | this.user.save(callback) 63 | } 64 | 65 | UserUpdater.prototype.init = function () { 66 | var self = this 67 | 68 | async.parallel({ 69 | organizations: function (callback) { 70 | (new OrgsUpdater(self.user)).then(function (result) { 71 | callback(null, result) 72 | }, callback, _.bind(self.progress, self)); 73 | }, 74 | projects: function (callback) { 75 | (new ProjectsUpdater(self.user)).then(function (result) { 76 | callback(null, result) 77 | }, callback, _.bind(self.progress, self)); 78 | } 79 | }, function (error, result) { 80 | if (error) return self.error(error) 81 | self.finish() 82 | }) 83 | } 84 | 85 | module.exports = UserUpdater -------------------------------------------------------------------------------- /app/views/404.ejs: -------------------------------------------------------------------------------- 1 | <% titleBuilder.add('Not found') %> 2 | <% include parts/head.ejs %> 3 | 4 |
5 |

404

6 | 7 |

We're Sorry, but the page you're looking for cannot be found

8 |
9 | 10 | <% include parts/foot.ejs %> 11 | -------------------------------------------------------------------------------- /app/views/500.ejs: -------------------------------------------------------------------------------- 1 | <% titleBuilder.add('Server error') %> 2 | <% include parts/head.ejs %> 3 | 4 |
5 |

500

6 | 7 |

Oops, something gone wrong

8 |
9 | <% include parts/foot.ejs %> 10 | -------------------------------------------------------------------------------- /app/views/emails/comment-for-author.ejs: -------------------------------------------------------------------------------- 1 |

2 | <% if (user) { %> 3 | User 4 | <%= user.username %> 5 | <% } else { %> 6 | Anonymous 7 | <% } %> 8 | 9 | sent a comment your project: 10 | <%= project.owner.username %> / <%= project.name %> 11 |

12 | 13 |

14 | <%= message %> 15 |

-------------------------------------------------------------------------------- /app/views/emails/donate-request-maintainer.ejs: -------------------------------------------------------------------------------- 1 |

2 | <% if (user) { %> 3 | The GitHub user <%= user.username %> 4 | <% } else { %> 5 | Anonymous 6 | <% } %> 7 |  wants to make a donation to 8 |  <%= project.owner.username %> / <%= project.name %> on 9 |  <%= siteUrl %> 10 |

11 |

12 | Please visit <%= siteUrl %> to set how you would like to receive donations. Head to the Maintainers page, locate the repo and edit your donation preferences. 13 |

14 |

15 | If you don't want to receive donations then just leave a message in the 'Other' field. This way you're acknowledging the acknowledgement. 16 |

17 |

18 | If you don't want to receive any donations requests notifications just click here to unsubscribe. 19 |

20 |

21 | I LOVE OPEN SOURCE 22 |

23 | -------------------------------------------------------------------------------- /app/views/emails/donate-request-notify-maintainer.ejs: -------------------------------------------------------------------------------- 1 |

2 | <% if (totalCount == 1) { %> 3 | <% if (usersCount) { %> 4 | The GitHub user <%= users[0].username %> 5 | <% } else { %> 6 | Anonymous user 7 | <% } %> 8 | <% } else { %> 9 | <%= totalCount %> of our users 10 | <% } %> 11 | wants to make a donation to your project 12 | <%= project.owner.username %> / <%= project.name %> 13 | on <%= siteUrl %> 14 |

15 |

16 | Please visit 17 | <%= siteUrl %> to set how you would like to receive donations. Head to the Maintainers page, locate the repo and edit your donation preferences. 18 |

19 |

20 | If you don't want to receive donations then just leave a message in the 'Other' field. This way you're acknowledging the acknowledgement. 21 |

22 | <% if (usersCount) { %> 23 |

24 | Registered users: <% _.each(users, function(user, i) { %> 25 | <%= user.username %><%= ((i+1) == usersCount ? '' : ', ') %> 26 | <% }) %> 27 |

28 | <% } %> 29 | <% if (anonsCount) { %> 30 |

31 | Anonymous users: <%= anonsCount %> 32 |

33 | <% } %> 34 |

35 | I LOVE OPEN SOURCE 36 |

37 | -------------------------------------------------------------------------------- /app/views/emails/donate-request-satisfied.ejs: -------------------------------------------------------------------------------- 1 |

2 | You recently wanted to make a donation to <%= project.owner.username %> / <%= project.name %> on <%= siteUrl %> 3 |

4 | 5 |

6 | The maintainer of that repo has now set the donation preferences, so now would be a great time to go and make your donation. 7 |

8 | 9 |

10 | Click here: <%= project.owner.username %> / <%= project.name %> 11 |

12 | -------------------------------------------------------------------------------- /app/views/emails/donate-request-support.ejs: -------------------------------------------------------------------------------- 1 |

2 | <% if (user) { %> 3 | The GitHub user <%= user.username %> 4 | <% } else { %> 5 | Anonymous 6 | <% } %> 7 |  wants to make a donation to 8 |  <%= project.owner.username %> / <%= project.name %> 9 |

10 |

11 | <% if (owner) { %> 12 | Project owned by  13 | <% if (project.owner.type.toLowerCase() == 'user') { %> 14 | our user: <%= project.owner.username %> 15 | <% } else { %> 16 | organization: <%= project.owner.username %> 17 | <% } %> 18 |  which didn't specify email! 19 | <% } else { %> 20 | Owner not registered yet, or not claimed this project 21 | <% } %> 22 |

23 |

24 | GitHub project link: <%= project.owner.username %> / <%= project.name %> 25 |

26 |

27 | You could check all donation requests here <%= siteUrl %>/service/donation-requests 28 |

29 | -------------------------------------------------------------------------------- /app/views/index.ejs: -------------------------------------------------------------------------------- 1 | <% var noHead = true %> 2 | <% var forkMe = true %> 3 | <% var bodyClass = 'home' %> 4 | <% include parts/head.ejs %> 5 |
6 | 7 | 8 |

Simple Recognition for Open Source Developers

9 | 10 |
11 |
12 |

Maintainers

13 | 14 |

All you do is sign in with GitHub and then tell us how you would like people to support your efforts (we've got various options as standard). Then grab the button widget and drop it into your README file or your site.

15 |
16 | 17 |
18 |

Supporters

19 | 20 |

If you use Open Source software, then giving vocal support really makes a difference to that Open Source Project as well as the open source community as a whole. We make it really easy for you to say what projects you use in your applications.

21 | 22 |
23 |
24 | 25 |

Watch a video of how I Love Open Source works.

26 | 27 |
28 | <% if (!user) { %> 29 | 30 | 31 | Start here...with your GitHub account 32 | 33 | <% } else { %> 34 | 35 | Welcome back <%= user.username %>! 36 | 37 | <% } %> 38 |
39 |
40 | 41 | <% include parts/foot.ejs %> 42 | -------------------------------------------------------------------------------- /app/views/maintainer-editor.ejs: -------------------------------------------------------------------------------- 1 | <% titleBuilder.add(user.username) %> 2 | <% titleBuilder.add('Repo Editor') %> 3 | <% var forkMe = true %> 4 | 5 | <% include parts/head.ejs %> 6 |
7 |
8 |
9 |
10 |
11 | 15 |
16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
Loading
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | <% include modals/index.ejs %> 32 | 33 | 34 | 35 | 42 | 43 | 44 | <% include parts/foot.ejs %> -------------------------------------------------------------------------------- /app/views/modals/email-to-author.ejs: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /app/views/modals/index.ejs: -------------------------------------------------------------------------------- 1 | <% include email-to-author.ejs %> 2 | <% include note-from-author.ejs %> 3 | <% include need-contribution-ways.ejs %> 4 | -------------------------------------------------------------------------------- /app/views/modals/need-contribution-ways.ejs: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /app/views/modals/note-from-author.ejs: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /app/views/organization.ejs: -------------------------------------------------------------------------------- 1 | <% titleBuilder.add('Organizations') %> 2 | <% titleBuilder.add(org.name) %> 3 | 4 | <% include parts/head.ejs %> 5 | 6 |
7 |
8 |
9 | 10 | 11 | 12 | 13 |
14 |

<%= org.name %>

15 | 16 |
17 | Administrators: <% _.each(org.admins, function(user) { %> 18 | <%= user.username %> 19 | <% }) %> 20 |
21 |
22 |
23 | 24 | <% if (!isLoggedIn) { %> 25 | <% var welcomeName = org.name %> 26 | <% include parts/welcome %> 27 | <% } %> 28 |
29 | 30 | 44 | 45 |
46 |
47 | <% if (!support.length) { %> 48 |

This organization hasn't added any projects yet.

49 | <% } else { %> 50 |
51 |
52 |
53 |
54 |
Click below to donate
55 |
56 | <% _.each(support, function(entry) { %> 57 | <% var project = entry.project %> 58 | <% project.support = _.omit(entry, 'project') %> 59 | <% include parts/project-row %> 60 | <% }) %> 61 |
62 | <% } %> 63 |
64 |
65 |
66 |
67 |
68 |
69 |
Click below to donate
70 |
71 | <% _.each(projects, function(entry) { %> 72 | <% var project = entry %> 73 | <% project.support = {} %> 74 | <% include parts/project-row %> 75 | <% }) %> 76 |
77 |
78 |
79 | 80 |
81 | 82 | 83 | 89 | 90 | 91 | <% include modals/index.ejs %> 92 | <% include parts/foot.ejs %> -------------------------------------------------------------------------------- /app/views/parts/contributions.ejs: -------------------------------------------------------------------------------- 1 | <% donateMethods = donateMethods || {} %> 2 | 48 | -------------------------------------------------------------------------------- /app/views/parts/foot.ejs: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 36 | <% if (typeof isNewUser != 'undefined') { %> 37 | 40 | <% } %> 41 | 42 | -------------------------------------------------------------------------------- /app/views/parts/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- titleBuilder.render() %> 8 | 9 | 10 | <% if (this.isLoggedIn) { %> 11 | 19 | <% } else { %> 20 | 23 | <% } %> 24 | 25 | 26 |
27 | <% if (!this.noHead) { %> 28 | <% include header.ejs %> 29 | <% } %> 30 | -------------------------------------------------------------------------------- /app/views/parts/header.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/parts/project-row.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |

6 | <% if (project.owner.user) { %> 7 | <%=project.owner.username %> 8 | <% } else if (project.owner.org) { %> 9 | <%=project.owner.username %> 10 | <% } else { %> 11 | <%=project.owner.username %> 12 | <% } %> 13 | / 14 | 15 | <%=project.name %> 16 | 17 |

18 | 19 |
20 | <% if (project.description) { %> 21 | <%- project.description %> 22 | <% } %> 23 |
24 |
25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 | <% var donateMethods = project.donateMethods.toObject(); %> 33 | <% var projectUrl = project.url; %> 34 | <% var projectId = project._id; %> 35 | <% include contributions.ejs %> 36 |
-------------------------------------------------------------------------------- /app/views/parts/welcome.ejs: -------------------------------------------------------------------------------- 1 |
2 |

3 | These are the Open Source projects that are used by <%= welcomeName %>. 4 | If you want, you may make a donation to any of the projects by clicking in the right hand column. 5 |

6 | 7 |

8 | Please feel free to make a donation to this OS project (on the left) or the OS projects it uses (below). 9 |

10 | 11 |

12 | <% if (typeof welcomeIsProject == 'undefined') { %> 13 | Want to list the projects you use in your project or get support for your own project? Then just sign in. 14 | <% } else { %> 15 | Why not join I Love Open Source yourself and help generate awareness and donations for yourself and other Open Source developers. Just sign in 16 | <% } %> 17 |

18 | 19 | 23 |
24 | 25 | -------------------------------------------------------------------------------- /app/views/project.ejs: -------------------------------------------------------------------------------- 1 | <% titleBuilder.add(project.name) %> 2 | <% titleBuilder.add(project.owner.username) %> 3 | 4 | <% include parts/head.ejs %> 5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 |
13 |

14 | 15 | <%=project.name %> 16 | 17 |

18 | 19 |

20 | Owner: 21 | <% if (project.owner.user) { %> 22 | <%=project.owner.username %> 23 | <% } else if (project.owner.org) { %> 24 | <%=project.owner.username %> 25 | <% } else { %> 26 | <%=project.owner.username %> 27 | <% } %> 28 |

29 |
30 | 31 | 46 | 47 |
48 | 49 | <% if (!isLoggedIn) { %> 50 | <% var welcomeName = project.name %> 51 | <% var welcomeIsProject = true %> 52 | <% include parts/welcome %> 53 | <% } %> 54 |
55 | 56 | <% if (supporting.length) { %> 57 |
58 |
59 |
Open Source projects used
60 |
61 |
Click below to donate
62 |
63 | <% _.each(supporting, function(entry) { %> 64 | <% var project = entry.project %> 65 | <% project.support = _.omit(entry, 'project') %> 66 | <% include parts/project-row %> 67 | <% }) %> 68 |
69 | <% } %> 70 |
71 | <% include modals/index.ejs %> 72 | 73 | 74 | 81 | <% include parts/foot.ejs %> 82 | -------------------------------------------------------------------------------- /app/views/registration.ejs: -------------------------------------------------------------------------------- 1 | <% var noHead = true %> 2 | <% var bodyClass = 'home' %> 3 | <% include parts/head.ejs %> 4 |
5 | 6 | 7 | 8 |
9 | 10 |

We are loading your repositories from GitHub, it will took few seconds...

11 |
12 |
13 |
14 | 15 | 16 | 37 | <% include parts/foot.ejs %> -------------------------------------------------------------------------------- /app/views/repo-editor.ejs: -------------------------------------------------------------------------------- 1 | <% titleBuilder.add(user.username) %> 2 | <% titleBuilder.add('Repo Editor') %> 3 | <% var forkMe = true %> 4 | 5 | <% include parts/head.ejs %> 6 |
7 | 8 |
9 |
10 |
11 | Loading 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | <% include modals/index.ejs %> 23 | 24 | 25 | 26 | 35 |
36 | 37 | <% include parts/foot.ejs %> -------------------------------------------------------------------------------- /app/views/service/donation-requests.ejs: -------------------------------------------------------------------------------- 1 | <% titleBuilder.add('Services') %> 2 | <% titleBuilder.add('Donation Requests') %> 3 | <% include ../parts/head.ejs %> 4 |
5 | 6 |
7 |
8 | 9 | 10 | 17 | <% include ../parts/foot.ejs %> 18 | -------------------------------------------------------------------------------- /app/views/settings.ejs: -------------------------------------------------------------------------------- 1 | <% titleBuilder.add('Settings') %> 2 | 3 | <% include parts/head.ejs %> 4 |
5 | 6 | <% var img = siteUrl + '/images/logo-lightbg.png' %> 7 | <% var link = siteUrl +'/users/' + loggedUser.username %> 8 | <% var alt = 'I Love Open Source' %> 9 | 10 |
11 |
12 | 13 |
14 | Embed 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 |

Copy and paste this into your GitHub README.md file to attract Supporters to the "I Love Open Source" movement.

23 |
24 |
25 | 26 |
27 | 28 | 29 |
30 | 31 | 32 |

Copy and paste this into your web pages to attract Supporters to the “I Love Open Source” movement.

33 |
34 |
35 |
36 | 37 |
38 | Contact Information 39 |
40 | 41 | 42 |
43 | 44 |
45 | 46 |
Saved!
47 |
48 | 49 |
50 | 51 | 52 |
53 | 54 |
55 | 56 |
Saved!
57 |
58 |
59 | 60 |
61 |
62 | 63 | 64 | 65 | 72 |
73 | 74 | <% include parts/foot.ejs %> 75 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-source-supporter", 3 | "version": "0.0.0", 4 | "ignore": [ 5 | "**/.*", 6 | "node_modules", 7 | "bower_components", 8 | "test", 9 | "tests" 10 | ], 11 | "dependencies": { 12 | "jquery": "~2.0.3", 13 | "lodash": "~1.3.1", 14 | "toastr": "latest", 15 | "requirejs": "~2.1.8", 16 | "text": "~2.0.10", 17 | "backbone": "~1.0.0", 18 | "json3": "~3.2.5", 19 | "requirejs-tpl": "*", 20 | "momentjs": "~2.2.1" 21 | }, 22 | "exportsOverride": { 23 | "toastr": { 24 | "js": "toastr.js", 25 | "css": "toastr.css" 26 | }, 27 | "jquery": { 28 | "js": "jquery.js" 29 | }, 30 | "momentjs": { 31 | "js": "moment.js" 32 | }, 33 | "backbone": { 34 | "js": "backbone.js" 35 | }, 36 | "json3": { 37 | "js": "lib/json3.js" 38 | }, 39 | "requirejs-tpl": { 40 | "js": "tpl.js" 41 | }, 42 | "text": { 43 | "js": "text.js" 44 | }, 45 | "lodash": { 46 | "js": "lodash.js" 47 | }, 48 | "requirejs": { 49 | "js": "require.js" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | require 'capistrano/ext/multistage' 2 | 3 | set :application, "iloveopensource" 4 | set :repository, "https://github.com/codio/iloveopensource.git" 5 | set :scm, :git 6 | set :main_js, "app.js" 7 | set :use_sudo, false 8 | set :envStr, 'NODE_ENV=production' 9 | set :user, 'deployer' 10 | set :normalize_asset_timestamps, false 11 | 12 | # Setup stages 13 | set :stages, %w(production staging) 14 | set :default_stage, "staging" 15 | 16 | set :keep_releases, 3 17 | 18 | # Deploys the current branch 19 | set(:current_branch) { `git branch --no-color`.match(/\*\s(.+)\n/)[1] || raise("Couldn't determine current branch") } 20 | set :branch, defer { current_branch } unless exists?(:branch) 21 | 22 | namespace :deploy do 23 | before 'deploy:start', 'deploy:npm_install' 24 | before 'deploy:restart', 'deploy:npm_install' 25 | after 'deploy:npm_install', 'deploy:grunt' 26 | after 'deploy:create_symlink', 'deploy:symlink_config' 27 | 28 | 29 | desc "START the servers" 30 | task :start, :roles => :app, :except => { :no_release => true } do 31 | run "#{envStr} forever start #{current_path}/#{main_js}" 32 | end 33 | 34 | desc "STOP the servers" 35 | task :stop, :roles => :app, :except => { :no_release => true } do 36 | run "forever stop #{current_path}/#{main_js}" 37 | end 38 | 39 | desc "RESTART the servers" 40 | task :restart, :roles => :app, :except => { :no_release => true } do 41 | run "#{envStr} forever restart #{current_path}/#{main_js}" 42 | end 43 | 44 | task :npm_install, :roles => :app, :except => { :no_release => true } do 45 | run "cd #{current_path} && npm install" 46 | end 47 | 48 | task :grunt, :roles => :app, :except => { :no_release => true } do 49 | run "cd #{current_path} && grunt update" 50 | end 51 | 52 | task :symlink_config, :roles => :app, :except => { :no_release => true } do 53 | run "ln -s #{applicationdir}/shared/config.production.js #{current_path}/config/config.production.js" 54 | end 55 | 56 | task :tail do 57 | resp = capture "forever logs | grep #{applicationdir}/current/#{main_js}" 58 | log = resp.split(" ").last 59 | log.gsub!("\e[35m", "") 60 | log.gsub!("\e[39m", "") 61 | run "tail -f #{log}" 62 | end 63 | end -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | server 'iloveopensource.io', :app, :web, :db, :primary => true 2 | set :applicationdir, "/var/www/#{application}" 3 | set :deploy_to, applicationdir 4 | -------------------------------------------------------------------------------- /config/deploy/staging.rb: -------------------------------------------------------------------------------- 1 | server 'staging.iloveopensource.io', :app, :web, :db, :primary => true 2 | set :applicationdir, "/var/www/staging.#{application}" 3 | set :deploy_to, applicationdir 4 | -------------------------------------------------------------------------------- /config/example.config.js: -------------------------------------------------------------------------------- 1 | //example config put it in same folder with name config..js (config.development.js by default) 2 | var config = module.exports = {}; 3 | //override any config option here 4 | config.hostname = 'your hostname'; 5 | config.github = { 6 | clientId: '', 7 | clientSecret: '' 8 | } 9 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/5/13 4 | * Time: 11:37 AM 5 | This is example config file, 6 | for saving actual settings place config..js 7 | in this folder on your server 8 | */ 9 | var _ = require('lodash'), 10 | fs = require('fs'), 11 | path = require('path'), 12 | nodemailer = require("nodemailer") 13 | 14 | var config = {}; 15 | //config.env used to quickly get environment app wide 16 | config.env = process.env.NODE_ENV || 'development'; 17 | config.isDev = config.env === 'development'; 18 | 19 | //config.logsDir absolute path for directory with logs, be sure it is writable 20 | config.logsDir = '/var/log/iloveopensource'; 21 | //config.hostname used to create site links 22 | config.hostname = 'www.iloveopensource.io'; 23 | //config.isHttps used to create site links 24 | config.isHttps = false; 25 | //config.usePort used to create site links 26 | config.usePort = false; 27 | //config.mongodb connection Uri string 28 | config.mongodbUri = 'mongodb://localhost/ilos' 29 | //config.github app settings 30 | config.github = { 31 | clientId: '', 32 | clientSecret: '' 33 | } 34 | //config.emails.from - name of sender 35 | //config.emails.to - email of support 36 | //config.emails.transport - nodemailer email transport 37 | config.emails = { 38 | from: 'robo@' + config.hostname, 39 | to: 'support@codio.com', 40 | transport: nodemailer.createTransport('sendmail', { 41 | path: '/usr/sbin/sendmail', 42 | args: ['-i', '-t'] 43 | }) 44 | } 45 | //config.sessionSecret to hash sessions 46 | config.sessionSecret = 's,dfjsklfj3k45j34k5kjLKj87093476ukvj jlk'; 47 | //config.port on which app should run 48 | config.port = '5000'; 49 | //config.port on which app should run 50 | config.jsPath = '/js/' 51 | 52 | config.fullUrl = function () { 53 | return 'http' 54 | + (this.isHttps ? 's' : '') 55 | + '://' 56 | + this.hostname 57 | + (this.usePort ? ':' + this.port : '') 58 | } 59 | 60 | config.path = path.join(__dirname, 'config.' + config.env + '.js') 61 | 62 | if (fs.existsSync(config.path)) { 63 | config = _.extend({}, config, require(config.path)) 64 | } 65 | 66 | module.exports = config -------------------------------------------------------------------------------- /less/main.less: -------------------------------------------------------------------------------- 1 | @import "./vendors/font-awesome/font-awesome"; 2 | @import "./vendors/bootstrap/bootstrap"; 3 | @import "./parts/font"; 4 | @import (less) "../public/js/vendors/toastr/css/toastr.css"; 5 | 6 | 7 | // Custom variables 8 | // ---------------- 9 | 10 | @FontAwesomePath: "/fonts"; 11 | 12 | @gray-lighter: darken(#FFF, 13.5%); // #222 13 | @gray-light: darken(#FFF, 20%); // #333 14 | @gray: darken(#FFF, 33.5%); // #555 15 | @gray-dark: darken(#FFF, 60%); // #999 16 | @gray-darker: darken(#FFF, 93.5%); // #eee 17 | 18 | @brand-info: #59BAAF; 19 | @body-bg: #212B37; 20 | @text-color: @gray-lighter; 21 | @link-color: @brand-info; 22 | @link-hover-color: lighten(@link-color, 15%); 23 | 24 | @font-family-sans-serif: "Quicksand", "Helvetica Neue", Helvetica, Arial, sans-serif; 25 | 26 | @nav-link-hover-bg: transparent; 27 | @nav-tabs-border-color: #000; 28 | @nav-tabs-link-hover-border-color: transparent; 29 | @nav-tabs-active-link-hover-border-color: transparent; 30 | 31 | @badge-bg: #000; 32 | @badge-font-weight: 400; 33 | 34 | @table-bg-accent: lighten(@body-bg, 3%); // for striping 35 | @table-bg-hover: lighten(@body-bg, 5%); 36 | 37 | @legend-border-color: #000; 38 | @legend-color: #ddd; 39 | @input-color: #666; 40 | 41 | @import "./base/body"; 42 | @import "./parts/profile-header"; 43 | @import "./parts/help-overlay"; 44 | @import "./parts/forms"; 45 | @import "./parts/repos-list"; 46 | @import "./pages/repo-selector"; 47 | @import "./pages/maintainers"; 48 | @import "./pages/project"; 49 | @import "./pages/account"; 50 | @import "./pages/donation-requests"; 51 | @import "./pages/index"; -------------------------------------------------------------------------------- /less/pages/account.less: -------------------------------------------------------------------------------- 1 | #user-repos { 2 | .orgs { 3 | padding: 1em 1em 0; 4 | font-size: 1.3em; 5 | .link-list; 6 | } 7 | 8 | .admins { 9 | .link-list; 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /less/pages/donation-requests.less: -------------------------------------------------------------------------------- 1 | #donation-requests { 2 | 3 | .search { 4 | 5 | .loading { 6 | display: none; 7 | } 8 | 9 | .switcher { 10 | cursor: pointer; 11 | font-size: 1.1em; 12 | padding-top: .4em; 13 | 14 | &.active { 15 | .toggler { 16 | text-shadow: 0 0 5px #c61a1a; 17 | color: #f00; 18 | } 19 | } 20 | 21 | .toggler { 22 | color: #000; 23 | 24 | &:after { 25 | .icon(@off); 26 | } 27 | } 28 | } 29 | } 30 | 31 | .header { 32 | font-size: .8em; 33 | } 34 | 35 | .requests .row:hover { 36 | background-color: @table-bg-hover; 37 | } 38 | 39 | .row { 40 | padding: 20px 10px; 41 | border-bottom: 1px solid #222; 42 | 43 | &:nth-child(even) { 44 | background-color: @table-bg-accent; 45 | } 46 | 47 | time { 48 | 49 | &.created { 50 | color: darken(#DDD, 20); 51 | font-size: .9em; 52 | } 53 | } 54 | 55 | .email { 56 | display: none; 57 | } 58 | 59 | .name { 60 | line-height: 2.3em; 61 | } 62 | 63 | .status { 64 | width: 2em; 65 | height: 2em; 66 | font-size: 1.5em; 67 | line-height: 2em; 68 | color: #000; 69 | 70 | &:last-child { 71 | margin-left: 1em; 72 | } 73 | 74 | &.active { 75 | color: #ff9900; 76 | } 77 | 78 | &.methods-set:after { 79 | .icon(@usd) 80 | } 81 | 82 | &.notified:after { 83 | .icon(@envelope) 84 | } 85 | } 86 | } 87 | 88 | .supporters { 89 | display: none; 90 | .clearfix; 91 | padding: 30px 40px 30px; 92 | clear: both; 93 | } 94 | } -------------------------------------------------------------------------------- /less/pages/index.less: -------------------------------------------------------------------------------- 1 | body.home { 2 | 3 | #wrap { 4 | background: transparent url('/images/bg-highlight.jpg') no-repeat center top; 5 | 6 | #logo { 7 | margin: 80px auto 0; 8 | text-align: center; 9 | display: block; 10 | } 11 | 12 | #tagline { 13 | margin: 14px auto 70px; 14 | text-align: center; 15 | font-size: 18px; 16 | font-weight: 300; 17 | color: #fff; 18 | text-shadow: 0 2px 3px #000; 19 | text-transform: uppercase; 20 | } 21 | 22 | #video-line { 23 | margin: 50px auto 0; 24 | text-align: center; 25 | font-size: 15px; 26 | font-weight: 300; 27 | 28 | a { 29 | text-decoration: underline; 30 | } 31 | } 32 | 33 | section { 34 | h2 { 35 | font: 46px 'Quicksand'; 36 | font-weight: 300; 37 | color: @brand-info; 38 | margin: 0 0 18px; 39 | padding: 0 26px; 40 | } 41 | 42 | p { 43 | line-height: 1.7em; 44 | font-size: 15px; 45 | font-weight: 300; 46 | padding: 0 26px; 47 | } 48 | 49 | // Maintainers 50 | &:first-child { 51 | text-align: right; 52 | border-right: 1px solid #666; 53 | } 54 | } 55 | 56 | #start { 57 | margin: 20px auto 0px; 58 | text-align: center; 59 | 60 | #login, #welcome { 61 | text-align: left; 62 | padding: 12px 49px 6px; 63 | .clearfix; 64 | 65 | i { 66 | float: left; 67 | font-size: 54px; 68 | margin: 0 20px 6px 0; 69 | } 70 | 71 | span { 72 | float: left; 73 | display: block; 74 | font-size: 30px; 75 | font-weight: 400; 76 | line-height: 0.85em; 77 | margin-top: 5px; 78 | 79 | small { 80 | font-size: 14px; 81 | display: block; 82 | font-weight: 300; 83 | } 84 | } 85 | } 86 | 87 | #welcome { 88 | padding: 20px 70px; 89 | font-size: 18px; 90 | 91 | i { 92 | font-size: 20px; 93 | float: none; 94 | margin-left: 6px; 95 | position: relative; 96 | top: 1px; 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /less/pages/maintainers.less: -------------------------------------------------------------------------------- 1 | #maintainers { 2 | position: relative; 3 | 4 | .show-help { 5 | position: absolute; 6 | top: -10px; 7 | right: 15px; 8 | cursor: pointer; 9 | font-size: 2em; 10 | 11 | &:after { 12 | .icon(@question-sign) 13 | } 14 | } 15 | 16 | .repo { 17 | .settings { 18 | display: none; 19 | } 20 | } 21 | 22 | .projects-updater { 23 | margin: 20px 0; 24 | } 25 | } -------------------------------------------------------------------------------- /less/pages/project.less: -------------------------------------------------------------------------------- 1 | #page-project { 2 | 3 | .profile-header { 4 | .avatar { 5 | &:after { 6 | .icon(@github-sign); 7 | font-size: 92px; 8 | position: absolute; 9 | position: absolute; 10 | left: 0px; 11 | display: block; 12 | vertical-align: top; 13 | top: -25px; 14 | } 15 | } 16 | 17 | .donate { 18 | clear: both; 19 | padding: 20px 0 0; 20 | 21 | .btn { 22 | font-size: 16px; 23 | } 24 | 25 | .methods { 26 | display: none; 27 | } 28 | } 29 | } 30 | 31 | .nav-tabs { 32 | border-bottom: 0px solid #000 33 | } 34 | 35 | .repos-list { 36 | &.anonymous { 37 | padding-top: 0; 38 | } 39 | 40 | .header .col.repo-name { 41 | color: #AAA; 42 | font-size: 1.3em; 43 | padding-left: 0; 44 | line-height: 1; 45 | min-height: 1em; 46 | } 47 | } 48 | 49 | .users { 50 | padding: 20px; 51 | 52 | h3 { 53 | margin-left: -20px; 54 | } 55 | 56 | .col { 57 | box-sizing: border-box; 58 | width: 33%; 59 | float: left; 60 | padding: 10px; 61 | 62 | &:nth-child(odd) { 63 | border-top: 0; 64 | border-bottom: 0; 65 | } 66 | 67 | a { 68 | &:after { 69 | content: ','; 70 | padding-right: 1.2em; 71 | 72 | } 73 | 74 | &:last-child { 75 | &:after { 76 | content: ''; 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /less/pages/repo-selector.less: -------------------------------------------------------------------------------- 1 | #repos-selector { 2 | 3 | .header { 4 | position: relative; 5 | min-height: 4em; 6 | 7 | .show-help { 8 | position: absolute; 9 | top: 0; 10 | right: 5px; 11 | cursor: pointer; 12 | font-size: 2em; 13 | 14 | &:after { 15 | .icon(@question-sign) 16 | } 17 | } 18 | 19 | .support-selector { 20 | font-size: 1.3em; 21 | padding: 0 .55em; 22 | 23 | .dropdown-toggle { 24 | font-size: 1em; 25 | margin-top: -2px; 26 | } 27 | 28 | .dropdown-menu { 29 | background: #222; 30 | max-height: 400px; 31 | min-width: 400px; 32 | overflow: hidden; 33 | overflow-y: auto; 34 | border: 1px solid #1C1C1C; 35 | border-radius: 10px; 36 | padding: 20px 24px; 37 | 38 | .dropdown-header { 39 | font-size: 1.2em; 40 | padding-left: 0px; 41 | padding-bottom: 5px; 42 | color: #fff; 43 | 44 | &:not(:first-of-type) { 45 | padding-top: 16px; 46 | } 47 | } 48 | 49 | & > li { 50 | font-weight: bold; 51 | & > a { 52 | padding: 2px 20px; 53 | font-weight: bold; 54 | position: relative; 55 | color: darken(#fff, 10); 56 | 57 | &.heart { 58 | &:after { 59 | .icon(@heart); 60 | color: #ff9900; 61 | position: absolute; 62 | left: 0em; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | .is-empty.message { 72 | text-align: center; 73 | padding-top: 30px; 74 | font-size: 22px; 75 | } 76 | 77 | .nav-tabs { 78 | li { 79 | 80 | .icon-spinner { 81 | display: none; 82 | } 83 | 84 | &.disabled { 85 | .icon-spinner { 86 | display: inline; 87 | } 88 | } 89 | 90 | &.selected-repos { 91 | margin-right: 2em; 92 | a { 93 | font-weight: 700; 94 | text-transform: uppercase; 95 | } 96 | } 97 | 98 | .btn { 99 | margin-left: 20px; 100 | 101 | a { 102 | color: #fff; 103 | text-decoration: none; 104 | } 105 | } 106 | } 107 | } 108 | 109 | .share { 110 | display: none; 111 | margin-bottom: 4em; 112 | border-bottom: 1px solid #000; 113 | 114 | .help { 115 | font-size: 1.1em; 116 | color: #FFF; 117 | padding: 20px 15px; 118 | } 119 | 120 | .form-group { 121 | .clearfix; 122 | margin-bottom: 1em; 123 | } 124 | } 125 | 126 | .search-form { 127 | margin: 20px 0; 128 | 129 | input { 130 | width: 30em; 131 | display: inline-block; 132 | } 133 | 134 | input, button { 135 | margin-right: 2em; 136 | } 137 | } 138 | 139 | .query-too-short { 140 | display: none; 141 | } 142 | } 143 | 144 | 145 | -------------------------------------------------------------------------------- /less/parts/font.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Quicksand'; 3 | src: url('/fonts/quicksand-bold-webfont.eot'); 4 | src: url('/fonts/quicksand-bold-webfont.eot?#iefix') format('embedded-opentype'), 5 | url('/fonts/quicksand-bold-webfont.woff') format('woff'), 6 | url('/fonts/quicksand-bold-webfont.ttf') format('truetype'), 7 | url('/fonts/quicksand-bold-webfont.svg#quicksandbold') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | 11 | } 12 | 13 | 14 | 15 | 16 | @font-face { 17 | font-family: 'Quicksand'; 18 | src: url('/fonts/quicksand-light-webfont.eot'); 19 | src: url('/fonts/quicksand-light-webfont.eot?#iefix') format('embedded-opentype'), 20 | url('/fonts/quicksand-light-webfont.woff') format('woff'), 21 | url('/fonts/quicksand-light-webfont.ttf') format('truetype'), 22 | url('/fonts/quicksand-light-webfont.svg#quicksandlight') format('svg'); 23 | font-weight: normal; 24 | font-style: normal; 25 | 26 | } 27 | 28 | 29 | 30 | 31 | @font-face { 32 | font-family: 'Quicksand'; 33 | src: url('/fonts/quicksand-regular-webfont.eot'); 34 | src: url('/fonts/quicksand-regular-webfont.eot?#iefix') format('embedded-opentype'), 35 | url('/fonts/quicksand-regular-webfont.woff') format('woff'), 36 | url('/fonts/quicksand-regular-webfont.ttf') format('truetype'), 37 | url('/fonts/quicksand-regular-webfont.svg#quicksandregular') format('svg'); 38 | font-weight: normal; 39 | font-style: normal; 40 | 41 | } -------------------------------------------------------------------------------- /less/parts/forms.less: -------------------------------------------------------------------------------- 1 | form { 2 | legend { 3 | font-weight: 200; 4 | text-transform: uppercase; 5 | font-size: 16px; 6 | padding: 30px 0 8px; 7 | margin-bottom: 28px; 8 | } 9 | 10 | .saved { 11 | display: none; 12 | color: #468847; 13 | line-height: 1.428571429; 14 | padding-top: 6px; 15 | text-align: left; 16 | } 17 | 18 | .col-lg-2 .saved { 19 | text-align: right; 20 | } 21 | 22 | .form-group { 23 | .help-block { 24 | color: #ddd; 25 | font-weight: 200; 26 | font-size: 12px; 27 | } 28 | 29 | .control-label { 30 | font-weight: 400; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /less/parts/help-overlay.less: -------------------------------------------------------------------------------- 1 | .help.layout { 2 | display: none; 3 | font-size: 15px; 4 | 5 | .helper { 6 | background: #222; 7 | border: 1px solid #1C1C1C; 8 | border-radius: 10px; 9 | padding: 20px 24px; 10 | margin: 20px; 11 | display: inline-block; 12 | 13 | .text { 14 | position: relative; 15 | padding-right: 440px; 16 | min-height: 300px; 17 | 18 | img { 19 | position: absolute; 20 | right: 0px; 21 | top: 0; 22 | } 23 | } 24 | } 25 | 26 | .hide-help { 27 | cursor: pointer; 28 | position: fixed; 29 | top: 90px; 30 | right: 20px; 31 | } 32 | } -------------------------------------------------------------------------------- /less/parts/profile-header.less: -------------------------------------------------------------------------------- 1 | .profile-header { 2 | .clearfix; 3 | margin: 20px 0; 4 | 5 | .info { 6 | .clearfix; 7 | 8 | position: relative; 9 | float: left; 10 | width: 40%; 11 | min-height: 80px; 12 | 13 | .avatar { 14 | position: absolute; 15 | overflow: hidden; 16 | left: 0; 17 | top: 0; 18 | text-decoration: none; 19 | display: block; 20 | width: 80px; 21 | height: 80px; 22 | 23 | &:hover { 24 | text-decoration: none; 25 | } 26 | } 27 | 28 | .data { 29 | margin-left: 100px; 30 | } 31 | } 32 | 33 | .welcome { 34 | float: right; 35 | width: 60%; 36 | padding: 0 20px 20px; 37 | font-size: 1.1em; 38 | color: #FFF; 39 | 40 | .login { 41 | margin-top: 5px; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /less/vendors/bootstrap/alerts.less: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base styles 7 | // ------------------------- 8 | 9 | .alert { 10 | padding: @alert-padding; 11 | margin-bottom: @line-height-computed; 12 | border: 1px solid transparent; 13 | border-radius: @alert-border-radius; 14 | 15 | // Headings for larger alerts 16 | h4 { 17 | margin-top: 0; 18 | // Specified for the h4 to prevent conflicts of changing @headingsColor 19 | color: inherit; 20 | } 21 | // Provide class for links that match alerts 22 | .alert-link { 23 | font-weight: @alert-link-font-weight; 24 | } 25 | 26 | // Improve alignment and spacing of inner content 27 | > p, 28 | > ul { 29 | margin-bottom: 0; 30 | } 31 | > p + p { 32 | margin-top: 5px; 33 | } 34 | } 35 | 36 | // Dismissable alerts 37 | // 38 | // Expand the right padding and account for the close button's positioning. 39 | 40 | .alert-dismissable { 41 | padding-right: (@alert-padding + 20); 42 | 43 | // Adjust close link position 44 | .close { 45 | position: relative; 46 | top: -2px; 47 | right: -21px; 48 | color: inherit; 49 | } 50 | } 51 | 52 | // Alternate styles 53 | // 54 | // Generate contextual modifier classes for colorizing the alert. 55 | 56 | .alert-success { 57 | .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); 58 | } 59 | .alert-info { 60 | .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); 61 | } 62 | .alert-warning { 63 | .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); 64 | } 65 | .alert-danger { 66 | .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); 67 | } 68 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/badges.less: -------------------------------------------------------------------------------- 1 | // 2 | // Badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base classes 7 | .badge { 8 | display: inline-block; 9 | min-width: 10px; 10 | padding: 3px 7px; 11 | font-size: @font-size-small; 12 | font-weight: @badge-font-weight; 13 | color: @badge-color; 14 | line-height: @badge-line-height; 15 | vertical-align: baseline; 16 | white-space: nowrap; 17 | text-align: center; 18 | background-color: @badge-bg; 19 | border-radius: @badge-border-radius; 20 | 21 | // Empty badges collapse automatically (not available in IE8) 22 | &:empty { 23 | display: none; 24 | } 25 | } 26 | 27 | // Hover state, but only for links 28 | a.badge { 29 | &:hover, 30 | &:focus { 31 | color: @badge-link-hover-color; 32 | text-decoration: none; 33 | cursor: pointer; 34 | } 35 | } 36 | 37 | // Quick fix for labels/badges in buttons 38 | .btn .badge { 39 | position: relative; 40 | top: -1px; 41 | } 42 | 43 | // Account for counters in navs 44 | a.list-group-item.active > .badge, 45 | .nav-pills > .active > a > .badge { 46 | color: @badge-active-color; 47 | background-color: @badge-active-bg; 48 | } 49 | .nav-pills > li > a > .badge { 50 | margin-left: 3px; 51 | } 52 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/bootstrap.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.0 3 | * 4 | * Copyright 2013 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 by @mdo and @fat. 9 | */ 10 | 11 | // Core variables and mixins 12 | @import "variables.less"; 13 | @import "mixins.less"; 14 | 15 | // Reset 16 | @import "normalize.less"; 17 | @import "print.less"; 18 | 19 | // Core CSS 20 | @import "scaffolding.less"; 21 | @import "type.less"; 22 | @import "code.less"; 23 | @import "grid.less"; 24 | @import "tables.less"; 25 | @import "forms.less"; 26 | @import "buttons.less"; 27 | 28 | // Components 29 | @import "component-animations.less"; 30 | @import "glyphicons.less"; 31 | @import "dropdowns.less"; 32 | @import "button-groups.less"; 33 | @import "input-groups.less"; 34 | @import "navs.less"; 35 | @import "navbar.less"; 36 | @import "breadcrumbs.less"; 37 | @import "pagination.less"; 38 | @import "pager.less"; 39 | @import "labels.less"; 40 | @import "badges.less"; 41 | @import "jumbotron.less"; 42 | @import "thumbnails.less"; 43 | @import "alerts.less"; 44 | @import "progress-bars.less"; 45 | @import "media.less"; 46 | @import "list-group.less"; 47 | @import "panels.less"; 48 | @import "wells.less"; 49 | @import "close.less"; 50 | 51 | // Components w/ JavaScript 52 | @import "modals.less"; 53 | @import "tooltip.less"; 54 | @import "popovers.less"; 55 | @import "carousel.less"; 56 | 57 | // Utility classes 58 | @import "utilities.less"; 59 | @import "responsive-utilities.less"; 60 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/breadcrumbs.less: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: 8px 15px; 8 | margin-bottom: @line-height-computed; 9 | list-style: none; 10 | background-color: @breadcrumb-bg; 11 | border-radius: @border-radius-base; 12 | > li { 13 | display: inline-block; 14 | &+li:before { 15 | content: "/\00a0"; // Unicode space added since inline-block means non-collapsing white-space 16 | padding: 0 5px; 17 | color: @breadcrumb-color; 18 | } 19 | } 20 | > .active { 21 | color: @breadcrumb-active-color; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/close.less: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: (@font-size-base * 1.5); 9 | font-weight: @close-font-weight; 10 | line-height: 1; 11 | color: @close-color; 12 | text-shadow: @close-text-shadow; 13 | .opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: @close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | .opacity(.5); 21 | } 22 | 23 | // Additional properties for button version 24 | // iOS requires the button element instead of an anchor tag. 25 | // If you want the anchor version, it requires `href="#"`. 26 | button& { 27 | padding: 0; 28 | cursor: pointer; 29 | background: transparent; 30 | border: 0; 31 | -webkit-appearance: none; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/code.less: -------------------------------------------------------------------------------- 1 | // 2 | // Code (inline and blocK) 3 | // -------------------------------------------------- 4 | 5 | 6 | // Inline and block code styles 7 | code, 8 | pre { 9 | font-family: @font-family-monospace; 10 | } 11 | 12 | // Inline code 13 | code { 14 | padding: 2px 4px; 15 | font-size: 90%; 16 | color: @code-color; 17 | background-color: @code-bg; 18 | white-space: nowrap; 19 | border-radius: @border-radius-base; 20 | } 21 | 22 | // Blocks of code 23 | pre { 24 | display: block; 25 | padding: ((@line-height-computed - 1) / 2); 26 | margin: 0 0 (@line-height-computed / 2); 27 | font-size: (@font-size-base - 1); // 14px to 13px 28 | line-height: @line-height-base; 29 | word-break: break-all; 30 | word-wrap: break-word; 31 | color: @pre-color; 32 | background-color: @pre-bg; 33 | border: 1px solid @pre-border-color; 34 | border-radius: @border-radius-base; 35 | 36 | // Make prettyprint styles more spaced out for readability 37 | &.prettyprint { 38 | margin-bottom: @line-height-computed; 39 | } 40 | 41 | // Account for some code outputs that place code tags in pre tags 42 | code { 43 | padding: 0; 44 | font-size: inherit; 45 | color: inherit; 46 | white-space: pre-wrap; 47 | background-color: transparent; 48 | border: 0; 49 | } 50 | } 51 | 52 | // Enable scrollable blocks of code 53 | .pre-scrollable { 54 | max-height: @pre-scrollable-max-height; 55 | overflow-y: scroll; 56 | } 57 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/component-animations.less: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | // Heads up! 6 | // 7 | // We don't use the `.opacity()` mixin here since it causes a bug with text 8 | // fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552. 9 | 10 | .fade { 11 | opacity: 0; 12 | .transition(opacity .15s linear); 13 | &.in { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | .collapse { 19 | display: none; 20 | &.in { 21 | display: block; 22 | } 23 | } 24 | .collapsing { 25 | position: relative; 26 | height: 0; 27 | overflow: hidden; 28 | .transition(height .35s ease); 29 | } 30 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/input-groups.less: -------------------------------------------------------------------------------- 1 | // 2 | // Input groups 3 | // -------------------------------------------------- 4 | 5 | // Base styles 6 | // ------------------------- 7 | .input-group { 8 | position: relative; // For dropdowns 9 | display: table; 10 | border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table 11 | 12 | // Undo padding and float of grid classes 13 | &.col { 14 | float: none; 15 | padding-left: 0; 16 | padding-right: 0; 17 | } 18 | 19 | .form-control { 20 | width: 100%; 21 | margin-bottom: 0; 22 | } 23 | } 24 | 25 | // Sizing options 26 | // 27 | // Remix the default form control sizing classes into new ones for easier 28 | // manipulation. 29 | 30 | .input-group-lg > .form-control, 31 | .input-group-lg > .input-group-addon, 32 | .input-group-lg > .input-group-btn > .btn { .input-lg(); } 33 | .input-group-sm > .form-control, 34 | .input-group-sm > .input-group-addon, 35 | .input-group-sm > .input-group-btn > .btn { .input-sm(); } 36 | 37 | 38 | // Display as table-cell 39 | // ------------------------- 40 | .input-group-addon, 41 | .input-group-btn, 42 | .input-group .form-control { 43 | display: table-cell; 44 | 45 | &:not(:first-child):not(:last-child) { 46 | border-radius: 0; 47 | } 48 | } 49 | // Addon and addon wrapper for buttons 50 | .input-group-addon, 51 | .input-group-btn { 52 | width: 1%; 53 | white-space: nowrap; 54 | vertical-align: middle; // Match the inputs 55 | } 56 | 57 | // Text input groups 58 | // ------------------------- 59 | .input-group-addon { 60 | padding: @padding-base-vertical @padding-base-horizontal; 61 | font-size: @font-size-base; 62 | font-weight: normal; 63 | line-height: 1; 64 | text-align: center; 65 | background-color: @input-group-addon-bg; 66 | border: 1px solid @input-group-addon-border-color; 67 | border-radius: @border-radius-base; 68 | 69 | // Sizing 70 | &.input-sm { 71 | padding: @padding-small-vertical @padding-small-horizontal; 72 | font-size: @font-size-small; 73 | border-radius: @border-radius-small; 74 | } 75 | &.input-lg { 76 | padding: @padding-large-vertical @padding-large-horizontal; 77 | font-size: @font-size-large; 78 | border-radius: @border-radius-large; 79 | } 80 | 81 | // Nuke default margins from checkboxes and radios to vertically center within. 82 | input[type="radio"], 83 | input[type="checkbox"] { 84 | margin-top: 0; 85 | } 86 | } 87 | 88 | // Reset rounded corners 89 | .input-group .form-control:first-child, 90 | .input-group-addon:first-child, 91 | .input-group-btn:first-child > .btn, 92 | .input-group-btn:first-child > .dropdown-toggle, 93 | .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { 94 | .border-right-radius(0); 95 | } 96 | .input-group-addon:first-child { 97 | border-right: 0; 98 | } 99 | .input-group .form-control:last-child, 100 | .input-group-addon:last-child, 101 | .input-group-btn:last-child > .btn, 102 | .input-group-btn:last-child > .dropdown-toggle, 103 | .input-group-btn:first-child > .btn:not(:first-child) { 104 | .border-left-radius(0); 105 | } 106 | .input-group-addon:last-child { 107 | border-left: 0; 108 | } 109 | 110 | // Button input groups 111 | // ------------------------- 112 | .input-group-btn { 113 | position: relative; 114 | white-space: nowrap; 115 | } 116 | .input-group-btn > .btn { 117 | position: relative; 118 | // Jankily prevent input button groups from wrapping 119 | + .btn { 120 | margin-left: -4px; 121 | } 122 | // Bring the "active" button to the front 123 | &:hover, 124 | &:active { 125 | z-index: 2; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/jumbotron.less: -------------------------------------------------------------------------------- 1 | // 2 | // Jumbotron 3 | // -------------------------------------------------- 4 | 5 | 6 | .jumbotron { 7 | padding: @jumbotron-padding; 8 | margin-bottom: @jumbotron-padding; 9 | font-size: (@font-size-base * 1.5); 10 | font-weight: 200; 11 | line-height: (@line-height-base * 1.5); 12 | color: @jumbotron-color; 13 | background-color: @jumbotron-bg; 14 | 15 | h1 { 16 | line-height: 1; 17 | color: @jumbotron-heading-color; 18 | } 19 | p { 20 | line-height: 1.4; 21 | } 22 | 23 | .container & { 24 | border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container 25 | } 26 | 27 | @media screen and (min-width: @screen-tablet) { 28 | padding-top: (@jumbotron-padding * 1.6); 29 | padding-bottom: (@jumbotron-padding * 1.6); 30 | 31 | .container & { 32 | padding-left: (@jumbotron-padding * 2); 33 | padding-right: (@jumbotron-padding * 2); 34 | } 35 | 36 | h1 { 37 | font-size: (@font-size-base * 4.5); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/labels.less: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // -------------------------------------------------- 4 | 5 | .label { 6 | display: inline; 7 | padding: .2em .6em .3em; 8 | font-size: 75%; 9 | font-weight: bold; 10 | line-height: 1; 11 | color: @label-color; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | border-radius: .25em; 16 | 17 | // Add hover effects, but only for links 18 | &[href] { 19 | &:hover, 20 | &:focus { 21 | color: @label-link-hover-color; 22 | text-decoration: none; 23 | cursor: pointer; 24 | } 25 | } 26 | 27 | // Empty labels collapse automatically (not available in IE8) 28 | &:empty { 29 | display: none; 30 | } 31 | } 32 | 33 | // Colors 34 | // Contextual variations (linked labels get darker on :hover) 35 | 36 | .label-default { 37 | .label-variant(@label-default-bg); 38 | } 39 | 40 | .label-primary { 41 | .label-variant(@label-primary-bg); 42 | } 43 | 44 | .label-success { 45 | .label-variant(@label-success-bg); 46 | } 47 | 48 | .label-info { 49 | .label-variant(@label-info-bg); 50 | } 51 | 52 | .label-warning { 53 | .label-variant(@label-warning-bg); 54 | } 55 | 56 | .label-danger { 57 | .label-variant(@label-danger-bg); 58 | } 59 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/list-group.less: -------------------------------------------------------------------------------- 1 | // 2 | // List groups 3 | // -------------------------------------------------- 4 | 5 | // Base class 6 | // 7 | // Easily usable on
    ,
      , or
      . 8 | .list-group { 9 | // No need to set list-style: none; since .list-group-item is block level 10 | margin-bottom: 20px; 11 | padding-left: 0; // reset padding because ul and ol 12 | } 13 | 14 | // Individual list items 15 | // ------------------------- 16 | 17 | .list-group-item { 18 | position: relative; 19 | display: block; 20 | padding: 10px 15px; 21 | // Place the border on the list items and negative margin up for better styling 22 | margin-bottom: -1px; 23 | background-color: @list-group-bg; 24 | border: 1px solid @list-group-border; 25 | 26 | // Round the first and last items 27 | &:first-child { 28 | .border-top-radius(@list-group-border-radius); 29 | } 30 | &:last-child { 31 | margin-bottom: 0; 32 | .border-bottom-radius(@list-group-border-radius); 33 | } 34 | 35 | // Align badges within list items 36 | > .badge { 37 | float: right; 38 | } 39 | > .badge + .badge { 40 | margin-right: 5px; 41 | } 42 | 43 | // Linked list items 44 | a& { 45 | color: @list-group-link-color; 46 | 47 | .list-group-item-heading { 48 | color: @list-group-link-heading-color; 49 | } 50 | 51 | // Hover state 52 | &:hover, 53 | &:focus { 54 | text-decoration: none; 55 | background-color: @list-group-hover-bg; 56 | } 57 | } 58 | 59 | // Active class on item itself, not parent 60 | &.active, 61 | &.active:hover, 62 | &.active:focus { 63 | z-index: 2; // Place active items above their siblings for proper border styling 64 | color: @list-group-active-color; 65 | background-color: @list-group-active-bg; 66 | border-color: @list-group-active-border; 67 | 68 | // Force color to inherit for custom content 69 | .list-group-item-heading { 70 | color: inherit; 71 | } 72 | .list-group-item-text { 73 | color: lighten(@list-group-active-bg, 40%); 74 | } 75 | } 76 | } 77 | 78 | // Custom content options 79 | // ------------------------- 80 | 81 | .list-group-item-heading { 82 | margin-top: 0; 83 | margin-bottom: 5px; 84 | } 85 | .list-group-item-text { 86 | margin-bottom: 0; 87 | line-height: 1.3; 88 | } 89 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/media.less: -------------------------------------------------------------------------------- 1 | // Media objects 2 | // Source: http://stubbornella.org/content/?p=497 3 | // -------------------------------------------------- 4 | 5 | 6 | // Common styles 7 | // ------------------------- 8 | 9 | // Clear the floats 10 | .media, 11 | .media-body { 12 | overflow: hidden; 13 | zoom: 1; 14 | } 15 | 16 | // Proper spacing between instances of .media 17 | .media, 18 | .media .media { 19 | margin-top: 15px; 20 | } 21 | .media:first-child { 22 | margin-top: 0; 23 | } 24 | 25 | // For images and videos, set to block 26 | .media-object { 27 | display: block; 28 | } 29 | 30 | // Reset margins on headings for tighter default spacing 31 | .media-heading { 32 | margin: 0 0 5px; 33 | } 34 | 35 | 36 | // Media image alignment 37 | // ------------------------- 38 | 39 | .media { 40 | > .pull-left { 41 | margin-right: 10px; 42 | } 43 | > .pull-right { 44 | margin-left: 10px; 45 | } 46 | } 47 | 48 | 49 | // Media list variation 50 | // ------------------------- 51 | 52 | // Undo default ul/ol styles 53 | .media-list { 54 | padding-left: 0; 55 | list-style: none; 56 | } 57 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/pager.less: -------------------------------------------------------------------------------- 1 | // 2 | // Pager pagination 3 | // -------------------------------------------------- 4 | 5 | 6 | .pager { 7 | padding-left: 0; 8 | margin: @line-height-computed 0; 9 | list-style: none; 10 | text-align: center; 11 | .clearfix(); 12 | li { 13 | display: inline; 14 | > a, 15 | > span { 16 | display: inline-block; 17 | padding: 5px 14px; 18 | background-color: @pagination-bg; 19 | border: 1px solid @pagination-border; 20 | border-radius: @pager-border-radius; 21 | } 22 | 23 | > a:hover, 24 | > a:focus { 25 | text-decoration: none; 26 | background-color: @pagination-hover-bg; 27 | } 28 | } 29 | 30 | .next { 31 | > a, 32 | > span { 33 | float: right; 34 | } 35 | } 36 | 37 | .previous { 38 | > a, 39 | > span { 40 | float: left; 41 | } 42 | } 43 | 44 | .disabled { 45 | > a, 46 | > a:hover, 47 | > a:focus, 48 | > span { 49 | color: @pager-disabled-color; 50 | background-color: @pagination-bg; 51 | cursor: not-allowed; 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/pagination.less: -------------------------------------------------------------------------------- 1 | // 2 | // Pagination (multiple pages) 3 | // -------------------------------------------------- 4 | .pagination { 5 | display: inline-block; 6 | padding-left: 0; 7 | margin: @line-height-computed 0; 8 | border-radius: @border-radius-base; 9 | 10 | > li { 11 | display: inline; // Remove list-style and block-level defaults 12 | > a, 13 | > span { 14 | position: relative; 15 | float: left; // Collapse white-space 16 | padding: @padding-base-vertical @padding-base-horizontal; 17 | line-height: @line-height-base; 18 | text-decoration: none; 19 | background-color: @pagination-bg; 20 | border: 1px solid @pagination-border; 21 | margin-left: -1px; 22 | } 23 | &:first-child { 24 | > a, 25 | > span { 26 | margin-left: 0; 27 | .border-left-radius(@border-radius-base); 28 | } 29 | } 30 | &:last-child { 31 | > a, 32 | > span { 33 | .border-right-radius(@border-radius-base); 34 | } 35 | } 36 | } 37 | 38 | > li > a, 39 | > li > span { 40 | &:hover, 41 | &:focus { 42 | background-color: @pagination-hover-bg; 43 | } 44 | } 45 | 46 | > .active > a, 47 | > .active > span { 48 | &, 49 | &:hover, 50 | &:focus { 51 | z-index: 2; 52 | color: @pagination-active-color; 53 | background-color: @pagination-active-bg; 54 | border-color: @pagination-active-bg; 55 | cursor: default; 56 | } 57 | } 58 | 59 | > .disabled { 60 | > span, 61 | > a, 62 | > a:hover, 63 | > a:focus { 64 | color: @pagination-disabled-color; 65 | background-color: @pagination-bg; 66 | border-color: @pagination-border; 67 | cursor: not-allowed; 68 | } 69 | } 70 | } 71 | 72 | // Sizing 73 | // -------------------------------------------------- 74 | 75 | // Large 76 | .pagination-lg { 77 | .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large); 78 | } 79 | 80 | // Small 81 | .pagination-sm { 82 | .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small); 83 | } 84 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/print.less: -------------------------------------------------------------------------------- 1 | // 2 | // Basic print styles 3 | // -------------------------------------------------- 4 | // Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css 5 | 6 | @media print { 7 | 8 | * { 9 | text-shadow: none !important; 10 | color: #000 !important; // Black prints faster: h5bp.com/s 11 | background: transparent !important; 12 | box-shadow: none !important; 13 | } 14 | 15 | a, 16 | a:visited { 17 | text-decoration: underline; 18 | } 19 | 20 | a[href]:after { 21 | content: " (" attr(href) ")"; 22 | } 23 | 24 | abbr[title]:after { 25 | content: " (" attr(title) ")"; 26 | } 27 | 28 | // Don't show links for images, or javascript/internal links 29 | .ir a:after, 30 | a[href^="javascript:"]:after, 31 | a[href^="#"]:after { 32 | content: ""; 33 | } 34 | 35 | pre, 36 | blockquote { 37 | border: 1px solid #999; 38 | page-break-inside: avoid; 39 | } 40 | 41 | thead { 42 | display: table-header-group; // h5bp.com/t 43 | } 44 | 45 | tr, 46 | img { 47 | page-break-inside: avoid; 48 | } 49 | 50 | img { 51 | max-width: 100% !important; 52 | } 53 | 54 | @page { 55 | margin: 2cm .5cm; 56 | } 57 | 58 | p, 59 | h2, 60 | h3 { 61 | orphans: 3; 62 | widows: 3; 63 | } 64 | 65 | h2, 66 | h3 { 67 | page-break-after: avoid; 68 | } 69 | 70 | // Bootstrap components 71 | .navbar { 72 | display: none; 73 | } 74 | .table { 75 | td, 76 | th { 77 | background-color: #fff !important; 78 | } 79 | } 80 | .btn, 81 | .dropup > .btn { 82 | > .caret { 83 | border-top-color: #000 !important; 84 | } 85 | } 86 | .label { 87 | border: 1px solid #000; 88 | } 89 | 90 | .table { 91 | border-collapse: collapse !important; 92 | } 93 | .table-bordered { 94 | th, 95 | td { 96 | border: 1px solid #ddd !important; 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/progress-bars.less: -------------------------------------------------------------------------------- 1 | // 2 | // Progress bars 3 | // -------------------------------------------------- 4 | 5 | 6 | // Bar animations 7 | // ------------------------- 8 | 9 | // Webkit 10 | @-webkit-keyframes progress-bar-stripes { 11 | from { background-position: 40px 0; } 12 | to { background-position: 0 0; } 13 | } 14 | 15 | // Firefox 16 | @-moz-keyframes progress-bar-stripes { 17 | from { background-position: 40px 0; } 18 | to { background-position: 0 0; } 19 | } 20 | 21 | // Opera 22 | @-o-keyframes progress-bar-stripes { 23 | from { background-position: 0 0; } 24 | to { background-position: 40px 0; } 25 | } 26 | 27 | // Spec and IE10+ 28 | @keyframes progress-bar-stripes { 29 | from { background-position: 40px 0; } 30 | to { background-position: 0 0; } 31 | } 32 | 33 | 34 | 35 | // Bar itself 36 | // ------------------------- 37 | 38 | // Outer container 39 | .progress { 40 | overflow: hidden; 41 | height: @line-height-computed; 42 | margin-bottom: @line-height-computed; 43 | background-color: @progress-bg; 44 | border-radius: @border-radius-base; 45 | .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); 46 | } 47 | 48 | // Bar of progress 49 | .progress-bar { 50 | float: left; 51 | width: 0%; 52 | height: 100%; 53 | font-size: @font-size-small; 54 | color: @progress-bar-color; 55 | text-align: center; 56 | background-color: @progress-bar-bg; 57 | .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); 58 | .transition(width .6s ease); 59 | } 60 | 61 | // Striped bars 62 | .progress-striped .progress-bar { 63 | #gradient > .striped(@progress-bar-bg); 64 | background-size: 40px 40px; 65 | } 66 | 67 | // Call animation for the active one 68 | .progress.active .progress-bar { 69 | -webkit-animation: progress-bar-stripes 2s linear infinite; 70 | -moz-animation: progress-bar-stripes 2s linear infinite; 71 | -ms-animation: progress-bar-stripes 2s linear infinite; 72 | -o-animation: progress-bar-stripes 2s linear infinite; 73 | animation: progress-bar-stripes 2s linear infinite; 74 | } 75 | 76 | 77 | 78 | // Variations 79 | // ------------------------- 80 | 81 | .progress-bar-success { 82 | .progress-bar-variant(@progress-bar-success-bg); 83 | } 84 | 85 | .progress-bar-info { 86 | .progress-bar-variant(@progress-bar-info-bg); 87 | } 88 | 89 | .progress-bar-warning { 90 | .progress-bar-variant(@progress-bar-warning-bg); 91 | } 92 | 93 | .progress-bar-danger { 94 | .progress-bar-variant(@progress-bar-danger-bg); 95 | } 96 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/scaffolding.less: -------------------------------------------------------------------------------- 1 | // 2 | // Scaffolding 3 | // -------------------------------------------------- 4 | 5 | 6 | // Reset the box-sizing 7 | 8 | *, 9 | *:before, 10 | *:after { 11 | .box-sizing(border-box); 12 | } 13 | 14 | 15 | // Body reset 16 | 17 | html { 18 | font-size: 62.5%; 19 | -webkit-tap-highlight-color: rgba(0,0,0,0); 20 | } 21 | 22 | body { 23 | font-family: @font-family-base; 24 | font-size: @font-size-base; 25 | line-height: @line-height-base; 26 | color: @text-color; 27 | background-color: @body-bg; 28 | } 29 | 30 | // Reset fonts for relevant elements 31 | input, 32 | button, 33 | select, 34 | textarea { 35 | font-family: inherit; 36 | font-size: inherit; 37 | line-height: inherit; 38 | } 39 | 40 | // Reset unusual Firefox-on-Android default style. 41 | // 42 | // See https://github.com/necolas/normalize.css/issues/214 43 | 44 | button, 45 | input, 46 | select[multiple], 47 | textarea { 48 | background-image: none; 49 | } 50 | 51 | 52 | // Links 53 | 54 | a { 55 | color: @link-color; 56 | text-decoration: none; 57 | 58 | &:hover, 59 | &:focus { 60 | color: @link-hover-color; 61 | text-decoration: underline; 62 | } 63 | 64 | &:focus { 65 | .tab-focus(); 66 | } 67 | } 68 | 69 | 70 | // Images 71 | 72 | img { 73 | vertical-align: middle; 74 | } 75 | 76 | // Responsive images (ensure images don't scale beyond their parents) 77 | .img-responsive { 78 | .img-responsive(); 79 | } 80 | 81 | // Rounded corners 82 | .img-rounded { 83 | border-radius: @border-radius-large; 84 | } 85 | 86 | // Image thumbnails 87 | // 88 | // Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`. 89 | .img-thumbnail { 90 | padding: @thumbnail-padding; 91 | line-height: @line-height-base; 92 | background-color: @thumbnail-bg; 93 | border: 1px solid @thumbnail-border; 94 | border-radius: @thumbnail-border-radius; 95 | .transition(all .2s ease-in-out); 96 | 97 | // Keep them at most 100% wide 98 | .img-responsive(inline-block); 99 | } 100 | 101 | // Perfect circle 102 | .img-circle { 103 | border-radius: 50%; // set radius in percents 104 | } 105 | 106 | 107 | // Horizontal rules 108 | 109 | hr { 110 | margin-top: @line-height-computed; 111 | margin-bottom: @line-height-computed; 112 | border: 0; 113 | border-top: 1px solid @hr-border; 114 | } 115 | 116 | 117 | // Only display content to screen readers 118 | // 119 | // See: http://a11yproject.com/posts/how-to-hide-content/ 120 | 121 | .sr-only { 122 | position: absolute; 123 | width: 1px; 124 | height: 1px; 125 | margin: -1px; 126 | padding: 0; 127 | overflow: hidden; 128 | clip: rect(0 0 0 0); 129 | border: 0; 130 | } 131 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/thumbnails.less: -------------------------------------------------------------------------------- 1 | // 2 | // Thumbnails 3 | // -------------------------------------------------- 4 | 5 | 6 | // Mixin and adjust the regular image class 7 | .thumbnail { 8 | .img-thumbnail(); 9 | display: block; // Override the inline-block from `.img-thumbnail` 10 | 11 | > img { 12 | .img-responsive(); 13 | } 14 | } 15 | 16 | 17 | // Add a hover state for linked versions only 18 | a.thumbnail:hover, 19 | a.thumbnail:focus { 20 | border-color: @link-color; 21 | } 22 | 23 | // Images and captions 24 | .thumbnail > img { 25 | margin-left: auto; 26 | margin-right: auto; 27 | } 28 | .thumbnail .caption { 29 | padding: @thumbnail-caption-padding; 30 | color: @thumbnail-caption-color; 31 | } 32 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/tooltip.less: -------------------------------------------------------------------------------- 1 | // 2 | // Tooltips 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .tooltip { 8 | position: absolute; 9 | z-index: @zindex-tooltip; 10 | display: block; 11 | visibility: visible; 12 | font-size: @font-size-small; 13 | line-height: 1.4; 14 | .opacity(0); 15 | 16 | &.in { .opacity(.9); } 17 | &.top { margin-top: -3px; padding: 5px 0; } 18 | &.right { margin-left: 3px; padding: 0 5px; } 19 | &.bottom { margin-top: 3px; padding: 5px 0; } 20 | &.left { margin-left: -3px; padding: 0 5px; } 21 | } 22 | 23 | // Wrapper for the tooltip content 24 | .tooltip-inner { 25 | max-width: @tooltip-max-width; 26 | padding: 3px 8px; 27 | color: @tooltip-color; 28 | text-align: center; 29 | text-decoration: none; 30 | background-color: @tooltip-bg; 31 | border-radius: @border-radius-base; 32 | } 33 | 34 | // Arrows 35 | .tooltip-arrow { 36 | position: absolute; 37 | width: 0; 38 | height: 0; 39 | border-color: transparent; 40 | border-style: solid; 41 | } 42 | .tooltip { 43 | &.top .tooltip-arrow { 44 | bottom: 0; 45 | left: 50%; 46 | margin-left: -@tooltip-arrow-width; 47 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0; 48 | border-top-color: @tooltip-arrow-color; 49 | } 50 | &.top-left .tooltip-arrow { 51 | bottom: 0; 52 | left: 5px; 53 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0; 54 | border-top-color: @tooltip-arrow-color; 55 | } 56 | &.top-right .tooltip-arrow { 57 | bottom: 0; 58 | right: 5px; 59 | border-width: @tooltip-arrow-width @tooltip-arrow-width 0; 60 | border-top-color: @tooltip-arrow-color; 61 | } 62 | &.right .tooltip-arrow { 63 | top: 50%; 64 | left: 0; 65 | margin-top: -@tooltip-arrow-width; 66 | border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0; 67 | border-right-color: @tooltip-arrow-color; 68 | } 69 | &.left .tooltip-arrow { 70 | top: 50%; 71 | right: 0; 72 | margin-top: -@tooltip-arrow-width; 73 | border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width; 74 | border-left-color: @tooltip-arrow-color; 75 | } 76 | &.bottom .tooltip-arrow { 77 | top: 0; 78 | left: 50%; 79 | margin-left: -@tooltip-arrow-width; 80 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; 81 | border-bottom-color: @tooltip-arrow-color; 82 | } 83 | &.bottom-left .tooltip-arrow { 84 | top: 0; 85 | left: 5px; 86 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; 87 | border-bottom-color: @tooltip-arrow-color; 88 | } 89 | &.bottom-right .tooltip-arrow { 90 | top: 0; 91 | right: 5px; 92 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; 93 | border-bottom-color: @tooltip-arrow-color; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/utilities.less: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // Floats 7 | // ------------------------- 8 | 9 | .clearfix { 10 | .clearfix(); 11 | } 12 | .pull-right { 13 | float: right !important; 14 | } 15 | .pull-left { 16 | float: left !important; 17 | } 18 | 19 | 20 | // Toggling content 21 | // ------------------------- 22 | 23 | .hide { 24 | display: none !important; 25 | } 26 | .show { 27 | display: block !important; 28 | } 29 | .invisible { 30 | visibility: hidden; 31 | } 32 | .text-hide { 33 | .hide-text(); 34 | } 35 | 36 | 37 | // For Affix plugin 38 | // ------------------------- 39 | 40 | .affix { 41 | position: fixed; 42 | } 43 | -------------------------------------------------------------------------------- /less/vendors/bootstrap/wells.less: -------------------------------------------------------------------------------- 1 | // 2 | // Wells 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .well { 8 | min-height: 20px; 9 | padding: 19px; 10 | margin-bottom: 20px; 11 | background-color: @well-bg; 12 | border: 1px solid darken(@well-bg, 7%); 13 | border-radius: @border-radius-base; 14 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); 15 | blockquote { 16 | border-color: #ddd; 17 | border-color: rgba(0,0,0,.15); 18 | } 19 | } 20 | 21 | // Sizes 22 | .well-lg { 23 | padding: 24px; 24 | border-radius: @border-radius-large; 25 | } 26 | .well-sm { 27 | padding: 9px; 28 | border-radius: @border-radius-small; 29 | } 30 | -------------------------------------------------------------------------------- /less/vendors/font-awesome/bootstrap.less: -------------------------------------------------------------------------------- 1 | /* BOOTSTRAP SPECIFIC CLASSES 2 | * -------------------------- */ 3 | 4 | /* Bootstrap 2.0 sprites.less reset */ 5 | [class^="icon-"], 6 | [class*=" icon-"] { 7 | display: inline; 8 | width: auto; 9 | height: auto; 10 | line-height: normal; 11 | vertical-align: baseline; 12 | background-image: none; 13 | background-position: 0% 0%; 14 | background-repeat: repeat; 15 | margin-top: 0; 16 | } 17 | 18 | /* more sprites.less reset */ 19 | .icon-white, 20 | .nav-pills > .active > a > [class^="icon-"], 21 | .nav-pills > .active > a > [class*=" icon-"], 22 | .nav-list > .active > a > [class^="icon-"], 23 | .nav-list > .active > a > [class*=" icon-"], 24 | .navbar-inverse .nav > .active > a > [class^="icon-"], 25 | .navbar-inverse .nav > .active > a > [class*=" icon-"], 26 | .dropdown-menu > li > a:hover > [class^="icon-"], 27 | .dropdown-menu > li > a:hover > [class*=" icon-"], 28 | .dropdown-menu > .active > a > [class^="icon-"], 29 | .dropdown-menu > .active > a > [class*=" icon-"], 30 | .dropdown-submenu:hover > a > [class^="icon-"], 31 | .dropdown-submenu:hover > a > [class*=" icon-"] { 32 | background-image: none; 33 | } 34 | 35 | 36 | /* keeps Bootstrap styles with and without icons the same */ 37 | .btn, .nav { 38 | [class^="icon-"], 39 | [class*=" icon-"] { 40 | // display: inline; 41 | &.icon-large { line-height: .9em; } 42 | &.icon-spin { display: inline-block; } 43 | } 44 | } 45 | .nav-tabs, .nav-pills { 46 | [class^="icon-"], 47 | [class*=" icon-"] { 48 | &, &.icon-large { line-height: .9em; } 49 | } 50 | } 51 | .btn { 52 | [class^="icon-"], 53 | [class*=" icon-"] { 54 | &.pull-left, &.pull-right { 55 | &.icon-2x { margin-top: .18em; } 56 | } 57 | &.icon-spin.icon-large { line-height: .8em; } 58 | } 59 | } 60 | .btn.btn-small { 61 | [class^="icon-"], 62 | [class*=" icon-"] { 63 | &.pull-left, &.pull-right { 64 | &.icon-2x { margin-top: .25em; } 65 | } 66 | } 67 | } 68 | .btn.btn-large { 69 | [class^="icon-"], 70 | [class*=" icon-"] { 71 | margin-top: 0; // overrides bootstrap default 72 | &.pull-left, &.pull-right { 73 | &.icon-2x { margin-top: .05em; } 74 | } 75 | &.pull-left.icon-2x { margin-right: .2em; } 76 | &.pull-right.icon-2x { margin-left: .2em; } 77 | } 78 | } 79 | 80 | /* Fixes alignment in nav lists */ 81 | .nav-list [class^="icon-"], 82 | .nav-list [class*=" icon-"] { 83 | line-height: inherit; 84 | } 85 | -------------------------------------------------------------------------------- /less/vendors/font-awesome/core.less: -------------------------------------------------------------------------------- 1 | /* FONT AWESOME CORE 2 | * -------------------------- */ 3 | 4 | [class^="icon-"], 5 | [class*=" icon-"] { 6 | .icon-FontAwesome(); 7 | } 8 | 9 | [class^="icon-"]:before, 10 | [class*=" icon-"]:before { 11 | text-decoration: inherit; 12 | display: inline-block; 13 | speak: none; 14 | } 15 | 16 | /* makes the font 33% larger relative to the icon container */ 17 | .icon-large:before { 18 | vertical-align: -10%; 19 | font-size: 4/3em; 20 | } 21 | 22 | /* makes sure icons active on rollover in links */ 23 | a { 24 | [class^="icon-"], 25 | [class*=" icon-"] { 26 | display: inline; 27 | } 28 | } 29 | 30 | /* increased font size for icon-large */ 31 | [class^="icon-"], 32 | [class*=" icon-"] { 33 | &.icon-fixed-width { 34 | display: inline-block; 35 | width: 16/14em; 36 | text-align: right; 37 | padding-right: 4/14em; 38 | &.icon-large { 39 | width: 20/14em; 40 | } 41 | } 42 | } 43 | 44 | .icons-ul { 45 | margin-left: @icons-li-width; 46 | list-style-type: none; 47 | 48 | > li { position: relative; } 49 | 50 | .icon-li { 51 | position: absolute; 52 | left: -@icons-li-width; 53 | width: @icons-li-width; 54 | text-align: center; 55 | line-height: inherit; 56 | } 57 | } 58 | 59 | // allows usage of the hide class directly on font awesome icons 60 | [class^="icon-"], 61 | [class*=" icon-"] { 62 | &.hide { 63 | display: none; 64 | } 65 | } 66 | 67 | .icon-muted { color: @iconMuted; } 68 | .icon-light { color: @iconLight; } 69 | .icon-dark { color: @iconDark; } 70 | 71 | // Icon Borders 72 | // ------------------------- 73 | 74 | .icon-border { 75 | border: solid 1px @borderColor; 76 | padding: .2em .25em .15em; 77 | .border-radius(3px); 78 | } 79 | 80 | // Icon Sizes 81 | // ------------------------- 82 | 83 | .icon-2x { 84 | font-size: 2em; 85 | &.icon-border { 86 | border-width: 2px; 87 | .border-radius(4px); 88 | } 89 | } 90 | .icon-3x { 91 | font-size: 3em; 92 | &.icon-border { 93 | border-width: 3px; 94 | .border-radius(5px); 95 | } 96 | } 97 | .icon-4x { 98 | font-size: 4em; 99 | &.icon-border { 100 | border-width: 4px; 101 | .border-radius(6px); 102 | } 103 | } 104 | 105 | .icon-5x { 106 | font-size: 5em; 107 | &.icon-border { 108 | border-width: 5px; 109 | .border-radius(7px); 110 | } 111 | } 112 | 113 | 114 | // Floats & Margins 115 | // ------------------------- 116 | 117 | // Quick floats 118 | .pull-right { float: right; } 119 | .pull-left { float: left; } 120 | 121 | [class^="icon-"], 122 | [class*=" icon-"] { 123 | &.pull-left { 124 | margin-right: .3em; 125 | } 126 | &.pull-right { 127 | margin-left: .3em; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /less/vendors/font-awesome/extras.less: -------------------------------------------------------------------------------- 1 | /* EXTRAS 2 | * -------------------------- */ 3 | 4 | /* Stacked and layered icon */ 5 | .icon-stack(); 6 | 7 | /* Animated rotating icon */ 8 | .icon-spin { 9 | display: inline-block; 10 | -moz-animation: spin 2s infinite linear; 11 | -o-animation: spin 2s infinite linear; 12 | -webkit-animation: spin 2s infinite linear; 13 | animation: spin 2s infinite linear; 14 | } 15 | 16 | /* Prevent stack and spinners from being taken inline when inside a link */ 17 | a .icon-stack, 18 | a .icon-spin { 19 | display: inline-block; 20 | text-decoration: none; 21 | } 22 | 23 | @-moz-keyframes spin { 24 | 0% { -moz-transform: rotate(0deg); } 25 | 100% { -moz-transform: rotate(359deg); } 26 | } 27 | @-webkit-keyframes spin { 28 | 0% { -webkit-transform: rotate(0deg); } 29 | 100% { -webkit-transform: rotate(359deg); } 30 | } 31 | @-o-keyframes spin { 32 | 0% { -o-transform: rotate(0deg); } 33 | 100% { -o-transform: rotate(359deg); } 34 | } 35 | @-ms-keyframes spin { 36 | 0% { -ms-transform: rotate(0deg); } 37 | 100% { -ms-transform: rotate(359deg); } 38 | } 39 | @keyframes spin { 40 | 0% { transform: rotate(0deg); } 41 | 100% { transform: rotate(359deg); } 42 | } 43 | 44 | /* Icon rotations and mirroring */ 45 | .icon-rotate-90:before { 46 | -webkit-transform: rotate(90deg); 47 | -moz-transform: rotate(90deg); 48 | -ms-transform: rotate(90deg); 49 | -o-transform: rotate(90deg); 50 | transform: rotate(90deg); 51 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 52 | } 53 | 54 | .icon-rotate-180:before { 55 | -webkit-transform: rotate(180deg); 56 | -moz-transform: rotate(180deg); 57 | -ms-transform: rotate(180deg); 58 | -o-transform: rotate(180deg); 59 | transform: rotate(180deg); 60 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 61 | } 62 | 63 | .icon-rotate-270:before { 64 | -webkit-transform: rotate(270deg); 65 | -moz-transform: rotate(270deg); 66 | -ms-transform: rotate(270deg); 67 | -o-transform: rotate(270deg); 68 | transform: rotate(270deg); 69 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 70 | } 71 | 72 | .icon-flip-horizontal:before { 73 | -webkit-transform: scale(-1, 1); 74 | -moz-transform: scale(-1, 1); 75 | -ms-transform: scale(-1, 1); 76 | -o-transform: scale(-1, 1); 77 | transform: scale(-1, 1); 78 | } 79 | 80 | .icon-flip-vertical:before { 81 | -webkit-transform: scale(1, -1); 82 | -moz-transform: scale(1, -1); 83 | -ms-transform: scale(1, -1); 84 | -o-transform: scale(1, -1); 85 | transform: scale(1, -1); 86 | } 87 | 88 | /* ensure rotation occurs inside anchor tags */ 89 | a { 90 | .icon-rotate-90, .icon-rotate-180, .icon-rotate-270, .icon-flip-horizontal, .icon-flip-vertical { 91 | &:before { display: inline-block; } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /less/vendors/font-awesome/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 3.2.1 3 | * the iconic font designed for Bootstrap 4 | * ------------------------------------------------------------------------------ 5 | * The full suite of pictographic icons, examples, and documentation can be 6 | * found at http://fontawesome.io. Stay up to date on Twitter at 7 | * http://twitter.com/fontawesome. 8 | * 9 | * License 10 | * ------------------------------------------------------------------------------ 11 | * - The Font Awesome font is licensed under SIL OFL 1.1 - 12 | * http://scripts.sil.org/OFL 13 | * - Font Awesome CSS, LESS, and SASS files are licensed under MIT License - 14 | * http://opensource.org/licenses/mit-license.html 15 | * - Font Awesome documentation licensed under CC BY 3.0 - 16 | * http://creativecommons.org/licenses/by/3.0/ 17 | * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: 18 | * "Font Awesome by Dave Gandy - http://fontawesome.io" 19 | * 20 | * Author - Dave Gandy 21 | * ------------------------------------------------------------------------------ 22 | * Email: dave@fontawesome.io 23 | * Twitter: http://twitter.com/davegandy 24 | * Work: Lead Product Designer @ Kyruus - http://kyruus.com 25 | */ 26 | 27 | @import "variables.less"; 28 | @import "mixins.less"; 29 | @import "path.less"; 30 | @import "core.less"; 31 | @import "bootstrap.less"; 32 | @import "extras.less"; 33 | @import "icons.less"; 34 | -------------------------------------------------------------------------------- /less/vendors/font-awesome/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .icon(@icon) { 5 | .icon-FontAwesome(); 6 | content: @icon; 7 | } 8 | 9 | .icon-FontAwesome() { 10 | font-family: FontAwesome; 11 | font-weight: normal; 12 | font-style: normal; 13 | text-decoration: inherit; 14 | -webkit-font-smoothing: antialiased; 15 | *margin-right: .3em; // fixes ie7 issues 16 | } 17 | 18 | .border-radius(@radius) { 19 | -webkit-border-radius: @radius; 20 | -moz-border-radius: @radius; 21 | border-radius: @radius; 22 | } 23 | 24 | .icon-stack(@width: 2em, @height: 2em, @top-font-size: 1em, @base-font-size: 2em) { 25 | .icon-stack { 26 | position: relative; 27 | display: inline-block; 28 | width: @width; 29 | height: @height; 30 | line-height: @width; 31 | vertical-align: -35%; 32 | [class^="icon-"], 33 | [class*=" icon-"] { 34 | display: block; 35 | text-align: center; 36 | position: absolute; 37 | width: 100%; 38 | height: 100%; 39 | font-size: @top-font-size; 40 | line-height: inherit; 41 | *line-height: @height; 42 | } 43 | .icon-stack-base { 44 | font-size: @base-font-size; 45 | *line-height: @height / @base-font-size; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /less/vendors/font-awesome/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{FontAwesomePath}/fontawesome-webfont.eot?v=@{FontAwesomeVersion}'); 7 | src: url('@{FontAwesomePath}/fontawesome-webfont.eot?#iefix&v=@{FontAwesomeVersion}') format('embedded-opentype'), 8 | url('@{FontAwesomePath}/fontawesome-webfont.woff?v=@{FontAwesomeVersion}') format('woff'), 9 | url('@{FontAwesomePath}/fontawesome-webfont.ttf?v=@{FontAwesomeVersion}') format('truetype'), 10 | url('@{FontAwesomePath}/fontawesome-webfont.svg#fontawesomeregular?v=@{FontAwesomeVersion}') format('svg'); 11 | // src: url('@{FontAwesomePath}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iloveopensource", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "~3.2.3", 10 | "ejs": "~0.8.4", 11 | "passport": "~0.1.17", 12 | "passport-github": "~0.1.5", 13 | "mongoose": "~3.6.19", 14 | "lodash": "~2.0.0", 15 | "connect-mongo": "~0.3.3", 16 | "async": "~0.2.9", 17 | "nodemailer": "~0.5.2", 18 | "passport.socketio": "~1.2.1", 19 | "q": "~0.9.7", 20 | "html-css-sanitizer": "0.0.3", 21 | "moment": "~2.3.0", 22 | "winston": "~0.7.2" 23 | }, 24 | "devDependencies": { 25 | "grunt-contrib-less": "~0.6.5", 26 | "grunt-autoprefixer": "~0.2.20130806", 27 | "grunt-contrib-watch": "~0.5.3", 28 | "grunt-bower-task": "~0.4.0", 29 | "grunt": "~0.4.1", 30 | "grunt-contrib-cssmin": "~0.6.1", 31 | "grunt-contrib-requirejs": "~0.4.1", 32 | "socket.io": "~0.9.16", 33 | "bower": "~1.7.2" 34 | }, 35 | "engines": { 36 | "node": "0.10.41" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/quicksand-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/quicksand-bold-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/quicksand-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/quicksand-bold-webfont.woff -------------------------------------------------------------------------------- /public/fonts/quicksand-light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/quicksand-light-webfont.eot -------------------------------------------------------------------------------- /public/fonts/quicksand-light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/quicksand-light-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/quicksand-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/quicksand-light-webfont.woff -------------------------------------------------------------------------------- /public/fonts/quicksand-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/quicksand-regular-webfont.eot -------------------------------------------------------------------------------- /public/fonts/quicksand-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/quicksand-regular-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/quicksand-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/fonts/quicksand-regular-webfont.woff -------------------------------------------------------------------------------- /public/images/bg-highlight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/bg-highlight.jpg -------------------------------------------------------------------------------- /public/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/bg.png -------------------------------------------------------------------------------- /public/images/codio-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/codio-icon.png -------------------------------------------------------------------------------- /public/images/digitalocean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/digitalocean.png -------------------------------------------------------------------------------- /public/images/flattr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/flattr.png -------------------------------------------------------------------------------- /public/images/github-ribbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/github-ribbon.png -------------------------------------------------------------------------------- /public/images/gittip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/gittip.png -------------------------------------------------------------------------------- /public/images/help/supporters/donate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/help/supporters/donate.png -------------------------------------------------------------------------------- /public/images/help/supporters/dropdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/help/supporters/dropdown.jpg -------------------------------------------------------------------------------- /public/images/help/supporters/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/help/supporters/preview.png -------------------------------------------------------------------------------- /public/images/help/supporters/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/help/supporters/project.png -------------------------------------------------------------------------------- /public/images/help/supporters/supporting-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/help/supporters/supporting-icons.png -------------------------------------------------------------------------------- /public/images/logo-darkbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/logo-darkbg.png -------------------------------------------------------------------------------- /public/images/logo-lightbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/logo-lightbg.png -------------------------------------------------------------------------------- /public/images/paypal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codio/iloveopensource/a3419cd770b0fa73db46941bc3cb96216ab10c31/public/images/paypal.gif -------------------------------------------------------------------------------- /public/js/common/templates/donate-methods.html: -------------------------------------------------------------------------------- 1 | <% if (donateMethods.paypal) { %> 2 |
      3 | <%= donateMethods.paypal %> 4 |
      5 | <% } %> 6 | 7 | <% if (donateMethods.gittip) { %> 8 |
      9 | gittip 10 |
      11 | <% } %> 12 | 13 | <% if (donateMethods.flattr) { %> 14 |
      15 | flattr 16 |
      17 | <% } %> 18 | 19 | <% if (donateMethods.other) { %> 20 |
      21 | 22 | 23 | 26 |
      27 | <% } %> 28 | 29 | <% if (donateMethods.emailMe) { %> 30 | 33 | <% } %> 34 | 35 | <% if (donateMethods.code) { %> 36 |
      37 | 38 | 39 | 40 |
      41 | <% } %> 42 | 43 | <% if (obj.needHelp && !_(donateMethods).values().compact().value().length) { %> 44 | <% if (obj.customHelp) { %> 45 | <%= obj.customHelp %> 46 | <% } else { %> 47 | 48 | donate 49 | 50 | <% } %> 51 | <% } %> 52 | 53 |
      54 | -------------------------------------------------------------------------------- /public/js/common/views/donate-methods.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 4:05 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var tpl = require('tpl!../templates/donate-methods.html') 9 | 10 | return Backbone.View.extend({ 11 | attributes: { 12 | class: 'col donate-methods' 13 | }, 14 | render: function () { 15 | this.$el.html(tpl({ 16 | project: this.model.toJSON(), 17 | donateMethods: this.model.get('donateMethods') || {}, 18 | needHelp: this.options.needHelp, 19 | customHelp: this.options.customHelp 20 | })) 21 | return this 22 | } 23 | }); 24 | }) -------------------------------------------------------------------------------- /public/js/modules/account/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/23/13 4 | * Time: 3:42 PM 5 | */ 6 | define(function (require) { 7 | require('plugins/activate-plugins') 8 | require('backbone') 9 | 10 | 11 | $(function () { 12 | var tabsHolder = $('.nav.nav-tabs') 13 | var defaultTab = $('a.tab:first', tabsHolder).attr('href').slice(1) 14 | 15 | new (Backbone.Router.extend({ 16 | routes: { 17 | '*actions': 'selectTab' 18 | }, 19 | selectTab: function (tab) { 20 | tab = tab || defaultTab 21 | $('a[href="#' + tab + '"]', tabsHolder).tab('show'); 22 | } 23 | })) 24 | 25 | Backbone.history.start(); 26 | }) 27 | }) -------------------------------------------------------------------------------- /public/js/modules/donate-requests/collections/requests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 8:41 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('repo-selector') 9 | var Request = require('../models/request') 10 | 11 | return Backbone.Collection.extend({ 12 | url: '/service/requests', 13 | model: Request, 14 | fetch: function(options) { 15 | var request = Backbone.Collection.prototype.fetch.apply(this, arguments); 16 | request.done(_.bind(function () { 17 | this.trigger('fetched') 18 | }, this)) 19 | 20 | return request 21 | } 22 | }) 23 | }) -------------------------------------------------------------------------------- /public/js/modules/donate-requests/collections/supporters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 10/7/13 4 | * Time: 1:21 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | return Backbone.Collection.extend({ 10 | url: function() { 11 | return '/service/requests/' + this.requestId + '/supporters' 12 | }, 13 | fetch: function (options) { 14 | var request = Backbone.Collection.prototype.fetch.apply(this, arguments); 15 | request.done(_.bind(function () { 16 | this.trigger('fetched') 17 | }, this)) 18 | 19 | return request 20 | } 21 | }) 22 | }) -------------------------------------------------------------------------------- /public/js/modules/donate-requests/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 6:25 PM 5 | */ 6 | define(function (require) { 7 | require('plugins/activate-plugins') 8 | require('moment') 9 | 10 | var toastr = require('toastr') 11 | var List = require('./views/list') 12 | var Search = require('./views/search') 13 | var Requests = require('./collections/requests') 14 | 15 | var store = require('store').getNamespace('donation-requests') 16 | 17 | $(function () { 18 | var container = $('#donation-requests') 19 | 20 | store().notify = toastr 21 | store().requests = new Requests() 22 | store().list = new List({ 23 | el: $('.list', container), 24 | collection: store().requests 25 | }).render() 26 | store().search = new Search({ 27 | el: $('.search', container), 28 | collection: store().requests 29 | }).render() 30 | 31 | store().requests.fetch() 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /public/js/modules/donate-requests/models/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 10/4/13 4 | * Time: 5:25 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | return Backbone.Model.extend({ 10 | idAttribute: '_id' 11 | }) 12 | }) -------------------------------------------------------------------------------- /public/js/modules/donate-requests/templates/list.html: -------------------------------------------------------------------------------- 1 |

      No results found by your request

      2 |
      3 |
      4 |
      5 | Last Request / Created 6 |
      7 |
      8 | Settings 9 |
      10 |
      11 | Notified 12 |
      13 |
      14 | Email 15 |
      16 |
      17 | Project 18 |
      19 |
      20 |
      21 |
      22 |
      -------------------------------------------------------------------------------- /public/js/modules/donate-requests/templates/request.html: -------------------------------------------------------------------------------- 1 | <% var format = 'MM/D/YY, h:mm:ss a' %> 2 |
      3 | 4 |
      5 | 6 |
      7 |
      8 | 9 |
      10 |
      11 | 12 |
      13 |
      14 | <%= request.maintainer.email || 'Not set' %> 15 | 16 |
      17 |
      18 | 19 | <% if (project.owner.user) { %> 20 | <%= project.owner.username %> 21 | <% } else if (project.owner.org) { %> 22 | <%= project.owner.username %> 23 | <% } else { %> 24 | <%= project.owner.username %> 25 | <% } %> 26 | / 27 | <%= project.name %> 28 |
      29 |
      30 | <% if (request.maintainer.notified) { %> 31 | re-notify 32 | <% } else { %> 33 | notify 34 | <% } %> 35 |
      36 |
      37 | 40 |
      41 |
      -------------------------------------------------------------------------------- /public/js/modules/donate-requests/templates/search.html: -------------------------------------------------------------------------------- 1 |
      2 |
      3 | 4 |
      5 |
      6 | Maintainer Notified: 7 |
      8 | 9 | 10 | 11 |
      12 | 13 |
      14 |
      -------------------------------------------------------------------------------- /public/js/modules/donate-requests/templates/supporter.html: -------------------------------------------------------------------------------- 1 | <% var format = 'MM/D/YY, h:mm:ss a' %> 2 |
      3 | 4 |
      5 |
      6 | <% if (!obj.isAnon) { %> 7 | <%= obj.username %> 8 | <% } else { %> 9 | Anonymous (<%= obj.ip %>) 10 | <% } %> 11 |
      12 |
      13 | <% if (obj.email) { %> 14 | <%= obj.email %> 15 | <% } else { %> 16 | User haven't set email 17 | <% if (obj.ref && obj.ref.email) { %> 18 | but here is current account email <%= obj.ref.email %> 19 | <% } %> 20 | <% } %> 21 |
      -------------------------------------------------------------------------------- /public/js/modules/donate-requests/templates/supporters.html: -------------------------------------------------------------------------------- 1 |
      2 |
      Created At
      3 |
      User
      4 |
      Email
      5 |
      6 |
      7 | -------------------------------------------------------------------------------- /public/js/modules/donate-requests/views/list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 1:29 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var tpl = require('tpl!../templates/list.html'), 10 | Row = require('./request') 11 | 12 | return Backbone.View.extend({ 13 | events: { 14 | }, 15 | initialize: function () { 16 | this.listenTo(this.collection, 'fetched', this.renderRows) 17 | this.rows = [] 18 | }, 19 | renderRows: function () { 20 | var list = this.$('.requests-list') 21 | _.invoke(this.rows, 'remove') 22 | this.rows = [] 23 | 24 | this.showEmpty() 25 | this.collection.each(function (entry) { 26 | var view = new Row({model: entry}) 27 | this.rows.push(view) 28 | view.render() 29 | }, this) 30 | 31 | list.append(_.pluck(this.rows, 'el')) 32 | }, 33 | showEmpty: function() { 34 | var cond = this.collection.length > 0 35 | this.$('.empty-message').toggle(!cond) 36 | this.$('.content').toggle(cond) 37 | }, 38 | remove: function() { 39 | _.invoke(this.rows, 'remove') 40 | Backbone.View.prototype.remove.apply(this, arguments) 41 | }, 42 | render: function () { 43 | this.$el.html(tpl()) 44 | return this 45 | } 46 | }); 47 | }) -------------------------------------------------------------------------------- /public/js/modules/donate-requests/views/search.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 1:33 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var store = require('store').getNamespace('donation-requests') 10 | var tpl = require('tpl!../templates/search.html') 11 | 12 | return Backbone.View.extend({ 13 | events: { 14 | 'click .switcher': 'toggleFilter', 15 | 'blur .search-query': 'search', 16 | 'keyup .search-query': 'search' 17 | }, 18 | initialize: function () { 19 | this.filters = {} 20 | }, 21 | fetchRequests: function () { 22 | this.$('.loading').show() 23 | this.undelegateEvents() 24 | store().requests.fetch({data: this.filters}) 25 | .always(_.bind(function() { 26 | this.$('.loading').hide() 27 | this.delegateEvents() 28 | }, this)) 29 | }, 30 | search: function (event) { 31 | if (event.type == 'keyup' && event.which != 13) return 32 | 33 | var query = this.$('.search-query').val() 34 | if (this.filters.search == query) return 35 | this.filters.search = query 36 | this.fetchRequests() 37 | }, 38 | toggleFilter: function (event) { 39 | var filter = $(event.currentTarget), 40 | isOn = filter.hasClass('active') 41 | 42 | filter.toggleClass('active', !isOn) 43 | if (!isOn) { 44 | this.filters[filter.data().field] = true 45 | } else { 46 | delete this.filters[filter.data().field] 47 | } 48 | this.fetchRequests() 49 | }, 50 | render: function () { 51 | this.$el.html(tpl()) 52 | return this 53 | } 54 | }); 55 | }) -------------------------------------------------------------------------------- /public/js/modules/donate-requests/views/supporter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 1:29 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var tpl = require('tpl!../templates/supporter.html') 9 | 10 | return Backbone.View.extend({ 11 | attributes: { 12 | class: 'row' 13 | }, 14 | render: function () { 15 | this.$el.html(tpl(this.model.toJSON())) 16 | return this 17 | } 18 | }); 19 | }) -------------------------------------------------------------------------------- /public/js/modules/donate-requests/views/supporters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 1:29 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var tpl = require('tpl!../templates/supporters.html'), 10 | Supporters = require('../collections/supporters'), 11 | Row = require('./supporter') 12 | 13 | return Backbone.View.extend({ 14 | initialize: function () { 15 | this.rows = [] 16 | this.collection = new Supporters 17 | this.collection.requestId = this.options.requestId 18 | this.listenTo(this.collection, 'fetched', this.renderRows) 19 | this.collection.fetch() 20 | this.render() 21 | }, 22 | renderRows: function () { 23 | var list = this.$('.supporters-list') 24 | _.invoke(this.rows, 'remove') 25 | this.rows = [] 26 | 27 | this.collection.each(function (entry) { 28 | var view = new Row({model: entry}) 29 | this.rows.push(view) 30 | view.render() 31 | }, this) 32 | 33 | list.append(_.pluck(this.rows, 'el')) 34 | }, 35 | remove: function() { 36 | _.invoke(this.rows, 'remove') 37 | Backbone.View.prototype.remove.apply(this, arguments) 38 | }, 39 | render: function () { 40 | this.$el.html(tpl()) 41 | return this 42 | } 43 | }); 44 | }) -------------------------------------------------------------------------------- /public/js/modules/maintainers/collections/project-groups.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 8:41 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var Projects = require('./projects') 10 | 11 | return Backbone.Collection.extend({ 12 | url: '/maintainer/projects', 13 | parse: function (data) { 14 | var owners = {} 15 | 16 | _.each(data, function (entry) { 17 | var group = owners[entry.owner.githubId] 18 | if (!group) { 19 | group = owners[entry.owner.githubId] = _.clone(entry.owner) 20 | group.repos = new Projects 21 | } 22 | 23 | group.repos.push(entry) 24 | }) 25 | 26 | return _.sortBy(_.values(owners), function(entry) { 27 | return entry.type.toLowerCase() == 'user' ? 0 : 1 28 | }); 29 | } 30 | }) 31 | }) -------------------------------------------------------------------------------- /public/js/modules/maintainers/collections/projects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 8:41 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var Project = require('../models/project') 10 | 11 | return Backbone.Collection.extend({ 12 | model: Project 13 | }) 14 | }) -------------------------------------------------------------------------------- /public/js/modules/maintainers/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 8:31 AM 5 | */ 6 | define(function (require) { 7 | require('bootstrap') 8 | require('plugins/activate-plugins') 9 | 10 | var toastr = require('toastr') 11 | var Layout = require('./views/list') 12 | var Projects = require('./collections/project-groups') 13 | var store = require('store').getNamespace('maintainer') 14 | 15 | $(function () { 16 | store().hub = _.extend({}, Backbone.Events) 17 | store().projects = new Projects() 18 | store().notify = toastr 19 | store().layout = new Layout({ 20 | el: $('#maintainers') 21 | }) 22 | store().projects.fetch() 23 | 24 | $('#subscribe-status').on('change', function(event) { 25 | var el = $(event.currentTarget) 26 | 27 | if (el.prop('disabled')) return 28 | el.prop('disabled', true) 29 | 30 | $.get('/maintainer/subscription/update') 31 | .done(function() { 32 | toastr.success('Your notification settings updated') 33 | }) 34 | .fail(function(xhr) { 35 | toastr.error(xhr.responseText) 36 | }) 37 | .always(function() { 38 | el.prop('disabled', false) 39 | }) 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /public/js/modules/maintainers/models/project.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/17/13 4 | * Time: 8:41 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | return Backbone.Model.extend({ 10 | idAttribute: '_id', 11 | urlRoot: '/maintainer/projects', 12 | save: function () { 13 | var req = Backbone.Model.prototype.save.apply(this, arguments) 14 | req.done(function (data, status, xhr) { 15 | if (xhr.getResponseHeader('Became-Maintainer')) { 16 | ga && ga('send', 'event', 'users', 'firstTimeMaintainer') 17 | } 18 | }) 19 | return req 20 | } 21 | }) 22 | }) -------------------------------------------------------------------------------- /public/js/modules/maintainers/templates/group.html: -------------------------------------------------------------------------------- 1 |
      2 | <%= (type.toLowerCase() == 'user' ? 'Personal projects' : username) %> 3 |
      4 |
      -------------------------------------------------------------------------------- /public/js/modules/maintainers/templates/help.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
      4 | hide 5 |
      6 | help 7 |
      8 |

      What is a Maintainer?

      9 |
      10 |

      You're a Maintainer if you have an Open Source project and you are looking to attract donations or just acknowledgement of your hard work.

      11 | 12 |

      It's really simple to set up. To get started, sign in with GitHub and then click on Maintainers at the top.

      13 |
      14 |

      How it works

      15 | 16 |
      17 |

      1. Find your project

      18 |

      You'll see your personal projects listed as well as any Organizations of which you are an administrator. You can expand and collapse these sections. Locate the project you want support for and click on the 'Donation preferences' button.

      19 |
      20 | 21 |
      22 |

      2. Enter your support preferences

      23 |

      You can request financial contributions from Gittip, Flattr or Paypal. Alternatively, you can use the 'Email for comments' section to get a comment direct from someone. You can also use the 'Other' field to provide other instructions such as your bank transfer / wire details.

      24 |
      25 | 26 |
      27 |

      3. Embed in your README.md or Website

      28 |

      Now all you need to do is to copy the Embed code at the top of the settings section and paste it into your README.md file or Website. This will direct anyone who clicks on it to your special Project page, where people can acknowledge you or send a donation.

      29 |
      30 | -------------------------------------------------------------------------------- /public/js/modules/maintainers/templates/list.html: -------------------------------------------------------------------------------- 1 |

      You don't own any repos on GitHub yet :-(

      2 | 4 |
      5 | 6 |
      -------------------------------------------------------------------------------- /public/js/modules/maintainers/templates/project.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 | 4 | 5 | <% if (project.homepage) { %> 6 | 7 | <% } %> 8 | 9 |

      10 | <% if (project.owner.user) { %> 11 | <%=project.owner.username %> 12 | <% } else { %> 13 | <%= project.owner.username %> 14 | <% } %> 15 | / 16 | <% if (project._id) { %> 17 | <%=project.name %> 18 | <% } else { %> 19 | <%= project.name %> 20 | <% } %> 21 |

      22 | 23 |
      24 | <% if (project.description) { %> 25 | <%= project.description %> 26 | <% } %> 27 |
      28 |
      29 | 30 | 31 | Donation preferences 32 | 33 | 34 |
      35 |
      -------------------------------------------------------------------------------- /public/js/modules/maintainers/views/group.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 1:46 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var tpl = require('tpl!../templates/group.html') 9 | var RepoRow = require('./project') 10 | 11 | return Backbone.View.extend({ 12 | attributes: { 13 | 'class': 'group' 14 | }, 15 | events: { 16 | 'click .group-header': 'collapse' 17 | }, 18 | initialize: function () { 19 | this.repoRows = [] 20 | }, 21 | collapse: function() { 22 | this.$el.toggleClass('folded') 23 | }, 24 | renderRepos: function () { 25 | var list = this.$('.projects') 26 | var repos = this.model.get('repos') 27 | _.invoke(this.repoRows, 'remove') 28 | this.repoRows = [] 29 | 30 | if (!repos.length) return 31 | 32 | repos.each(function (repo) { 33 | var view = new RepoRow({model: repo}) 34 | this.repoRows.push(view) 35 | view.render() 36 | }, this) 37 | 38 | list.removeClass('hidden').append(_.pluck(this.repoRows, 'el')) 39 | }, 40 | render: function () { 41 | if (this.model.get('type').toLowerCase() != 'user') { 42 | this.$el.addClass('folded') 43 | } 44 | this.$el.html(tpl(this.model.toJSON())) 45 | this.renderRepos() 46 | return this 47 | }, 48 | remove: function() { 49 | _.invoke(this.repoRows, 'remove') 50 | Backbone.View.prototype.remove.apply(this, arguments) 51 | } 52 | }); 53 | }) -------------------------------------------------------------------------------- /public/js/modules/maintainers/views/list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 1:46 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var store = require('store').getNamespace('maintainer') 10 | var updater = require('plugins/project.updater') 11 | var tpl = require('tpl!../templates/list.html') 12 | var helpTpl = require('tpl!../templates/help.html') 13 | var Group = require('./group') 14 | 15 | return Backbone.View.extend({ 16 | events: { 17 | 'click .update-projects-info': 'updateProjects', 18 | 'click .show-help': 'showHelp', 19 | 'click .hide-help': 'hideHelp' 20 | }, 21 | initialize: function () { 22 | this.groups = [] 23 | this.collection = store().projects 24 | this.listenTo(store().projects, 'sync', this.showProjects) 25 | this.listenTo(store().projects, 'request', this.showLoading) 26 | updater.on('done', _.bind(function () { 27 | store().projects.fetch() 28 | }, this)) 29 | }, 30 | showHelp: function() { 31 | this.$('.content-holder').hide() 32 | this.$('.help.layout').show() 33 | }, 34 | hideHelp: function() { 35 | this.$('.content-holder').show() 36 | this.$('.help.layout').hide() 37 | }, 38 | showProjects: function () { 39 | this.$('.loading').hide() 40 | this.$('.content').slideDown() 41 | this.render() 42 | }, 43 | showLoading: function () { 44 | this.$('.loading').show() 45 | this.$('.content').slideUp() 46 | }, 47 | renderGroups: function () { 48 | var list = this.$('.repos-list') 49 | _.invoke(this.groups, 'remove') 50 | this.groups = [] 51 | 52 | if (!this.collection.length) return 53 | 54 | this.collection.each(function (repo) { 55 | var view = new Group({model: repo}) 56 | this.groups.push(view) 57 | view.render() 58 | }, this) 59 | 60 | list.removeClass('hidden').append(_.pluck(this.groups, 'el')) 61 | }, 62 | render: function () { 63 | this.$('.content').html(tpl()) 64 | this.$('.help.layout').html(helpTpl()) 65 | this.renderGroups() 66 | this.checkIsEmpty() 67 | return this 68 | }, 69 | checkIsEmpty: function () { 70 | var el = this.$('.is-empty.message') 71 | 72 | if (this.collection.length) { 73 | el.addClass('hidden') 74 | } else { 75 | el.removeClass('hidden') 76 | } 77 | } 78 | }); 79 | }) -------------------------------------------------------------------------------- /public/js/modules/maintainers/views/project-settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 1:46 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('maintainer') 9 | var tpl = require('tpl!../templates/project-settings.html') 10 | 11 | return Backbone.View.extend({ 12 | attributes: { 13 | class: 'repo' 14 | }, 15 | events: { 16 | 'change [data-field]': 'updateField', 17 | 'focus [data-field]': 'resetField', 18 | 'click .set-account-email': 'setAccountEmail', 19 | 'click .embed': 'selectAllText' 20 | }, 21 | setAccountEmail: function(event) { 22 | event.preventDefault() 23 | if (!currentUserEmail) return 24 | this.$('[data-field="emailMe"]').val(currentUserEmail).trigger('change') 25 | }, 26 | selectAllText: function(event) { 27 | $(event.currentTarget).select() 28 | }, 29 | updateField: function (event) { 30 | var el = $(event.currentTarget), 31 | field = el.data().field, 32 | val = $.trim(el.val()), 33 | methods = this.model.get('donateMethods') || {} 34 | 35 | if (el.attr('type') == 'checkbox') { 36 | val = el.prop('checked') 37 | } 38 | 39 | methods[field] = val 40 | this.model.save('donateMethods', methods, {patch: true}) 41 | .done(_.bind(this.setSuccess, this, el)) 42 | .fail(_.bind(this.setError, this, el)) 43 | }, 44 | setSuccess: function (field) { 45 | field.val(this.model.get('donateMethods')[field.data().field]) 46 | var group = field.closest('.form-group') 47 | group.removeClass('has-error').addClass('has-success') 48 | group.find('.saved').show().fadeOut(3000, function () { 49 | group.removeClass('has-success') 50 | }) 51 | }, 52 | resetField: function (event) { 53 | $(event.currentTarget).closest('.form-group').removeClass('has-error has-success') 54 | }, 55 | setError: function (field) { 56 | var group = field.closest('.form-group') 57 | group.addClass('has-error').removeClass('has-success') 58 | group.find('.saved').stop().hide() 59 | }, 60 | render: function () { 61 | this.$el.html(tpl({ 62 | project: this.model.toJSON() 63 | })) 64 | 65 | return this 66 | } 67 | }); 68 | }) -------------------------------------------------------------------------------- /public/js/modules/maintainers/views/project.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 1:46 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('maintainer') 9 | var tpl = require('tpl!../templates/project.html') 10 | var ProjectSettings = require('./project-settings') 11 | var DonateMethods = require('common/views/donate-methods') 12 | 13 | return Backbone.View.extend({ 14 | attributes: { 15 | class: 'repo' 16 | }, 17 | events: { 18 | 'click .settings-toggler': 'toggleSettings' 19 | }, 20 | initialize: function () { 21 | this.listenTo(this.model, 'sync', this.updateDonateMethods) 22 | }, 23 | updateDonateMethods: function () { 24 | this.donateMethods.render() 25 | }, 26 | render: function () { 27 | this.model.get('fork') && this.$el.addClass('fork') 28 | 29 | this.$el.html(tpl({ 30 | project: this.model.toJSON() 31 | })) 32 | 33 | this.donateMethods = new DonateMethods({ 34 | model: this.model, 35 | needHelp: false 36 | }) 37 | this.donateMethods.render().$el.insertAfter(this.$('.repo-name')) 38 | 39 | return this 40 | }, 41 | remove: function() { 42 | this.donateMethods.remove() 43 | Backbone.View.prototype.remove.apply(this, arguments) 44 | }, 45 | toggleSettings: function () { 46 | var btn = this.$('.settings-toggler'), 47 | altText = btn.data().altText, 48 | settingsEl = this.$('.settings') 49 | 50 | if (btn.prop('disabled')) return 51 | 52 | if (!this.settings) { 53 | this.settings = new ProjectSettings({ 54 | model: this.model, 55 | el: settingsEl 56 | }) 57 | 58 | this.settings.render() 59 | this.settings.rendered = true 60 | } 61 | 62 | btn.prop('disabled', true) 63 | settingsEl.slideToggle(function () { 64 | btn.data().altText = btn.text() 65 | btn.text(altText) 66 | btn.removeProp('disabled') 67 | }) 68 | } 69 | }); 70 | }) -------------------------------------------------------------------------------- /public/js/modules/project/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 6:19 PM 5 | */ 6 | define(function (require) { 7 | require('plugins/activate-plugins') 8 | var toastr = require('toastr') 9 | 10 | $(function () { 11 | var projectId = window.location.pathname.split('/').pop() 12 | var usersList = $('.users') 13 | 14 | $('.donate .toggle').on('click', function (event) { 15 | var btn = $(this), methods = btn.parent().find('.methods') 16 | btn.slideUp() 17 | methods.slideDown() 18 | }) 19 | 20 | if (!currentUserName) return 21 | 22 | $('.support-type').on('click', function (event) { 23 | var el = $(this), 24 | href = '/users/' + currentUserName, 25 | newState = !el.hasClass('active') 26 | 27 | if (el.prop('disabled')) return 28 | 29 | el.prop('disabled', true) 30 | $.get('/projects/' + projectId + '/subscribe/' + el.data().type + '/' + newState) 31 | .done(function () { 32 | var container = usersList.find('.col.' + el.data().type), 33 | counter = container.find('.counter') 34 | 35 | if (newState) { 36 | var link = $('').text(currentUserName).attr('href', href) 37 | container.append(link) 38 | counter.text(parseInt(counter.text()) + 1) 39 | } else { 40 | container.find('a[href="' + href + '"]').remove() 41 | counter.text(parseInt(counter.text()) - 1) 42 | } 43 | el.toggleClass('active') 44 | }) 45 | .fail(function (xhr) { 46 | toastr.error(xhr.responseText) 47 | }) 48 | .always(function () { 49 | el.removeProp('disabled') 50 | }) 51 | }) 52 | }) 53 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/collections/project-groups.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 8:41 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('repo-selector') 9 | var Projects = Backbone.Collection.extend({}) 10 | 11 | return Backbone.Collection.extend({ 12 | url: '/supporter/groups', 13 | parse: function (data) { 14 | _.each(data, function (entry) { 15 | entry.repos = new Projects(entry.repos) 16 | }) 17 | 18 | return data 19 | }, 20 | findCurrent: function () { 21 | var current = store().currentType, 22 | entity 23 | 24 | if (current.type == 'user') { 25 | entity = this.findWhere({type: 'User'}) 26 | } else if (current.type == 'org') { 27 | entity = this.findWhere({type: 'Organization', org: current.id}) 28 | } else { 29 | this.each(function (group) { 30 | var found = group.get('repos').findWhere({_id: current.id}) 31 | if (!found) return 32 | entity = found 33 | }) 34 | } 35 | 36 | return entity 37 | }, 38 | currentHasSupport: function () { 39 | var current = this.findCurrent() 40 | 41 | if (!current) return false 42 | 43 | return current.get('hasSupport') 44 | }, 45 | updateCurrentSupport: function (hasSupport) { 46 | var current = this.findCurrent() 47 | 48 | if (!current) return false 49 | 50 | current.set('hasSupport', hasSupport) 51 | }, 52 | comparator: function (item) { 53 | return [item.get('type') != 'user', item.get('username')] 54 | } 55 | }) 56 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/collections/projects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/14/13 4 | * Time: 12:25 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var Project = require('../models/project') 10 | var store = require('store').getNamespace('repo-selector') 11 | 12 | return Backbone.Collection.extend({ 13 | url: function () { 14 | return '/supporter/support/' + store().currentType.type + '/' + store().currentType.id 15 | }, 16 | model: Project, 17 | fetch: function () { 18 | var self = this 19 | this.trigger('prefetch') 20 | return Backbone.Collection.prototype.fetch.call(this, arguments) 21 | .done(function () { 22 | self.trigger('fetched') 23 | }) 24 | }, 25 | save: function (options) { 26 | return this.sync('update', this, options); 27 | } 28 | }) 29 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/collections/repos.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 6:26 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('repo-selector') 9 | var Repo = require('../models/repo') 10 | 11 | return Backbone.Collection.extend({ 12 | model: Repo, 13 | url: function () { 14 | return '/supporter/github/' + this.path; 15 | }, 16 | initialize: function (models, options) { 17 | options = options || {} 18 | options.path && (this.path = options.path) 19 | options.type && (this.type = options.type) 20 | this.listenTo(store().selected, 'fetched', this.setUpstreamSupport) 21 | }, 22 | setUpstreamSupport: function() { 23 | _.invoke(this.models, 'setUpstreamSupport') 24 | this.trigger('repos-loaded') 25 | }, 26 | fetch: function (options) { 27 | var self = this 28 | var request = Backbone.Collection.prototype.fetch.call(this, options); 29 | request.done(function () { 30 | self.trigger('repos-loaded') 31 | store().hub.trigger('repos-loaded', self, self.type) 32 | }) 33 | 34 | return request 35 | } 36 | }) 37 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/collections/search.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/17/13 4 | * Time: 1:24 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('repo-selector') 9 | var Repos = require('./repos') 10 | 11 | return Repos.extend({ 12 | path: 'search/repositories', 13 | type: 'search', 14 | fetchRepo: function (owner, name) { 15 | var path = this.path, self = this 16 | this.path = 'repos/' + owner + '/' + name 17 | return this.fetch().always(function() { 18 | self.path = path 19 | }) 20 | } 21 | }) 22 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 6:25 PM 5 | */ 6 | define(function (require) { 7 | require('bootstrap') 8 | require('plugins/activate-plugins') 9 | 10 | var toastr = require('toastr') 11 | var Layout = require('./views/layout') 12 | var RepoList = require('./views/repo-list') 13 | var RepoSearch = require('./views/search') 14 | var SelectedRepos = require('./views/selected') 15 | 16 | var Repos = require('./collections/repos') 17 | var Search = require('./collections/search') 18 | var Projects = require('./collections/projects') 19 | var ProjectGroups = require('./collections/project-groups') 20 | 21 | var Router = require('./router') 22 | var store = require('store').getNamespace('repo-selector') 23 | 24 | $(function () { 25 | store().currentType = { 26 | } 27 | 28 | store().hub = _.extend({}, Backbone.Events) 29 | store().selected = new Projects() 30 | store().groups = new ProjectGroups() 31 | store().repos = { 32 | search: new Search() 33 | } 34 | 35 | store().layout = new Layout({ 36 | el: $('#repos-selector') 37 | }) 38 | 39 | store().reposLists = { 40 | search: new RepoSearch({ 41 | el: store().layout.$('#repo-search'), 42 | collection: store().repos.search 43 | }).render(), 44 | selected: new SelectedRepos({ 45 | el: store().layout.$('#selected-repos'), 46 | collection: store().selected 47 | }).render() 48 | } 49 | 50 | store().groups.fetch() 51 | 52 | _.each({ 53 | subscriptions: 'watched', 54 | starred: 'starred', 55 | repos: 'own' 56 | }, function (type, url) { 57 | store().repos[type] = new Repos(null, { 58 | path: 'user/' + url, 59 | type: type 60 | }) 61 | 62 | store().reposLists[type] = new RepoList({ 63 | el: store().layout.$('#' + type + '-repos'), 64 | collection: store().repos[type] 65 | }).render() 66 | 67 | store().repos[type].fetch() 68 | .fail(function (xhr) { 69 | toastr.error(xhr.responseText, 'Failed to load your ' + type + ' repos') 70 | }) 71 | }) 72 | 73 | store().router = new Router() 74 | Backbone.history.start(); 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /public/js/modules/repo-selector/models/project.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/17/13 4 | * Time: 2:51 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var Repo = require('./repo') 9 | var Support = require('./support') 10 | 11 | return Repo.extend({ 12 | idAttribute: 'githubId', 13 | isProject: true, 14 | parse: function (entity) { 15 | var result = entity 16 | if (entity.project) { 17 | result = _.extend(entity.project, { 18 | support: { 19 | contributing: entity.contributing, 20 | donating: entity.donating, 21 | supporting: entity.supporting 22 | } 23 | }) 24 | } 25 | 26 | result.support = new Support(result.support) 27 | 28 | return result; 29 | }, 30 | save: function () { 31 | var req = Backbone.Model.prototype.save.apply(this, arguments) 32 | req.done(function(data, status, xhr) { 33 | if (xhr.getResponseHeader('Became-Supporter')) { 34 | ga && ga('send', 'event', 'users', 'firstTimeSupporter') 35 | } 36 | }) 37 | return req 38 | }, 39 | destroy: function (options) { 40 | options = options || {} 41 | options.data = JSON.stringify({id: this.get('_id')}) 42 | options.contentType = 'application/json' 43 | 44 | return Backbone.Model.prototype.destroy.apply(this, [options]) 45 | } 46 | }) 47 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/models/repo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 6:25 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('repo-selector') 9 | var Support = require('./support') 10 | 11 | return Backbone.Model.extend({ 12 | idAttribute: 'githubId', 13 | defaults: { 14 | name: '', 15 | url: '', 16 | support: new Support 17 | }, 18 | toJSON: function () { 19 | var obj = Backbone.Model.prototype.toJSON.apply(this, arguments) 20 | obj.support = obj.support.toJSON() 21 | return obj 22 | }, 23 | setUpstreamSupport: function() { 24 | var exists = store().selected.get(this.id), 25 | support = {} 26 | if (exists) { 27 | support = exists.get('support').toJSON() 28 | } 29 | this.set('support', new Support(support)) 30 | }, 31 | parse: function (repo) { 32 | var exists = store().selected.get(repo.githubId) 33 | if (exists) { 34 | repo.support = exists.get('support').toJSON() 35 | } 36 | repo.support = new Support(repo.support) 37 | 38 | return repo; 39 | }, 40 | destroy: function (options) { 41 | options = options ? _.clone(options) : {}; 42 | var model = this; 43 | var success = options.success; 44 | 45 | var destroy = function () { 46 | model.trigger('destroy', model, model.collection, options); 47 | }; 48 | 49 | options.success = function (resp) { 50 | destroy(); 51 | if (success) success(model, resp, options); 52 | if (!model.isNew()) model.trigger('sync', model, resp, options); 53 | }; 54 | 55 | options.success(); 56 | return false; 57 | } 58 | }) 59 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/models/support.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 6:47 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | return Backbone.Model.extend({ 10 | defaults: { 11 | contributing: false, 12 | donating: false, 13 | supporting: false 14 | }, 15 | flat: function() { 16 | return _(this.attributes).pick(_.keys(this.defaults)).value() 17 | }, 18 | count: function() { 19 | return _(this.attributes).pick(_.keys(this.defaults)).values().compact().value().length 20 | }, 21 | isEmpty: function () { 22 | return (!this.attributes.contributing && !this.attributes.supporting && !this.attributes.donating) 23 | } 24 | }) 25 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/17/13 4 | * Time: 8:39 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('repo-selector') 9 | 10 | return Backbone.Router.extend({ 11 | initialize: function () { 12 | if (!localStorage) return 13 | var lastUrl = localStorage.getItem('supporters-last-url') 14 | if (lastUrl && lastUrl != location.hash) location.hash = lastUrl 15 | }, 16 | routes: { 17 | 'type/:type/id/:id': 'switchType', 18 | 'type/:type/id/:id/tab/:tab': 'switchType', 19 | '*actions': 'switchType' 20 | }, 21 | switchType: function (type, id, tab) { 22 | if (_.indexOf(['user', 'project', 'organization'], type) == -1) { 23 | type = store().currentType.type 24 | id = store().currentType.id 25 | } 26 | 27 | if (!store().currentType.type || type != store().currentType.type || id != store().currentType.id) { 28 | store().currentType = { 29 | type: type || 'user', 30 | id: id || currentUserId 31 | } 32 | 33 | store().selected.fetch() 34 | } 35 | 36 | localStorage && localStorage.setItem('supporters-last-url', location.hash) 37 | this.selectTab(tab) 38 | }, 39 | selectTab: function () { 40 | var tab = Array.prototype.pop.call(arguments) 41 | tab = tab || 'selected-repos' 42 | var el = store().layout.$('.nav .tab[href="#' + tab + '"]') 43 | el.tab('show') 44 | } 45 | }); 46 | }) 47 | -------------------------------------------------------------------------------- /public/js/modules/repo-selector/templates/group-selector.html: -------------------------------------------------------------------------------- 1 | <% var own = _.find(groups, {type: 'User'}) %> 2 | <% var orgs = _.filter(groups, {type: 'Organization'}) %> 3 | <% var selected = 'My General Support' %> 4 | 5 | <% if (own || orgs.length) {%> 6 | 42 | <% } %> 43 | -------------------------------------------------------------------------------- /public/js/modules/repo-selector/templates/help.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
      4 | hide 5 |
      6 | help 7 |
      8 |

      What is a Supporter?

      9 | 10 |
      11 | A Supporter is a person, project or organisation that wants to show appreciation for the Open Source projects they are using. You don't need to make any financial contribution, just setting up a Supporter page and linking to it from your README.md or web site tells the world you are grateful for the existence of the Open Source projects you support. 12 |
      13 | 14 |

      How It Works?

      15 | 16 |
      17 |
      18 |

      1. Specify the capacity in which you're supporting

      19 | 20 |

      21 | Click on the 'You are editing' drop down at the top of the screen. 22 | You can support in more than on of the capacities you see in the drop down. 23 | For each one where you are supporting, you will see a heart icon. 24 |

      25 | 26 |

      27 | You can choose to give support generally without being project specific (in which case select 'General support') and you can also give support at the individual project level (in which case you can select an individual repo). 28 |

      29 | 30 |

      31 | Wherever you are either an Administrator at the Organizational level, you will see those organizations listed. As with personal account, you can choose to offer support at the general level and/or the project level. 32 |

      33 | 34 | 35 |
      36 |
      37 |
      38 |

      2. Specify Open Source projects you use

      39 | 40 |
      41 |

      You can find the open source projects you are using by the four methods shown (Search, Owned, Watched and Starred). Having located the repo, you indicate the way you are supporting that project by clicking on any or all of the 3 icons. Hover over the icon to see what each one means.

      42 | 43 |

      Each time you indicate support for a repo, it will be added to SELECTED PROJECTS which you can click on at any time.

      44 | 45 |
      46 |
      47 |
      48 |

      3. You might want to donate now

      49 | 50 |
      51 |

      There is absolutely no obligation to donate. Just acknowledging your support (the heart icon) is great. But if you do want to donate then you can donate by clicking on any of the donate icons on the right side. I Love Open Source (itself an Open Source project) does not process any financial transactions at all. The Repo Maintainers are responsible for saying how they would like donations to be given and this involves 3rd party systems such as Gittip, Flattr, Paypal etc.

      52 | 53 |
      54 |
      55 |
      56 |

      4. Preview your page and generate link code

      57 | 58 |
      59 |

      You can press the Preview Project button at any time in order to see how your support page looks.

      60 | 61 |

      At any time, you can press the 'Share' button to get the code to paste into your web site or README.md file. By inlacing the "I Love Open Source" icon, you are helping people drive awareness and encouraging others to acknowledge and also donate.

      62 | 63 |
      64 | 65 |
      66 | -------------------------------------------------------------------------------- /public/js/modules/repo-selector/templates/layout.html: -------------------------------------------------------------------------------- 1 | 35 | 37 |
      38 |
      39 |
      40 |
      41 | 42 |
      43 |
      -------------------------------------------------------------------------------- /public/js/modules/repo-selector/templates/repo-list.html: -------------------------------------------------------------------------------- 1 | 8 | <% if (hasMore) { %> 9 | 10 | <% } %> 11 | -------------------------------------------------------------------------------- /public/js/modules/repo-selector/templates/repo-row.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 | 4 | 5 | <% if (repo.homepage) { %> 6 | 7 | <% } %> 8 | 9 |

      10 | <% if (repo.owner.user) { %> 11 | <%=repo.owner.username %> 12 | <% } else { %> 13 | <%= repo.owner.username %> 14 | <% } %> 15 | / 16 | <% if (repo._id) { %> 17 | <%=repo.name %> 18 | <% } else { %> 19 | <%= repo.name %> 20 | <% } %> 21 |

      22 | 23 |
      24 | <% if (repo.description) { %> 25 | <%= repo.description %> 26 | <% } %> 27 |
      28 | 29 |
      30 | 31 |
      32 | 33 | 34 | 35 | <% if (obj.canDelete) { %> 36 | 37 | <% } %> 38 | 39 |
      -------------------------------------------------------------------------------- /public/js/modules/repo-selector/templates/repo-search.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 | 4 | 5 |
      6 | 7 |
      8 | Your query is too short 9 |
      10 | 11 | <% if (!obj.reposCount && obj.searchString) { %> 12 |

      Nothing found by your request

      13 | <% } %> 14 | 15 | 16 | 23 | -------------------------------------------------------------------------------- /public/js/modules/repo-selector/templates/selected.html: -------------------------------------------------------------------------------- 1 |

      2 | This page will display all of the Open Source projects you are using.
      3 | Please use Search, Owned, Watched or Starred to locate a Repo and then add it 4 |

      5 | -------------------------------------------------------------------------------- /public/js/modules/repo-selector/templates/share.html: -------------------------------------------------------------------------------- 1 | <% var siteUrl = window.location.origin %> 2 | <% var img = siteUrl + '/images/logo-lightbg.png' %> 3 | <% var alt = 'I Love Open Source' %> 4 | 5 |
      6 | Please copy and paste the Markdown or HTML link into your site. This is very important as it publicly expresses your support and encourages others to do so as well. 7 |
      8 | 9 |
      10 | 11 |
      12 | 13 |
      14 |
      15 |
      16 | 17 |
      18 | 19 |
      20 |
      21 | -------------------------------------------------------------------------------- /public/js/modules/repo-selector/views/group-selector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 9/25/13 4 | * Time: 10:52 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var store = require('store').getNamespace('repo-selector') 10 | var tpl = require('tpl!../templates/group-selector.html') 11 | 12 | return Backbone.View.extend({ 13 | initialize: function () { 14 | this.listenTo(store().groups, 'sync', this.render) 15 | this.listenTo(store().selected, 'add', this.addHeart) 16 | this.listenTo(store().selected, 'remove', this.removeHeart) 17 | $('#projects-sync').on('github-info-updated', this.updateGroups) 18 | }, 19 | events: { 20 | 'click a[role="menuitem"]': 'selectGroup' 21 | }, 22 | updateGroups: function() { 23 | store().groups.fetch() 24 | }, 25 | addHeart: function () { 26 | if (!store().groups.currentHasSupport()) { 27 | store().groups.updateCurrentSupport(true) 28 | this.render() 29 | } 30 | }, 31 | removeHeart: function (model, collection) { 32 | if (collection.length) return 33 | store().groups.updateCurrentSupport(false) 34 | this.render() 35 | }, 36 | selectGroup: function (event) { 37 | var el = $(event.currentTarget) 38 | this.$('.value').text(el.data().val || el.text()) 39 | }, 40 | render: function () { 41 | this.$el.html(tpl({ 42 | groups: store().groups.toJSON(), 43 | current: store().currentType 44 | })) 45 | 46 | return this 47 | } 48 | }); 49 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/views/layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 1:33 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | var toastr = require('toastr') 10 | var store = require('store').getNamespace('repo-selector') 11 | var GroupSelector = require('./group-selector') 12 | var tpl = require('tpl!../templates/layout.html') 13 | var shareTpl = require('tpl!../templates/share.html') 14 | var helpTpl = require('tpl!../templates/help.html') 15 | 16 | return Backbone.View.extend({ 17 | initialize: function () { 18 | this.listenTo(store().hub, 'repos-loaded', this.loadedRepoList) 19 | 20 | this.listenTo(store().selected, 'add remove', this.updateSelectedCount) 21 | this.listenTo(store().selected, 'prefetch', this.showLoading) 22 | this.listenTo(store().selected, 'fetched', this.hideLoading) 23 | this.render() 24 | }, 25 | events: { 26 | 'click .share-trigger': 'toggleShare', 27 | 'click .show-help': 'showHelp', 28 | 'click .hide-help': 'hideHelp', 29 | 'click .nav-tabs .tab': 'selectTab' 30 | }, 31 | showHelp: function() { 32 | this.$('.content-holder').hide() 33 | this.$('.help.layout').show() 34 | }, 35 | hideHelp: function() { 36 | this.$('.content-holder').show() 37 | this.$('.help.layout').hide() 38 | }, 39 | toggleShare: function () { 40 | var btn = this.$('.share-trigger'), 41 | altText = btn.data().altText, 42 | el = this.$('.share') 43 | 44 | if (!el.is(':visible')) { 45 | el.slideDown() 46 | } else { 47 | el.slideUp() 48 | } 49 | 50 | btn.data().altText = btn.text() 51 | btn.text(altText) 52 | }, 53 | selectTab: function (event) { 54 | event.preventDefault() 55 | var tab = $(event.currentTarget).attr('href').replace('#', '') 56 | store().router.navigate( 57 | '/type/' + store().currentType.type + '/id/' + store().currentType.id + '/tab/' + tab, 58 | {trigger: true} 59 | ) 60 | }, 61 | hideLoading: function () { 62 | this.$('.loading').hide() 63 | this.$('.content').slideDown() 64 | 65 | var link = this.getProjectLink() 66 | this.$('.share').html(shareTpl({ 67 | shareLink: link 68 | })) 69 | 70 | this.$('.preview').attr('href', link).html('Preview ' + store().currentType.type) 71 | }, 72 | showLoading: function () { 73 | this.$('.loading').show() 74 | this.$('.content').slideUp() 75 | }, 76 | updateSelectedCount: function (model, collection) { 77 | this.$('#selected-count').text(collection.length) 78 | }, 79 | loadedRepoList: function (collection, type) { 80 | if (type == 'search') return 81 | this.$('.nav .tab[href="#' + type + '-repos"]').parent().removeClass('disabled') 82 | }, 83 | render: function () { 84 | this.$('.loading').hide() 85 | this.$('.content').html(tpl()).slideDown() 86 | this.$('.help.layout').html(helpTpl()) 87 | this.updateSelectedCount(null, store().selected) 88 | new GroupSelector({ 89 | el: this.$('.support-selector') 90 | }) 91 | }, 92 | getProjectLink: function () { 93 | var link = [] 94 | 95 | if (store().currentType.type == 'organization') { 96 | link.push('orgs') 97 | } else if (store().currentType.type == 'project') { 98 | link.push('projects') 99 | } else { 100 | link.push('users') 101 | } 102 | 103 | if (store().currentType.type != 'user') { 104 | link.push(store().currentType.id) 105 | } else { 106 | link.push(currentUserName) 107 | } 108 | 109 | return '/' + link.join('/') 110 | } 111 | }); 112 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/views/project-row.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 1:29 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('repo-selector') 9 | var toastr = require('toastr') 10 | var tpl = require('tpl!../templates/repo-row.html') 11 | var RepoRow = require('./repo-row') 12 | 13 | return RepoRow.extend({ 14 | initialize: function (options) { 15 | this.listenTo(store().hub, 'support.update:' + this.model.id, this.updateSupport) 16 | this.listenTo(this.model, 'request', this.asleep) 17 | this.listenTo(this.model, 'error', this.errorNotify) 18 | this.listenTo(this.model, 'sync', this.onSuccess) 19 | this.listenTo(this.model, 'destroy', this.onDestroy) 20 | }, 21 | removeWarning: 'If you do not check any icons, this item will be removed. OK?', 22 | attributes: { 23 | class: 'repo' 24 | }, 25 | events: { 26 | 'click .support-type': 'toggleSupport', 27 | 'click .remove': 'removeProject' 28 | }, 29 | asleep: function () { 30 | store().hub.trigger('sleep:' + this.model.id) 31 | }, 32 | awake: function () { 33 | store().hub.trigger('wakeup:' + this.model.id) 34 | }, 35 | onSuccess: function () { 36 | store().hub.trigger('support.set:' + this.model.id, this.model.get('support').toJSON()) 37 | this.awake() 38 | this.renderSupport() 39 | }, 40 | updateSupport: function (type, val) { 41 | if (!val && this.model.get('support').count() == 1) { 42 | window.confirm(this.removeWarning) && this.removeProject() 43 | } else { 44 | this.model.get('support').set(type, val) 45 | this.model.save() 46 | } 47 | }, 48 | errorNotify: function (model, xhr) { 49 | this.awake() 50 | toastr.error(xhr.responseText) 51 | }, 52 | onDestroy: function () { 53 | var support = this.model.get('support').flat() 54 | _.each(support, function (val, key) { 55 | support[key] = false 56 | }) 57 | store().hub.trigger('support.set:' + this.model.id, support) 58 | this.awake() 59 | this.remove() 60 | }, 61 | removeProject: function () { 62 | this.model.destroy() 63 | }, 64 | toggleSupport: function (event) { 65 | var support = this.model.get('support'), 66 | type = $(event.currentTarget).data().type, 67 | val = !support.get(type) 68 | 69 | this.updateSupport(type, val) 70 | } 71 | }); 72 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/views/repo-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 1:29 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | 9 | return Backbone.View.extend({ 10 | tpl: require('tpl!../templates/repo-list.html'), 11 | RepoRow: require('./repo-row'), 12 | events: { 13 | 'click .load-more': 'loadMore', 14 | 'click .controls .support-type': 'toogleReposSupport' 15 | }, 16 | initialize: function () { 17 | this.listenTo(this.collection, 'repos-loaded', this.renderRepos) 18 | this.repoRows = [] 19 | }, 20 | loadMore: function () { 21 | var btn = this.$('.load-more') 22 | if (btn.attr('disabled')) return 23 | btn.button('loading') 24 | 25 | this.collection.path = this.collection.hasMore 26 | this.collection.fetch({remove: false}) 27 | }, 28 | toogleReposSupport: function (event) { 29 | var el = $(event.currentTarget) 30 | el.toggleClass('active') 31 | 32 | _.invoke(this.repoRows, 'toggleSupportByType', el.data().type, el.hasClass('active')) 33 | }, 34 | renderRepos: function () { 35 | var list = this.$('.repos-list') 36 | _.invoke(this.repoRows, 'remove') 37 | this.repoRows = [] 38 | 39 | if (!this.collection.length) { 40 | return list.addClass('hidden') 41 | } 42 | 43 | this.collection.each(function (repo) { 44 | var view = new this.RepoRow({model: repo}) 45 | this.repoRows.push(view) 46 | view.render() 47 | }, this) 48 | 49 | list.removeClass('hidden').append(_.pluck(this.repoRows, 'el')) 50 | }, 51 | renderRepo: function (repo) { 52 | var list = this.$('.repos-list') 53 | var view = new this.RepoRow({model: repo}) 54 | this.repoRows.push(view) 55 | view.render() 56 | 57 | list.removeClass('hidden').append(view.el) 58 | }, 59 | render: function () { 60 | this.$el.html(this.tpl({ 61 | hasMore: this.collection.hasMore 62 | })) 63 | 64 | this.checkIsEmpty() 65 | this.renderRepos() 66 | return this 67 | }, 68 | checkIsEmpty: function () { 69 | var el = this.$('.is-empty.message'), 70 | list = this.$('.repos-list') 71 | 72 | if (this.collection.length) { 73 | list.removeClass('hidden') 74 | el.addClass('hidden') 75 | } else { 76 | list.addClass('hidden') 77 | el.removeClass('hidden') 78 | } 79 | } 80 | }); 81 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/views/repo-row.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 1:29 AM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | var store = require('store').getNamespace('repo-selector') 9 | var tpl = require('tpl!../templates/repo-row.html') 10 | var DonateMethods = require('common/views/donate-methods') 11 | 12 | return Backbone.View.extend({ 13 | initialize: function (options) { 14 | this.listenTo(store().hub, 'support.set:' + this.model.id, this.updateSupport) 15 | this.listenTo(store().hub, 'wakeup:' + this.model.id, this.unsetLoading) 16 | this.listenTo(store().hub, 'sleep:' + this.model.id, this.setLoading) 17 | }, 18 | attributes: { 19 | class: 'repo' 20 | }, 21 | events: { 22 | 'click .support-type': 'toggleSupport' 23 | }, 24 | setLoading: function () { 25 | this.undelegateEvents() 26 | this.$el.addClass('loading') 27 | }, 28 | unsetLoading: function () { 29 | this.delegateEvents() 30 | this.$el.removeClass('loading') 31 | }, 32 | updateSupport: function (support) { 33 | this.model.get('support').set(support) 34 | this.renderSupport() 35 | }, 36 | renderSupport: function () { 37 | var types = this.$('.support-type') 38 | _.each(this.model.get('support').flat(), function (val, type) { 39 | types.filter('.' + type).toggleClass('active', val) 40 | }) 41 | }, 42 | toggleSupport: function (event) { 43 | var support = this.model.get('support'), 44 | type = $(event.currentTarget).data().type, 45 | val = !support.get(type), 46 | exists = store().selected.get(this.model.id) 47 | 48 | if (exists) { 49 | store().hub.trigger('support.update:' + this.model.id, type, val) 50 | } else { 51 | var newModel = this.model.toJSON() 52 | newModel.support[type] = val 53 | store().selected.create(newModel, {parse: true}) 54 | } 55 | }, 56 | render: function () { 57 | this.model.get('fork') && this.$el.addClass('fork') 58 | this.$el.html(tpl({ 59 | repo: this.model.toJSON(), 60 | canDelete: this.model.isProject 61 | })) 62 | 63 | this.donateMethods = new DonateMethods({ 64 | model: this.model, 65 | needHelp: true 66 | }) 67 | this.donateMethods.render().$el.insertAfter(this.$('.support-types')) 68 | 69 | if (!this.model.isProject) { 70 | var projectData = this.model.toJSON(), 71 | els = this.$('.want-contribute, .email-to-author') 72 | 73 | if (els.length) els.data().projectData = this.model.toJSON() 74 | } 75 | 76 | return this 77 | }, 78 | remove: function() { 79 | this.donateMethods && this.donateMethods.remove() 80 | Backbone.View.prototype.remove.apply(this, arguments) 81 | } 82 | }); 83 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/views/search.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 2:54 AM 5 | */ 6 | define(function (require) { 7 | var toastr = require('toastr') 8 | var RepoList = require('./repo-list') 9 | var urlRegExp = /^((https+:\/\/github.com\/)([^\/]+)\/([^\/#]+)\/*)(.*)$/ 10 | 11 | return RepoList.extend({ 12 | minLength: 1, 13 | tpl: require('tpl!../templates/repo-search.html'), 14 | events: { 15 | 'keyup .search-string': 'triggerSearch', 16 | 'click .controls .support-type': 'toogleReposSupport', 17 | 'focus .search-string': 'clearSearchString', 18 | 'click .search': 'searchRepo', 19 | 'click .add': 'addRepo' 20 | }, 21 | triggerSearch: function (event) { 22 | if (event.which != 13) return 23 | var query = $.trim(this.$('.search-string').val()) 24 | 25 | if (!query || query.length < this.minLength) return 26 | 27 | var match = query.match(urlRegExp) 28 | if (match) { 29 | this.addRepo() 30 | } else { 31 | this.searchRepo() 32 | } 33 | }, 34 | clearSearchString: function () { 35 | this.$('.alert').hide() 36 | }, 37 | addRepo: function () { 38 | var btn = this.$('.add'), 39 | query = $.trim(this.$('.search-string').val()), 40 | self = this 41 | 42 | if (!query) return 43 | 44 | if (btn.attr('disabled')) return 45 | btn.button('loading') 46 | 47 | var match = query.match(urlRegExp) 48 | 49 | if (!match) return toastr.warning('You have entered wrong URL') 50 | 51 | this.collection.fetchRepo(match[3], match[4]) 52 | .fail(function () { 53 | toastr.warning('Invalid Repo Url') 54 | }) 55 | .always(function () { 56 | btn.button('reset') 57 | }) 58 | }, 59 | searchRepo: function () { 60 | var btn = this.$('.search'), 61 | query = $.trim(this.$('.search-string').val()), 62 | self = this 63 | 64 | if (btn.attr('disabled')) return 65 | 66 | if (query.length < this.minLength) return this.$('.alert').show() 67 | 68 | btn.button('loading') 69 | 70 | this.collection.fetch({ 71 | data: {q: query + '+in:name'} 72 | }) 73 | .fail(function () { 74 | toastr.warning('Failed search attempt') 75 | }) 76 | .always(function () { 77 | btn.button('reset') 78 | }) 79 | 80 | } 81 | }); 82 | }) -------------------------------------------------------------------------------- /public/js/modules/repo-selector/views/selected.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 3:27 AM 5 | */ 6 | define(function (require) { 7 | var RepoList = require('./repo-list') 8 | 9 | return RepoList.extend({ 10 | RepoRow: require('./project-row'), 11 | isProjectList: true, 12 | tpl: require('tpl!../templates/selected.html'), 13 | initialize: function () { 14 | RepoList.prototype.initialize.apply(this, arguments) 15 | this.listenTo(this.collection, 'add', this.renderRepo) 16 | this.listenTo(this.collection, 'destroy', this.checkIsEmpty) 17 | this.listenTo(this.collection, 'add', this.checkIsEmpty) 18 | this.listenTo(this.collection, 'sync', this.renderRepos) 19 | } 20 | }); 21 | }) -------------------------------------------------------------------------------- /public/js/modules/user-settings/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/17/13 4 | * Time: 6:20 PM 5 | */ 6 | define(function (require) { 7 | require('backbone') 8 | require('plugins/activate-plugins') 9 | 10 | var View = Backbone.View.extend({ 11 | events: { 12 | 'change [data-field]': 'updateField', 13 | 'focus [data-field]': 'resetField' 14 | }, 15 | updateField: function (event) { 16 | var el = $(event.currentTarget), 17 | field = el.data().field, 18 | val = $.trim(el.val()) 19 | 20 | if (el.attr('required') && !val) { 21 | return this.setError(el) 22 | } 23 | 24 | if (el.attr('type') == 'checkbox') { 25 | val = el.prop('checked') 26 | } 27 | 28 | this.saveField(field, el, val) 29 | }, 30 | setSuccess: function (field) { 31 | var group = field.closest('.form-group') 32 | group.removeClass('has-error').addClass('has-success') 33 | group.find('.saved').show().fadeOut(3000, function () { 34 | group.removeClass('has-success') 35 | }) 36 | }, 37 | saveField: function (field, el, val) { 38 | $.post('/settings/' + field, {value: val}) 39 | .done(_.bind(this.setSuccess, this, el)) 40 | .fail(_.bind(this.setError, this, el)) 41 | }, 42 | resetField: function (event) { 43 | $(event.currentTarget).closest('.form-group').removeClass('has-error has-success') 44 | }, 45 | setError: function (field) { 46 | var group = field.closest('.form-group') 47 | group.addClass('has-error').removeClass('has-success') 48 | group.find('.saved').stop().hide() 49 | } 50 | }) 51 | 52 | $(function () { 53 | var view = new View({ 54 | el: $('#page-settings') 55 | }) 56 | 57 | view.$('.form-group') 58 | .on('mouseover', '.help, .popover', function () { 59 | var group = $(this).closest('.form-group'), 60 | popover = group.find('.popover'), 61 | help = group.find('.help') 62 | 63 | popover.show().css('top', -(popover.outerHeight() / 2 + help.position().top - help.height())) 64 | }) 65 | .on('mouseout', '.help, .popover', function () { 66 | $(this).closest('.form-group').find('.popover').hide() 67 | }) 68 | }) 69 | }) -------------------------------------------------------------------------------- /public/js/plugins/project.updater.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by krasu on 11/11/13. 3 | */ 4 | define(function (require) { 5 | if (!window.isLoggedIn) return null 6 | 7 | var io = require('socket.io') 8 | var toastr = require('toastr') 9 | var sio = io.connect(window.location.origin + '/projects-update/status') 10 | 11 | $(function () { 12 | var projectSync = $('#projects-sync'), 13 | loader = projectSync.find('.loading'); 14 | 15 | sio.on('error', function (error) { 16 | projectSync.addClass('error').removeClass('loading') 17 | toastr.error(error, 'Failed to retrieve your info from GitHub') 18 | updateTooltip() 19 | }) 20 | 21 | sio.on('done', function () { 22 | projectSync.removeClass('error loading') 23 | toastr.success('Your projects loaded from GitHub!') 24 | projectSync.trigger('github-info-updated') 25 | updateTooltip() 26 | }) 27 | 28 | sio.on('progress', function (msg) { 29 | projectSync.removeClass('error').addClass('loading') 30 | projectSync.trigger('github-info-updating') 31 | updateTooltip(msg) 32 | }) 33 | 34 | 35 | projectSync.find('.trigger, .error').on('click', function () { 36 | if (projectSync.hasClass('loading')) return 37 | projectSync.removeClass('error') 38 | projectSync.addClass('loading') 39 | sio.emit('start') 40 | }) 41 | 42 | function updateTooltip(msg) { 43 | loader.attr('data-original-title', msg || loader.data('title')) 44 | .tooltip('fixTitle').tooltip('show') 45 | } 46 | }) 47 | 48 | return sio 49 | }) -------------------------------------------------------------------------------- /public/js/plugins/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/13/13 4 | * Time: 4:32 AM 5 | */ 6 | define(function () { 7 | var storage = {} 8 | 9 | //storage().property 10 | 11 | return { 12 | getNamespace: function (namespace) { 13 | storage[namespace] = storage[namespace] ? storage[namespace] : {}; 14 | 15 | return function () { 16 | return storage[namespace] 17 | }; 18 | }, 19 | 'set' : function (namespace, key, value) { 20 | storage[namespace][key] = value 21 | }, 22 | 'get' : function (namespace, key) { 23 | return storage[namespace][key] 24 | } 25 | } 26 | }) -------------------------------------------------------------------------------- /public/js/require.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: krasu 3 | * Date: 8/4/13 4 | * Time: 4:59 PM 5 | */ 6 | require.config({ 7 | paths: { 8 | store: './plugins/store', 9 | 10 | rv: 'vendors/require-ractive-plugin/js/rv', 11 | tpl: 'vendors/requirejs-tpl/js/tpl', 12 | text: 'vendors/text/js/text', 13 | 14 | Ractive: 'vendors/ractive/js/Ractive', 15 | jquery: 'vendors/jquery/js/jquery', 16 | toastr: 'vendors/toastr/js/toastr', 17 | lodash: 'vendors/lodash/js/lodash', 18 | backbone: 'vendors/backbone/js/backbone', 19 | moment: 'vendors/momentjs/js/moment', 20 | 'require-tpl': 'vendors/require-tpl/js/require-tpl', 21 | json: 'vendors/json3/js/json3', 22 | 'socket.io': '/socket.io/socket.io', 23 | 24 | gitRepoRequester: 'plugins/repo-requester' 25 | }, 26 | shim: { 27 | bootstrap: ['jquery'], 28 | backbone: ['jquery', 'lodash', 'json'] 29 | } 30 | }); 31 | 32 | --------------------------------------------------------------------------------