├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── Gruntfile.js
├── LICENSE
├── Procfile
├── Procfile.local
├── README.md
├── Vagrantfile
├── _Design Assets
└── Logo.sketch
│ ├── Data
│ ├── QuickLook
│ ├── Preview.png
│ └── Thumbnail.png
│ ├── fonts
│ ├── metadata
│ └── version
├── app.js
├── cluster_app.js
├── components
├── GithubContributors.js
└── MarkdownParser.js
├── config
├── passport.js
├── secrets.js
└── userlist.js
├── constants.js
├── controllers
├── api.js
├── chat.js
├── comments.js
├── contact.js
├── home.js
├── issues.js
├── news.js
├── user.js
└── votes.js
├── forever.js
├── gulpfile.js
├── hackathon-starter-readme.md
├── models
├── Comment.js
├── Issue.js
├── NewsItem.js
├── User.js
└── Vote.js
├── package.json
├── public
├── css
│ ├── lib
│ │ ├── animate.css
│ │ ├── 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.min.css
│ │ └── jquery.atwho.css
│ ├── styles.less
│ └── themes
│ │ ├── default.less
│ │ ├── flatly.less
│ │ └── ios7.less
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
├── humans.txt
├── img
│ ├── appicon.png
│ ├── favicon.ico
│ ├── hacker_news.png
│ ├── paypal.png
│ └── sendgrid.png
└── js
│ ├── globals.js
│ ├── keen.js
│ ├── lib
│ ├── ansi_up.js
│ ├── bootstrap.min.js
│ ├── jquery-2.1.0.min.js
│ ├── jquery.atwho.js
│ ├── jquery.caret.js
│ └── keen.min.js
│ ├── logs.js
│ └── main.js
├── tasks
├── db_clear_down.js
└── db_seed.js
├── test
├── NewsItem.test.js
├── config.userlist.test.js
└── voteCounts.test.js
├── utils
└── index.js
├── vagrant
├── ansible-windows.sh
├── playbook.yml
└── tasks
│ ├── essentials.yml
│ ├── mongo.yml
│ ├── nodejs.yml
│ └── setup_app.yml
└── views
├── 404.jade
├── about.jade
├── account
├── comments.jade
├── contributions.jade
├── news.jade
├── settings.jade
└── signup.jade
├── analytics.jade
├── bookmarklet.jade
├── chat
└── index.jade
├── comments
├── comment.jade
├── index.jade
├── listcomments.jade
└── postcomment.jade
├── contact.jade
├── error.jade
├── howtosignup.jade
├── issues
├── index.jade
└── show.jade
├── layout.jade
├── logs.jade
├── mixins
└── delete.jade
├── news
├── index.jade
├── newsitem.jade
├── newslist.jade
├── show.jade
└── submit.jade
├── partials
├── data.jade
├── flash.jade
├── footer.jade
├── joinsite.jade
├── navigation.jade
├── nocomments.jade
├── noscript.jade
└── profile.jade
└── rss.jade
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 | *.swp
10 |
11 | pids
12 | logs
13 | results
14 | tmp
15 |
16 | .env
17 |
18 | npm-debug.log
19 | node_modules
20 | .idea
21 | *.iml
22 | .DS_Store
23 | Thumbs.db
24 |
25 | public/vendor
26 | public/css/styles.css
27 |
28 | .vagrant/
29 | vagrant_ansible_inventory_default
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | services:
5 | - mongodb
6 | before_script:
7 | - npm install gulp -g
8 | deploy:
9 | provider: heroku
10 | api_key:
11 | secure: yNgE0NqIUewmXddVOx6WS64EbBBbr9OwkpWDLQWugiH/eKGMqAGSlMsGcpiqC2tOR6M0tq0FINEcWBduGWs9OhFOY4ggFe+RSFnQgiAoKwzQLlla3v/vHA+SJ15xidENEWdrBfy4i66gn/j2jpWWcpgnY0gsaU6claJc+aHlbKE=
12 | app: pullup
13 | on:
14 | repo: larvalabs/pullup
15 | git:
16 | depth: 10
17 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | The only way to join the pullup community is via pull request. Please note
4 | you must **add a feature** or **fix a bug**, along with adding your username
5 | to our [userlist file](https://github.com/larvalabs/pullup/blob/master/config/userlist.js).
6 | In general, we prefer the following for your first pull request:
7 |
8 | * It includes a user facing feature or bug fix.
9 | * It is not a refactor, re-org of the code, or change to how the project functions.
10 | * It works :)
11 |
12 | Save your big refactors and "how it should work" changes until after you're
13 | a member. This seems to cut down on debate and keep the focus on moving
14 | forward. Other than that, go for it!
15 |
16 | # Additional Resources
17 |
18 | * [Current bugs and requested features](https://github.com/larvalabs/pullup/issues?state=open)
19 | * [Setup and general instructions](https://github.com/larvalabs/pullup/blob/master/README.md)
20 | * IRC: #pullup on freenode [(join via Webchat)](https://webchat.freenode.net?channels=%23pullup)
21 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | var dbClearDown = require('./tasks/db_clear_down'),
2 | dbSeed = require('./tasks/db_seed');
3 |
4 | module.exports = function(grunt) {
5 | grunt.registerTask('db-clear', dbClearDown.task);
6 | grunt.registerTask('db-seed', dbClearDown.task, dbSeed.task);
7 | };
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Larva Labs
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 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node forever.js
2 |
--------------------------------------------------------------------------------
/Procfile.local:
--------------------------------------------------------------------------------
1 | web: node forever.js
2 | mongo: mongod
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PullUp [](https://travis-ci.org/larvalabs/pullup) [](https://codeclimate.com/repos/52fba7f66956805f68002062/feed) [](https://gitter.im/larvalabs/pullup)
2 | ======
3 |
4 | A website you join via pull request. See it live at http://pullup.io
5 |
6 | What would it be like if every user of a site had contributed some code? Let's find out! Right now the site is little more
7 | than a terrible Hacker News type thing, but let's see if it can grow into something more.
8 |
9 | How to join
10 | -----------
11 |
12 | Summary:
13 | - Fork and set up project for development (see below)
14 | - Add your GitHub username to the [authorized users list](https://github.com/larvalabs/pullup/blob/master/config/userlist.js).
15 | - Add a feature, fix a bug, improve the design, etc.
16 | - Submit a pull request! When we merge, you'll be allowed to log in.
17 |
18 | Development Setup
19 | ---------------
20 |
21 | Prerequisites: [Node.js](http://nodejs.org/) and [MongoDB](http://mongodb.org/).
22 |
23 | First, [register a new developer application on GitHub](https://github.com/settings/applications/new). Set the **Homepage URL** to your development server (example: `http://localhost:3000`) and set your **Authorization callback URL** to `http://localhost:3000/auth/github/callback`. Take note of the **Client ID** and **Client Secret**, as you will need them in the next steps.
24 |
25 | ```bash
26 | # Fetch only the latest commits.
27 | git clone git@github.com:larvalabs/pullup.git
28 |
29 | cd pullup
30 |
31 | # Install NPM dependencies
32 | npm install
33 | npm install -g gulp
34 |
35 | # Set the following environment variables:
36 |
37 | export GITHUB_CLIENTID=CLIENTID
38 | export GITHUB_SECRET=SECRET
39 |
40 | # Or, on Windows...
41 |
42 | SET GITHUB_CLIENTID=CLIENTID
43 | SET GITHUB_SECRET=SECRET
44 | ```
45 |
46 | Once those are set you can run the local development version:
47 |
48 | node app.js
49 |
50 | Or start the app with [foreman](https://github.com/ddollar/foreman):
51 |
52 | foreman start -f Procfile.local
53 |
54 | And perform build tasks and linting with:
55 |
56 | gulp
57 |
58 |
59 | You can find out more technical details in the [Readme for Hackathon Starter](https://github.com/larvalabs/pullup/blob/master/hackathon-starter-readme.md).
60 |
61 | Seeding The Database
62 | --------------------
63 |
64 | Firstly make sure you have installed ```grunt-cli``` globally
65 |
66 | ```bash
67 | npm install -g grunt-cli
68 | ```
69 |
70 | Once this has installed you can seed the database by running the following grunt task.
71 |
72 | ```bash
73 | grunt db-seed
74 | ```
75 |
76 | You may also wish to completely clear down the database, you can do so by running the ```db-clear``` task.
77 |
78 | ```bash
79 | grunt db-clear
80 | ```
81 |
82 | Pullup Dev Community
83 | --------------------
84 |
85 | We hang out on [Gitter](https://gitter.im/larvalabs/pullup)
86 |
87 | Using the Vagrant-based Development Environment
88 | -----------------------
89 |
90 | You'll need [VirtualBox](https://www.virtualbox.org/wiki/Downloads), [Vagrant](http://www.vagrantup.com/downloads.html), and [Ansible](https://devopsu.com/guides/ansible-mac-osx.html) installed to use this environment.
91 |
92 | **Note**: Windows users do not need Ansible installed. (A script will run and install Ansible on the guest machine for you)
93 |
94 | Update the GitHub environment variables in `vagrant/tasks/setup_app.yml`
95 |
96 | Fire up the Vagrant VM:
97 |
98 | vagrant up
99 |
100 | Ensure Ansible has run successfully and provisioned the boxes. If not, try again using `vagrant provision`
101 |
102 | Then, ssh in and follow the installation steps:
103 |
104 | vagrant ssh
105 | cd /vagrant/
106 | npm install
107 | node app.js
108 |
109 | #### Vagrant Development on a Windows Host
110 | Windows lacks support for symlinks in synced folders. Use `npm install --no-bin-links` instead of `npm install` when installing.
111 |
112 | Credits
113 | -------
114 |
115 | This project is based on the awesome [Hackathon Starter project](https://github.com/sahat/hackathon-starter). Thanks @sahat!
116 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | CUSTOM_CONFIG = {
5 | "BOX_NAME" => "precise64",
6 | "BOX_URL" => "http://files.vagrantup.com/precise64.box",
7 | "HEADLESS" => false
8 | }
9 |
10 | Vagrant.configure("2") do |config|
11 | # Change this to the name of your Vagrant base box.
12 | config.vm.box = CUSTOM_CONFIG['BOX_NAME']
13 |
14 | # Change this to a URL from which the base box can be downloaded, if you like.
15 | config.vm.box_url = CUSTOM_CONFIG['BOX_URL']
16 |
17 | # we will run node.js on port 3000
18 | config.vm.network :forwarded_port, guest: 3000, host: 3000
19 |
20 | # headless? uncomment this to have the VM's window available
21 | config.vm.provider :virtualbox do |vb|
22 | vb.gui = CUSTOM_CONFIG['HEADLESS']
23 | end
24 |
25 | # forward SSH keys
26 | config.ssh.forward_agent = true
27 |
28 | if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM)
29 | # provisioning with ansible on windows
30 | config.vm.provision "shell", path: "./vagrant/ansible-windows.sh"
31 | else
32 | # provisioning with ansible
33 | config.vm.provision :ansible do |ansible|
34 | ansible.playbook = "./vagrant/playbook.yml"
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/_Design Assets/Logo.sketch/Data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/_Design Assets/Logo.sketch/Data
--------------------------------------------------------------------------------
/_Design Assets/Logo.sketch/QuickLook/Preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/_Design Assets/Logo.sketch/QuickLook/Preview.png
--------------------------------------------------------------------------------
/_Design Assets/Logo.sketch/QuickLook/Thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/_Design Assets/Logo.sketch/QuickLook/Thumbnail.png
--------------------------------------------------------------------------------
/_Design Assets/Logo.sketch/fonts:
--------------------------------------------------------------------------------
1 | Lato-Regular
--------------------------------------------------------------------------------
/_Design Assets/Logo.sketch/metadata:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | app
6 | com.bohemiancoding.sketch
7 | build
8 | 5302
9 | commit
10 | 9460a4bc62af5e9ba50dd4143578fd9401710ce5
11 | version
12 | 18
13 |
14 |
15 |
--------------------------------------------------------------------------------
/_Design Assets/Logo.sketch/version:
--------------------------------------------------------------------------------
1 | 18
--------------------------------------------------------------------------------
/cluster_app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 |
5 | var os = require('os');
6 | var cluster = require('cluster');
7 |
8 | /**
9 | * Cluster setup.
10 | */
11 |
12 | // Setup the cluster to use app.js
13 | cluster.setupMaster({
14 | exec: 'app.js'
15 | });
16 |
17 | // Listen for dying workers
18 | cluster.on('exit', function(worker) {
19 | console.log('Worker ' + worker.id + ' died');
20 | // Replace the dead worker
21 | cluster.fork();
22 | });
23 |
24 | // Fork a worker for each available CPU
25 | for (var i = 0; i < os.cpus().length; i++) {
26 | cluster.fork();
27 | }
28 |
--------------------------------------------------------------------------------
/components/GithubContributors.js:
--------------------------------------------------------------------------------
1 |
2 | var NodeCache = require( "node-cache" );
3 | var github = require('octonode');
4 |
5 | /**
6 | * Used to retrieve and manage contributors list requested through GitHub
7 | * API.
8 | */
9 | function GithubContributors() {
10 |
11 | /* Unauthenticated GitHub API requests a rate limited to 60 per hour.
12 | TTL of cache should be set appropriately */
13 | this.cache = new NodeCache({ stdTTL: 15 * 60, checkperiod: 15 * 60 });
14 | };
15 |
16 | /**
17 | * Looks for contributors list in cache. In the event of a cache miss makes
18 | * a GitHub API request using the current users GitHub authentication
19 | * information.
20 | * @param {Object} args May contain on error and on success callbacks
21 | */
22 | GithubContributors.prototype.getContributors = function(args) {
23 | var that = this;
24 | var errorCallback = args.onError || function() {};
25 | var successCallback = args.onSuccess || function() {};
26 |
27 | this.cache.get('githubContributors', function (err, value) {
28 |
29 | if (err) {
30 | errorCallback();
31 | } else if (!Object.keys(value).length) {
32 | /* Since contributors data isn't in the cache, request it via the
33 | GitHub API */
34 | console.log ('getUserGithubData: cache miss');
35 |
36 | var client = github.client();
37 | var ghrepo = client.repo('larvalabs/pullup');
38 | ghrepo.contributors (function(err, data, headers) {
39 | if (err) {
40 | errorCallback();
41 | return;
42 | }
43 | successCallback (data);
44 |
45 | // cache the results
46 | that.cache.set('githubContributors', data,
47 | function (err, success) {
48 |
49 | if(!err && success) {
50 | console.log ('cached github contributors');
51 | } else {
52 | console.warn ('failed to cache github contributors');
53 | }
54 | });
55 | });
56 |
57 | } else {
58 | console.log ('getUserGithubData: cache hit');
59 | successCallback(value);
60 | }
61 |
62 | });
63 | };
64 |
65 | /**
66 | * @param {String} username Name of the user for which the number of
67 | * contributions should be returned
68 | * @param {Array} contributors The response of the GitHub API request made
69 | * in getContributors()
70 | * @return {Number}
71 | */
72 | GithubContributors.prototype.getContributions = function(
73 | username, contributors) {
74 |
75 | var contributions = 0;
76 | for (var i in contributors) {
77 | if (contributors[i].login === username) {
78 | contributions = contributors[i].contributions;
79 | break;
80 | }
81 | }
82 | return contributions;
83 | };
84 |
85 | GithubContributors.prototype.getIssues = function(onSuccess, onError) {
86 | var that = this;
87 |
88 | this.cache.get('githubIssues', function (err, value) {
89 | if (err) {
90 | onError();
91 | } else if (!Object.keys(value).length) {
92 | var client = github.client(),
93 | ghrepo = client.repo('larvalabs/pullup');
94 |
95 | ghrepo.issues({state: 'all'}, function (err, data, headers) {
96 | if (err) {
97 | onError();
98 | return;
99 | }
100 |
101 | onSuccess(data);
102 |
103 | that.cache.set('githubIssues', data,
104 | function (err, success) {
105 |
106 | if (!err && success) {
107 | console.log('cached github issues');
108 | } else {
109 | console.warn('failed to cache github issues');
110 | }
111 | });
112 | });
113 | } else {
114 | console.log('getUserGithubIssueData: cache hit');
115 | onSuccess(value.githubIssues);
116 | }
117 | });
118 | };
119 |
120 | GithubContributors.prototype.getIssuesForUser = function(username, allIssues) {
121 | var issues = [];
122 | for (var i in allIssues) {
123 | if (allIssues[i].user.login === username) {
124 | issues.push(allIssues[i]);
125 | }
126 | }
127 | return issues;
128 | };
129 |
130 | module.exports = new GithubContributors();
--------------------------------------------------------------------------------
/components/MarkdownParser.js:
--------------------------------------------------------------------------------
1 | var marked = require('marked');
2 |
3 | marked.setOptions({
4 | sanitize: true,
5 | silent: true
6 | });
7 |
8 | module.exports = marked;
--------------------------------------------------------------------------------
/config/secrets.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | db: process.env.MONGOHQ_URL || 'localhost',
3 |
4 | sendgrid: {
5 | user: process.env.SENDGRID_USERNAME,
6 | password: process.env.SENDGRID_PASSWORD
7 | },
8 |
9 | facebook: {
10 | clientID: 'Your App ID',
11 | clientSecret: 'Your App Secret',
12 | callbackURL: '/auth/facebook/callback',
13 | passReqToCallback: true
14 | },
15 |
16 | github: {
17 | clientID: process.env.GITHUB_CLIENTID,
18 | clientSecret: process.env.GITHUB_SECRET,
19 | callbackURL: process.env.GITHUB_CALLBACK || '/auth/github/callback',
20 | scope: ['user:email'],
21 | passReqToCallback: true
22 | },
23 |
24 | heroku: {
25 | email: process.env.HEROKU_EMAIL || false,
26 | authToken: process.env.HEROKU_AUTH_TOKEN || false,
27 | app: process.env.HEROKU_APP || false
28 | },
29 |
30 | twitter: {
31 | consumerKey: 'Your Consumer Key',
32 | consumerSecret: 'Your Consumer Secret',
33 | callbackURL: '/auth/twitter/callback',
34 | passReqToCallback: true
35 | },
36 |
37 | google: {
38 | clientID: 'Your Client ID',
39 | clientSecret: 'Your Client Secret',
40 | callbackURL: '/auth/google/callback',
41 | passReqToCallback: true
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/config/userlist.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | users: [ // ADD YOUR USERNAME IN ITS ALPHABETICAL SLOT
3 | 'amac441',
4 | 'andersos',
5 | 'arminkhoshbin',
6 | 'avaynshtok',
7 | 'barretts',
8 | 'bluetidepro',
9 | 'bobnisco',
10 | 'briansoule',
11 | 'bwlang',
12 | 'Caraheacock',
13 | 'cgroner',
14 | 'chall8908',
15 | 'ChristianGaertner',
16 | 'cpdean',
17 | 'crandles',
18 | 'crankeye',
19 | 'd7p',
20 | 'dcancel',
21 | 'dck273',
22 | 'dfjones',
23 | 'djstearns',
24 | 'DusanSacha',
25 | 'dylnclrk',
26 | 'eik3',
27 | 'euank',
28 | 'evsie001',
29 | 'exadeci',
30 | 'fridays',
31 | 'gwillen',
32 | 'hemanth',
33 | 'humd',
34 | 'ianwalter',
35 | 'isaaczafuta',
36 | 'jackcarter',
37 | 'jeffawang',
38 | 'jensenbox',
39 | 'jeromegn',
40 | 'jgillich',
41 | 'jmcelwain',
42 | 'joewright',
43 | 'jordancalder',
44 | 'josegonzalez',
45 | 'josephwegner',
46 | 'jphatton',
47 | 'julbaxter',
48 | 'kalmi',
49 | 'kanakiyajay',
50 | 'kasperlewau',
51 | 'kckolz',
52 | 'KeenRivals',
53 | 'kennethrapp',
54 | 'kevinsearle',
55 | 'lablayers',
56 | 'LeandroLovisolo',
57 | 'lgarron',
58 | 'lukemiles',
59 | 'marco-fiset',
60 | 'markbao',
61 | 'mattclaw',
62 | 'megamattron',
63 | 'michaelnovakjr',
64 | 'mreinhardt',
65 | 'msied',
66 | 'naturalethic',
67 | 'negitivezero',
68 | 'omphalosskeptic',
69 | 'parenparen',
70 | 'pents90',
71 | 'pksjce',
72 | 'qguv',
73 | 'rickhanlonii',
74 | 'ritvik1512',
75 | 'roryokane',
76 | 'rtfeldman',
77 | 'ryanwi',
78 | 'samcal',
79 | 'shedd',
80 | 'singularperturbation',
81 | 'sorpaas',
82 | 'sp1d3rx',
83 | 'sriehl',
84 | 'suprememoocow',
85 | 'swelham',
86 | 'tdhsmith',
87 | 'tejohnso',
88 | 'testuser1',
89 | 'theprofessor117',
90 | 'thomascate',
91 | 'tito0224',
92 | 'travishorn',
93 | 'treygriffith',
94 | 'utdemir',
95 | 'vallyath',
96 | 'vellip',
97 | 'vvayne',
98 | 'williamle8300',
99 | 'willurd',
100 | 'zhanglun'
101 | ]
102 | };
103 |
--------------------------------------------------------------------------------
/constants.js:
--------------------------------------------------------------------------------
1 |
2 | var constants = {};
3 |
4 | Object.defineProperty (constants, "DEBUG", {
5 | value: false,
6 | writable: false
7 | });
8 |
9 | module.exports = constants;
10 |
--------------------------------------------------------------------------------
/controllers/api.js:
--------------------------------------------------------------------------------
1 | var secrets = require('../config/secrets');
2 | var User = require('../models/User');
3 | var querystring = require('querystring');
4 | var async = require('async');
5 | var cheerio = require('cheerio');
6 | var request = require('request');
7 | var _ = require('underscore');
8 | var marked = require('marked');
9 | var userlist = require('../config/userlist.js');
10 |
11 | marked.setOptions({
12 | sanitize: true,
13 | silent: true
14 | });
15 |
16 | exports.getMarkdown = function(req, res) {
17 | if (!req.user) {
18 | return res.send({
19 | messages: [{ msg: 'Only members can upvote items.' }]
20 | });
21 | }
22 |
23 | if(typeof req.body.source == 'undefined') {
24 | return res.send({
25 | messages: [{ msg: "You didn't send any markdown."}]
26 | });
27 | }
28 |
29 | if(req.body.source === ''){
30 | return res.send({
31 | result: ''
32 | });
33 | }
34 |
35 | return res.send({
36 | result: marked(req.body.source)
37 | });
38 | };
39 |
40 | exports.getUserList = function(req, res){
41 | return res.send(userlist);
42 | };
--------------------------------------------------------------------------------
/controllers/chat.js:
--------------------------------------------------------------------------------
1 |
2 | exports.index = function (req, res) {
3 | res.render('chat/index');
4 | };
--------------------------------------------------------------------------------
/controllers/contact.js:
--------------------------------------------------------------------------------
1 | var secrets = require('../config/secrets');
2 | var sendgrid = require('sendgrid')(secrets.sendgrid.user, secrets.sendgrid.password);
3 |
4 | /**
5 | * GET /contact
6 | * Contact form page.
7 | */
8 |
9 | exports.getContact = function(req, res) {
10 | res.render('contact', {
11 | title: 'Contact'
12 | });
13 | };
14 |
15 | /**
16 | * POST /contact
17 | * Send a contact form via SendGrid.
18 | * @param {string} email
19 | * @param {string} name
20 | * @param {string} message
21 | */
22 |
23 | exports.postContact = function(req, res) {
24 | req.assert('name', 'Name cannot be blank').notEmpty();
25 | req.assert('email', 'Email cannot be blank').notEmpty();
26 | req.assert('email', 'Email is not valid').isEmail();
27 | req.assert('message', 'Message cannot be blank').notEmpty();
28 |
29 | var errors = req.validationErrors();
30 |
31 | if (errors) {
32 | req.flash('errors', errors);
33 | return res.redirect('/contact');
34 | }
35 |
36 | var from = req.body.email;
37 | var name = req.body.name;
38 | var body = req.body.message;
39 | var to = 'you@email.com';
40 | var subject = 'API Example | Contact Form';
41 |
42 | var email = new sendgrid.Email({
43 | to: to,
44 | from: from,
45 | subject: subject,
46 | text: body + '\n\n' + name
47 | });
48 |
49 | sendgrid.send(email, function(err) {
50 | if (err) {
51 | req.flash('errors', { msg: err.message });
52 | return res.redirect('/contact');
53 | }
54 | req.flash('success', { msg: 'Email has been sent successfully!' });
55 | res.redirect('/contact');
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/controllers/home.js:
--------------------------------------------------------------------------------
1 | /**
2 | * GET /
3 | * Home page.
4 | */
5 |
6 | exports.index = function(req, res) {
7 | res.redirect('/news');
8 | };
9 |
10 | exports.signup = function(req, res) {
11 | res.render('howtosignup', {
12 | title: 'Signup'
13 | });
14 | };
15 |
16 | exports.about = function(req, res) {
17 | res.render('about', {
18 | title: 'About'
19 | });
20 | };
21 |
22 | exports.analytics = function(req, res) {
23 | res.render('analytics', {
24 | title: 'Analytics'
25 | });
26 | };
27 |
28 | exports.logs = function(req, res) {
29 | res.render('logs', {
30 | title: 'Logs'
31 | });
32 | };
33 |
34 | exports.bookmarklet = function(req, res) {
35 | res.render('bookmarklet', {
36 | title: 'Bookmarklet'
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/controllers/user.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var passport = require('passport');
3 | var _ = require('underscore');
4 | var User = require('../models/User');
5 | var Comment = require('../models/Comment');
6 | var githubContributors = require('../components/GithubContributors');
7 | var async = require('async');
8 | var news = require('./news');
9 | var markdownParser = require('../components/MarkdownParser');
10 | var utils = require('../utils');
11 |
12 |
13 | /**
14 | * GET /signup
15 | * Signup page.
16 | */
17 |
18 | exports.getSignup = function(req, res) {
19 | if (req.user) return res.redirect('/');
20 | res.render('account/signup', {
21 | title: 'Create Account'
22 | });
23 | };
24 |
25 | /**
26 | * GET /account
27 | * Profile page.
28 | */
29 |
30 | exports.getAccount = function(req, res) {
31 | console.log (res.locals.user);
32 | res.render('account/settings', {
33 | title: 'Account Management'
34 | });
35 | };
36 |
37 | /**
38 | * POST /account/profile
39 | * Update profile information.
40 | */
41 |
42 | exports.postUpdateProfile = function(req, res, next) {
43 | User.findById(req.user.id, function(err, user) {
44 | if (err) return next(err);
45 | user.email = req.body.email || '';
46 | user.profile.name = req.body.name || '';
47 | user.profile.location = req.body.location || '';
48 | user.profile.bio = req.body.bio || '';
49 |
50 | if(req.body.description.length < 32){
51 | user.profile.description = req.body.description || '';
52 | }
53 |
54 | if (req.body.website.match(/https?:\/\//i)) {
55 | user.profile.website = req.body.website;
56 | } else if (user.profile.website) {
57 | user.profile.website = 'http://' + req.body.website;
58 | } else {
59 | user.profile.website = '';
60 | }
61 |
62 | user.save(function(err) {
63 | if (err) return next(err);
64 | req.flash('success', { msg: 'Profile information updated.' });
65 | res.redirect('/account');
66 | });
67 | });
68 | };
69 |
70 | /**
71 | * POST /account/password
72 | * Update current password.
73 | * @param {string} password
74 | */
75 |
76 | exports.postUpdatePassword = function(req, res, next) {
77 | req.assert('password', 'Password must be at least 4 characters long').len(4);
78 | req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
79 |
80 | var errors = req.validationErrors();
81 |
82 | if (errors) {
83 | req.flash('errors', errors);
84 | return res.redirect('/account');
85 | }
86 |
87 | User.findById(req.user.id, function(err, user) {
88 | if (err) return next(err);
89 |
90 | user.password = req.body.password;
91 |
92 | user.save(function(err) {
93 | if (err) return next(err);
94 | req.flash('success', { msg: 'Password has been changed.' });
95 | res.redirect('/account');
96 | });
97 | });
98 | };
99 |
100 | /**
101 | * POST /account/delete
102 | * Delete user account.
103 | * @param {string} id
104 | */
105 |
106 | exports.postDeleteAccount = function(req, res, next) {
107 | User.remove({ _id: req.user.id }, function(err) {
108 | if (err) return next(err);
109 | req.logout();
110 | res.redirect('/');
111 | });
112 | };
113 |
114 | /**
115 | * GET /account/unlink/:provider
116 | * Unlink OAuth2 provider from the current user.
117 | * @param {string} provider
118 | * @param {string} id
119 | */
120 |
121 | exports.getOauthUnlink = function(req, res, next) {
122 | var provider = req.params.provider;
123 | User.findById(req.user.id, function(err, user) {
124 | if (err) return next(err);
125 |
126 | user[provider] = undefined;
127 | user.tokens = _.reject(user.tokens, function(token) { return token.kind === provider; });
128 |
129 | user.save(function(err) {
130 | if (err) return next(err);
131 | req.flash('info', { msg: provider + ' account has been unlinked.' });
132 | res.redirect('/account');
133 | });
134 | });
135 | };
136 |
137 | function getUserOrRespond(req, resp, next, callback){
138 | User
139 | .findOne({'username': req.params.id})
140 | .exec(function(err, user) {
141 |
142 | if (err) return next(err);
143 |
144 | if (!user) {
145 | req.flash('errors', {msg: "That user does not exist. "});
146 | return res.redirect('/');
147 | }
148 |
149 | user.profile.bio = markdownParser(user.profile.bio);
150 |
151 | githubContributors.getContributors({
152 | onError: function() {
153 | callback(user);
154 | },
155 | onSuccess: function(data) {
156 | user.profile.contributions_count = githubContributors.getContributions(user.username, data);
157 | callback(user);
158 | }
159 | });
160 | });
161 | }
162 |
163 | function getCommentsForUser(user, callback){
164 | async.waterfall([
165 | function (cb) {
166 | Comment
167 | .find({'poster': user.id})
168 | .sort('-created')
169 | .limit(30)
170 | .populate('poster')
171 | .exec(cb);
172 | },
173 | function (comments, cb) {
174 | news.getNewsItemsForComments(comments, user, cb);
175 | },
176 | function (comments, cb){
177 | _.each(comments, function (comment,i,l) {
178 | comment.contents = markdownParser(utils.replaceUserMentions(comment.contents));
179 | });
180 | cb(null, comments);
181 | }
182 | ], callback);
183 | }
184 |
185 | /**
186 | * GET /user/:id
187 | */
188 | exports.user = function(req, res, next) {
189 | getUserOrRespond(req, res, next, function(user){
190 | news.getNewsItems({'poster': user.id}, req.user, function(err, newsItems){
191 | if (err) return next(err);
192 | res.render('account/news', {
193 | title: 'Posts by ' + user.username,
194 | tab: 'news',
195 | items: newsItems,
196 | userView: user
197 | });
198 | });
199 | });
200 | };
201 |
202 | /**
203 | * GET /user/:id/comments
204 | */
205 | exports.userComments = function(req, res, next) {
206 | getUserOrRespond(req, res, next, function(user){
207 | getCommentsForUser(user, function (err, comments) {
208 | if (err) return next(err);
209 |
210 | res.render('account/comments', {
211 | title: 'Comments by ' + user.username,
212 | tab: 'comments',
213 | comments: comments,
214 | userView: user
215 | });
216 | });
217 | });
218 | };
219 |
220 | /**
221 | * GET /user/:id/comments
222 | */
223 | exports.userContributions = function(req, res, next) {
224 | getUserOrRespond(req, res, next, function(user){
225 | githubContributors.getIssues(function(allIssues) {
226 | var contributions = githubContributors.getIssuesForUser(user.username, allIssues);
227 |
228 | res.render('account/contributions', {
229 | title: 'Contributions by ' + user.username,
230 | tab: 'contributions',
231 | contributions: contributions,
232 | userView: user
233 | });
234 | });
235 | });
236 | };
237 |
238 | /**
239 | * GET /logout
240 | * Log out.
241 | */
242 |
243 | exports.logout = function(req, res) {
244 | req.logout();
245 | res.redirect('/');
246 | };
247 |
--------------------------------------------------------------------------------
/controllers/votes.js:
--------------------------------------------------------------------------------
1 | var Vote = require('../models/Vote');
2 | var votesController = exports;
3 |
4 | // generic vote controller
5 | exports.voteFor = function (type, root) {
6 |
7 | return function (req, res, next) {
8 |
9 | req.assert('amount', 'Items can only be upvoted.').equals('1');
10 |
11 | var errors = req.validationErrors();
12 |
13 | if (errors) {
14 | return res.send({
15 | messages: errors
16 | });
17 | }
18 |
19 | if (!req.user) {
20 | return res.send({
21 | messages: [{ msg: 'Only members can upvote items.' }]
22 | });
23 | }
24 |
25 | var vote = new Vote({
26 | item: req.params.id,
27 | voter: req.user.id,
28 | amount: req.body.amount,
29 | itemType: type
30 | });
31 |
32 | vote.save(function (err) {
33 | if (err) {
34 | var errors = [];
35 |
36 | if (err.code === 11000) {
37 | errors.push({ msg: 'You can only upvote an item once.' });
38 | }
39 | console.warn(err);
40 |
41 | return res.send({
42 | messages: errors,
43 | });
44 | }
45 |
46 | return res.send({
47 | messages: [{ msg: 'Item upvoted. Awesome!' }],
48 | success: true
49 | });
50 | });
51 | };
52 |
53 | };
54 |
55 | // generic vote retrieval
56 | /**
57 | * Query the Votes collection to find votes for item(s)
58 | * @param {String} type Type of item. Should be `news`, `comment`, or `issue`
59 | * @param {String | Array} id Either a string or an array of strings (or object id's) defining the item(s) to be queried about
60 | * @param {Function} callback Evaluated with an error as the first parameter and the votes found as the second
61 | */
62 | exports.retrieveVotesFor = function (type, id, callback) {
63 |
64 | if(arguments.length === 1) {
65 | return function (id, callback) {
66 | return votesController.retrieveVotesFor(type, id, callback);
67 | };
68 | }
69 |
70 | // special case of allowing a null type for news
71 | if(type === 'news') {
72 | type = { $in: ['news', null] };
73 | }
74 |
75 | // pass an array of ids to find votes for all of them
76 | if(Array.isArray(id)) {
77 | id = { $in: id };
78 | }
79 |
80 | Vote
81 | .find({
82 | item: id,
83 | itemType: type
84 | })
85 | .populate('voter')
86 | .exec(callback);
87 | };
88 |
89 | // add a `votes` count and `votedFor` property for a particular item, vote array, and user
90 | exports.addVotesToItem = function (item, item_id, user, votes) {
91 |
92 | item = typeof item.toObject === 'function' ? item.toObject() : item;
93 | item_id = item_id.toString();
94 |
95 | item.votes = votes
96 | .filter(function (vote) {
97 | return vote.item.toString() === item_id;
98 | })
99 | .reduce(function (prev, curr, i) {
100 |
101 | // count this item as voted for if the logged in user has a vote tallied
102 | if(user && user.id && curr.voter.id.toString() === user.id.toString()) {
103 | item.votedFor = true;
104 | }
105 |
106 | return prev + curr.amount;
107 | }, 0);
108 |
109 | item.voters = votes.filter(function(vote){
110 | return vote.item.toString() === item_id;
111 | })
112 | .map(function (vote) {
113 | return vote.voter.username;
114 | });
115 |
116 | return item;
117 | };
118 |
119 | // all-in-one query
120 | /**
121 | * Add a `votes` property to an Array of Objects based on their type, `id` property, and the current user
122 | * @param {String} type Type of item. Should be `news`, `comment`, or `issue`
123 | * @param {Array | Object} items Array of objects (or a single object) that votes should be added to. This converts mongoose objects into plain objects.
124 | * @param {User} user A user object with an `id` property
125 | * @param {Function} callback Function to be evaluated with an error as the first parameter, and the modified objects as the second
126 | */
127 | exports.addVotesFor = function (type, items, user, callback) {
128 |
129 | var isArray;
130 |
131 | if(arguments.length === 1) {
132 | return function (items, user, callback) {
133 | return votesController.addVotesFor(type, items, user, callback);
134 | };
135 | }
136 |
137 | wasArray = Array.isArray(items);
138 |
139 | // make a non-array argument behave as an array for now to simplify our dealing with it
140 | if(!wasArray) items = [items];
141 |
142 | votesController.retrieveVotesFor(type, items, function (err, votes) {
143 |
144 | if(err) return callback(err);
145 |
146 | // add the votes to the objects themselves
147 | items = items.map(function (item) {
148 | return votesController.addVotesToItem(item, item._id, user, votes);
149 | });
150 |
151 | // convert back if it wasn't passed in as an array
152 | if(!wasArray) items = items[0];
153 |
154 | callback(null, items);
155 | });
156 | };
157 |
--------------------------------------------------------------------------------
/forever.js:
--------------------------------------------------------------------------------
1 | var forever = require('forever-monitor');
2 |
3 | var child = new (forever.Monitor)('app.js', {
4 | max: 3
5 | });
6 |
7 | child.on('exit', function () {
8 | console.warn('app.js has exited after 3 restarts');
9 | });
10 |
11 | child.start();
12 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var jshint = require('gulp-jshint');
3 | var mocha = require('gulp-mocha');
4 |
5 | var paths = {
6 | scripts: ['./*.js', './config/*.js', 'controllers/*.js', 'models/*.js', 'public/js/*.js', '!public/js/lib/**/*.js', 'test/*.js'],
7 | tests: ['./test/*.js']
8 | };
9 |
10 | gulp.task('jshint', function () {
11 | gulp.src(paths.scripts)
12 | .pipe(jshint())
13 | .pipe(jshint.reporter('default'))
14 | .pipe(jshint.reporter('fail'));
15 | });
16 |
17 | gulp.task('mocha-tests', function () {
18 | gulp.src(paths.tests)
19 | .pipe(mocha({reporter: 'spec'}));
20 | });
21 |
22 | gulp.task('test', ['jshint', 'mocha-tests']);
23 | gulp.task('default', ['test']);
24 |
--------------------------------------------------------------------------------
/models/Comment.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose'), Schema = mongoose.Schema;
2 | var userSchema = require('./User.js');
3 |
4 | var commentSchema = new mongoose.Schema({
5 | item: {
6 | type: Schema.Types.ObjectId,
7 | required: true
8 | },
9 | itemType: {
10 | type: String,
11 | required: true,
12 | enum: ['news', 'comment']
13 | },
14 | contents: {
15 | type: String,
16 | default: '',
17 | required: true
18 | },
19 | poster: {
20 | type: String,
21 | ref: 'User'
22 | },
23 | vote_count: {
24 | type: Number,
25 | default: 0
26 | },
27 | created: {
28 | type: Date,
29 | default: Date.now
30 | }
31 | });
32 |
33 | var User = mongoose.model('User', userSchema);
34 | var Comment;
35 | module.exports = Comment = mongoose.model('Comment', commentSchema);
36 |
--------------------------------------------------------------------------------
/models/Issue.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose'), Schema = mongoose.Schema;
2 |
3 | var issueSchema = new mongoose.Schema({
4 | number: {
5 | type: Number,
6 | required: true,
7 | unique: true
8 | },
9 | poster: {
10 | type: Schema.Types.ObjectId
11 | },
12 | vote_count: {
13 | type: Number,
14 | default: 0
15 | }
16 | });
17 |
18 | var Issue;
19 | module.exports = Issue = mongoose.model('Issue', issueSchema);
20 |
--------------------------------------------------------------------------------
/models/NewsItem.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose'), Schema = mongoose.Schema;
2 | var userSchema = require('./User.js');
3 |
4 | var newsItemSchema = new mongoose.Schema({
5 | title: { type: String, default: '' },
6 | url: { type: String, unique: true },
7 | summary: { type: String },
8 | source: { type: String },
9 | poster: { type: String, ref: 'User' },
10 | vote_count: {type: Number, default: 0 },
11 | created: { type: Date, default: Date.now }
12 | });
13 |
14 | newsItemSchema.methods.isSelfPost = function() {
15 | return this.url === '/news/' + this._id;
16 | };
17 |
18 | /**
19 | * Returns a properly formatted URL in case it is missing information, i.e. protocol
20 | * @param {string} url URL to format
21 | * @returns {string} url Formatted URL
22 | */
23 | newsItemSchema.statics.formatUrl = function(url) {
24 | return /^(http:\/\/|https:\/\/)/.test(url) ? url : 'http://' + url;
25 | };
26 |
27 | // Logic to be executed before a model is saved to Mongo
28 | newsItemSchema.pre('save', function (next) {
29 | if(!this.isSelfPost()) {
30 | this.url = NewsItem.formatUrl(this.url);
31 | }
32 |
33 | next();
34 | });
35 |
36 | var User = mongoose.model('User', userSchema);
37 |
38 | var NewsItem;
39 | module.exports = NewsItem = mongoose.model('NewsItem', newsItemSchema);
40 |
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var bcrypt = require('bcrypt-nodejs');
3 |
4 | var userSchema = new mongoose.Schema({
5 | username: { type: String, unique: true }, // = github username
6 | email: { type: String, default:'' },
7 | password: String,
8 |
9 | facebook: { type: String, unique: true, sparse: true },
10 | twitter: { type: String, unique: true, sparse: true },
11 | google: { type: String, unique: true, sparse: true },
12 | github: { type: String, unique: true, sparse: true },
13 | tokens: [ { kind: String, accessToken: String, tokenSecret: String } ],
14 |
15 | profile: {
16 | name: { type: String, default: '' },
17 | location: { type: String, default: '' },
18 | website: { type: String, default: '' },
19 | picture: { type: String, default: '' },
20 | description: { type: String, default: '' },
21 | bio: { type: String, default: '' }
22 | }
23 | });
24 |
25 | /**
26 | * Hash the password for security.
27 | */
28 |
29 | userSchema.pre('save', function(next) {
30 | var user = this;
31 | var SALT_FACTOR = 5;
32 |
33 | if (!user.isModified('password')) return next();
34 |
35 | bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
36 | if (err) return next(err);
37 |
38 | bcrypt.hash(user.password, salt, null, function(err, hash) {
39 | if (err) return next(err);
40 | user.password = hash;
41 | next();
42 | });
43 | });
44 | });
45 |
46 | userSchema.methods.comparePassword = function(candidatePassword, cb) {
47 | bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
48 | if(err) return cb(err);
49 | cb(null, isMatch);
50 | });
51 | };
52 |
53 | module.exports = mongoose.model('User', userSchema);
54 |
--------------------------------------------------------------------------------
/models/Vote.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose'),
2 | Schema = mongoose.Schema,
3 |
4 | Comment = require('./Comment.js'),
5 | Issue = require('./Issue.js'),
6 | NewsItem = require('./NewsItem.js');
7 |
8 |
9 | var voteSchema = new mongoose.Schema({
10 | item: {
11 | type: Schema.Types.ObjectId,
12 | required: true
13 | },
14 | itemType: {
15 | type: String,
16 | required: true,
17 | enum: ['news', 'comment', 'issue'],
18 | default: 'news'
19 | },
20 | voter: {
21 | type: Schema.Types.ObjectId,
22 | ref: 'User',
23 | required: true
24 | },
25 | amount: {
26 | type: Number,
27 | default: 1,
28 | required: true,
29 | min: 1,
30 | max: 1
31 | }
32 | });
33 |
34 | // each user can only vote on an item once
35 | voteSchema.index({ item: 1, voter: 1 }, { unique: true });
36 |
37 | // increment item vote count on save.
38 | voteSchema.pre('save', function(next) {
39 |
40 | var initem = this;
41 |
42 | // ensure this isn't a duplicate vote, as this happens before the
43 | // unique index is enforced for the new vote.
44 | Vote.findOne({item: initem.item, voter: initem.voter}, function(err, dup) {
45 | if ( dup === null) {
46 | incrementVote(initem.item, initem.itemType, 1, next);
47 | } else {
48 | next();
49 | }
50 | });
51 |
52 | });
53 |
54 | // decrement item vote count on remove.
55 | voteSchema.pre('remove', function(next) {
56 | incrementVote(this.item, this.itemType, -1, next);
57 | });
58 |
59 | // increment/decrement the aggregated vote count by amount.
60 | function incrementVote(itemId, type, amount, next) {
61 |
62 | var item;
63 |
64 | if (type === 'issue') {
65 | item = Issue;
66 | } else if (type === 'comment') {
67 | item = Comment;
68 | } else {
69 | item = NewsItem;
70 | }
71 |
72 |
73 | item.findById(itemId, function(err, item){
74 |
75 | if (err) { next(err); }
76 |
77 | item.vote_count += amount;
78 | item.save(function(err) {
79 |
80 | if (err) {
81 | next(err);
82 | } else {
83 | next();
84 | }
85 | });
86 | });
87 |
88 | }
89 |
90 | var Vote;
91 | module.exports = Vote = mongoose.model('Vote', voteSchema);
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pullup",
3 | "version": "0.0.0",
4 | "description": "A website you join via pull request.",
5 | "license": {
6 | "type": "MIT",
7 | "url": "https://github.com/larvalabs/pullup/blob/master/LICENSE"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/larvalabs/pullup.git"
12 | },
13 | "scripts": {
14 | "test": "gulp test"
15 | },
16 | "dependencies": {
17 | "async": "~0.2.10",
18 | "bcrypt-nodejs": "~0.0.3",
19 | "cheerio": "~0.13.1",
20 | "express": "~3.4.8",
21 | "express-flash": "~0.0.2",
22 | "express-validator": "~1.0.1",
23 | "forever-monitor": "~1.2",
24 | "github": "~0.1",
25 | "jade": "~1.1.5",
26 | "less-middleware": "~0.1.15",
27 | "mongoose": "~3.8.5",
28 | "node-cache": "~0.4.0",
29 | "octonode": "~0.5.0",
30 | "passport": "~0.2.0",
31 | "passport-facebook": "~1.0.2",
32 | "passport-github": "alexgorbatchev/passport-github#9fef321dc7237c977b81fbf4a4f9589fc8ae9aaf",
33 | "passport-google-oauth": "~0.1.5",
34 | "passport-local": "~0.1.6",
35 | "passport-oauth": "~1.0.0",
36 | "passport-twitter": "~1.0.2",
37 | "request": "~2.33.0",
38 | "sendgrid": "~0.4.6",
39 | "underscore": "~1.5.2",
40 | "connect-mongo": "~0.4.0",
41 | "optimist": "latest",
42 | "timeago": "~0.1.0",
43 | "xmldom": "~0.1.19",
44 | "marked": "~0.3.1"
45 | },
46 | "devDependencies": {
47 | "gulp": "~3.5.2",
48 | "gulp-util": "~2.2.14",
49 | "gulp-jshint": "~1.4.0",
50 | "gulp-mocha": "~0.4.1",
51 | "mocha": "~1.17.1",
52 | "should": "~3.1.2",
53 | "marked": "~0.3.1",
54 | "grunt": "^0.4.2"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/public/css/lib/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 @headings-color
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 |
--------------------------------------------------------------------------------
/public/css/lib/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 | // Quick fix for badges in buttons
27 | .btn & {
28 | position: relative;
29 | top: -1px;
30 | }
31 | .btn-xs & {
32 | top: 0;
33 | padding: 1px 5px;
34 | }
35 | }
36 |
37 | // Hover state, but only for links
38 | a.badge {
39 | &:hover,
40 | &:focus {
41 | color: @badge-link-hover-color;
42 | text-decoration: none;
43 | cursor: pointer;
44 | }
45 | }
46 |
47 | // Account for counters in navs
48 | a.list-group-item.active > .badge,
49 | .nav-pills > .active > a > .badge {
50 | color: @badge-active-color;
51 | background-color: @badge-active-bg;
52 | }
53 | .nav-pills > li > a > .badge {
54 | margin-left: 3px;
55 | }
56 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/bootstrap.less:
--------------------------------------------------------------------------------
1 | // Core variables and mixins
2 | @import "variables.less";
3 | @import "mixins.less";
4 |
5 | // Reset
6 | @import "normalize.less";
7 | @import "print.less";
8 |
9 | // Core CSS
10 | @import "scaffolding.less";
11 | @import "type.less";
12 | @import "code.less";
13 | @import "grid.less";
14 | @import "tables.less";
15 | @import "forms.less";
16 | @import "buttons.less";
17 |
18 | // Components
19 | @import "component-animations.less";
20 | @import "glyphicons.less";
21 | @import "dropdowns.less";
22 | @import "button-groups.less";
23 | @import "input-groups.less";
24 | @import "navs.less";
25 | @import "navbar.less";
26 | @import "breadcrumbs.less";
27 | @import "pagination.less";
28 | @import "pager.less";
29 | @import "labels.less";
30 | @import "badges.less";
31 | @import "jumbotron.less";
32 | @import "thumbnails.less";
33 | @import "alerts.less";
34 | @import "progress-bars.less";
35 | @import "media.less";
36 | @import "list-group.less";
37 | @import "panels.less";
38 | @import "wells.less";
39 | @import "close.less";
40 |
41 | // Components w/ JavaScript
42 | @import "modals.less";
43 | @import "tooltip.less";
44 | @import "popovers.less";
45 | @import "carousel.less";
46 |
47 | // Utility classes
48 | @import "utilities.less";
49 | @import "responsive-utilities.less";
50 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/breadcrumbs.less:
--------------------------------------------------------------------------------
1 | //
2 | // Breadcrumbs
3 | // --------------------------------------------------
4 |
5 |
6 | .breadcrumb {
7 | padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;
8 | margin-bottom: @line-height-computed;
9 | list-style: none;
10 | background-color: @breadcrumb-bg;
11 | border-radius: @border-radius-base;
12 |
13 | > li {
14 | display: inline-block;
15 |
16 | + li:before {
17 | content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space
18 | padding: 0 5px;
19 | color: @breadcrumb-color;
20 | }
21 | }
22 |
23 | > .active {
24 | color: @breadcrumb-active-color;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/button-groups.less:
--------------------------------------------------------------------------------
1 | //
2 | // Button groups
3 | // --------------------------------------------------
4 |
5 | // Make the div behave like a button
6 | .btn-group,
7 | .btn-group-vertical {
8 | position: relative;
9 | display: inline-block;
10 | vertical-align: middle; // match .btn alignment given font-size hack above
11 | > .btn {
12 | position: relative;
13 | float: left;
14 | // Bring the "active" button to the front
15 | &:hover,
16 | &:focus,
17 | &:active,
18 | &.active {
19 | z-index: 2;
20 | }
21 | &:focus {
22 | // Remove focus outline when dropdown JS adds it after closing the menu
23 | outline: none;
24 | }
25 | }
26 | }
27 |
28 | // Prevent double borders when buttons are next to each other
29 | .btn-group {
30 | .btn + .btn,
31 | .btn + .btn-group,
32 | .btn-group + .btn,
33 | .btn-group + .btn-group {
34 | margin-left: -1px;
35 | }
36 | }
37 |
38 | // Optional: Group multiple button groups together for a toolbar
39 | .btn-toolbar {
40 | margin-left: -5px; // Offset the first child's margin
41 | &:extend(.clearfix all);
42 |
43 | .btn-group,
44 | .input-group {
45 | float: left;
46 | }
47 | > .btn,
48 | > .btn-group,
49 | > .input-group {
50 | margin-left: 5px;
51 | }
52 | }
53 |
54 | .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
55 | border-radius: 0;
56 | }
57 |
58 | // Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match
59 | .btn-group > .btn:first-child {
60 | margin-left: 0;
61 | &:not(:last-child):not(.dropdown-toggle) {
62 | .border-right-radius(0);
63 | }
64 | }
65 | // Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
66 | .btn-group > .btn:last-child:not(:first-child),
67 | .btn-group > .dropdown-toggle:not(:first-child) {
68 | .border-left-radius(0);
69 | }
70 |
71 | // Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)
72 | .btn-group > .btn-group {
73 | float: left;
74 | }
75 | .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
76 | border-radius: 0;
77 | }
78 | .btn-group > .btn-group:first-child {
79 | > .btn:last-child,
80 | > .dropdown-toggle {
81 | .border-right-radius(0);
82 | }
83 | }
84 | .btn-group > .btn-group:last-child > .btn:first-child {
85 | .border-left-radius(0);
86 | }
87 |
88 | // On active and open, don't show outline
89 | .btn-group .dropdown-toggle:active,
90 | .btn-group.open .dropdown-toggle {
91 | outline: 0;
92 | }
93 |
94 |
95 | // Sizing
96 | //
97 | // Remix the default button sizing classes into new ones for easier manipulation.
98 |
99 | .btn-group-xs > .btn { .btn-xs(); }
100 | .btn-group-sm > .btn { .btn-sm(); }
101 | .btn-group-lg > .btn { .btn-lg(); }
102 |
103 |
104 | // Split button dropdowns
105 | // ----------------------
106 |
107 | // Give the line between buttons some depth
108 | .btn-group > .btn + .dropdown-toggle {
109 | padding-left: 8px;
110 | padding-right: 8px;
111 | }
112 | .btn-group > .btn-lg + .dropdown-toggle {
113 | padding-left: 12px;
114 | padding-right: 12px;
115 | }
116 |
117 | // The clickable button for toggling the menu
118 | // Remove the gradient and set the same inset shadow as the :active state
119 | .btn-group.open .dropdown-toggle {
120 | .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
121 |
122 | // Show no shadow for `.btn-link` since it has no other button styles.
123 | &.btn-link {
124 | .box-shadow(none);
125 | }
126 | }
127 |
128 |
129 | // Reposition the caret
130 | .btn .caret {
131 | margin-left: 0;
132 | }
133 | // Carets in other button sizes
134 | .btn-lg .caret {
135 | border-width: @caret-width-large @caret-width-large 0;
136 | border-bottom-width: 0;
137 | }
138 | // Upside down carets for .dropup
139 | .dropup .btn-lg .caret {
140 | border-width: 0 @caret-width-large @caret-width-large;
141 | }
142 |
143 |
144 | // Vertical button groups
145 | // ----------------------
146 |
147 | .btn-group-vertical {
148 | > .btn,
149 | > .btn-group,
150 | > .btn-group > .btn {
151 | display: block;
152 | float: none;
153 | width: 100%;
154 | max-width: 100%;
155 | }
156 |
157 | // Clear floats so dropdown menus can be properly placed
158 | > .btn-group {
159 | &:extend(.clearfix all);
160 | > .btn {
161 | float: none;
162 | }
163 | }
164 |
165 | > .btn + .btn,
166 | > .btn + .btn-group,
167 | > .btn-group + .btn,
168 | > .btn-group + .btn-group {
169 | margin-top: -1px;
170 | margin-left: 0;
171 | }
172 | }
173 |
174 | .btn-group-vertical > .btn {
175 | &:not(:first-child):not(:last-child) {
176 | border-radius: 0;
177 | }
178 | &:first-child:not(:last-child) {
179 | border-top-right-radius: @border-radius-base;
180 | .border-bottom-radius(0);
181 | }
182 | &:last-child:not(:first-child) {
183 | border-bottom-left-radius: @border-radius-base;
184 | .border-top-radius(0);
185 | }
186 | }
187 | .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
188 | border-radius: 0;
189 | }
190 | .btn-group-vertical > .btn-group:first-child:not(:last-child) {
191 | > .btn:last-child,
192 | > .dropdown-toggle {
193 | .border-bottom-radius(0);
194 | }
195 | }
196 | .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
197 | .border-top-radius(0);
198 | }
199 |
200 |
201 |
202 | // Justified button groups
203 | // ----------------------
204 |
205 | .btn-group-justified {
206 | display: table;
207 | width: 100%;
208 | table-layout: fixed;
209 | border-collapse: separate;
210 | > .btn,
211 | > .btn-group {
212 | float: none;
213 | display: table-cell;
214 | width: 1%;
215 | }
216 | > .btn-group .btn {
217 | width: 100%;
218 | }
219 | }
220 |
221 |
222 | // Checkbox and radio options
223 | [data-toggle="buttons"] > .btn > input[type="radio"],
224 | [data-toggle="buttons"] > .btn > input[type="checkbox"] {
225 | display: none;
226 | }
227 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/buttons.less:
--------------------------------------------------------------------------------
1 | //
2 | // Buttons
3 | // --------------------------------------------------
4 |
5 |
6 | // Base styles
7 | // --------------------------------------------------
8 |
9 | .btn {
10 | display: inline-block;
11 | margin-bottom: 0; // For input.btn
12 | font-weight: @btn-font-weight;
13 | text-align: center;
14 | vertical-align: middle;
15 | cursor: pointer;
16 | background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
17 | border: 1px solid transparent;
18 | white-space: nowrap;
19 | .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);
20 | .user-select(none);
21 |
22 | &:focus {
23 | .tab-focus();
24 | }
25 |
26 | &:hover,
27 | &:focus {
28 | color: @btn-default-color;
29 | text-decoration: none;
30 | }
31 |
32 | &:active,
33 | &.active {
34 | outline: 0;
35 | background-image: none;
36 | .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
37 | }
38 |
39 | &.disabled,
40 | &[disabled],
41 | fieldset[disabled] & {
42 | cursor: not-allowed;
43 | pointer-events: none; // Future-proof disabling of clicks
44 | .opacity(.65);
45 | .box-shadow(none);
46 | }
47 | }
48 |
49 |
50 | // Alternate buttons
51 | // --------------------------------------------------
52 |
53 | .btn-default {
54 | .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);
55 | }
56 | .btn-primary {
57 | .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);
58 | }
59 | // Success appears as green
60 | .btn-success {
61 | .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);
62 | }
63 | // Info appears as blue-green
64 | .btn-info {
65 | .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);
66 | }
67 | // Warning appears as orange
68 | .btn-warning {
69 | .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);
70 | }
71 | // Danger and error appear as red
72 | .btn-danger {
73 | .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);
74 | }
75 |
76 |
77 | // Link buttons
78 | // -------------------------
79 |
80 | // Make a button look and behave like a link
81 | .btn-link {
82 | color: @link-color;
83 | font-weight: normal;
84 | cursor: pointer;
85 | border-radius: 0;
86 |
87 | &,
88 | &:active,
89 | &[disabled],
90 | fieldset[disabled] & {
91 | background-color: transparent;
92 | .box-shadow(none);
93 | }
94 | &,
95 | &:hover,
96 | &:focus,
97 | &:active {
98 | border-color: transparent;
99 | }
100 | &:hover,
101 | &:focus {
102 | color: @link-hover-color;
103 | text-decoration: underline;
104 | background-color: transparent;
105 | }
106 | &[disabled],
107 | fieldset[disabled] & {
108 | &:hover,
109 | &:focus {
110 | color: @btn-link-disabled-color;
111 | text-decoration: none;
112 | }
113 | }
114 | }
115 |
116 |
117 | // Button Sizes
118 | // --------------------------------------------------
119 |
120 | .btn-lg {
121 | // line-height: ensure even-numbered height of button next to large input
122 | .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);
123 | }
124 | .btn-sm {
125 | // line-height: ensure proper height of button next to small input
126 | .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);
127 | }
128 | .btn-xs {
129 | .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);
130 | }
131 |
132 |
133 | // Block button
134 | // --------------------------------------------------
135 |
136 | .btn-block {
137 | display: block;
138 | width: 100%;
139 | padding-left: 0;
140 | padding-right: 0;
141 | }
142 |
143 | // Vertically space out multiple block buttons
144 | .btn-block + .btn-block {
145 | margin-top: 5px;
146 | }
147 |
148 | // Specificity overrides
149 | input[type="submit"],
150 | input[type="reset"],
151 | input[type="button"] {
152 | &.btn-block {
153 | width: 100%;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/carousel.less:
--------------------------------------------------------------------------------
1 | //
2 | // Carousel
3 | // --------------------------------------------------
4 |
5 |
6 | // Wrapper for the slide container and indicators
7 | .carousel {
8 | position: relative;
9 | }
10 |
11 | .carousel-inner {
12 | position: relative;
13 | overflow: hidden;
14 | width: 100%;
15 |
16 | > .item {
17 | display: none;
18 | position: relative;
19 | .transition(.6s ease-in-out left);
20 |
21 | // Account for jankitude on images
22 | > img,
23 | > a > img {
24 | .img-responsive();
25 | line-height: 1;
26 | }
27 | }
28 |
29 | > .active,
30 | > .next,
31 | > .prev { display: block; }
32 |
33 | > .active {
34 | left: 0;
35 | }
36 |
37 | > .next,
38 | > .prev {
39 | position: absolute;
40 | top: 0;
41 | width: 100%;
42 | }
43 |
44 | > .next {
45 | left: 100%;
46 | }
47 | > .prev {
48 | left: -100%;
49 | }
50 | > .next.left,
51 | > .prev.right {
52 | left: 0;
53 | }
54 |
55 | > .active.left {
56 | left: -100%;
57 | }
58 | > .active.right {
59 | left: 100%;
60 | }
61 |
62 | }
63 |
64 | // Left/right controls for nav
65 | // ---------------------------
66 |
67 | .carousel-control {
68 | position: absolute;
69 | top: 0;
70 | left: 0;
71 | bottom: 0;
72 | width: @carousel-control-width;
73 | .opacity(@carousel-control-opacity);
74 | font-size: @carousel-control-font-size;
75 | color: @carousel-control-color;
76 | text-align: center;
77 | text-shadow: @carousel-text-shadow;
78 | // We can't have this transition here because WebKit cancels the carousel
79 | // animation if you trip this while in the middle of another animation.
80 |
81 | // Set gradients for backgrounds
82 | &.left {
83 | #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));
84 | }
85 | &.right {
86 | left: auto;
87 | right: 0;
88 | #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));
89 | }
90 |
91 | // Hover/focus state
92 | &:hover,
93 | &:focus {
94 | outline: none;
95 | color: @carousel-control-color;
96 | text-decoration: none;
97 | .opacity(.9);
98 | }
99 |
100 | // Toggles
101 | .icon-prev,
102 | .icon-next,
103 | .glyphicon-chevron-left,
104 | .glyphicon-chevron-right {
105 | position: absolute;
106 | top: 50%;
107 | z-index: 5;
108 | display: inline-block;
109 | }
110 | .icon-prev,
111 | .glyphicon-chevron-left {
112 | left: 50%;
113 | }
114 | .icon-next,
115 | .glyphicon-chevron-right {
116 | right: 50%;
117 | }
118 | .icon-prev,
119 | .icon-next {
120 | width: 20px;
121 | height: 20px;
122 | margin-top: -10px;
123 | margin-left: -10px;
124 | font-family: serif;
125 | }
126 |
127 | .icon-prev {
128 | &:before {
129 | content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)
130 | }
131 | }
132 | .icon-next {
133 | &:before {
134 | content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)
135 | }
136 | }
137 | }
138 |
139 | // Optional indicator pips
140 | //
141 | // Add an unordered list with the following class and add a list item for each
142 | // slide your carousel holds.
143 |
144 | .carousel-indicators {
145 | position: absolute;
146 | bottom: 10px;
147 | left: 50%;
148 | z-index: 15;
149 | width: 60%;
150 | margin-left: -30%;
151 | padding-left: 0;
152 | list-style: none;
153 | text-align: center;
154 |
155 | li {
156 | display: inline-block;
157 | width: 10px;
158 | height: 10px;
159 | margin: 1px;
160 | text-indent: -999px;
161 | border: 1px solid @carousel-indicator-border-color;
162 | border-radius: 10px;
163 | cursor: pointer;
164 |
165 | // IE8-9 hack for event handling
166 | //
167 | // Internet Explorer 8-9 does not support clicks on elements without a set
168 | // `background-color`. We cannot use `filter` since that's not viewed as a
169 | // background color by the browser. Thus, a hack is needed.
170 | //
171 | // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we
172 | // set alpha transparency for the best results possible.
173 | background-color: #000 \9; // IE8
174 | background-color: rgba(0,0,0,0); // IE9
175 | }
176 | .active {
177 | margin: 0;
178 | width: 12px;
179 | height: 12px;
180 | background-color: @carousel-indicator-active-bg;
181 | }
182 | }
183 |
184 | // Optional captions
185 | // -----------------------------
186 | // Hidden by default for smaller viewports
187 | .carousel-caption {
188 | position: absolute;
189 | left: 15%;
190 | right: 15%;
191 | bottom: 20px;
192 | z-index: 10;
193 | padding-top: 20px;
194 | padding-bottom: 20px;
195 | color: @carousel-caption-color;
196 | text-align: center;
197 | text-shadow: @carousel-text-shadow;
198 | & .btn {
199 | text-shadow: none; // No shadow for button elements in carousel-caption
200 | }
201 | }
202 |
203 |
204 | // Scale up controls for tablets and up
205 | @media screen and (min-width: @screen-sm-min) {
206 |
207 | // Scale up the controls a smidge
208 | .carousel-control {
209 | .glyphicons-chevron-left,
210 | .glyphicons-chevron-right,
211 | .icon-prev,
212 | .icon-next {
213 | width: 30px;
214 | height: 30px;
215 | margin-top: -15px;
216 | margin-left: -15px;
217 | font-size: 30px;
218 | }
219 | }
220 |
221 | // Show and left align the captions
222 | .carousel-caption {
223 | left: 20%;
224 | right: 20%;
225 | padding-bottom: 30px;
226 | }
227 |
228 | // Move up the indicators
229 | .carousel-indicators {
230 | bottom: 20px;
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/public/css/lib/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 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/code.less:
--------------------------------------------------------------------------------
1 | //
2 | // Code (inline and block)
3 | // --------------------------------------------------
4 |
5 |
6 | // Inline and block code styles
7 | code,
8 | kbd,
9 | pre,
10 | samp {
11 | font-family: @font-family-monospace;
12 | }
13 |
14 | // Inline code
15 | code {
16 | padding: 2px 4px;
17 | font-size: 90%;
18 | color: @code-color;
19 | background-color: @code-bg;
20 | white-space: nowrap;
21 | border-radius: @border-radius-base;
22 | }
23 |
24 | // User input typically entered via keyboard
25 | kbd {
26 | padding: 2px 4px;
27 | font-size: 90%;
28 | color: @kbd-color;
29 | background-color: @kbd-bg;
30 | border-radius: @border-radius-small;
31 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);
32 | }
33 |
34 | // Blocks of code
35 | pre {
36 | display: block;
37 | padding: ((@line-height-computed - 1) / 2);
38 | margin: 0 0 (@line-height-computed / 2);
39 | font-size: (@font-size-base - 1); // 14px to 13px
40 | line-height: @line-height-base;
41 | word-break: break-all;
42 | word-wrap: break-word;
43 | color: @pre-color;
44 | background-color: @pre-bg;
45 | border: 1px solid @pre-border-color;
46 | border-radius: @border-radius-base;
47 |
48 | // Account for some code outputs that place code tags in pre tags
49 | code {
50 | padding: 0;
51 | font-size: inherit;
52 | color: inherit;
53 | white-space: pre-wrap;
54 | background-color: transparent;
55 | border-radius: 0;
56 | }
57 | }
58 |
59 | // Enable scrollable blocks of code
60 | .pre-scrollable {
61 | max-height: @pre-scrollable-max-height;
62 | overflow-y: scroll;
63 | }
64 |
--------------------------------------------------------------------------------
/public/css/lib/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 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/dropdowns.less:
--------------------------------------------------------------------------------
1 | //
2 | // Dropdown menus
3 | // --------------------------------------------------
4 |
5 |
6 | // Dropdown arrow/caret
7 | .caret {
8 | display: inline-block;
9 | width: 0;
10 | height: 0;
11 | margin-left: 2px;
12 | vertical-align: middle;
13 | border-top: @caret-width-base solid;
14 | border-right: @caret-width-base solid transparent;
15 | border-left: @caret-width-base solid transparent;
16 | }
17 |
18 | // The dropdown wrapper (div)
19 | .dropdown {
20 | position: relative;
21 | }
22 |
23 | // Prevent the focus on the dropdown toggle when closing dropdowns
24 | .dropdown-toggle:focus {
25 | outline: 0;
26 | }
27 |
28 | // The dropdown menu (ul)
29 | .dropdown-menu {
30 | position: absolute;
31 | top: 100%;
32 | left: 0;
33 | z-index: @zindex-dropdown;
34 | display: none; // none by default, but block on "open" of the menu
35 | float: left;
36 | min-width: 160px;
37 | padding: 5px 0;
38 | margin: 2px 0 0; // override default ul
39 | list-style: none;
40 | font-size: @font-size-base;
41 | background-color: @dropdown-bg;
42 | border: 1px solid @dropdown-fallback-border; // IE8 fallback
43 | border: 1px solid @dropdown-border;
44 | border-radius: @border-radius-base;
45 | .box-shadow(0 6px 12px rgba(0,0,0,.175));
46 | background-clip: padding-box;
47 |
48 | // Aligns the dropdown menu to right
49 | //
50 | // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`
51 | &.pull-right {
52 | right: 0;
53 | left: auto;
54 | }
55 |
56 | // Dividers (basically an hr) within the dropdown
57 | .divider {
58 | .nav-divider(@dropdown-divider-bg);
59 | }
60 |
61 | // Links within the dropdown menu
62 | > li > a {
63 | display: block;
64 | padding: 3px 20px;
65 | clear: both;
66 | font-weight: normal;
67 | line-height: @line-height-base;
68 | color: @dropdown-link-color;
69 | white-space: nowrap; // prevent links from randomly breaking onto new lines
70 | }
71 | }
72 |
73 | // Hover/Focus state
74 | .dropdown-menu > li > a {
75 | &:hover,
76 | &:focus {
77 | text-decoration: none;
78 | color: @dropdown-link-hover-color;
79 | background-color: @dropdown-link-hover-bg;
80 | }
81 | }
82 |
83 | // Active state
84 | .dropdown-menu > .active > a {
85 | &,
86 | &:hover,
87 | &:focus {
88 | color: @dropdown-link-active-color;
89 | text-decoration: none;
90 | outline: 0;
91 | background-color: @dropdown-link-active-bg;
92 | }
93 | }
94 |
95 | // Disabled state
96 | //
97 | // Gray out text and ensure the hover/focus state remains gray
98 |
99 | .dropdown-menu > .disabled > a {
100 | &,
101 | &:hover,
102 | &:focus {
103 | color: @dropdown-link-disabled-color;
104 | }
105 | }
106 | // Nuke hover/focus effects
107 | .dropdown-menu > .disabled > a {
108 | &:hover,
109 | &:focus {
110 | text-decoration: none;
111 | background-color: transparent;
112 | background-image: none; // Remove CSS gradient
113 | .reset-filter();
114 | cursor: not-allowed;
115 | }
116 | }
117 |
118 | // Open state for the dropdown
119 | .open {
120 | // Show the menu
121 | > .dropdown-menu {
122 | display: block;
123 | }
124 |
125 | // Remove the outline when :focus is triggered
126 | > a {
127 | outline: 0;
128 | }
129 | }
130 |
131 | // Menu positioning
132 | //
133 | // Add extra class to `.dropdown-menu` to flip the alignment of the dropdown
134 | // menu with the parent.
135 | .dropdown-menu-right {
136 | left: auto; // Reset the default from `.dropdown-menu`
137 | right: 0;
138 | }
139 | // With v3, we enabled auto-flipping if you have a dropdown within a right
140 | // aligned nav component. To enable the undoing of that, we provide an override
141 | // to restore the default dropdown menu alignment.
142 | //
143 | // This is only for left-aligning a dropdown menu within a `.navbar-right` or
144 | // `.pull-right` nav component.
145 | .dropdown-menu-left {
146 | left: 0;
147 | right: auto;
148 | }
149 |
150 | // Dropdown section headers
151 | .dropdown-header {
152 | display: block;
153 | padding: 3px 20px;
154 | font-size: @font-size-small;
155 | line-height: @line-height-base;
156 | color: @dropdown-header-color;
157 | }
158 |
159 | // Backdrop to catch body clicks on mobile, etc.
160 | .dropdown-backdrop {
161 | position: fixed;
162 | left: 0;
163 | right: 0;
164 | bottom: 0;
165 | top: 0;
166 | z-index: (@zindex-dropdown - 10);
167 | }
168 |
169 | // Right aligned dropdowns
170 | .pull-right > .dropdown-menu {
171 | right: 0;
172 | left: auto;
173 | }
174 |
175 | // Allow for dropdowns to go bottom up (aka, dropup-menu)
176 | //
177 | // Just add .dropup after the standard .dropdown class and you're set, bro.
178 | // TODO: abstract this so that the navbar fixed styles are not placed here?
179 |
180 | .dropup,
181 | .navbar-fixed-bottom .dropdown {
182 | // Reverse the caret
183 | .caret {
184 | border-top: 0;
185 | border-bottom: @caret-width-base solid;
186 | content: "";
187 | }
188 | // Different positioning for bottom up menu
189 | .dropdown-menu {
190 | top: auto;
191 | bottom: 100%;
192 | margin-bottom: 1px;
193 | }
194 | }
195 |
196 |
197 | // Component alignment
198 | //
199 | // Reiterate per navbar.less and the modified component alignment there.
200 |
201 | @media (min-width: @grid-float-breakpoint) {
202 | .navbar-right {
203 | .dropdown-menu {
204 | .dropdown-menu-right();
205 | }
206 | // Necessary for overrides of the default right aligned menu.
207 | // Will remove come v4 in all likelihood.
208 | .dropdown-menu-left {
209 | .dropdown-menu-left();
210 | }
211 | }
212 | }
213 |
214 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/grid.less:
--------------------------------------------------------------------------------
1 | //
2 | // Grid system
3 | // --------------------------------------------------
4 |
5 |
6 | // Container widths
7 | //
8 | // Set the container width, and override it for fixed navbars in media queries.
9 |
10 | .container {
11 | .container-fixed();
12 |
13 | @media (min-width: @screen-sm-min) {
14 | width: @container-sm;
15 | }
16 | @media (min-width: @screen-md-min) {
17 | width: @container-md;
18 | }
19 | @media (min-width: @screen-lg-min) {
20 | width: @container-lg;
21 | }
22 | }
23 |
24 |
25 | // Fluid container
26 | //
27 | // Utilizes the mixin meant for fixed width containers, but without any defined
28 | // width for fluid, full width layouts.
29 |
30 | .container-fluid {
31 | .container-fixed();
32 | }
33 |
34 |
35 | // Row
36 | //
37 | // Rows contain and clear the floats of your columns.
38 |
39 | .row {
40 | .make-row();
41 | }
42 |
43 |
44 | // Columns
45 | //
46 | // Common styles for small and large grid columns
47 |
48 | .make-grid-columns();
49 |
50 |
51 | // Extra small grid
52 | //
53 | // Columns, offsets, pushes, and pulls for extra small devices like
54 | // smartphones.
55 |
56 | .make-grid-columns-float(xs);
57 | .make-grid(@grid-columns, xs, width);
58 | .make-grid(@grid-columns, xs, pull);
59 | .make-grid(@grid-columns, xs, push);
60 | .make-grid(@grid-columns, xs, offset);
61 |
62 |
63 | // Small grid
64 | //
65 | // Columns, offsets, pushes, and pulls for the small device range, from phones
66 | // to tablets.
67 |
68 | @media (min-width: @screen-sm-min) {
69 | .make-grid-columns-float(sm);
70 | .make-grid(@grid-columns, sm, width);
71 | .make-grid(@grid-columns, sm, pull);
72 | .make-grid(@grid-columns, sm, push);
73 | .make-grid(@grid-columns, sm, offset);
74 | }
75 |
76 |
77 | // Medium grid
78 | //
79 | // Columns, offsets, pushes, and pulls for the desktop device range.
80 |
81 | @media (min-width: @screen-md-min) {
82 | .make-grid-columns-float(md);
83 | .make-grid(@grid-columns, md, width);
84 | .make-grid(@grid-columns, md, pull);
85 | .make-grid(@grid-columns, md, push);
86 | .make-grid(@grid-columns, md, offset);
87 | }
88 |
89 |
90 | // Large grid
91 | //
92 | // Columns, offsets, pushes, and pulls for the large desktop device range.
93 |
94 | @media (min-width: @screen-lg-min) {
95 | .make-grid-columns-float(lg);
96 | .make-grid(@grid-columns, lg, width);
97 | .make-grid(@grid-columns, lg, pull);
98 | .make-grid(@grid-columns, lg, push);
99 | .make-grid(@grid-columns, lg, offset);
100 | }
101 |
--------------------------------------------------------------------------------
/public/css/lib/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 | &[class*="col-"] {
14 | float: none;
15 | padding-left: 0;
16 | padding-right: 0;
17 | }
18 |
19 | .form-control {
20 | // IE9 fubars the placeholder attribute in text inputs and the arrows on
21 | // select elements in input groups. To fix it, we float the input. Details:
22 | // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855
23 | float: left;
24 |
25 | width: 100%;
26 | margin-bottom: 0;
27 | }
28 | }
29 |
30 | // Sizing options
31 | //
32 | // Remix the default form control sizing classes into new ones for easier
33 | // manipulation.
34 |
35 | .input-group-lg > .form-control,
36 | .input-group-lg > .input-group-addon,
37 | .input-group-lg > .input-group-btn > .btn { .input-lg(); }
38 | .input-group-sm > .form-control,
39 | .input-group-sm > .input-group-addon,
40 | .input-group-sm > .input-group-btn > .btn { .input-sm(); }
41 |
42 |
43 | // Display as table-cell
44 | // -------------------------
45 | .input-group-addon,
46 | .input-group-btn,
47 | .input-group .form-control {
48 | display: table-cell;
49 |
50 | &:not(:first-child):not(:last-child) {
51 | border-radius: 0;
52 | }
53 | }
54 | // Addon and addon wrapper for buttons
55 | .input-group-addon,
56 | .input-group-btn {
57 | width: 1%;
58 | white-space: nowrap;
59 | vertical-align: middle; // Match the inputs
60 | }
61 |
62 | // Text input groups
63 | // -------------------------
64 | .input-group-addon {
65 | padding: @padding-base-vertical @padding-base-horizontal;
66 | font-size: @font-size-base;
67 | font-weight: normal;
68 | line-height: 1;
69 | color: @input-color;
70 | text-align: center;
71 | background-color: @input-group-addon-bg;
72 | border: 1px solid @input-group-addon-border-color;
73 | border-radius: @border-radius-base;
74 |
75 | // Sizing
76 | &.input-sm {
77 | padding: @padding-small-vertical @padding-small-horizontal;
78 | font-size: @font-size-small;
79 | border-radius: @border-radius-small;
80 | }
81 | &.input-lg {
82 | padding: @padding-large-vertical @padding-large-horizontal;
83 | font-size: @font-size-large;
84 | border-radius: @border-radius-large;
85 | }
86 |
87 | // Nuke default margins from checkboxes and radios to vertically center within.
88 | input[type="radio"],
89 | input[type="checkbox"] {
90 | margin-top: 0;
91 | }
92 | }
93 |
94 | // Reset rounded corners
95 | .input-group .form-control:first-child,
96 | .input-group-addon:first-child,
97 | .input-group-btn:first-child > .btn,
98 | .input-group-btn:first-child > .btn-group > .btn,
99 | .input-group-btn:first-child > .dropdown-toggle,
100 | .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),
101 | .input-group-btn:last-child > .btn-group:not(:last-child) > .btn {
102 | .border-right-radius(0);
103 | }
104 | .input-group-addon:first-child {
105 | border-right: 0;
106 | }
107 | .input-group .form-control:last-child,
108 | .input-group-addon:last-child,
109 | .input-group-btn:last-child > .btn,
110 | .input-group-btn:last-child > .btn-group > .btn,
111 | .input-group-btn:last-child > .dropdown-toggle,
112 | .input-group-btn:first-child > .btn:not(:first-child),
113 | .input-group-btn:first-child > .btn-group:not(:first-child) > .btn {
114 | .border-left-radius(0);
115 | }
116 | .input-group-addon:last-child {
117 | border-left: 0;
118 | }
119 |
120 | // Button input groups
121 | // -------------------------
122 | .input-group-btn {
123 | position: relative;
124 | // Jankily prevent input button groups from wrapping with `white-space` and
125 | // `font-size` in combination with `inline-block` on buttons.
126 | font-size: 0;
127 | white-space: nowrap;
128 |
129 | // Negative margin for spacing, position for bringing hovered/focused/actived
130 | // element above the siblings.
131 | > .btn {
132 | position: relative;
133 | + .btn {
134 | margin-left: -1px;
135 | }
136 | // Bring the "active" button to the front
137 | &:hover,
138 | &:focus,
139 | &:active {
140 | z-index: 2;
141 | }
142 | }
143 |
144 | // Negative margin to only have a 1px border between the two
145 | &:first-child {
146 | > .btn,
147 | > .btn-group {
148 | margin-right: -1px;
149 | }
150 | }
151 | &:last-child {
152 | > .btn,
153 | > .btn-group {
154 | margin-left: -1px;
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/jumbotron.less:
--------------------------------------------------------------------------------
1 | //
2 | // Jumbotron
3 | // --------------------------------------------------
4 |
5 |
6 | .jumbotron {
7 | padding: @jumbotron-padding;
8 | margin-bottom: @jumbotron-padding;
9 | color: @jumbotron-color;
10 | background-color: @jumbotron-bg;
11 |
12 | h1,
13 | .h1 {
14 | color: @jumbotron-heading-color;
15 | }
16 | p {
17 | margin-bottom: (@jumbotron-padding / 2);
18 | font-size: @jumbotron-font-size;
19 | font-weight: 200;
20 | }
21 |
22 | .container & {
23 | border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container
24 | }
25 |
26 | .container {
27 | max-width: 100%;
28 | }
29 |
30 | @media screen and (min-width: @screen-sm-min) {
31 | padding-top: (@jumbotron-padding * 1.6);
32 | padding-bottom: (@jumbotron-padding * 1.6);
33 |
34 | .container & {
35 | padding-left: (@jumbotron-padding * 2);
36 | padding-right: (@jumbotron-padding * 2);
37 | }
38 |
39 | h1,
40 | .h1 {
41 | font-size: (@font-size-base * 4.5);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/public/css/lib/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 | // Quick fix for labels in buttons
33 | .btn & {
34 | position: relative;
35 | top: -1px;
36 | }
37 | }
38 |
39 | // Colors
40 | // Contextual variations (linked labels get darker on :hover)
41 |
42 | .label-default {
43 | .label-variant(@label-default-bg);
44 | }
45 |
46 | .label-primary {
47 | .label-variant(@label-primary-bg);
48 | }
49 |
50 | .label-success {
51 | .label-variant(@label-success-bg);
52 | }
53 |
54 | .label-info {
55 | .label-variant(@label-info-bg);
56 | }
57 |
58 | .label-warning {
59 | .label-variant(@label-warning-bg);
60 | }
61 |
62 | .label-danger {
63 | .label-variant(@label-danger-bg);
64 | }
65 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/list-group.less:
--------------------------------------------------------------------------------
1 | //
2 | // List groups
3 | // --------------------------------------------------
4 |
5 |
6 | // Base class
7 | //
8 | // Easily usable on
, , or .
9 |
10 | .list-group {
11 | // No need to set list-style: none; since .list-group-item is block level
12 | margin-bottom: 20px;
13 | padding-left: 0; // reset padding because ul and ol
14 | }
15 |
16 |
17 | // Individual list items
18 | //
19 | // Use on `li`s or `div`s within the `.list-group` parent.
20 |
21 | .list-group-item {
22 | position: relative;
23 | display: block;
24 | padding: 10px 15px;
25 | // Place the border on the list items and negative margin up for better styling
26 | margin-bottom: -1px;
27 | background-color: @list-group-bg;
28 | border: 1px solid @list-group-border;
29 |
30 | // Round the first and last items
31 | &:first-child {
32 | .border-top-radius(@list-group-border-radius);
33 | }
34 | &:last-child {
35 | margin-bottom: 0;
36 | .border-bottom-radius(@list-group-border-radius);
37 | }
38 |
39 | // Align badges within list items
40 | > .badge {
41 | float: right;
42 | }
43 | > .badge + .badge {
44 | margin-right: 5px;
45 | }
46 | }
47 |
48 |
49 | // Linked list items
50 | //
51 | // Use anchor elements instead of `li`s or `div`s to create linked list items.
52 | // Includes an extra `.active` modifier class for showing selected items.
53 |
54 | a.list-group-item {
55 | color: @list-group-link-color;
56 |
57 | .list-group-item-heading {
58 | color: @list-group-link-heading-color;
59 | }
60 |
61 | // Hover state
62 | &:hover,
63 | &:focus {
64 | text-decoration: none;
65 | background-color: @list-group-hover-bg;
66 | }
67 |
68 | // Active class on item itself, not parent
69 | &.active,
70 | &.active:hover,
71 | &.active:focus {
72 | z-index: 2; // Place active items above their siblings for proper border styling
73 | color: @list-group-active-color;
74 | background-color: @list-group-active-bg;
75 | border-color: @list-group-active-border;
76 |
77 | // Force color to inherit for custom content
78 | .list-group-item-heading {
79 | color: inherit;
80 | }
81 | .list-group-item-text {
82 | color: @list-group-active-text-color;
83 | }
84 | }
85 | }
86 |
87 |
88 | // Contextual variants
89 | //
90 | // Add modifier classes to change text and background color on individual items.
91 | // Organizationally, this must come after the `:hover` states.
92 |
93 | .list-group-item-variant(success; @state-success-bg; @state-success-text);
94 | .list-group-item-variant(info; @state-info-bg; @state-info-text);
95 | .list-group-item-variant(warning; @state-warning-bg; @state-warning-text);
96 | .list-group-item-variant(danger; @state-danger-bg; @state-danger-text);
97 |
98 |
99 | // Custom content options
100 | //
101 | // Extra classes for creating well-formatted content within `.list-group-item`s.
102 |
103 | .list-group-item-heading {
104 | margin-top: 0;
105 | margin-bottom: 5px;
106 | }
107 | .list-group-item-text {
108 | margin-bottom: 0;
109 | line-height: 1.3;
110 | }
111 |
--------------------------------------------------------------------------------
/public/css/lib/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 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/modals.less:
--------------------------------------------------------------------------------
1 | //
2 | // Modals
3 | // --------------------------------------------------
4 |
5 | // .modal-open - body class for killing the scroll
6 | // .modal - container to scroll within
7 | // .modal-dialog - positioning shell for the actual modal
8 | // .modal-content - actual modal w/ bg and corners and shit
9 |
10 | // Kill the scroll on the body
11 | .modal-open {
12 | overflow: hidden;
13 | }
14 |
15 | // Container that the modal scrolls within
16 | .modal {
17 | display: none;
18 | overflow: auto;
19 | overflow-y: scroll;
20 | position: fixed;
21 | top: 0;
22 | right: 0;
23 | bottom: 0;
24 | left: 0;
25 | z-index: @zindex-modal;
26 | -webkit-overflow-scrolling: touch;
27 |
28 | // Prevent Chrome on Windows from adding a focus outline. For details, see
29 | // https://github.com/twbs/bootstrap/pull/10951.
30 | outline: 0;
31 |
32 | // When fading in the modal, animate it to slide down
33 | &.fade .modal-dialog {
34 | .translate(0, -25%);
35 | .transition-transform(~"0.3s ease-out");
36 | }
37 | &.in .modal-dialog { .translate(0, 0)}
38 | }
39 |
40 | // Shell div to position the modal with bottom padding
41 | .modal-dialog {
42 | position: relative;
43 | width: auto;
44 | margin: 10px;
45 | }
46 |
47 | // Actual modal
48 | .modal-content {
49 | position: relative;
50 | background-color: @modal-content-bg;
51 | border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)
52 | border: 1px solid @modal-content-border-color;
53 | border-radius: @border-radius-large;
54 | .box-shadow(0 3px 9px rgba(0,0,0,.5));
55 | background-clip: padding-box;
56 | // Remove focus outline from opened modal
57 | outline: none;
58 | }
59 |
60 | // Modal background
61 | .modal-backdrop {
62 | position: fixed;
63 | top: 0;
64 | right: 0;
65 | bottom: 0;
66 | left: 0;
67 | z-index: @zindex-modal-background;
68 | background-color: @modal-backdrop-bg;
69 | // Fade for backdrop
70 | &.fade { .opacity(0); }
71 | &.in { .opacity(@modal-backdrop-opacity); }
72 | }
73 |
74 | // Modal header
75 | // Top section of the modal w/ title and dismiss
76 | .modal-header {
77 | padding: @modal-title-padding;
78 | border-bottom: 1px solid @modal-header-border-color;
79 | min-height: (@modal-title-padding + @modal-title-line-height);
80 | }
81 | // Close icon
82 | .modal-header .close {
83 | margin-top: -2px;
84 | }
85 |
86 | // Title text within header
87 | .modal-title {
88 | margin: 0;
89 | line-height: @modal-title-line-height;
90 | }
91 |
92 | // Modal body
93 | // Where all modal content resides (sibling of .modal-header and .modal-footer)
94 | .modal-body {
95 | position: relative;
96 | padding: @modal-inner-padding;
97 | }
98 |
99 | // Footer (for actions)
100 | .modal-footer {
101 | margin-top: 15px;
102 | padding: (@modal-inner-padding - 1) @modal-inner-padding @modal-inner-padding;
103 | text-align: right; // right align buttons
104 | border-top: 1px solid @modal-footer-border-color;
105 | &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons
106 |
107 | // Properly space out buttons
108 | .btn + .btn {
109 | margin-left: 5px;
110 | margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
111 | }
112 | // but override that for button groups
113 | .btn-group .btn + .btn {
114 | margin-left: -1px;
115 | }
116 | // and override it for block buttons as well
117 | .btn-block + .btn-block {
118 | margin-left: 0;
119 | }
120 | }
121 |
122 | // Scale up the modal
123 | @media (min-width: @screen-sm-min) {
124 |
125 | // Automatically set modal's width for larger viewports
126 | .modal-dialog {
127 | width: @modal-md;
128 | margin: 30px auto;
129 | }
130 | .modal-content {
131 | .box-shadow(0 5px 15px rgba(0,0,0,.5));
132 | }
133 |
134 | // Modal sizes
135 | .modal-sm { width: @modal-sm; }
136 | .modal-lg { width: @modal-lg; }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/navs.less:
--------------------------------------------------------------------------------
1 | //
2 | // Navs
3 | // --------------------------------------------------
4 |
5 |
6 | // Base class
7 | // --------------------------------------------------
8 |
9 | .nav {
10 | margin-bottom: 0;
11 | padding-left: 0; // Override default ul/ol
12 | list-style: none;
13 | &:extend(.clearfix all);
14 |
15 | > li {
16 | position: relative;
17 | display: block;
18 |
19 | > a {
20 | position: relative;
21 | display: block;
22 | padding: @nav-link-padding;
23 | &:hover,
24 | &:focus {
25 | text-decoration: none;
26 | background-color: @nav-link-hover-bg;
27 | }
28 | }
29 |
30 | // Disabled state sets text to gray and nukes hover/tab effects
31 | &.disabled > a {
32 | color: @nav-disabled-link-color;
33 |
34 | &:hover,
35 | &:focus {
36 | color: @nav-disabled-link-hover-color;
37 | text-decoration: none;
38 | background-color: transparent;
39 | cursor: not-allowed;
40 | }
41 | }
42 | }
43 |
44 | // Open dropdowns
45 | .open > a {
46 | &,
47 | &:hover,
48 | &:focus {
49 | background-color: @nav-link-hover-bg;
50 | border-color: @link-color;
51 | }
52 | }
53 |
54 | // Nav dividers (deprecated with v3.0.1)
55 | //
56 | // This should have been removed in v3 with the dropping of `.nav-list`, but
57 | // we missed it. We don't currently support this anywhere, but in the interest
58 | // of maintaining backward compatibility in case you use it, it's deprecated.
59 | .nav-divider {
60 | .nav-divider();
61 | }
62 |
63 | // Prevent IE8 from misplacing imgs
64 | //
65 | // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989
66 | > li > a > img {
67 | max-width: none;
68 | }
69 | }
70 |
71 |
72 | // Tabs
73 | // -------------------------
74 |
75 | // Give the tabs something to sit on
76 | .nav-tabs {
77 | border-bottom: 1px solid @nav-tabs-border-color;
78 | > li {
79 | float: left;
80 | // Make the list-items overlay the bottom border
81 | margin-bottom: -1px;
82 |
83 | // Actual tabs (as links)
84 | > a {
85 | margin-right: 2px;
86 | line-height: @line-height-base;
87 | border: 1px solid transparent;
88 | border-radius: @border-radius-base @border-radius-base 0 0;
89 | &:hover {
90 | border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;
91 | }
92 | }
93 |
94 | // Active state, and its :hover to override normal :hover
95 | &.active > a {
96 | &,
97 | &:hover,
98 | &:focus {
99 | color: @nav-tabs-active-link-hover-color;
100 | background-color: @nav-tabs-active-link-hover-bg;
101 | border: 1px solid @nav-tabs-active-link-hover-border-color;
102 | border-bottom-color: transparent;
103 | cursor: default;
104 | }
105 | }
106 | }
107 | // pulling this in mainly for less shorthand
108 | &.nav-justified {
109 | .nav-justified();
110 | .nav-tabs-justified();
111 | }
112 | }
113 |
114 |
115 | // Pills
116 | // -------------------------
117 | .nav-pills {
118 | > li {
119 | float: left;
120 |
121 | // Links rendered as pills
122 | > a {
123 | border-radius: @nav-pills-border-radius;
124 | }
125 | + li {
126 | margin-left: 2px;
127 | }
128 |
129 | // Active state
130 | &.active > a {
131 | &,
132 | &:hover,
133 | &:focus {
134 | color: @nav-pills-active-link-hover-color;
135 | background-color: @nav-pills-active-link-hover-bg;
136 | }
137 | }
138 | }
139 | }
140 |
141 |
142 | // Stacked pills
143 | .nav-stacked {
144 | > li {
145 | float: none;
146 | + li {
147 | margin-top: 2px;
148 | margin-left: 0; // no need for this gap between nav items
149 | }
150 | }
151 | }
152 |
153 |
154 | // Nav variations
155 | // --------------------------------------------------
156 |
157 | // Justified nav links
158 | // -------------------------
159 |
160 | .nav-justified {
161 | width: 100%;
162 |
163 | > li {
164 | float: none;
165 | > a {
166 | text-align: center;
167 | margin-bottom: 5px;
168 | }
169 | }
170 |
171 | > .dropdown .dropdown-menu {
172 | top: auto;
173 | left: auto;
174 | }
175 |
176 | @media (min-width: @screen-sm-min) {
177 | > li {
178 | display: table-cell;
179 | width: 1%;
180 | > a {
181 | margin-bottom: 0;
182 | }
183 | }
184 | }
185 | }
186 |
187 | // Move borders to anchors instead of bottom of list
188 | //
189 | // Mixin for adding on top the shared `.nav-justified` styles for our tabs
190 | .nav-tabs-justified {
191 | border-bottom: 0;
192 |
193 | > li > a {
194 | // Override margin from .nav-tabs
195 | margin-right: 0;
196 | border-radius: @border-radius-base;
197 | }
198 |
199 | > .active > a,
200 | > .active > a:hover,
201 | > .active > a:focus {
202 | border: 1px solid @nav-tabs-justified-link-border-color;
203 | }
204 |
205 | @media (min-width: @screen-sm-min) {
206 | > li > a {
207 | border-bottom: 1px solid @nav-tabs-justified-link-border-color;
208 | border-radius: @border-radius-base @border-radius-base 0 0;
209 | }
210 | > .active > a,
211 | > .active > a:hover,
212 | > .active > a:focus {
213 | border-bottom-color: @nav-tabs-justified-active-link-border-color;
214 | }
215 | }
216 | }
217 |
218 |
219 | // Tabbable tabs
220 | // -------------------------
221 |
222 | // Hide tabbable panes to start, show them when `.active`
223 | .tab-content {
224 | > .tab-pane {
225 | display: none;
226 | }
227 | > .active {
228 | display: block;
229 | }
230 | }
231 |
232 |
233 | // Dropdowns
234 | // -------------------------
235 |
236 | // Specific dropdowns
237 | .nav-tabs .dropdown-menu {
238 | // make dropdown border overlap tab border
239 | margin-top: -1px;
240 | // Remove the top rounded corners here since there is a hard edge above the menu
241 | .border-top-radius(0);
242 | }
243 |
--------------------------------------------------------------------------------
/public/css/lib/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 | &:extend(.clearfix all);
12 | li {
13 | display: inline;
14 | > a,
15 | > span {
16 | display: inline-block;
17 | padding: 5px 14px;
18 | background-color: @pager-bg;
19 | border: 1px solid @pager-border;
20 | border-radius: @pager-border-radius;
21 | }
22 |
23 | > a:hover,
24 | > a:focus {
25 | text-decoration: none;
26 | background-color: @pager-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: @pager-bg;
51 | cursor: not-allowed;
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/public/css/lib/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 | color: @pagination-color;
20 | background-color: @pagination-bg;
21 | border: 1px solid @pagination-border;
22 | margin-left: -1px;
23 | }
24 | &:first-child {
25 | > a,
26 | > span {
27 | margin-left: 0;
28 | .border-left-radius(@border-radius-base);
29 | }
30 | }
31 | &:last-child {
32 | > a,
33 | > span {
34 | .border-right-radius(@border-radius-base);
35 | }
36 | }
37 | }
38 |
39 | > li > a,
40 | > li > span {
41 | &:hover,
42 | &:focus {
43 | color: @pagination-hover-color;
44 | background-color: @pagination-hover-bg;
45 | border-color: @pagination-hover-border;
46 | }
47 | }
48 |
49 | > .active > a,
50 | > .active > span {
51 | &,
52 | &:hover,
53 | &:focus {
54 | z-index: 2;
55 | color: @pagination-active-color;
56 | background-color: @pagination-active-bg;
57 | border-color: @pagination-active-border;
58 | cursor: default;
59 | }
60 | }
61 |
62 | > .disabled {
63 | > span,
64 | > span:hover,
65 | > span:focus,
66 | > a,
67 | > a:hover,
68 | > a:focus {
69 | color: @pagination-disabled-color;
70 | background-color: @pagination-disabled-bg;
71 | border-color: @pagination-disabled-border;
72 | cursor: not-allowed;
73 | }
74 | }
75 | }
76 |
77 | // Sizing
78 | // --------------------------------------------------
79 |
80 | // Large
81 | .pagination-lg {
82 | .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);
83 | }
84 |
85 | // Small
86 | .pagination-sm {
87 | .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);
88 | }
89 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/panels.less:
--------------------------------------------------------------------------------
1 | //
2 | // Panels
3 | // --------------------------------------------------
4 |
5 |
6 | // Base class
7 | .panel {
8 | margin-bottom: @line-height-computed;
9 | background-color: @panel-bg;
10 | border: 1px solid transparent;
11 | border-radius: @panel-border-radius;
12 | .box-shadow(0 1px 1px rgba(0,0,0,.05));
13 | }
14 |
15 | // Panel contents
16 | .panel-body {
17 | padding: @panel-body-padding;
18 | &:extend(.clearfix all);
19 | }
20 |
21 |
22 | // List groups in panels
23 | //
24 | // By default, space out list group content from panel headings to account for
25 | // any kind of custom content between the two.
26 |
27 | .panel {
28 | > .list-group {
29 | margin-bottom: 0;
30 | .list-group-item {
31 | border-width: 1px 0;
32 | border-radius: 0;
33 | &:first-child {
34 | border-top: 0;
35 | }
36 | &:last-child {
37 | border-bottom: 0;
38 | }
39 | }
40 | // Add border top radius for first one
41 | &:first-child {
42 | .list-group-item:first-child {
43 | .border-top-radius((@panel-border-radius - 1));
44 | }
45 | }
46 | // Add border bottom radius for last one
47 | &:last-child {
48 | .list-group-item:last-child {
49 | .border-bottom-radius((@panel-border-radius - 1));
50 | }
51 | }
52 | }
53 | }
54 | // Collapse space between when there's no additional content.
55 | .panel-heading + .list-group {
56 | .list-group-item:first-child {
57 | border-top-width: 0;
58 | }
59 | }
60 |
61 |
62 | // Tables in panels
63 | //
64 | // Place a non-bordered `.table` within a panel (not within a `.panel-body`) and
65 | // watch it go full width.
66 |
67 | .panel {
68 | > .table,
69 | > .table-responsive > .table {
70 | margin-bottom: 0;
71 | }
72 | // Add border top radius for first one
73 | > .table:first-child,
74 | > .table-responsive:first-child > .table:first-child {
75 | > thead:first-child,
76 | > tbody:first-child {
77 | > tr:first-child {
78 | td:first-child,
79 | th:first-child {
80 | border-top-left-radius: (@panel-border-radius - 1);
81 | }
82 | td:last-child,
83 | th:last-child {
84 | border-top-right-radius: (@panel-border-radius - 1);
85 | }
86 | }
87 | }
88 | }
89 | // Add border bottom radius for last one
90 | > .table:last-child,
91 | > .table-responsive:last-child > .table:last-child {
92 | > tbody:last-child,
93 | > tfoot:last-child {
94 | > tr:last-child {
95 | td:first-child,
96 | th:first-child {
97 | border-bottom-left-radius: (@panel-border-radius - 1);
98 | }
99 | td:last-child,
100 | th:last-child {
101 | border-bottom-right-radius: (@panel-border-radius - 1);
102 | }
103 | }
104 | }
105 | }
106 | > .panel-body + .table,
107 | > .panel-body + .table-responsive {
108 | border-top: 1px solid @table-border-color;
109 | }
110 | > .table > tbody:first-child > tr:first-child th,
111 | > .table > tbody:first-child > tr:first-child td {
112 | border-top: 0;
113 | }
114 | > .table-bordered,
115 | > .table-responsive > .table-bordered {
116 | border: 0;
117 | > thead,
118 | > tbody,
119 | > tfoot {
120 | > tr {
121 | > th:first-child,
122 | > td:first-child {
123 | border-left: 0;
124 | }
125 | > th:last-child,
126 | > td:last-child {
127 | border-right: 0;
128 | }
129 | &:first-child > th,
130 | &:first-child > td {
131 | border-top: 0;
132 | }
133 | &:last-child > th,
134 | &:last-child > td {
135 | border-bottom: 0;
136 | }
137 | }
138 | }
139 | }
140 | > .table-responsive {
141 | border: 0;
142 | margin-bottom: 0;
143 | }
144 | }
145 |
146 |
147 | // Optional heading
148 | .panel-heading {
149 | padding: 10px 15px;
150 | border-bottom: 1px solid transparent;
151 | .border-top-radius((@panel-border-radius - 1));
152 |
153 | > .dropdown .dropdown-toggle {
154 | color: inherit;
155 | }
156 | }
157 |
158 | // Within heading, strip any `h*` tag of its default margins for spacing.
159 | .panel-title {
160 | margin-top: 0;
161 | margin-bottom: 0;
162 | font-size: ceil((@font-size-base * 1.125));
163 | color: inherit;
164 |
165 | > a {
166 | color: inherit;
167 | }
168 | }
169 |
170 | // Optional footer (stays gray in every modifier class)
171 | .panel-footer {
172 | padding: 10px 15px;
173 | background-color: @panel-footer-bg;
174 | border-top: 1px solid @panel-inner-border;
175 | .border-bottom-radius((@panel-border-radius - 1));
176 | }
177 |
178 |
179 | // Collapsable panels (aka, accordion)
180 | //
181 | // Wrap a series of panels in `.panel-group` to turn them into an accordion with
182 | // the help of our collapse JavaScript plugin.
183 |
184 | .panel-group {
185 | margin-bottom: @line-height-computed;
186 |
187 | // Tighten up margin so it's only between panels
188 | .panel {
189 | margin-bottom: 0;
190 | border-radius: @panel-border-radius;
191 | overflow: hidden; // crop contents when collapsed
192 | + .panel {
193 | margin-top: 5px;
194 | }
195 | }
196 |
197 | .panel-heading {
198 | border-bottom: 0;
199 | + .panel-collapse .panel-body {
200 | border-top: 1px solid @panel-inner-border;
201 | }
202 | }
203 | .panel-footer {
204 | border-top: 0;
205 | + .panel-collapse .panel-body {
206 | border-bottom: 1px solid @panel-inner-border;
207 | }
208 | }
209 | }
210 |
211 |
212 | // Contextual variations
213 | .panel-default {
214 | .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);
215 | }
216 | .panel-primary {
217 | .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);
218 | }
219 | .panel-success {
220 | .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);
221 | }
222 | .panel-info {
223 | .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);
224 | }
225 | .panel-warning {
226 | .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);
227 | }
228 | .panel-danger {
229 | .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);
230 | }
231 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/popovers.less:
--------------------------------------------------------------------------------
1 | //
2 | // Popovers
3 | // --------------------------------------------------
4 |
5 |
6 | .popover {
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | z-index: @zindex-popover;
11 | display: none;
12 | max-width: @popover-max-width;
13 | padding: 1px;
14 | text-align: left; // Reset given new insertion method
15 | background-color: @popover-bg;
16 | background-clip: padding-box;
17 | border: 1px solid @popover-fallback-border-color;
18 | border: 1px solid @popover-border-color;
19 | border-radius: @border-radius-large;
20 | .box-shadow(0 5px 10px rgba(0,0,0,.2));
21 |
22 | // Overrides for proper insertion
23 | white-space: normal;
24 |
25 | // Offset the popover to account for the popover arrow
26 | &.top { margin-top: -10px; }
27 | &.right { margin-left: 10px; }
28 | &.bottom { margin-top: 10px; }
29 | &.left { margin-left: -10px; }
30 | }
31 |
32 | .popover-title {
33 | margin: 0; // reset heading margin
34 | padding: 8px 14px;
35 | font-size: @font-size-base;
36 | font-weight: normal;
37 | line-height: 18px;
38 | background-color: @popover-title-bg;
39 | border-bottom: 1px solid darken(@popover-title-bg, 5%);
40 | border-radius: 5px 5px 0 0;
41 | }
42 |
43 | .popover-content {
44 | padding: 9px 14px;
45 | }
46 |
47 | // Arrows
48 | //
49 | // .arrow is outer, .arrow:after is inner
50 |
51 | .popover .arrow {
52 | &,
53 | &:after {
54 | position: absolute;
55 | display: block;
56 | width: 0;
57 | height: 0;
58 | border-color: transparent;
59 | border-style: solid;
60 | }
61 | }
62 | .popover .arrow {
63 | border-width: @popover-arrow-outer-width;
64 | }
65 | .popover .arrow:after {
66 | border-width: @popover-arrow-width;
67 | content: "";
68 | }
69 |
70 | .popover {
71 | &.top .arrow {
72 | left: 50%;
73 | margin-left: -@popover-arrow-outer-width;
74 | border-bottom-width: 0;
75 | border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback
76 | border-top-color: @popover-arrow-outer-color;
77 | bottom: -@popover-arrow-outer-width;
78 | &:after {
79 | content: " ";
80 | bottom: 1px;
81 | margin-left: -@popover-arrow-width;
82 | border-bottom-width: 0;
83 | border-top-color: @popover-arrow-color;
84 | }
85 | }
86 | &.right .arrow {
87 | top: 50%;
88 | left: -@popover-arrow-outer-width;
89 | margin-top: -@popover-arrow-outer-width;
90 | border-left-width: 0;
91 | border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback
92 | border-right-color: @popover-arrow-outer-color;
93 | &:after {
94 | content: " ";
95 | left: 1px;
96 | bottom: -@popover-arrow-width;
97 | border-left-width: 0;
98 | border-right-color: @popover-arrow-color;
99 | }
100 | }
101 | &.bottom .arrow {
102 | left: 50%;
103 | margin-left: -@popover-arrow-outer-width;
104 | border-top-width: 0;
105 | border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback
106 | border-bottom-color: @popover-arrow-outer-color;
107 | top: -@popover-arrow-outer-width;
108 | &:after {
109 | content: " ";
110 | top: 1px;
111 | margin-left: -@popover-arrow-width;
112 | border-top-width: 0;
113 | border-bottom-color: @popover-arrow-color;
114 | }
115 | }
116 |
117 | &.left .arrow {
118 | top: 50%;
119 | right: -@popover-arrow-outer-width;
120 | margin-top: -@popover-arrow-outer-width;
121 | border-right-width: 0;
122 | border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback
123 | border-left-color: @popover-arrow-outer-color;
124 | &:after {
125 | content: " ";
126 | right: 1px;
127 | border-right-width: 0;
128 | border-left-color: @popover-arrow-color;
129 | bottom: -@popover-arrow-width;
130 | }
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/public/css/lib/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 | a[href^="javascript:"]:after,
30 | a[href^="#"]:after {
31 | content: "";
32 | }
33 |
34 | pre,
35 | blockquote {
36 | border: 1px solid #999;
37 | page-break-inside: avoid;
38 | }
39 |
40 | thead {
41 | display: table-header-group; // h5bp.com/t
42 | }
43 |
44 | tr,
45 | img {
46 | page-break-inside: avoid;
47 | }
48 |
49 | img {
50 | max-width: 100% !important;
51 | }
52 |
53 | p,
54 | h2,
55 | h3 {
56 | orphans: 3;
57 | widows: 3;
58 | }
59 |
60 | h2,
61 | h3 {
62 | page-break-after: avoid;
63 | }
64 |
65 | // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245
66 | // Once fixed, we can just straight up remove this.
67 | select {
68 | background: #fff !important;
69 | }
70 |
71 | // Bootstrap components
72 | .navbar {
73 | display: none;
74 | }
75 | .table {
76 | td,
77 | th {
78 | background-color: #fff !important;
79 | }
80 | }
81 | .btn,
82 | .dropup > .btn {
83 | > .caret {
84 | border-top-color: #000 !important;
85 | }
86 | }
87 | .label {
88 | border: 1px solid #000;
89 | }
90 |
91 | .table {
92 | border-collapse: collapse !important;
93 | }
94 | .table-bordered {
95 | th,
96 | td {
97 | border: 1px solid #ddd !important;
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/public/css/lib/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 | // Spec and IE10+
16 | @keyframes progress-bar-stripes {
17 | from { background-position: 40px 0; }
18 | to { background-position: 0 0; }
19 | }
20 |
21 |
22 |
23 | // Bar itself
24 | // -------------------------
25 |
26 | // Outer container
27 | .progress {
28 | overflow: hidden;
29 | height: @line-height-computed;
30 | margin-bottom: @line-height-computed;
31 | background-color: @progress-bg;
32 | border-radius: @border-radius-base;
33 | .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
34 | }
35 |
36 | // Bar of progress
37 | .progress-bar {
38 | float: left;
39 | width: 0%;
40 | height: 100%;
41 | font-size: @font-size-small;
42 | line-height: @line-height-computed;
43 | color: @progress-bar-color;
44 | text-align: center;
45 | background-color: @progress-bar-bg;
46 | .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
47 | .transition(width .6s ease);
48 | }
49 |
50 | // Striped bars
51 | .progress-striped .progress-bar {
52 | #gradient > .striped();
53 | background-size: 40px 40px;
54 | }
55 |
56 | // Call animation for the active one
57 | .progress.active .progress-bar {
58 | .animation(progress-bar-stripes 2s linear infinite);
59 | }
60 |
61 |
62 |
63 | // Variations
64 | // -------------------------
65 |
66 | .progress-bar-success {
67 | .progress-bar-variant(@progress-bar-success-bg);
68 | }
69 |
70 | .progress-bar-info {
71 | .progress-bar-variant(@progress-bar-info-bg);
72 | }
73 |
74 | .progress-bar-warning {
75 | .progress-bar-variant(@progress-bar-warning-bg);
76 | }
77 |
78 | .progress-bar-danger {
79 | .progress-bar-variant(@progress-bar-danger-bg);
80 | }
81 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/responsive-utilities.less:
--------------------------------------------------------------------------------
1 | //
2 | // Responsive: Utility classes
3 | // --------------------------------------------------
4 |
5 |
6 | // IE10 in Windows (Phone) 8
7 | //
8 | // Support for responsive views via media queries is kind of borked in IE10, for
9 | // Surface/desktop in split view and for Windows Phone 8. This particular fix
10 | // must be accompanied by a snippet of JavaScript to sniff the user agent and
11 | // apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at
12 | // our Getting Started page for more information on this bug.
13 | //
14 | // For more information, see the following:
15 | //
16 | // Issue: https://github.com/twbs/bootstrap/issues/10497
17 | // Docs: http://getbootstrap.com/getting-started/#browsers
18 | // Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/
19 |
20 | @-ms-viewport {
21 | width: device-width;
22 | }
23 |
24 |
25 | // Visibility utilities
26 | .visible-xs {
27 | .responsive-invisibility();
28 |
29 | @media (max-width: @screen-xs-max) {
30 | .responsive-visibility();
31 | }
32 | }
33 | .visible-sm {
34 | .responsive-invisibility();
35 |
36 | @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
37 | .responsive-visibility();
38 | }
39 | }
40 | .visible-md {
41 | .responsive-invisibility();
42 |
43 | @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
44 | .responsive-visibility();
45 | }
46 | }
47 | .visible-lg {
48 | .responsive-invisibility();
49 |
50 | @media (min-width: @screen-lg-min) {
51 | .responsive-visibility();
52 | }
53 | }
54 |
55 | .hidden-xs {
56 | @media (max-width: @screen-xs-max) {
57 | .responsive-invisibility();
58 | }
59 | }
60 | .hidden-sm {
61 | @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
62 | .responsive-invisibility();
63 | }
64 | }
65 | .hidden-md {
66 | @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
67 | .responsive-invisibility();
68 | }
69 | }
70 | .hidden-lg {
71 | @media (min-width: @screen-lg-min) {
72 | .responsive-invisibility();
73 | }
74 | }
75 |
76 |
77 | // Print utilities
78 | //
79 | // Media queries are placed on the inside to be mixin-friendly.
80 |
81 | .visible-print {
82 | .responsive-invisibility();
83 |
84 | @media print {
85 | .responsive-visibility();
86 | }
87 | }
88 |
89 | .hidden-print {
90 | @media print {
91 | .responsive-invisibility();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/scaffolding.less:
--------------------------------------------------------------------------------
1 | //
2 | // Scaffolding
3 | // --------------------------------------------------
4 |
5 |
6 | // Reset the box-sizing
7 | //
8 | // Heads up! This reset may cause conflicts with some third-party widgets.
9 | // For recommendations on resolving such conflicts, see
10 | // http://getbootstrap.com/getting-started/#third-box-sizing
11 | * {
12 | .box-sizing(border-box);
13 | }
14 | *:before,
15 | *:after {
16 | .box-sizing(border-box);
17 | }
18 |
19 |
20 | // Body reset
21 |
22 | html {
23 | font-size: 62.5%;
24 | -webkit-tap-highlight-color: rgba(0,0,0,0);
25 | }
26 |
27 | body {
28 | font-family: @font-family-base;
29 | font-size: @font-size-base;
30 | line-height: @line-height-base;
31 | color: @text-color;
32 | background-color: @body-bg;
33 | }
34 |
35 | // Reset fonts for relevant elements
36 | input,
37 | button,
38 | select,
39 | textarea {
40 | font-family: inherit;
41 | font-size: inherit;
42 | line-height: inherit;
43 | }
44 |
45 |
46 | // Links
47 |
48 | a {
49 | color: @link-color;
50 | text-decoration: none;
51 |
52 | &:hover,
53 | &:focus {
54 | color: @link-hover-color;
55 | text-decoration: underline;
56 | }
57 |
58 | &:focus {
59 | .tab-focus();
60 | }
61 | }
62 |
63 |
64 | // Figures
65 | //
66 | // We reset this here because previously Normalize had no `figure` margins. This
67 | // ensures we don't break anyone's use of the element.
68 |
69 | figure {
70 | margin: 0;
71 | }
72 |
73 |
74 | // Images
75 |
76 | img {
77 | vertical-align: middle;
78 | }
79 |
80 | // Responsive images (ensure images don't scale beyond their parents)
81 | .img-responsive {
82 | .img-responsive();
83 | }
84 |
85 | // Rounded corners
86 | .img-rounded {
87 | border-radius: @border-radius-large;
88 | }
89 |
90 | // Image thumbnails
91 | //
92 | // Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.
93 | .img-thumbnail {
94 | padding: @thumbnail-padding;
95 | line-height: @line-height-base;
96 | background-color: @thumbnail-bg;
97 | border: 1px solid @thumbnail-border;
98 | border-radius: @thumbnail-border-radius;
99 | .transition(all .2s ease-in-out);
100 |
101 | // Keep them at most 100% wide
102 | .img-responsive(inline-block);
103 | }
104 |
105 | // Perfect circle
106 | .img-circle {
107 | border-radius: 50%; // set radius in percents
108 | }
109 |
110 |
111 | // Horizontal rules
112 |
113 | hr {
114 | margin-top: @line-height-computed;
115 | margin-bottom: @line-height-computed;
116 | border: 0;
117 | border-top: 1px solid @hr-border;
118 | }
119 |
120 |
121 | // Only display content to screen readers
122 | //
123 | // See: http://a11yproject.com/posts/how-to-hide-content/
124 |
125 | .sr-only {
126 | position: absolute;
127 | width: 1px;
128 | height: 1px;
129 | margin: -1px;
130 | padding: 0;
131 | overflow: hidden;
132 | clip: rect(0,0,0,0);
133 | border: 0;
134 | }
135 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/tables.less:
--------------------------------------------------------------------------------
1 | //
2 | // Tables
3 | // --------------------------------------------------
4 |
5 |
6 | table {
7 | max-width: 100%;
8 | background-color: @table-bg;
9 | }
10 | th {
11 | text-align: left;
12 | }
13 |
14 |
15 | // Baseline styles
16 |
17 | .table {
18 | width: 100%;
19 | margin-bottom: @line-height-computed;
20 | // Cells
21 | > thead,
22 | > tbody,
23 | > tfoot {
24 | > tr {
25 | > th,
26 | > td {
27 | padding: @table-cell-padding;
28 | line-height: @line-height-base;
29 | vertical-align: top;
30 | border-top: 1px solid @table-border-color;
31 | }
32 | }
33 | }
34 | // Bottom align for column headings
35 | > thead > tr > th {
36 | vertical-align: bottom;
37 | border-bottom: 2px solid @table-border-color;
38 | }
39 | // Remove top border from thead by default
40 | > caption + thead,
41 | > colgroup + thead,
42 | > thead:first-child {
43 | > tr:first-child {
44 | > th,
45 | > td {
46 | border-top: 0;
47 | }
48 | }
49 | }
50 | // Account for multiple tbody instances
51 | > tbody + tbody {
52 | border-top: 2px solid @table-border-color;
53 | }
54 |
55 | // Nesting
56 | .table {
57 | background-color: @body-bg;
58 | }
59 | }
60 |
61 |
62 | // Condensed table w/ half padding
63 |
64 | .table-condensed {
65 | > thead,
66 | > tbody,
67 | > tfoot {
68 | > tr {
69 | > th,
70 | > td {
71 | padding: @table-condensed-cell-padding;
72 | }
73 | }
74 | }
75 | }
76 |
77 |
78 | // Bordered version
79 | //
80 | // Add borders all around the table and between all the columns.
81 |
82 | .table-bordered {
83 | border: 1px solid @table-border-color;
84 | > thead,
85 | > tbody,
86 | > tfoot {
87 | > tr {
88 | > th,
89 | > td {
90 | border: 1px solid @table-border-color;
91 | }
92 | }
93 | }
94 | > thead > tr {
95 | > th,
96 | > td {
97 | border-bottom-width: 2px;
98 | }
99 | }
100 | }
101 |
102 |
103 | // Zebra-striping
104 | //
105 | // Default zebra-stripe styles (alternating gray and transparent backgrounds)
106 |
107 | .table-striped {
108 | > tbody > tr:nth-child(odd) {
109 | > td,
110 | > th {
111 | background-color: @table-bg-accent;
112 | }
113 | }
114 | }
115 |
116 |
117 | // Hover effect
118 | //
119 | // Placed here since it has to come after the potential zebra striping
120 |
121 | .table-hover {
122 | > tbody > tr:hover {
123 | > td,
124 | > th {
125 | background-color: @table-bg-hover;
126 | }
127 | }
128 | }
129 |
130 |
131 | // Table cell sizing
132 | //
133 | // Reset default table behavior
134 |
135 | table col[class*="col-"] {
136 | position: static; // Prevent border hiding in Firefox and IE9/10 (see https://github.com/twbs/bootstrap/issues/11623)
137 | float: none;
138 | display: table-column;
139 | }
140 | table {
141 | td,
142 | th {
143 | &[class*="col-"] {
144 | position: static; // Prevent border hiding in Firefox and IE9/10 (see https://github.com/twbs/bootstrap/issues/11623)
145 | float: none;
146 | display: table-cell;
147 | }
148 | }
149 | }
150 |
151 |
152 | // Table backgrounds
153 | //
154 | // Exact selectors below required to override `.table-striped` and prevent
155 | // inheritance to nested tables.
156 |
157 | // Generate the contextual variants
158 | .table-row-variant(active; @table-bg-active);
159 | .table-row-variant(success; @state-success-bg);
160 | .table-row-variant(info; @state-info-bg);
161 | .table-row-variant(warning; @state-warning-bg);
162 | .table-row-variant(danger; @state-danger-bg);
163 |
164 |
165 | // Responsive tables
166 | //
167 | // Wrap your tables in `.table-responsive` and we'll make them mobile friendly
168 | // by enabling horizontal scrolling. Only applies <768px. Everything above that
169 | // will display normally.
170 |
171 | @media (max-width: @screen-xs-max) {
172 | .table-responsive {
173 | width: 100%;
174 | margin-bottom: (@line-height-computed * 0.75);
175 | overflow-y: hidden;
176 | overflow-x: scroll;
177 | -ms-overflow-style: -ms-autohiding-scrollbar;
178 | border: 1px solid @table-border-color;
179 | -webkit-overflow-scrolling: touch;
180 |
181 | // Tighten up spacing
182 | > .table {
183 | margin-bottom: 0;
184 |
185 | // Ensure the content doesn't wrap
186 | > thead,
187 | > tbody,
188 | > tfoot {
189 | > tr {
190 | > th,
191 | > td {
192 | white-space: nowrap;
193 | }
194 | }
195 | }
196 | }
197 |
198 | // Special overrides for the bordered tables
199 | > .table-bordered {
200 | border: 0;
201 |
202 | // Nuke the appropriate borders so that the parent can handle them
203 | > thead,
204 | > tbody,
205 | > tfoot {
206 | > tr {
207 | > th:first-child,
208 | > td:first-child {
209 | border-left: 0;
210 | }
211 | > th:last-child,
212 | > td:last-child {
213 | border-right: 0;
214 | }
215 | }
216 | }
217 |
218 | // Only nuke the last row's bottom-border in `tbody` and `tfoot` since
219 | // chances are there will be only one `tr` in a `thead` and that would
220 | // remove the border altogether.
221 | > tbody,
222 | > tfoot {
223 | > tr:last-child {
224 | > th,
225 | > td {
226 | border-bottom: 0;
227 | }
228 | }
229 | }
230 |
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/thumbnails.less:
--------------------------------------------------------------------------------
1 | //
2 | // Thumbnails
3 | // --------------------------------------------------
4 |
5 |
6 | // Mixin and adjust the regular image class
7 | .thumbnail {
8 | display: block;
9 | padding: @thumbnail-padding;
10 | margin-bottom: @line-height-computed;
11 | line-height: @line-height-base;
12 | background-color: @thumbnail-bg;
13 | border: 1px solid @thumbnail-border;
14 | border-radius: @thumbnail-border-radius;
15 | .transition(all .2s ease-in-out);
16 |
17 | > img,
18 | a > img {
19 | .img-responsive();
20 | margin-left: auto;
21 | margin-right: auto;
22 | }
23 |
24 | // Add a hover state for linked versions only
25 | a&:hover,
26 | a&:focus,
27 | a&.active {
28 | border-color: @link-color;
29 | }
30 |
31 | // Image captions
32 | .caption {
33 | padding: @thumbnail-caption-padding;
34 | color: @thumbnail-caption-color;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/public/css/lib/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(@tooltip-opacity); }
17 | &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }
18 | &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }
19 | &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }
20 | &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }
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: @tooltip-arrow-width;
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: @tooltip-arrow-width;
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: @tooltip-arrow-width;
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: @tooltip-arrow-width;
92 | border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
93 | border-bottom-color: @tooltip-arrow-color;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/type.less:
--------------------------------------------------------------------------------
1 | //
2 | // Typography
3 | // --------------------------------------------------
4 |
5 |
6 | // Headings
7 | // -------------------------
8 |
9 | h1, h2, h3, h4, h5, h6,
10 | .h1, .h2, .h3, .h4, .h5, .h6 {
11 | font-family: @headings-font-family;
12 | font-weight: @headings-font-weight;
13 | line-height: @headings-line-height;
14 | color: @headings-color;
15 |
16 | small,
17 | .small {
18 | font-weight: normal;
19 | line-height: 1;
20 | color: @headings-small-color;
21 | }
22 | }
23 |
24 | h1, .h1,
25 | h2, .h2,
26 | h3, .h3 {
27 | margin-top: @line-height-computed;
28 | margin-bottom: (@line-height-computed / 2);
29 |
30 | small,
31 | .small {
32 | font-size: 65%;
33 | }
34 | }
35 | h4, .h4,
36 | h5, .h5,
37 | h6, .h6 {
38 | margin-top: (@line-height-computed / 2);
39 | margin-bottom: (@line-height-computed / 2);
40 |
41 | small,
42 | .small {
43 | font-size: 75%;
44 | }
45 | }
46 |
47 | h1, .h1 { font-size: @font-size-h1; }
48 | h2, .h2 { font-size: @font-size-h2; }
49 | h3, .h3 { font-size: @font-size-h3; }
50 | h4, .h4 { font-size: @font-size-h4; }
51 | h5, .h5 { font-size: @font-size-h5; }
52 | h6, .h6 { font-size: @font-size-h6; }
53 |
54 |
55 | // Body text
56 | // -------------------------
57 |
58 | p {
59 | margin: 0 0 (@line-height-computed / 2);
60 | }
61 |
62 | .lead {
63 | margin-bottom: @line-height-computed;
64 | font-size: floor((@font-size-base * 1.15));
65 | font-weight: 200;
66 | line-height: 1.4;
67 |
68 | @media (min-width: @screen-sm-min) {
69 | font-size: (@font-size-base * 1.5);
70 | }
71 | }
72 |
73 |
74 | // Emphasis & misc
75 | // -------------------------
76 |
77 | // Ex: 14px base font * 85% = about 12px
78 | small,
79 | .small { font-size: 85%; }
80 |
81 | // Undo browser default styling
82 | cite { font-style: normal; }
83 |
84 | // Alignment
85 | .text-left { text-align: left; }
86 | .text-right { text-align: right; }
87 | .text-center { text-align: center; }
88 | .text-justify { text-align: justify; }
89 |
90 | // Contextual colors
91 | .text-muted {
92 | color: @text-muted;
93 | }
94 | .text-primary {
95 | .text-emphasis-variant(@brand-primary);
96 | }
97 | .text-success {
98 | .text-emphasis-variant(@state-success-text);
99 | }
100 | .text-info {
101 | .text-emphasis-variant(@state-info-text);
102 | }
103 | .text-warning {
104 | .text-emphasis-variant(@state-warning-text);
105 | }
106 | .text-danger {
107 | .text-emphasis-variant(@state-danger-text);
108 | }
109 |
110 | // Contextual backgrounds
111 | // For now we'll leave these alongside the text classes until v4 when we can
112 | // safely shift things around (per SemVer rules).
113 | .bg-primary {
114 | // Given the contrast here, this is the only class to have its color inverted
115 | // automatically.
116 | color: #fff;
117 | .bg-variant(@brand-primary);
118 | }
119 | .bg-success {
120 | .bg-variant(@state-success-bg);
121 | }
122 | .bg-info {
123 | .bg-variant(@state-info-bg);
124 | }
125 | .bg-warning {
126 | .bg-variant(@state-warning-bg);
127 | }
128 | .bg-danger {
129 | .bg-variant(@state-danger-bg);
130 | }
131 |
132 |
133 | // Page header
134 | // -------------------------
135 |
136 | .page-header {
137 | padding-bottom: ((@line-height-computed / 2) - 1);
138 | margin: (@line-height-computed * 2) 0 @line-height-computed;
139 | border-bottom: 1px solid @page-header-border-color;
140 | }
141 |
142 |
143 | // Lists
144 | // --------------------------------------------------
145 |
146 | // Unordered and Ordered lists
147 | ul,
148 | ol {
149 | margin-top: 0;
150 | margin-bottom: (@line-height-computed / 2);
151 | ul,
152 | ol {
153 | margin-bottom: 0;
154 | }
155 | }
156 |
157 | // List options
158 |
159 | // Unstyled keeps list items block level, just removes default browser padding and list-style
160 | .list-unstyled {
161 | padding-left: 0;
162 | list-style: none;
163 | }
164 |
165 | // Inline turns list items into inline-block
166 | .list-inline {
167 | .list-unstyled();
168 |
169 | > li {
170 | display: inline-block;
171 | padding-left: 5px;
172 | padding-right: 5px;
173 |
174 | &:first-child {
175 | padding-left: 0;
176 | }
177 | }
178 | }
179 |
180 | // Description Lists
181 | dl {
182 | margin-top: 0; // Remove browser default
183 | margin-bottom: @line-height-computed;
184 | }
185 | dt,
186 | dd {
187 | line-height: @line-height-base;
188 | }
189 | dt {
190 | font-weight: bold;
191 | }
192 | dd {
193 | margin-left: 0; // Undo browser default
194 | }
195 |
196 | // Horizontal description lists
197 | //
198 | // Defaults to being stacked without any of the below styles applied, until the
199 | // grid breakpoint is reached (default of ~768px).
200 |
201 | @media (min-width: @grid-float-breakpoint) {
202 | .dl-horizontal {
203 | dt {
204 | float: left;
205 | width: (@component-offset-horizontal - 20);
206 | clear: left;
207 | text-align: right;
208 | .text-overflow();
209 | }
210 | dd {
211 | margin-left: @component-offset-horizontal;
212 | &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
213 | }
214 | }
215 | }
216 |
217 | // MISC
218 | // ----
219 |
220 | // Abbreviations and acronyms
221 | abbr[title],
222 | // Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257
223 | abbr[data-original-title] {
224 | cursor: help;
225 | border-bottom: 1px dotted @abbr-border-color;
226 | }
227 | .initialism {
228 | font-size: 90%;
229 | text-transform: uppercase;
230 | }
231 |
232 | // Blockquotes
233 | blockquote {
234 | padding: (@line-height-computed / 2) @line-height-computed;
235 | margin: 0 0 @line-height-computed;
236 | font-size: (@font-size-base * 1.05);
237 | border-left: 5px solid @blockquote-border-color;
238 |
239 | p,
240 | ul,
241 | ol {
242 | &:last-child {
243 | margin-bottom: 0;
244 | }
245 | }
246 |
247 | // Note: Deprecated small and .small as of v3.1.0
248 | // Context: https://github.com/twbs/bootstrap/issues/11660
249 | footer,
250 | small,
251 | .small {
252 | display: block;
253 | font-size: 80%; // back to default font-size
254 | line-height: @line-height-base;
255 | color: @blockquote-small-color;
256 |
257 | &:before {
258 | content: '\2014 \00A0'; // em dash, nbsp
259 | }
260 | }
261 | }
262 |
263 | // Opposite alignment of blockquote
264 | //
265 | // Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.
266 | .blockquote-reverse,
267 | blockquote.pull-right {
268 | padding-right: 15px;
269 | padding-left: 0;
270 | border-right: 5px solid @blockquote-border-color;
271 | border-left: 0;
272 | text-align: right;
273 |
274 | // Account for citation
275 | footer,
276 | small,
277 | .small {
278 | &:before { content: ''; }
279 | &:after {
280 | content: '\00A0 \2014'; // nbsp, em dash
281 | }
282 | }
283 | }
284 |
285 | // Quotes
286 | blockquote:before,
287 | blockquote:after {
288 | content: "";
289 | }
290 |
291 | // Addresses
292 | address {
293 | margin-bottom: @line-height-computed;
294 | font-style: normal;
295 | line-height: @line-height-base;
296 | }
297 |
--------------------------------------------------------------------------------
/public/css/lib/bootstrap/utilities.less:
--------------------------------------------------------------------------------
1 | //
2 | // Utility classes
3 | // --------------------------------------------------
4 |
5 |
6 | // Floats
7 | // -------------------------
8 |
9 | .clearfix {
10 | .clearfix();
11 | }
12 | .center-block {
13 | .center-block();
14 | }
15 | .pull-right {
16 | float: right !important;
17 | }
18 | .pull-left {
19 | float: left !important;
20 | }
21 |
22 |
23 | // Toggling content
24 | // -------------------------
25 |
26 | // Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1
27 | .hide {
28 | display: none !important;
29 | }
30 | .show {
31 | display: block !important;
32 | }
33 | .invisible {
34 | visibility: hidden;
35 | }
36 | .text-hide {
37 | .text-hide();
38 | }
39 |
40 |
41 | // Hide from screenreaders and browsers
42 | //
43 | // Credit: HTML5 Boilerplate
44 |
45 | .hidden {
46 | display: none !important;
47 | visibility: hidden !important;
48 | }
49 |
50 |
51 | // For Affix plugin
52 | // -------------------------
53 |
54 | .affix {
55 | position: fixed;
56 | }
57 |
--------------------------------------------------------------------------------
/public/css/lib/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 @well-border;
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 |
--------------------------------------------------------------------------------
/public/css/lib/jquery.atwho.css:
--------------------------------------------------------------------------------
1 | .atwho-view {
2 | position:absolute;
3 | top: 0;
4 | left: 0;
5 | display: none;
6 | margin-top: 18px;
7 | background: white;
8 | color: black;
9 | border: 1px solid #DDD;
10 | border-radius: 3px;
11 | box-shadow: 0 0 5px rgba(0,0,0,0.1);
12 | min-width: 120px;
13 | z-index: 11110 !important;
14 | }
15 |
16 | .atwho-view .atwho-header {
17 | padding: 5px;
18 | margin: 5px;
19 | cursor: pointer;
20 | border-bottom: solid 1px #eaeff1;
21 | color: #6f8092;
22 | font-size: 11px;
23 | font-weight: bold;
24 | }
25 |
26 | .atwho-view .atwho-header .small {
27 | color: #6f8092;
28 | float: right;
29 | padding-top: 2px;
30 | margin-right: -5px;
31 | font-size: 12px;
32 | font-weight: normal;
33 | }
34 |
35 | .atwho-view .atwho-header:hover {
36 | cursor: default;
37 | }
38 |
39 | .atwho-view .cur {
40 | background: #3366FF;
41 | color: white;
42 | }
43 | .atwho-view .cur small {
44 | color: white;
45 | }
46 | .atwho-view strong {
47 | color: #3366FF;
48 | }
49 | .atwho-view .cur strong {
50 | color: white;
51 | font:bold;
52 | }
53 | .atwho-view ul {
54 | /* width: 100px; */
55 | list-style:none;
56 | padding:0;
57 | margin:auto;
58 | max-height: 200px;
59 | overflow-y: auto;
60 | }
61 | .atwho-view ul li {
62 | display: block;
63 | padding: 5px 10px;
64 | border-bottom: 1px solid #DDD;
65 | cursor: pointer;
66 | /* border-top: 1px solid #C8C8C8; */
67 | }
68 | .atwho-view small {
69 | font-size: smaller;
70 | color: #777;
71 | font-weight: normal;
72 | }
73 |
--------------------------------------------------------------------------------
/public/css/themes/default.less:
--------------------------------------------------------------------------------
1 | // Brand Colors
2 | // -------------------------
3 |
4 | @brand-primary: #4d90fc;
5 | @brand-success: #60bf60;
6 | @brand-warning: #ff9800;
7 | @brand-danger: #de4b33;
8 | @brand-info: #5bc0dd;
9 |
10 | // Typography
11 | // -------------------------
12 |
13 | // Buttons
14 | // -------------------------
15 |
16 | @btn-primary-border: darken(@btn-primary-bg, 3.2%);
17 | @btn-success-border: darken(@btn-success-bg, 3.2%);
18 | @btn-warning-border: darken(@btn-warning-bg, 3.2%);
19 | @btn-danger-border: darken(@btn-danger-bg, 3.2%);
20 | @btn-info-border: darken(@btn-info-bg, 3.2%);
21 |
22 | .btn {
23 | border-radius: 0;
24 | box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.11), 1px 1px 0 rgba(255, 255, 255, 0.21) inset;
25 |
26 | &:focus {
27 | outline: none;
28 | }
29 | }
30 |
31 | .btn-default, .btn-default:focus {
32 | background-image: linear-gradient(to bottom, #ffffff 60%, #f8f8f8 100%);
33 | }
34 |
35 | // Forms
36 | // -------------------------
37 |
38 | @input-border-radius: 0;
39 | @input-border-focus: #2598f9;
40 |
41 | .form-control:focus {
42 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset;
43 | }
44 |
45 | // Form states and alerts
46 | // -------------------------
47 |
48 | @state-success-text: #569845;
49 | @state-success-bg: #dbf5d3;
50 | @state-success-border: #aed3a5;
51 |
52 | @state-info-text: #3a87ad;
53 | @state-info-bg: #d9edf7;
54 | @state-info-border: #98cce7;
55 |
56 | @state-warning-text: #bf9853;
57 | @state-warning-bg: #fdf8e2;
58 | @state-warning-border: #f2daab;
59 |
60 | @state-danger-text: #b94a48;
61 | @state-danger-bg: #f2dede;
62 | @state-danger-border: #e0b1b8;
63 |
64 | // Alerts
65 | // -------------------------
66 |
67 | .alert {
68 | border-radius: 0;
69 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.10);
70 | }
71 |
72 | // Navbar
73 | // -------------------------
74 |
75 | .navbar-default .navbar-nav > li a {
76 | transition: color 0.3s;
77 | }
78 |
79 | // Footer
80 | // -------------------------
81 |
82 | #footer {
83 | background-color: @navbar-default-bg;
84 | border-top: 1px solid @navbar-default-border;
85 | }
--------------------------------------------------------------------------------
/public/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/public/humans.txt:
--------------------------------------------------------------------------------
1 | /* TEAM */
2 | Founder: Larva Labs (http://larvalabs.com/)
3 | Contributors: https://github.com/larvalabs/pullup/blob/master/config/userlist.js
4 |
5 | /* SITE */
6 | Language: English
7 | Software: NodeJS, MongoDB, Bootstrap 3, Flatly
8 |
--------------------------------------------------------------------------------
/public/img/appicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/img/appicon.png
--------------------------------------------------------------------------------
/public/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/img/favicon.ico
--------------------------------------------------------------------------------
/public/img/hacker_news.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/img/hacker_news.png
--------------------------------------------------------------------------------
/public/img/paypal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/img/paypal.png
--------------------------------------------------------------------------------
/public/img/sendgrid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larvalabs/pullup/e4e3778d3debf7b7682e2a72b7d6d9f80c2c10cd/public/img/sendgrid.png
--------------------------------------------------------------------------------
/public/js/globals.js:
--------------------------------------------------------------------------------
1 |
2 | // Extracts data-* attribute values from elements in data.jade
3 |
4 | var pullup = {};
5 |
6 | $('#pullup-global-js-object > .property').each (function () {
7 | var key = $(this).attr ('id');
8 | var val = $(this).attr ('data-val');
9 | pullup[key] = val;
10 | });
11 |
--------------------------------------------------------------------------------
/public/js/keen.js:
--------------------------------------------------------------------------------
1 | function reportPageView(client) {
2 | if (window.location.hostname !== 'localhost') {
3 | var pageViewData = {
4 | ip_address: "${keen.ip}",
5 | user_agent: "${keen.user_agent}",
6 | referrer: document.referrer,
7 | page: window.location.href
8 | };
9 |
10 | client.addEvent("pageviews", pageViewData, function (err, res) {
11 | if (err) {
12 | console.log("Keen.io error", err);
13 | }
14 | });
15 | }
16 | }
17 |
18 | function renderPageViews(client) {
19 | Keen.ready(function(){
20 | var referrerQuery = new Keen.Query("count", {
21 | eventCollection: "pageviews",
22 | filters: [
23 | {
24 | "operator": "not_contains",
25 | "property_name": "page",
26 | "property_value": "localhost"
27 | }
28 | ],
29 | groupBy: [
30 | "referrer"
31 | ],
32 | timeframe: "this_14_days",
33 | timezone: "UTC"
34 | });
35 |
36 | client.draw(referrerQuery, document.getElementById("keen-chart-referrer"), {
37 | title: "Referrer"
38 | });
39 | });
40 | }
41 |
42 | function renderReferrals(client) {
43 | Keen.ready(function() {
44 | var query = new Keen.Query("count", {
45 | eventCollection: "pageviews",
46 | filters: [
47 | {
48 | "operator": "not_contains",
49 | "property_name": "page",
50 | "property_value": "localhost"
51 | }
52 | ],
53 | groupBy: [
54 | "page"
55 | ],
56 | timeframe: "this_14_days",
57 | timezone: "UTC"
58 | });
59 |
60 | client.draw(query, document.getElementById("keen-chart-page"), {
61 | title: "Pages"
62 | });
63 | });
64 | }
65 |
66 | $(function() {
67 | var client = new Keen({
68 | projectId: pullup.keenProjectID,
69 | writeKey: pullup.keenWriteKey,
70 | readKey: pullup.keenReadKey
71 | });
72 |
73 | reportPageView(client);
74 | renderPageViews(client);
75 | renderReferrals(client);
76 | });
77 |
--------------------------------------------------------------------------------
/public/js/lib/ansi_up.js:
--------------------------------------------------------------------------------
1 | // ansi_up.js
2 | // version : 1.1.0
3 | // author : Dru Nelson
4 | // license : MIT
5 | // http://github.com/drudru/ansi_up
6 |
7 | (function (Date, undefined) {
8 |
9 | var ansi_up,
10 | VERSION = "1.1.0",
11 |
12 | // check for nodeJS
13 | hasModule = (typeof module !== 'undefined'),
14 |
15 | // Normal and then Bright
16 | ANSI_COLORS = [
17 | [
18 | { color: "0, 0, 0", class: "ansi-black" },
19 | { color: "187, 0, 0", class: "ansi-red" },
20 | { color: "0, 187, 0", class: "ansi-green" },
21 | { color: "187, 187, 0", class: "ansi-yellow" },
22 | { color: "0, 0, 187", class: "ansi-blue" },
23 | { color: "187, 0, 187", class: "ansi-magenta" },
24 | { color: "0, 187, 187", class: "ansi-cyan" },
25 | { color: "255,255,255", class: "ansi-white" }
26 | ],
27 | [
28 | { color: "85, 85, 85", class: "ansi-bright-black" },
29 | { color: "255, 85, 85", class: "ansi-bright-red" },
30 | { color: "0, 255, 0", class: "ansi-bright-green" },
31 | { color: "255, 255, 85", class: "ansi-bright-yellow" },
32 | { color: "85, 85, 255", class: "ansi-bright-blue" },
33 | { color: "255, 85, 255", class: "ansi-bright-magenta" },
34 | { color: "85, 255, 255", class: "ansi-bright-cyan" },
35 | { color: "255, 255, 255", class: "ansi-bright-white" }
36 | ]
37 | ];
38 |
39 | function Ansi_Up() {
40 | this.fg = this.bg = null;
41 | this.bright = 0;
42 | }
43 |
44 | Ansi_Up.prototype.escape_for_html = function (txt) {
45 | return txt.replace(/[&<>]/gm, function(str) {
46 | if (str == "&") return "&";
47 | if (str == "<") return "<";
48 | if (str == ">") return ">";
49 | });
50 | };
51 |
52 | Ansi_Up.prototype.linkify = function (txt) {
53 | return txt.replace(/(https?:\/\/[^\s]+)/gm, function(str) {
54 | return "
" + str + "";
55 | });
56 | };
57 |
58 | Ansi_Up.prototype.ansi_to_html = function (txt, options) {
59 |
60 | var data4 = txt.split(/\033\[/);
61 |
62 | var first = data4.shift(); // the first chunk is not the result of the split
63 |
64 | var self = this;
65 | var data5 = data4.map(function (chunk) {
66 | return self.process_chunk(chunk, options);
67 | });
68 |
69 | data5.unshift(first);
70 |
71 | var flattened_data = data5.reduce( function (a, b) {
72 | if (Array.isArray(b))
73 | return a.concat(b);
74 |
75 | a.push(b);
76 | return a;
77 | }, []);
78 |
79 | var escaped_data = flattened_data.join('');
80 |
81 | return escaped_data;
82 | };
83 |
84 | Ansi_Up.prototype.process_chunk = function (text, options) {
85 |
86 | // Are we using classes or styles?
87 | options = typeof options == 'undefined' ? {} : options;
88 | var use_classes = typeof options.use_classes != 'undefined' && options.use_classes;
89 | var key = use_classes ? 'class' : 'color';
90 |
91 | // Do proper handling of sequences (aka - injest vi split(';') into state machine
92 | //match,codes,txt = text.match(/([\d;]+)m(.*)/m);
93 | var matches = text.match(/([\d;]*)m([^]*)/m);
94 |
95 | if (!matches) return text;
96 |
97 | var orig_txt = matches[2];
98 | var nums = matches[1].split(';');
99 |
100 | var self = this;
101 | nums.map(function (num_str) {
102 |
103 | var num = parseInt(num_str);
104 |
105 | if (isNaN(num) || num === 0) {
106 | self.fg = self.bg = null;
107 | self.bright = 0;
108 | } else if (num === 1) {
109 | self.bright = 1;
110 | } else if ((num >= 30) && (num < 38)) {
111 | self.fg = ANSI_COLORS[self.bright][(num % 10)][key];
112 | } else if ((num >= 40) && (num < 48)) {
113 | self.bg = ANSI_COLORS[0][(num % 10)][key];
114 | }
115 | });
116 |
117 | if ((self.fg === null) && (self.bg === null)) {
118 | return orig_txt;
119 | } else {
120 | var styles = classes = [];
121 | if (self.fg) {
122 | if (use_classes) {
123 | classes.push(self.fg + "-fg");
124 | } else {
125 | styles.push("color:rgb(" + self.fg + ")");
126 | }
127 | }
128 | if (self.bg) {
129 | if (use_classes) {
130 | classes.push(self.bg + "-bg");
131 | } else {
132 | styles.push("background-color:rgb(" + self.bg + ")");
133 | }
134 | }
135 | if (use_classes) {
136 | return ["
", orig_txt, ""];
137 | } else {
138 | return ["
", orig_txt, ""];
139 | }
140 | }
141 | };
142 |
143 | // Module exports
144 | ansi_up = {
145 |
146 | escape_for_html: function (txt) {
147 | var a2h = new Ansi_Up();
148 | return a2h.escape_for_html(txt);
149 | },
150 |
151 | linkify: function (txt) {
152 | var a2h = new Ansi_Up();
153 | return a2h.linkify(txt);
154 | },
155 |
156 | ansi_to_html: function (txt, options) {
157 | var a2h = new Ansi_Up();
158 | return a2h.ansi_to_html(txt, options);
159 | },
160 |
161 | ansi_to_html_obj: function () {
162 | return new Ansi_Up();
163 | }
164 | };
165 |
166 | // CommonJS module is defined
167 | if (hasModule) {
168 | module.exports = ansi_up;
169 | }
170 | /*global ender:false */
171 | if (typeof window !== 'undefined' && typeof ender === 'undefined') {
172 | window.ansi_up = ansi_up;
173 | }
174 | /*global define:false */
175 | if (typeof define === "function" && define.amd) {
176 | define("ansi_up", [], function () {
177 | return ansi_up;
178 | });
179 | }
180 | })(Date);
--------------------------------------------------------------------------------
/public/js/logs.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $list = $("#log-list");
3 |
4 | var socket = io.connect("http://sysinct.herokuapp.com:80");
5 | socket.on('log', function(message) {
6 | writeLog(message);
7 | });
8 | socket.on('catchup', function(messages) {
9 | for(var i=0,max=messages.length; i
(\d) ([\d\-T\:\.\+]+) (\w+) (\w+) ([\w\.\d]+) - (.*)$/.exec(log);
28 | var date = new Date(Date.parse(data[4]));
29 | $list.append('' + date.toLocaleString() + '
' + data[7] + '
' + ansi_up.ansi_to_html(data[8]) + '
');
30 | if($list.children().length > 1000) {
31 | $list.children().first.remove();
32 | }
33 |
34 | if(doScroll) {
35 | forcedScroll = true;
36 | $list.scrollTop($list.children().last().position().top + $list.scrollTop());
37 | }
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/public/js/main.js:
--------------------------------------------------------------------------------
1 | var FlashBuilder = function (type, messages) {
2 | function createFlashContainer () {
3 | var container = $('')
4 | .addClass('alert animated fadeIn alert-dismissable')
5 | .html(createDismissButton());
6 |
7 | switch(type) {
8 | case 'success':
9 | container.addClass('alert-success');
10 | break;
11 | case 'info':
12 | container.addClass('alert-info');
13 | break;
14 | case 'error':
15 | container.addClass('alert-danger');
16 | break;
17 | }
18 |
19 | return container;
20 | }
21 |
22 | function createDismissButton () {
23 | return $('')
24 | .attr('type', 'button')
25 | .attr('data-dismiss', 'alert')
26 | .attr('aria-hidden', true)
27 | .addClass('close')
28 | .text('x');
29 | }
30 |
31 | function createMessage (message) {
32 | return $('')
33 | .text(message);
34 | }
35 |
36 | function clearFlash () {
37 | $('#flash').empty();
38 | }
39 |
40 | function buildMessage () {
41 | if (messages && messages.length > 0) {
42 | clearFlash();
43 |
44 | var container = createFlashContainer(type);
45 |
46 | messages.forEach(function (message) {
47 | container.append(createMessage(message.msg));
48 | });
49 |
50 | $('#flash').append(container);
51 | }
52 | }
53 |
54 | return {
55 | build: buildMessage
56 | };
57 | };
58 |
59 | $(document).ready(function() {
60 |
61 | if ($("button.alert-joinsite").length > 0) {
62 | $("button.alert-joinsite").click(function () {
63 | document.cookie = "skip_alert=true; path=/";
64 | });
65 | }
66 |
67 | if ($("#url").length > 0) {
68 | $("#title").on('focus', function() {
69 | var url = $("#url").val();
70 | $.get("/news/summarize?url=" + url, function(response) {
71 | if (response) {
72 | if(typeof response.title !== "undefined"){
73 | $("#title").val(response.title);
74 | }
75 |
76 | if(typeof response.source !== "undefined"){
77 | $("#source").val(response.source);
78 | }
79 | else $("#source").val(url);
80 |
81 | if(typeof response.summary !== "undefined"){
82 | $("#summary").val(response.summary.join(" "));
83 | }
84 |
85 | }
86 | });
87 | });
88 | $('input[autofocus]').trigger('focus');//force fire it on the autofocus element
89 | }
90 |
91 | if($(".show-summary").length > 0) {
92 | $(".show-summary").on('click', function(e) {
93 |
94 | if ($("p.item-summary").length ) {
95 | $("p.item-summary").toggleClass('hidden');
96 | }
97 |
98 | if ($(this).siblings("p.summary").length ) {
99 | $(this).siblings("p").toggleClass('hidden');
100 | }
101 |
102 | e.preventDefault();
103 | });
104 | }
105 |
106 | if ($("label.post-type-btn").length) {
107 | $("label.post-type-btn").click( function(e) {
108 | if($(this).hasClass('post-type-url')) {
109 | $('.form-group-url').slideDown();
110 | $('.form-group-source').slideDown();
111 | } else {
112 | $('.form-group-url').slideUp();
113 | $('.form-group-source').slideUp();
114 | }
115 | });
116 | }
117 |
118 | $("#copyright").text(new Date().getFullYear());
119 |
120 | (function hideNavMenuOnBodyClick() {
121 | $('body').on('click', function(e) {
122 | if (! $('.navbar').has(e.target).length) {
123 | $('.navbar-collapse.in').collapse('hide');
124 | }
125 | });
126 | }());
127 |
128 | $('form').submit(function(ev) {
129 | $(this).append(
130 | ''
131 | );
132 | });
133 |
134 | if ($('form.upvote-form').length > 0) {
135 | $('.upvote-form').on('submit', function (e) {
136 | var form = $(this);
137 |
138 | $.post(form.attr('action'), form.serialize())
139 | .done(function (data) {
140 | if (data.messages) {
141 | var builder = new FlashBuilder(data.success ? 'success' : 'error', data.messages);
142 |
143 | builder.build();
144 | }
145 |
146 | if (data.success) {
147 | $('button.upvote', form).remove();
148 |
149 | var voteElement = $('.vote-count', form.closest('.news-item'));
150 | voteElement.text(parseInt(voteElement.text()) + 1);
151 | }
152 | })
153 | .fail(function (data) {
154 | var builder = new FlashBuilder('error', [{ msg: 'Something went wrong!'}]);
155 | });
156 |
157 | e.preventDefault();
158 | });
159 | }
160 |
161 | if($(".form-group.markdown").length) {
162 | $(document).on('show.bs.tab', '.form-group.markdown', function(e) {
163 | if(e.target.getAttribute("href") === "#preview-tab-content") {
164 | $content = $(this).find("#preview-tab-content");
165 | $content.html("Loading...");
166 |
167 | $.ajax({
168 | type: "POST",
169 | url: "/api/markdown",
170 | data: {
171 | source: $(this).find("textarea").val()
172 | },
173 | success: function(msg) {
174 | $content.html(msg.result);
175 | },
176 | error: function(msg) {
177 | $content.html("There was an error retrieving your markdown...");
178 | }
179 | });
180 | }
181 | });
182 | }
183 |
184 | $.ajax ({
185 | url: pullup.baseUrl + '/api/userlist',
186 | method: 'GET',
187 | success: function (data) {
188 | $('.user-atwho').atwho({
189 | at: "@",
190 | data: data.users
191 | });
192 | }
193 | });
194 |
195 |
196 | /*
197 | * If the user has installed pullup.io to their home screen, we want
198 | * links to open up within the web app, instead of pushing out to safari
199 | */
200 | if(window.navigator.standalone) {
201 | $(document).on('click', 'a', function(e) {
202 | if($(this).attr('href').indexOf("http") !== 0) {
203 | e.preventDefault();
204 | window.location = $(this).attr('href');
205 | }
206 | });
207 | }
208 |
209 | var scrollDebouncer,
210 | $flash = $('#flash'), $pageHeader = $('.page-header:first'),
211 | initPageHeaderTopMargin = parseFloat($pageHeader.css('margin-top')),
212 | positionFlash = function() {
213 | var flashPosition = window.scrollY <= 0 ? 'static' : 'fixed',
214 | pageHeaderTopMargin = initPageHeaderTopMargin + (window.scrollY <= 0 ? 0 : $flash.height());
215 |
216 | $flash.css('position', flashPosition);
217 | $pageHeader.css('margin-top', pageHeaderTopMargin + 'px');
218 | scrollDebouncer = null;
219 | },
220 | onWindowScroll = function(ev) {
221 | if (scrollDebouncer) { clearTimeout(scrollDebouncer); } else { positionFlash(); }
222 | if (window.scrollY <= 0) { positionFlash(); } else { scrollDebouncer = setTimeout(positionFlash, 50); }
223 | };
224 |
225 | $(window).scroll(onWindowScroll);
226 | $flash.find('.alert .close').click(function(ev) { setTimeout(positionFlash, 1); });
227 | window.scrollTo(0, $('input[name="windowscrollto"]').val() || 0);
228 | positionFlash();
229 |
230 | });
231 |
--------------------------------------------------------------------------------
/tasks/db_clear_down.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose'),
2 | secrets = require('../config/secrets'),
3 | async = require('async'),
4 | NewsItem = require('../models/NewsItem'),
5 | Vote = require('../models/Vote'),
6 | Comment = require('../models/Comment'),
7 | User = require('../models/User'),
8 | Issue = require('../models/Issue');
9 |
10 | function clearDown (cb) {
11 | mongoose.connect(secrets.db, function (err) {
12 | if (err) return cb(err);
13 |
14 | var models = [NewsItem, Vote, Comment, User, Issue];
15 |
16 | async.eachSeries(models, function (model, done) {
17 | model.collection.remove(done);
18 | }, cb);
19 | });
20 | };
21 |
22 | exports.clearDown = clearDown;
23 |
24 | exports.task = function () {
25 | var done = this.async();
26 |
27 | clearDown(function (err) {
28 | if (err) {
29 | console.log(err);
30 | return done(false);
31 | }
32 |
33 | done();
34 | });
35 | };
36 |
--------------------------------------------------------------------------------
/tasks/db_seed.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose'),
2 | secrets = require('../config/secrets'),
3 | async = require('async'),
4 | dbClearDown = require('./db_clear_down'),
5 | User = require('../models/User'),
6 | NewsItem = require('../models/NewsItem'),
7 | Vote = require('../models/Vote');
8 |
9 | exports.task = function () {
10 | var done = this.async();
11 |
12 | async.series([
13 | function (cb) {
14 | dbClearDown.clearDown(function (err) {
15 | cb(err);
16 | });
17 | },
18 | function (cb) {
19 | mongoose.connect(secrets.db, function (err) {
20 | var user = new User({
21 | username: 'DummyUser'
22 | });
23 |
24 | async.series([
25 | function (next) {
26 | user.save(next);
27 | },
28 | function (next) {
29 | var newsItem = new NewsItem({
30 | title: 'Dummy Discussion',
31 | summary: 'Some dummy text created by db-seed',
32 | source: 'pullup.io',
33 | poster: user._id
34 | });
35 |
36 | newsItem.url = '/news/' + newsItem._id;
37 | newsItem.save(function (err) {
38 | var vote = new Vote({
39 | item: newsItem,
40 | voter: user._id,
41 | amount: 1,
42 | itemType: 'news'
43 | });
44 |
45 | vote.save(next);
46 | });
47 | }
48 | ], cb);
49 | });
50 | }
51 | ], function (err) {
52 | if (err) {
53 | console.log(err);
54 | return done(false);
55 | }
56 |
57 | done();
58 | });
59 | };
60 |
61 |
--------------------------------------------------------------------------------
/test/NewsItem.test.js:
--------------------------------------------------------------------------------
1 | describe('NewsItem', function(){
2 |
3 | var should = require('should'),
4 | NewsItem = require('../models/NewsItem'),
5 | item = {};
6 |
7 | beforeEach(function(done) {
8 | item = new NewsItem();
9 | done();
10 | });
11 |
12 | describe('sanity test', function() {
13 | it('Item should exist and have an ID', function(){
14 | /*jshint expr: true*/
15 | item.should.be.ok;
16 | item._id.should.be.ok;
17 | });
18 | });
19 |
20 | describe('#isSelfPost()', function(){
21 | it('should be true when the URL points to this post', function(){
22 | item.url = '/news/' + item._id;
23 | item.isSelfPost().should.equal(true);
24 | });
25 |
26 | it('should NOT be true if the URL does not point to this post', function(){
27 | item.url = 'http://pullup.io/about';
28 | item.isSelfPost().should.not.equal(true);
29 | });
30 |
31 | it('should NOT be true just because the source is "pullup.io"', function(){
32 | item.source = 'pullup.io';
33 | item.isSelfPost().should.not.equal(true);
34 | });
35 | });
36 |
37 | describe('#formatUrl()', function() {
38 | it('should append "http://" to a given URL if a protocol is not specified', function() {
39 | item.url = NewsItem.formatUrl('ianvonwalter.com');
40 | item.url.lastIndexOf('http://').should.equal(0);
41 | });
42 |
43 | it('should NOT modify a given URL if a protocol is specified', function() {
44 | item.url = NewsItem.formatUrl('https://gmail.com');
45 | item.url.lastIndexOf('https://').should.equal(0);
46 | });
47 | });
48 |
49 | });
--------------------------------------------------------------------------------
/test/config.userlist.test.js:
--------------------------------------------------------------------------------
1 | describe('config.userlist', function(){
2 |
3 | var should = require('should'),
4 | _ = require('underscore'),
5 | users = require('../config/userlist').users;
6 |
7 | it('should be alphabetical', function(){
8 | var usersSorted = users.slice().sort(function(a,b){
9 | return a.toLowerCase().localeCompare(b.toLowerCase());
10 | });
11 | users.should.eql(usersSorted);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/utils/index.js:
--------------------------------------------------------------------------------
1 | // Return a URL with just a single query parameter stripped out of it
2 | exports.urlWithoutQueryParam = function urlWithoutQueryParam(originalUrl, paramName) {
3 | var queryStart = originalUrl.indexOf('?'),
4 | queryString = originalUrl.slice(queryStart + 1),
5 | urlWithoutQueryString = originalUrl.slice(0, queryStart),
6 | params = queryString.split('&');
7 |
8 | params = params.filter(function (param) {
9 | return param.indexOf(paramName) !== 0;
10 | });
11 |
12 | if(!params.length) {
13 | return urlWithoutQueryString;
14 | }
15 |
16 | return urlWithoutQueryString + '?' + params.join('&');
17 | };
18 |
19 | exports.usernameRegexp = "@\\w+(?!(\\]|\\w|\\/))";
20 |
21 | exports.replaceUserMentions = function(body) {
22 | var match,
23 | usernameRegexp = new RegExp(this.usernameRegexp);
24 |
25 | while(match = usernameRegexp.exec(body)) {
26 | body = body.replace(match[0], '[' + match[0] + ']' + '(/user/' + match[0].slice(1) + '/)');
27 | }
28 |
29 | return body;
30 | };
31 |
32 | exports.findUserMentions = function (body) {
33 | var match,
34 | matches = [],
35 | usernameRegexp = new RegExp(this.usernameRegexp, 'g');
36 |
37 | while(match = usernameRegexp.exec(body)) {
38 | matches.push(match[0].slice(1));
39 | }
40 |
41 | return matches;
42 | };
43 |
44 | exports.uniqueMongo = function (arr) {
45 | var foundIds = [],
46 | found = [];
47 |
48 | arr.forEach(function (obj) {
49 | if(foundIds.indexOf(obj.id.toString()) < 0){
50 | found.push(obj);
51 | foundIds.push(obj.id.toString());
52 | }
53 | });
54 |
55 | return found;
56 | };
57 |
--------------------------------------------------------------------------------
/vagrant/ansible-windows.sh:
--------------------------------------------------------------------------------
1 | echo "Installing/Updating Ansible..."
2 | apt-get update -qq && apt-get install ansible -y -qq
3 | COUNT_HOSTS=`grep \\\[precise64\\\] /etc/ansible/hosts -c`
4 | if [ $COUNT_HOSTS -lt 1 ]; then
5 | printf "\n[precise64] \n127.0.0.1 ansible_connection=local\n" >> /etc/ansible/hosts
6 | fi
7 | echo "Running Ansible playbook..."
8 | ansible-playbook /vagrant/vagrant/playbook.yml
--------------------------------------------------------------------------------
/vagrant/playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - hosts: all
3 |
4 | tasks:
5 | - include: ./tasks/essentials.yml
6 | - include: ./tasks/nodejs.yml
7 | - include: ./tasks/mongo.yml
8 | - include: ./tasks/setup_app.yml
--------------------------------------------------------------------------------
/vagrant/tasks/essentials.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: ensure apt-get is up to date
3 | apt: update_cache=yes cache_valid_time=3600
4 | sudo: yes
5 |
6 | - name: ensure build-essential installed
7 | apt: pkg=build-essential state=present
8 | sudo: yes
9 |
10 | - name: ensure git is installed
11 | apt: pkg=git state=present
12 | sudo: yes
13 |
14 | - name: ensure curl is installed
15 | apt: pkg=curl state=present
16 | sudo: yes
--------------------------------------------------------------------------------
/vagrant/tasks/mongo.yml:
--------------------------------------------------------------------------------
1 | - name: add apt-get key for mongo
2 | shell: apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
3 | sudo: yes
4 |
5 | - name: add apt-get repository for latest mongo
6 | shell: echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/mongodb.list
7 | sudo: yes
8 |
9 | - name: ensure apt-get is up to date with added dependencies
10 | apt: update_cache=yes
11 | sudo: yes
12 |
13 | - name: install mongo
14 | apt: pkg=mongodb-10gen state=present
15 | sudo: yes
--------------------------------------------------------------------------------
/vagrant/tasks/nodejs.yml:
--------------------------------------------------------------------------------
1 | - name: install node.js build dependencies
2 | apt: pkg={{item}} state=present
3 | sudo: yes
4 | with_items:
5 | - python-software-properties
6 | - python
7 |
8 | - name: add apt-get repository for latest node
9 | shell: add-apt-repository -y ppa:chris-lea/node.js
10 | sudo: yes
11 |
12 | - name: ensure apt-get is up to date with added node dependencies
13 | apt: update_cache=yes
14 | sudo: yes
15 |
16 | - name: install node.js
17 | apt: pkg=nodejs state=present
18 | sudo: yes
--------------------------------------------------------------------------------
/vagrant/tasks/setup_app.yml:
--------------------------------------------------------------------------------
1 | - name: Add Environment Variables
2 | lineinfile:
3 | dest=/etc/environment
4 | line={{item}}
5 | state=present
6 | insertafter=EOF
7 | create=True
8 | with_items:
9 | - "GITHUB_CLIENTID='CLIENTID'"
10 | - "GITHUB_SECRET='SECRET'"
11 | sudo: yes
12 |
--------------------------------------------------------------------------------
/views/404.jade:
--------------------------------------------------------------------------------
1 | extends ./layout
2 |
3 | block content
4 | if !user
5 | .alert.alert-info
6 | i.fa.fa-cloud-upload
7 | strong Join this site via pull request!
8 | | Make the site better and become a full member. See the about page for more details.
9 | h1 You Found Me!
10 | p I'm pretty sure that this is the page you're looking for if you're trying to get lost in the jungle of links.
11 | a(href='/') Back to Top
12 |
--------------------------------------------------------------------------------
/views/about.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | div.jumbotron
5 | h1 What is pullup?
6 | p.lead It’s a site you join via pull request. Other than that, it’s up to you.
7 | a(class='button btn btn-info btn-large', href='https://github.com/larvalabs/pullup')
8 | span.fa.fa-download
9 | | Pull from GitHub
10 | |
11 | a(class='button btn btn-info btn-large', href='https://twitter.com/intent/tweet?text=Make%20the%20site%20better%20and%20become%20a%20full%20member.%20Join%20Pullup.io%20via%20pull%20request!&url=http://pullup.io')
12 | | Tweet this Page
13 |
14 | h3 FAQ
15 | dl.faq
16 | dt What does the site do?
17 | dd Not much yet! So far it’s just a Hacker News clone, but it would be great if it ended up growing into something else.
18 | dt How exactly do I join?
19 | dd Add a feature, fix a bug, improve the design, whatever you like. Find the details on the signup page.
20 | dt What feature should I add?
21 | dd Almost anything! If you don't know where to start, check out the open issues labeled beginner.
22 | dt What technology is the site using?
23 | dd
24 | a(href="http://nodejs.org/") NodeJS
25 | | ,#{' '}
26 | a(href="http://expressjs.com/") Express
27 | | , and#{' '}
28 | a(href="http://www.mongodb.org/") Mongo
29 | | . We figured everybody who's worked on a website knows JavaScript, so it’d be a good universal language for the site.
30 | dt Why did you make this?
31 | dd We wondered what a site would be like where every user had contributed some code. It should mean no spam, no content marketing, and hopefully a stronger feeling of community.
32 |
--------------------------------------------------------------------------------
/views/account/comments.jade:
--------------------------------------------------------------------------------
1 | extends ../partials/profile
2 |
3 | block tab-content
4 | div#user-comments.tab-pane.fade.in.active
5 | ul
6 | li.row.headers.hidden-xs
7 | div.col-sm-1.text-muted #
8 | div.col-sm-7 Comment
9 | div.col-sm-4 Parent
10 | each comment, index in comments
11 | li.row.news-item
12 | div.col-sm-1.hidden-xs
13 | span.hidden-xs.text-muted= index + 1
14 | div.col-sm-7
15 | .visible-xs
16 | small.timeago(title="#{comment.created}")= timeago(comment.created)
17 | small on
18 | a(href=comment.newsItem.url)= comment.newsItem.title
19 | a(href='/news/' + comment.newsItem._id + (comment.newsItem.latestCommentAt ? "?last_comment=#{comment.newsItem.latestCommentAt}" : ""), class='comments')
20 | small #{comment.newsItem.comment_count}
21 | i.fa.fa-comment-o
22 | blockquote.content!= comment.contents
23 | +delete(comment.poster, '/news/' + comment.newsItem._id + '/comments/' + comment._id + '/delete')
24 | small.timeago.hidden-xs(title="#{comment.created}")= timeago(comment.created)
25 | div.col-sm-4.hidden-xs
26 | a(href=comment.newsItem.url)
27 | | #{comment.newsItem.title}
28 | br
29 | span
30 | small.submit-date(title="#{comment.newsItem.created}")= timeago(comment.newsItem.created)
31 | br
32 | a(href='/news/' + comment.newsItem._id + (comment.newsItem.latestCommentAt ? "?last_comment=#{comment.newsItem.latestCommentAt}" : ""), class='comments')
33 | case comment.newsItem.comment_count
34 | when 0
35 | small no comments
36 | when 1
37 | small 1 comment
38 | default
39 | small #{comment.newsItem.comment_count} comments
40 | if comment.newsItem.comment_count > 0
41 | small.hidden-xs.text-muted(title="#{comment.newsItem.latestCommentAt}") (last comment #{timeago(comment.newsItem.latestCommentAt)})
42 |
--------------------------------------------------------------------------------
/views/account/contributions.jade:
--------------------------------------------------------------------------------
1 | extends ../partials/profile
2 |
3 | block tab-content
4 | div#user-contributions.tab-pane.fade.in.active
5 | ul
6 | li.row.headers.hidden-xs
7 | div.col-sm-1 Type
8 | div.col-sm-8 Title
9 | div.col-sm-3
10 | each contribution, index in contributions
11 | li.row.news-item
12 | div.col-sm-1.type
13 | if contribution.pull_request
14 | span.label.label-info="pull"
15 | else
16 | span.label.label-default="issue"
17 | div.col-sm-8
18 | a(href="#{contribution.html_url}", target="_blank")="#" + contribution.number + ": " + contribution.title
19 | div.col-sm-3
20 | span=timeago(contribution.created_at)
21 |
--------------------------------------------------------------------------------
/views/account/news.jade:
--------------------------------------------------------------------------------
1 | extends ../partials/profile
2 |
3 | block tab-content
4 | div#news.tab-pane.fade.in.active
5 | include ../news/newslist
6 |
--------------------------------------------------------------------------------
/views/account/settings.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | .col-md-3.sidebar
5 | ul.nav.nav-tabs.nav-stacked
6 | li.active
7 | a(href="#profile-information", data-toggle="tab") Profile Information
8 | li
9 | a(href="#delete-account", data-toggle="tab") Delete Account
10 |
11 | .col-md-9.tab-content
12 | #profile-information.tab-pane.active
13 | .page-header
14 | h3 Profile Information
15 |
16 | form.form-horizontal(id='profile', action='/account/profile', method='POST')
17 | .form-group
18 | label.col-xs-12.col-sm-2.control-label(for='email') Email
19 | .col-xs-12.col-sm-4
20 | input.form-control(type='email', name='email', id='email', value='#{user.email}')
21 | .form-group
22 | label.col-xs-12.col-sm-2.control-label(for='name') Name
23 | .col-xs-12.col-sm-4
24 | input.form-control(type='text', name='name', id='name', value='#{user.profile.name}')
25 | .form-group
26 | label.col-xs-12.col-sm-2.control-label(for='location') Location
27 | .col-xs-12.col-sm-4
28 | input.form-control(type='text', name='location', id='location', value='#{user.profile.location}')
29 | .form-group
30 | label.col-xs-12.col-sm-2.control-label(for='website') Website
31 | .col-xs-12.col-sm-4
32 | input.form-control(type='text', name='website', id='website', value='#{user.profile.website}')
33 | .form-group
34 | label.col-xs-12.col-sm-2.control-label(for='description') Description
35 | .col-xs-12.col-sm-4
36 | input.form-control(type='text', name='description', id='description', value='#{user.profile.description}', maxlength=32)
37 | .form-group
38 | label.col-xs-12.col-sm-2.control-label(for='bio') Bio
39 | .col-xs-12.col-sm-4
40 | textarea.form-control(name='bio', id='bio', form="profile")= user.profile.bio
41 | .form-group
42 | .col-xs-offset-0.col-xs-12.col-sm-offset-2.col-sm-4
43 | button.btn.btn.btn-primary(type='submit') Update Profile
44 |
45 | #delete-account.tab-pane
46 | .page-header
47 | h3 Delete Account
48 |
49 | p You can delete your account, but keep in mind this action is irreversible.
50 | button(class="btn btn-danger", data-toggle="modal", data-target="#deleteModal").
51 | Delete my account
52 |
53 | div.modal(id="deleteModal", role="dialog")
54 | .modal-dialog
55 | .modal-content
56 | .modal-header
57 | button(type="button", class="close", data-dismiss="modal") x
58 | h3 Are you sure?
59 | .modal-body
60 | p You can delete your account, but keep in mind this action is irreversible.
61 | form(action='/account/delete', method='POST')
62 | button.btn.btn-danger(type='submit') I understand, delete my account
63 | .modal-footer
64 |
--------------------------------------------------------------------------------
/views/account/signup.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | form.form-horizontal(id='signup-form', method='POST')
5 | legend Signup
6 | .form-group
7 | label.col-sm-3.control-label(for='email') Email
8 | .col-sm-7
9 | input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus)
10 | .form-group
11 | label.col-sm-3.control-label(for='password') Password
12 | .col-sm-7
13 | input.form-control(type='password', name='password', id='password', placeholder='Password')
14 | .form-group
15 | label.col-sm-3.control-label(for='confirmPassword') Confirm Password
16 | .col-sm-7
17 | input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password')
18 | .form-group
19 | .col-sm-offset-3.col-sm-7
20 | button.btn.btn-success(type='submit')
21 | i.fa.fa-check
22 | | Signup
23 |
--------------------------------------------------------------------------------
/views/analytics.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | div.jumbotron.small
5 | div#keen-chart-page
6 |
7 | div.jumbotron.small
8 | div#keen-chart-referrer
9 |
--------------------------------------------------------------------------------
/views/bookmarklet.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | div.jumbotron
5 | h1 Bookmarklet
6 | p.lead Submit links to Pullup directly from your browser
7 | p To install, drag this link to your browser toolbar:
8 | a.small(style="text-decoration:underline;", href="javascript:window.location=%22http://pullup.io/news/submit?u=%22+encodeURIComponent(document.location)") post to Pullup
9 | p When you click on the bookmarklet, it will submit the page you're on.
10 |
--------------------------------------------------------------------------------
/views/chat/index.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div#chat.container
5 | iframe(src="https://gitter.im/larvalabs/pullup/~chat")
--------------------------------------------------------------------------------
/views/comments/comment.jade:
--------------------------------------------------------------------------------
1 | div.comment
2 | - var editable = comment.editable && !editing;
3 | - var userCanEdit = user && comment.poster.username === user.username;
4 | if editable && userCanEdit
5 | a(href="/news/#{item.id}/comments/#{comment.id}/edit", style="float:right")="edit"
6 |
7 | a(href='/user/' + comment.poster.username)= comment.poster.username
8 | if comment.poster.profile.description
9 | span.description=comment.poster.profile.description
10 | a.timeago(href="/news/#{item.id}/comments/#{comment.id}", title="#{comment.created}")= timeago(comment.created)
11 |
12 | blockquote.content
13 | if editing
14 | include postcomment
15 | else
16 | +delete(comment.poster, '/news/' + item.id + '/comments/' + comment.id + '/delete')
17 | != comment.contents
18 |
19 |
--------------------------------------------------------------------------------
/views/comments/index.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 | include ../mixins/delete
3 |
4 | block content
5 | #comment-show.news-item
6 | include ../partials/joinsite
7 | include ../news/newsitem
8 | include comment
9 |
--------------------------------------------------------------------------------
/views/comments/listcomments.jade:
--------------------------------------------------------------------------------
1 | each comment, index in comments
2 | include comment
3 |
4 | if !user && comments.length == 0
5 | include ../partials/nocomments
6 |
--------------------------------------------------------------------------------
/views/comments/postcomment.jade:
--------------------------------------------------------------------------------
1 | if user
2 | .row
3 |
4 | if editing
5 | - var url = '/news/' + item._id + '/comments/' + comment.id + "/update"
6 | else
7 | - var url = '/news/' + item._id + '/comments'
8 | form(action=url, method='POST', class="post-comment")
9 | .form-group.markdown.row
10 | ul.nav.nav-tabs.col-sm-12
11 | li.active
12 | a(href="#edit-tab-content", data-toggle="tab") Write
13 | li
14 | a(href="#preview-tab-content", data-toggle="tab") Preview
15 | .tab-content.col-sm-12
16 | .tab-pane.fade.in.active#edit-tab-content
17 | textarea.form-control(type='text', class="user-atwho" name='contents', id='summary', style='height:200px')
18 | if comment
19 | != comment.source
20 | .tab-pane.fade.in#preview-tab-content
21 | span Loading...
22 | .row
23 | .col-sm-3
24 | input.btn.btn-primary.btn-block(type='submit', value=editing ? 'Update Comment' : 'Add Comment')
25 |
--------------------------------------------------------------------------------
/views/contact.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | .page-header
5 | h3 Contact Form
6 |
7 | form.form-horizontal(role='form', method='POST')
8 | .form-group
9 | label(class='col-sm-2 control-label', for='name') Name
10 | .col-sm-8
11 | input.form-control(type='name', name='name', id='name', autofocus=true)
12 | .form-group
13 | label(class='col-sm-2 control-label', for='email') Email
14 | .col-sm-8
15 | input.form-control(type='text', name='email', id='email')
16 | .form-group
17 | label(class='col-sm-2 control-label', for='message') Body
18 | .col-sm-8
19 | textarea.form-control(type='text', name='message', id='message', rows='7')
20 | .form-group
21 | .col-sm-offset-2.col-sm-8
22 | button.btn.btn-default(type='submit')
23 | i.fa.fa-mail-forward
24 | | Send
25 | img.pull-right(height='34', src='/img/sendgrid.png')
26 |
--------------------------------------------------------------------------------
/views/error.jade:
--------------------------------------------------------------------------------
1 | extends ./layout
2 |
3 | block content
4 | h1 You broke it :(
5 | h2
6 | | You can help by reporting this or by fixing it, find out how on our GitHub page.
7 | p. The stack trace is located in the page source.
8 |
9 | .error
10 | .name #{error.name}
11 | .message #{error.message}
12 | //
13 | #{error.stack}
14 |
--------------------------------------------------------------------------------
/views/howtosignup.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1 How to Sign Up
5 | p.lead
6 | | There’s only one way to become a user on pullup: via pull request.
7 | p
8 | | More specifically:
9 | ol
10 | li
11 | | Fork#{' '}
12 | a(href="https://github.com/larvalabs/pullup") this project
13 | | on GitHub.
14 | li Add a feature, fix a bug, or improve the design.
15 | li Add your GitHub username to the authorized user list.
16 | li Submit a pull request. When we merge, you’ll be a full user!
17 |
--------------------------------------------------------------------------------
/views/issues/index.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | include ../partials/joinsite
5 |
6 | .page-header
7 | h4 Open Issues
8 | a.btn.btn-primary.btn-sm.pull-right(href='https://github.com/larvalabs/pullup/issues/new')
9 | i.fa.fa-plus-square-o.fa-lg
10 | |New Issue
11 |
12 | div#issues.tab-pane.fade.in.active
13 | ul
14 | li.row.headers.hidden-xs
15 | div.col-sm-1.col-sm-offset-1 Votes
16 | div.col-sm-10 Title
17 | each issue, index in issues
18 | li.row.news-item
19 | div.col-xs-2.col-sm-1.upvote-wrapper(class=issue.votedFor ? 'has-vote' : '')
20 | if !issue.votedFor
21 | form(action='/issues/' + issue._id, method='POST', class='upvote-form')
22 | input(type='hidden', name='amount', value='1')
23 | button(type='submit', class='upvote')
24 | i.fa.fa-chevron-up
25 | div.col-xs-2.col-xs-pull-2.col-sm-1.col-sm-pull-0.vote-count-wrapper
26 | span.badge.vote-count= issue.votes
27 | div.col-xs-10.col-xs-push-2.col-sm-6.col-sm-push-0.col-lg-7.title
28 | a(href='/issues/' + issue._id)
29 | | #{issue.title}
30 | if issue.body
31 | |
32 | a(href="#", class="show-summary")
33 | | ...
34 | br
35 | span
36 | small.submit-date(title="#{issue.created_at}")= timeago(issue.created_at)
37 | small.hidden-xs #{' '}by#{' '}
38 | a.poster(href='/user/' + issue.user.login)
39 | small #{issue.user.login}
40 | small.hidden-xs #{' '}with#{' '}
41 | br.visible-xs
42 | a.comment-count(href='/issues/' + issue._id + "?last_comment=#{issue.updated_at}")
43 | case issue.comments
44 | when 0
45 | small.hidden-xs no comments
46 | small.hidden-sm.hidden-md.hidden-lg 0
47 | i.fa.fa-comment-o.hidden-sm.hidden-md.hidden-lg
48 | when 1
49 | small 1
50 | small.hidden-xs comment
51 | i.fa.fa-comment-o.hidden-sm.hidden-md.hidden-lg
52 | default
53 | small #{issue.comments}
54 | small.hidden-xs comments
55 | i.fa.fa-comment-o.hidden-sm.hidden-md.hidden-lg
56 | if issue.comments > 0
57 | small.hidden-xs.text-muted(title="#{issue.updated_at}") (last comment #{timeago(issue.updated_at)})
58 | br
59 | p(class='hidden summary')
60 | em= issue.body
61 | if page > 1
62 | a(href=(page-1), class='btn btn-sm btn-default')
63 | i(class="fa fa-angle-double-left fa-lg")
64 | |Previous Page
65 | if maxPages !== page
66 | a(href= '/issues/page/' + (page+1), class='btn btn-sm btn-default pull-right')
67 | |Next Page
68 | i(class="fa fa-angle-double-right fa-lg")
69 |
70 |
--------------------------------------------------------------------------------
/views/issues/show.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.news-item
5 | include ../partials/joinsite
6 |
7 | .page-header
8 | h3
9 | a(href=item.html_url)= item.title
10 | = 'submitted '
11 | span.timeago(title="#{item.created_at}")= timeago(item.created_at)
12 | = ' by '
13 | a(href='/user/' + item.user.login, class='user')= item.user.login
14 | h4
15 | div(class="item-summary")!= item.body
16 | include ../comments/postcomment
17 |
18 | each comment, index in comments
19 | div.comment
20 | a(href='/user/' + comment.user.login)= comment.user.login
21 | span.timeago(title="#{comment.created_at}")= timeago(comment.created_at)
22 | blockquote.content
23 | != comment.body
24 | button.btn.btn-link.btn-xs(data-toggle='modal', data-target='##{index}modal', style= 'float: right;') View MD Source
25 | div(style= 'clear:both;')
26 | div(id= index + 'modal', tabindex= "-1", role= "dialog").modal.fade
27 | div.modal-dialog
28 | div.modal-content
29 | div.modal-header
30 | button.close(data-dismiss= "modal")
31 | span(aria-hidden= "true", st) ×
32 | h4.modal-title
33 | | Comment’s Markdown Source
34 | div.modal-body
35 | textarea(readonly, style="width: 100%; height: 300px;")
36 | = comment.mdBody
37 |
--------------------------------------------------------------------------------
/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | meta(charset='utf-8')
5 | meta(http-equiv='X-UA-Compatible', content='IE=edge')
6 | meta(name='viewport', content='width=device-width, user-scalable=no')
7 | meta(name='description', content='')
8 | meta(name='author', content='')
9 | meta(name='apple-mobile-web-app-capable', content='yes')
10 | meta(name='apple-mobile-web-app-status-bar-style' content='black')
11 | meta(name="apple-mobile-web-app-title" content="Pullup")
12 | link(rel='author', href='humans.txt')
13 |
14 | title #{title} - pullup
15 |
16 | link(href='/css/lib/jquery.atwho.css?v=#{cacheBuster}', rel='stylesheet')
17 | link(href='/css/styles.css?v=#{cacheBuster}', rel='stylesheet')
18 | link(rel='apple-touch-icon', href='/img/appicon.png?v=#{cacheBuster}')
19 | link(title="Pullup - new items", type="application/rss+xml", rel="alternate", href="/rss")
20 |
21 | script(src='/js/lib/jquery-2.1.0.min.js?v=#{cacheBuster}')
22 | script(src='/js/lib/bootstrap.min.js?v=#{cacheBuster}')
23 | script(src='/js/lib/jquery.caret.js?v=#{cacheBuster}')
24 | script(src='/js/lib/jquery.atwho.js?v=#{cacheBuster}')
25 | script(src='/js/main.js?v=#{cacheBuster}')
26 | if user
27 | script(src="https://d26b395fwzu5fz.cloudfront.net/3.4.0/keen.min.js", type="text/javascript")
28 | script(src='/js/keen.js?v=#{cacheBuster}')
29 | body
30 | #wrap
31 | include partials/data
32 | include partials/navigation
33 | include partials/noscript
34 | .container
35 | include partials/flash
36 | block content
37 | include partials/footer
38 |
--------------------------------------------------------------------------------
/views/logs.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | script(type="text/javascript", src="http://sysinct.herokuapp.com/socket.io/socket.io.js")
5 | script(type="text/javascript", src="/js/logs.js")
6 | script(type="text/javascript", src="/js/lib/ansi_up.js")
7 | h3 Logs
8 | div#log-list
9 |
--------------------------------------------------------------------------------
/views/mixins/delete.jade:
--------------------------------------------------------------------------------
1 | mixin delete(owner, url)
2 | - var modalId = url.replace(/\//g, "")
3 | if user && user.username == owner.username
4 | button(type='button', data-toggle='modal', data-target='##{modalId}' class='delete btn btn-danger btn-xs pull-right', title='Delete') ×
5 | div.modal.fade(id=modalId)
6 | div.modal-dialog
7 | div.modal-content
8 | div.modal-header
9 | button.close(type='button', data-dismiss='modal', aria-hidden='true') ×
10 | h4.modal-title Confirm Delete
11 | div.modal-body
12 | p Are you sure you want to permanently delete this item?
13 | div.modal-footer
14 | button.btn.btn-default(type='button', data-dismiss='modal') Cancel
15 | form(action=url, method='POST', class='delete-form')
16 | button.btn.btn-danger(type='submit') Delete
17 |
--------------------------------------------------------------------------------
/views/news/index.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 | include ../mixins/delete
3 |
4 |
5 | block content
6 | include ../partials/joinsite
7 | .page-header
8 | h4= title
9 | if page > 1
10 | small
11 | | · Page #{page}
12 | if user
13 | a.btn.btn-primary.btn-sm.pull-right(href='/news/submit') Submit News
14 |
15 | div.tab-content
16 | div#news.tab-pane.fade.in.active
17 | include newslist
18 |
--------------------------------------------------------------------------------
/views/news/newsitem.jade:
--------------------------------------------------------------------------------
1 | .page-header
2 | h3
3 | span.vote-count=votes.votes
4 | if !votes.votedFor
5 | form(action='/news/' + item._id, method='POST', class='upvote-form')
6 | input(type='hidden', name='amount', value='1')
7 | button(type='submit', class='upvote')
8 | i.fa.fa-chevron-up
9 |
10 | a(href=item.url)= item.title
11 | if item.summary && ! item.isSelfPost()
12 | |
13 | a(href="#", class="show-summary")
14 | | ...
15 | +delete(item.poster, '/news/' + item._id + '/delete')
16 | p.voters
17 | each voter, index in votes.voters
18 | a.voter(href="/user/#{voter}")=voter
19 | h4
20 | div(class="item-summary")!= item.summary
21 | = 'submitted '
22 | a.timeago(href="/news/#{item.id}", title="#{item.created}")= timeago(item.created)
23 | = ' from '
24 | a(href='/news/source/' + item.source, class='source')= item.source
25 | = ' by '
26 | a(href='/user/' + item.poster.username, class='user')= item.poster.username
27 | if item.poster.profile.description
28 | = ', '
29 | span(title="User description")= item.poster.profile.description
30 |
--------------------------------------------------------------------------------
/views/news/newslist.jade:
--------------------------------------------------------------------------------
1 | ul
2 | li.row.headers.hidden-xs
3 | div.col-sm-2.col-lg-1.text-muted
4 | div.col-sm-1 #
5 | div.col-sm-1 Votes
6 | div.col-sm-6.col-lg-7 Title
7 | if !filteredSource
8 | div.col-sm-3 Source
9 | each item, index in items
10 | li.row.news-item
11 | div.col-xs-2.col-sm-2.col-lg-1.upvote-wrapper(class=item.votedFor ? 'has-vote' : '')
12 | if page
13 | span.hidden-xs.text-muted.col-sm-1= ((page - 1) * newsItemsPerPage) + (index + 1)
14 | else
15 | span.hidden-xs.text-muted.col-sm-1= index + 1
16 | if !item.votedFor
17 | form(action='/news/' + item._id, method='POST', class='upvote-form')
18 | input(type='hidden', name='amount', value='1')
19 | button(type='submit', class='upvote')
20 | i.fa.fa-chevron-up
21 | div.col-xs-2.col-xs-pull-2.col-sm-1.col-sm-pull-0.vote-count-wrapper
22 | span.badge.vote-count= item.votes
23 | div.col-xs-10.col-xs-push-2.col-sm-6.col-sm-push-0.col-lg-7.title
24 | a(href=item.url)
25 | | #{item.title}
26 | if item.summary
27 | |
28 | a(href="#", class="show-summary")
29 | | ...
30 | br
31 | span
32 | small.submit-date(title="#{item.created}")= timeago(item.created)
33 | small.hidden-xs #{' '}by#{' '}
34 | if item.poster
35 | a.poster(href='/user/' + item.poster.username, title=item.poster.profile.description)
36 | small #{item.poster.username}
37 | else
38 | span.text-muted
39 | | [deleted]
40 | small.hidden-xs #{' '}with#{' '}
41 | br.visible-xs
42 | a.comment-count(href='/news/' + item._id + (item.latestCommentAt ? "?last_comment=#{item.latestCommentAt}" : ""))
43 | case item.comment_count
44 | when 0
45 | small.hidden-xs no comments
46 | small.hidden-sm.hidden-md.hidden-lg 0
47 | i.fa.fa-comment-o.hidden-sm.hidden-md.hidden-lg
48 | when 1
49 | small 1
50 | small.hidden-xs comment
51 | i.fa.fa-comment-o.hidden-sm.hidden-md.hidden-lg
52 | default
53 | small #{item.comment_count}
54 | small.hidden-xs comments
55 | i.fa.fa-comment-o.hidden-sm.hidden-md.hidden-lg
56 | if item.comment_count > 0
57 | small.hidden-xs.text-muted(title="#{item.latestCommentAt}") (last comment #{timeago(item.latestCommentAt)})
58 | p(class='hidden summary')
59 | em= item.summary
60 | if !filteredSource
61 | div.col-xs-10.col-xs-push-2.col-sm-push-0.col-sm-3.source
62 | a(href='/news/source/' + item.source)
63 | | #{item.source}
64 |
65 | if page > 1
66 | a(href=(page-1), class='btn btn-sm btn-default')
67 | i(class="fa fa-angle-double-left")
68 | |Previous Page
69 | if items.length === newsItemsPerPage
70 | a(href= (filteredSource ? '/news/source/' + filteredSource : '/news') + '/page/' + (page+1), class='btn btn-sm btn-default pull-right')
71 | |Next Page
72 | i(class="fa fa-angle-double-right")
73 |
--------------------------------------------------------------------------------
/views/news/show.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 | include ../mixins/delete
3 |
4 | block content
5 | #news-show.news-item
6 | include ../partials/joinsite
7 | include newsitem
8 | include ../comments/postcomment
9 | include ../comments/listcomments
10 |
--------------------------------------------------------------------------------
/views/news/submit.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | form.form-horizontal(id='signup-form', method='POST')
5 | legend Submit News
6 | p
7 | em Just populate the URL and press tab, and let us fill out the rest!
8 | | You can also use our
9 | a(href='/bookmarklet') bookmarklet.
10 |
11 | .form-group
12 | .col-sm-7.col-sm-offset-3
13 | .btn-group(data-toggle="buttons")
14 | label.btn.btn-success.post-type-btn.post-type-url(class="#{posttype === 'self' ? '' : 'active'}")
15 | input(type="radio", name="posttype", value="url")
16 | | Create Link
17 | label.btn.btn-success.post-type-btn(class="#{posttype === 'self' ? 'active' : ''}")
18 | if (posttype === self)
19 | input(type="radio", name="posttype", value="self")
20 | else
21 | input(type="radio", name="posttype", value="self", checked)
22 | | Create Discussion
23 | .form-group.form-group-url(style="#{posttype === 'self' ? 'display:none;' : ''}")
24 | label.col-sm-3.control-label(for='url') URL
25 | .col-sm-7
26 | if newsItem.url
27 | input.form-control(type='text', name='url', id='url', value='#{newsItem.url}', placeholder='URL')
28 | else
29 | input.form-control(type='text', name='url', id='url', value='#{newsItem.url}', placeholder='URL', autofocus)
30 | .form-group
31 | label.col-sm-3.control-label(for='title') Title
32 | .col-sm-7
33 | if newsItem.url
34 | input.form-control(type='text', name='title', id='title', placeholder='Title', value='#{newsItem.title}', autofocus)
35 | else
36 | input.form-control(type='text', name='title', id='title', placeholder='Title', value='#{newsItem.title}')
37 | .form-group.form-group-source(style="#{posttype === 'self' ? 'display:none;' : '' }")
38 | label.col-sm-3.control-label(for='source') Source
39 | .col-sm-7
40 | input.form-control(type='text', name='source', id='source', value='#{newsItem.source}')
41 | .form-group.markdown
42 | label.col-sm-3.control-label(for='summary') Summary
43 | ul.nav.nav-tabs.col-sm-7
44 | li.active
45 | a(href="#edit-tab-content", data-toggle="tab") Write
46 | li
47 | a(href="#preview-tab-content", data-toggle="tab") Preview
48 | .tab-content.col-sm-7.col-sm-offset-3
49 | .tab-pane.fade.in.active#edit-tab-content
50 | textarea.form-control(type='text', name='summary', id='summary', style='height:200px')
51 | = newsItem.summary
52 | .tab-pane.fade.in#preview-tab-content
53 | span Loading...
54 | .form-group
55 | .col-sm-offset-3.col-sm-7
56 | button.btn.btn-success(type='submit')
57 | i.fa.fa-check
58 | | Submit
59 |
--------------------------------------------------------------------------------
/views/partials/data.jade:
--------------------------------------------------------------------------------
1 |
2 | //
3 | Make data in pullup object available on the client.
4 | Passing variables to the client through data-* attributes prevents
5 | us from having to use inline scripts.
6 |
7 | div#pullup-global-js-object
8 | each val, index in pullup
9 | div(id=index, class='property' data-val=val)
10 |
11 | script(src='/js/globals.js')
12 |
--------------------------------------------------------------------------------
/views/partials/flash.jade:
--------------------------------------------------------------------------------
1 | #flash
2 | if messages.errors
3 | .alert.alert-danger.animated.fadeIn.alert-dismissable
4 | button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
5 | for error in messages.errors
6 | div= error.msg
7 | if messages.info
8 | .alert.alert-info.animated.fadeIn.alert-dismissable
9 | button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
10 | for info in messages.info
11 | div= info.msg
12 | if messages.success
13 | .alert.alert-success.animated.fadeIn.alert-dismissable
14 | button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
15 | for success in messages.success
16 | div= success.msg
--------------------------------------------------------------------------------
/views/partials/footer.jade:
--------------------------------------------------------------------------------
1 | #footer.hidden-xs
2 | .container.text-center
3 | div
4 | strong pullup
5 | | ©#{' '}
6 | span#copyright
7 | | Larva Labs
8 |
9 | div
10 | a(href='/bookmarklet') Bookmarklet
11 | | ·
12 | a(href='/rss') RSS
13 | | ·
14 | a(href='https://github.com/larvalabs/pullup') GitHub Project
15 | | ·
16 | a(href='/logs') Logs
17 |
18 | #mobile-footer.visible-xs
19 | .container
20 | .row
21 | a.col-xs-4.footer-tab(href='/', class=tab=='news'?'active':undefined)
22 | | News
23 | a.col-xs-4.footer-tab(href='/issues', class=tab=='issues'?'active':undefined)
24 | | Issues
25 | a.col-xs-4.footer-tab(href='/chat')
26 | | Chat
27 |
28 |
29 | input(type='hidden', name='windowscrollto', value=windowscrolly)
30 |
--------------------------------------------------------------------------------
/views/partials/joinsite.jade:
--------------------------------------------------------------------------------
1 | if !user && ! cookies.skip_alert
2 | .alert.alert-info.alert-dismissable
3 | button.close.alert-joinsite(type='button', data-dismiss='alert', aria-hidden='true') ×
4 | i.fa.fa-cloud-upload
5 | strong Join this site via pull request!
6 | | Make the site better and become a full member. See the about page for more details.
7 |
--------------------------------------------------------------------------------
/views/partials/navigation.jade:
--------------------------------------------------------------------------------
1 | .navbar.navbar-inverse.navbar-fixed-top
2 | .container
3 | .navbar-header
4 | button.navbar-toggle(type='button', data-toggle='collapse', data-target='.navbar-collapse')
5 | span.sr-only Toggle navigation
6 | span.icon-bar
7 | span.icon-bar
8 | span.icon-bar
9 | a.navbar-brand(href='/') pullup
10 | .collapse.navbar-collapse
11 | ul.nav.navbar-nav
12 | li.hidden-xs(class=tab=='top'?'active':undefined)
13 | a(href='/news') Top
14 | li.hidden-xs(class=tab =='new' ? 'active' : undefined)
15 | a(href='/news/new') New
16 | li.hidden-xs(class=tab=='issues'?'active':undefined)
17 | a(href='/issues') Issues
18 | li.hidden-xs
19 | a(href='/chat') Chat
20 | li(class=title=='About'?'active':undefined)
21 | a(href='/about') About
22 | if user
23 | li.hidden-xs(class=title=='Analytics'?'active':undefined)
24 | a(href='/analytics') Analytics
25 | ul.nav.navbar-nav.navbar-right
26 | if !user
27 | li(class=title=='Login'?'active':undefined)
28 | a(href='/auth/github') Login
29 | li(class=title=='Signup'?'active':undefined)
30 | a(href='/signup') Signup
31 | else
32 | li.dropdown(class=title=='Account Management'?'active':undefined)
33 | a.dropdown-toggle(href='#', data-toggle='dropdown')
34 | if user.profile.picture
35 | img.profile-image(src='#{user.profile.picture}')
36 | | #{user.username || user.profile.name || user.email || user.id}
37 | i.caret
38 | ul.dropdown-menu
39 | li: a(href='/account') Settings
40 | li.divider
41 | li: a(href='/user/#{user.username}') Profile
42 | li.divider
43 | li: a(href='/logout') Logout
44 |
--------------------------------------------------------------------------------
/views/partials/nocomments.jade:
--------------------------------------------------------------------------------
1 | if !user
2 | div.comment
3 | blockquote.content
4 | p
5 | strong="There are no comments. "
6 | span Join this site via pull request to be the first!
7 |
--------------------------------------------------------------------------------
/views/partials/noscript.jade:
--------------------------------------------------------------------------------
1 | noscript
2 | .alert.alert-danger.animated.fadeIn
3 | div
4 | | This site works best with javascript enabled. Please
5 | a(href='http://www.draac.com/JavaEnable.html') enable javascript in your browser
6 | | .
7 |
--------------------------------------------------------------------------------
/views/partials/profile.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 | include ../mixins/delete
3 |
4 |
5 | block content
6 | include joinsite
7 |
8 | .row.user-profile
9 | .col-lg-8
10 | if userView.profile
11 | if userView.profile.picture
12 | img(src='#{userView.profile.picture}')
13 | if userView.profile.name
14 | div.user-header
15 | h1.name #{userView.profile.name}
16 | if userView.profile.description
17 | h5.description #{userView.profile.description}
18 | if userView.profile.bio
19 | p!= userView.profile.bio
20 | br
21 | .col-lg-4
22 | dl.pull-right-lg
23 | dt GitHub:
24 | dd
25 | a(href='http://github.com/#{userView.username}', target='_blank') http://github.com/#{userView.username}
26 | br
27 | dt Contributions:
28 | dd
29 | span=userView.profile.contributions_count
30 | br
31 | if userView.profile.location
32 | dt Location:
33 | dd= userView.profile.location
34 | br
35 | if userView.profile.website
36 | dt Website:
37 | dd
38 | if (!userView.profile.website.match(/^http[s]?:\/\//))
39 | - userView.profile.website = 'http://' + userView.profile.website
40 |
41 | a(href='#{userView.profile.website}', target='_blank') #{userView.profile.website}
42 | br
43 | .page-header
44 | ul.nav.nav-tabs
45 | li(class=tab == 'news' ? 'active' : '')
46 | a(href='/user/#{userView.username}')
47 | h4 News Submitted
48 | li(class=tab == 'comments' ? 'active' : '')
49 | a(href='/user/#{userView.username}/comments')
50 | h4 Comments
51 | li(class=tab == 'contributions' ? 'active' : '')
52 | a(href='/user/#{userView.username}/contributions')
53 | h4 Contributions
54 |
55 | div.tab-content
56 | block tab-content
57 |
--------------------------------------------------------------------------------
/views/rss.jade:
--------------------------------------------------------------------------------
1 | doctype xml
2 | rss(version="2.0")
3 | channel
4 | title pullup.io
5 | description= 'It’s a site you join via pull request. Other than that, it’s up to you.'
6 | link= 'http://pullup.io'
7 | if items.length
8 | lastBuildDate= new Date(items[0].created).toUTCString()
9 | each item, index in items
10 | item
11 | title= item.title
12 | description
13 | | #{item.summary}
14 | br
15 | a(href='/news/#{item._id}')
16 | | Comments
17 | link= item.url
18 | guid= item.url
19 | pubDate= item.created
20 |
--------------------------------------------------------------------------------