├── .gitignore ├── Gemfile ├── Gemfile.lock ├── bower.json ├── cloud ├── getstream.js ├── github.js ├── main.js ├── settings.js ├── utils.js └── views │ ├── error.ejs │ ├── foot.ejs │ ├── head.ejs │ ├── login.ejs │ ├── main.ejs │ └── store_auth.ejs ├── config.rb ├── config └── global.json ├── gulpfile.js ├── js ├── app.js ├── components │ ├── app_activity.js │ └── app_user.js ├── controllers │ ├── application_controller.js │ └── index_controller.js ├── libs │ ├── ember-1.8.1.js │ ├── ember-parse-adapter.js │ ├── ember.min.js │ ├── handlebars-v1.3.0.js │ ├── jquery-1.10.2.js │ └── underscore.js ├── router.js ├── routes │ ├── application_route.js │ ├── index_route.js │ ├── people_route.js │ └── profile_route.js └── views │ └── custom_file_input.js ├── package.json ├── public ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff ├── images │ ├── loading.gif │ ├── logo.png │ ├── profile-pic.png │ ├── refences.png │ ├── temp1.png │ ├── temp2.png │ ├── temp3.png │ └── temp4.png └── index.html ├── readme.md ├── styles ├── _icons.scss ├── _style.scss ├── bootstrap.scss ├── bootstrap │ ├── _alerts.scss │ ├── _badges.scss │ ├── _breadcrumbs.scss │ ├── _button-groups.scss │ ├── _buttons.scss │ ├── _carousel.scss │ ├── _close.scss │ ├── _code.scss │ ├── _component-animations.scss │ ├── _dropdowns.scss │ ├── _forms.scss │ ├── _glyphicons.scss │ ├── _grid.scss │ ├── _input-groups.scss │ ├── _jumbotron.scss │ ├── _labels.scss │ ├── _list-group.scss │ ├── _media.scss │ ├── _mixins.scss │ ├── _modals.scss │ ├── _navbar.scss │ ├── _navs.scss │ ├── _normalize.scss │ ├── _pager.scss │ ├── _pagination.scss │ ├── _panels.scss │ ├── _popovers.scss │ ├── _print.scss │ ├── _progress-bars.scss │ ├── _responsive-utilities.scss │ ├── _scaffolding.scss │ ├── _tables.scss │ ├── _theme.scss │ ├── _thumbnails.scss │ ├── _tooltip.scss │ ├── _type.scss │ ├── _utilities.scss │ ├── _variables.scss │ ├── _wells.scss │ └── bootstrap.scss └── main.scss └── templates ├── _feed.hbs ├── _loading.hbs ├── application.hbs ├── components ├── app-activity.hbs ├── app-user-header.hbs └── app-user.hbs ├── index.hbs ├── people.hbs ├── profile.hbs └── views └── custom_file_input.hbs /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'sass', '3.3.14' 3 | gem 'compass', '1.0.0' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | chunky_png (1.3.1) 5 | compass (1.0.0) 6 | chunky_png (~> 1.2) 7 | compass-core (~> 1.0.0) 8 | compass-import-once (~> 1.0.5) 9 | rb-fsevent (>= 0.9.3) 10 | rb-inotify (>= 0.9) 11 | sass (>= 3.3.13, < 3.5) 12 | compass-core (1.0.0) 13 | multi_json (~> 1.0) 14 | sass (>= 3.3.0, < 3.5) 15 | compass-import-once (1.0.5) 16 | sass (>= 3.2, < 3.5) 17 | ffi (1.9.3) 18 | multi_json (1.10.1) 19 | rb-fsevent (0.9.4) 20 | rb-inotify (0.9.4) 21 | ffi (>= 0.5.0) 22 | sass (3.3.14) 23 | 24 | PLATFORMS 25 | ruby 26 | 27 | DEPENDENCIES 28 | compass (= 1.0.0) 29 | sass (= 3.3.14) 30 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-app", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "ember": "1.5.0", 6 | "handlebars": "1.2.1", 7 | "ember-data": "1.0.0-beta.7", 8 | "bootstrap-sass-official": "3.1.1", 9 | "moment": "~2.8.1", 10 | "jquery-cookie": "~1.4.1", 11 | "ember-uploader": "~0.2.8", 12 | "getstream": "2.2.2", 13 | "ember-simple-auth": "~0.7.1", 14 | "momentjs": "~2.8.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cloud/github.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Login With GitHub 3 | * 4 | * An example web application implementing OAuth2 in Cloud Code 5 | * 6 | * There will be four routes: 7 | * / - The main route will show a page with a Login with GitHub link 8 | * JavaScript will detect if it's logged in and navigate to /main 9 | * /authorize - This url will start the OAuth process and redirect to GitHub 10 | * /oauthCallback - Sent back from GitHub, this will validate the authorization 11 | * and create/update a Parse User before using 'become' to 12 | * set the user on the client side and redirecting to /main 13 | * /main - The application queries and displays some of the users GitHub data 14 | * 15 | * @author Fosco Marotto (Facebook) 16 | */ 17 | 18 | /** 19 | * Load needed modules. 20 | */ 21 | var express = require('express'); 22 | var querystring = require('querystring'); 23 | var _ = require('underscore'); 24 | var Buffer = require('buffer').Buffer; 25 | var Image = require("parse-image"); 26 | var settings = require('cloud/settings.js'); 27 | 28 | /** 29 | * Create an express application instance 30 | */ 31 | var app = express(); 32 | 33 | /** 34 | * GitHub specific details, including application id and secret 35 | */ 36 | 37 | var githubClientId = settings.githubClientId; 38 | var githubClientSecret = settings.githubClientSecret; 39 | var githubRedirectEndpoint = 'https://github.com/login/oauth/authorize?'; 40 | var githubValidateEndpoint = 'https://github.com/login/oauth/access_token'; 41 | var githubUserEndpoint = 'https://api.github.com/user'; 42 | 43 | /** 44 | * In the Data Browser, set the Class Permissions for these 2 classes to 45 | * disallow public access for Get/Find/Create/Update/Delete operations. 46 | * Only the master key should be able to query or write to these classes. 47 | */ 48 | var TokenRequest = Parse.Object.extend("TokenRequest"); 49 | var TokenStorage = Parse.Object.extend("TokenStorage"); 50 | 51 | /** 52 | * Create a Parse ACL which prohibits public access. This will be used 53 | * in several places throughout the application, to explicitly protect 54 | * Parse User, TokenRequest, and TokenStorage objects. 55 | */ 56 | var restrictedAcl = new Parse.ACL(); 57 | restrictedAcl.setPublicReadAccess(false); 58 | restrictedAcl.setPublicWriteAccess(false); 59 | 60 | /** 61 | * Global app configuration section 62 | */ 63 | app.set('views', 'cloud/views'); 64 | // Specify the folder to find templates 65 | app.set('view engine', 'ejs'); 66 | // Set the template engine 67 | app.use(express.bodyParser()); 68 | // Middleware for reading request body 69 | 70 | /** 71 | * Main route. 72 | * 73 | * When called, render the login.ejs view 74 | */ 75 | app.get('/', function(req, res) { 76 | res.render('login', {}); 77 | }); 78 | 79 | /** 80 | * Login with GitHub route. 81 | * 82 | * When called, generate a request token and redirect the browser to GitHub. 83 | */ 84 | app.get('/authorize', function(req, res) { 85 | 86 | var tokenRequest = new TokenRequest(); 87 | // Secure the object against public access. 88 | tokenRequest.setACL(restrictedAcl); 89 | /** 90 | * Save this request in a Parse Object for validation when GitHub responds 91 | * Use the master key because this class is protected 92 | */ 93 | tokenRequest.save(null, { 94 | useMasterKey : true 95 | }).then(function(obj) { 96 | /** 97 | * Redirect the browser to GitHub for authorization. 98 | * This uses the objectId of the new TokenRequest as the 'state' 99 | * variable in the GitHub redirect. 100 | */ 101 | res.redirect(githubRedirectEndpoint + querystring.stringify({ 102 | client_id : githubClientId, 103 | state : obj.id, 104 | scope : 'user:email' 105 | })); 106 | }, function(error) { 107 | // If there's an error storing the request, render the error page. 108 | res.render('error', { 109 | errorMessage : 'Failed to save auth request.' 110 | }); 111 | }); 112 | 113 | }); 114 | 115 | /** 116 | * OAuth Callback route. 117 | * 118 | * This is intended to be accessed via redirect from GitHub. The request 119 | * will be validated against a previously stored TokenRequest and against 120 | * another GitHub endpoint, and if valid, a User will be created and/or 121 | * updated with details from GitHub. A page will be rendered which will 122 | * 'become' the user on the client-side and redirect to the /main page. 123 | */ 124 | app.get('/oauthCallback', function(req, res) { 125 | var data = req.query; 126 | var token; 127 | /** 128 | * Validate that code and state have been passed in as query parameters. 129 | * Render an error page if this is invalid. 130 | */ 131 | if (!(data && data.code && data.state)) { 132 | res.render('error', { 133 | errorMessage : 'Invalid auth response received.' 134 | }); 135 | return; 136 | } 137 | var query = new Parse.Query(TokenRequest); 138 | /** 139 | * Check if the provided state object exists as a TokenRequest 140 | * Use the master key as operations on TokenRequest are protected 141 | */ 142 | Parse.Cloud.useMasterKey(); 143 | Parse.Promise.as().then(function() { 144 | return query.get(data.state); 145 | }).then(function(obj) { 146 | // Destroy the TokenRequest before continuing. 147 | return obj.destroy(); 148 | }).then(function() { 149 | // Validate & Exchange the code parameter for an access token from GitHub 150 | return getGitHubAccessToken(data.code); 151 | }).then(function(access) { 152 | /** 153 | * Process the response from GitHub, return either the getGitHubUserDetails 154 | * promise, or reject the promise. 155 | */ 156 | var githubData = access.data; 157 | if (githubData && githubData.access_token && githubData.token_type) { 158 | token = githubData.access_token; 159 | return getGitHubUserDetails(token); 160 | } else { 161 | return Parse.Promise.error("Invalid access request."); 162 | } 163 | }).then(function(userDataResponse) { 164 | /** 165 | * Process the users GitHub details, return either the upsertGitHubUser 166 | * promise, or reject the promise. 167 | */ 168 | var userData = userDataResponse.data; 169 | if (userData && userData.login && userData.id) { 170 | return upsertGitHubUser(token, userData); 171 | } else { 172 | return Parse.Promise.error("Unable to parse GitHub data"); 173 | } 174 | }).then(function(user) { 175 | /** 176 | * Render a page which sets the current user on the client-side and then 177 | * redirects to /main 178 | */ 179 | res.render('store_auth', { 180 | sessionToken : user.getSessionToken() 181 | }); 182 | }, function(error) { 183 | /** 184 | * If the error is an object error (e.g. from a Parse function) convert it 185 | * to a string for display to the user. 186 | */ 187 | if (error && error.code && error.error) { 188 | error = error.code + ' ' + error.error; 189 | } 190 | res.render('error', { 191 | errorMessage : JSON.stringify(error) 192 | }); 193 | }); 194 | 195 | }); 196 | 197 | /** 198 | * Logged in route. 199 | * 200 | * JavaScript will validate login and call a Cloud function to get the users 201 | * GitHub details using the stored access token. 202 | */ 203 | app.get('/main', function(req, res) { 204 | res.render('main', {}); 205 | }); 206 | 207 | /** 208 | * Attach the express app to Cloud Code to process the inbound request. 209 | */ 210 | app.listen(); 211 | 212 | /** 213 | * Cloud function which will load a user's accessToken from TokenStorage and 214 | * request their details from GitHub for display on the client side. 215 | */ 216 | Parse.Cloud.define('getGitHubData', function(request, response) { 217 | if (!request.user) { 218 | return response.error('Must be logged in.'); 219 | } 220 | var query = new Parse.Query(TokenStorage); 221 | query.equalTo('user', request.user); 222 | query.ascending('createdAt'); 223 | Parse.Promise.as().then(function() { 224 | return query.first({ 225 | useMasterKey : true 226 | }); 227 | }).then(function(tokenData) { 228 | if (!tokenData) { 229 | return Parse.Promise.error('No GitHub data found.'); 230 | } 231 | return getGitHubUserDetails(tokenData.get('accessToken')); 232 | }).then(function(userDataResponse) { 233 | var userData = userDataResponse.data; 234 | response.success(userData); 235 | }, function(error) { 236 | response.error(error); 237 | }); 238 | }); 239 | 240 | /** 241 | * This function is called when GitHub redirects the user back after 242 | * authorization. It calls back to GitHub to validate and exchange the code 243 | * for an access token. 244 | */ 245 | var getGitHubAccessToken = function(code) { 246 | var body = querystring.stringify({ 247 | client_id : githubClientId, 248 | client_secret : githubClientSecret, 249 | code : code 250 | }); 251 | return Parse.Cloud.httpRequest({ 252 | method : 'POST', 253 | url : githubValidateEndpoint, 254 | headers : { 255 | 'Accept' : 'application/json', 256 | 'User-Agent' : 'Parse.com Cloud Code' 257 | }, 258 | body : body 259 | }); 260 | }; 261 | 262 | /** 263 | * This function calls the githubUserEndpoint to get the user details for the 264 | * provided access token, returning the promise from the httpRequest. 265 | */ 266 | var getGitHubUserDetails = function(accessToken) { 267 | return Parse.Cloud.httpRequest({ 268 | method : 'GET', 269 | url : githubUserEndpoint, 270 | params : { 271 | access_token : accessToken 272 | }, 273 | headers : { 274 | 'User-Agent' : 'Parse.com Cloud Code' 275 | } 276 | }); 277 | }; 278 | /** 279 | * This function checks to see if this GitHub user has logged in before. 280 | * If the user is found, update the accessToken (if necessary) and return 281 | * the users session token. If not found, return the newGitHubUser promise. 282 | */ 283 | var upsertGitHubUser = function(accessToken, githubData) { 284 | var query = new Parse.Query(TokenStorage); 285 | query.equalTo('githubId', githubData.id); 286 | query.ascending('createdAt'); 287 | // Check if this githubId has previously logged in, using the master key 288 | return query.first({ 289 | useMasterKey : true 290 | }).then(function(tokenData) { 291 | // If not, create a new user. 292 | if (!tokenData) { 293 | return newGitHubUser(accessToken, githubData); 294 | } 295 | // If found, fetch the user. 296 | var user = tokenData.get('user'); 297 | return user.fetch({ 298 | useMasterKey : true 299 | }).then(function(user) { 300 | // Update the accessToken if it is different. 301 | if (accessToken !== tokenData.get('accessToken')) { 302 | tokenData.set('accessToken', accessToken); 303 | } 304 | /** 305 | * This save will not use an API request if the token was not changed. 306 | * e.g. when a new user is created and upsert is called again. 307 | */ 308 | return tokenData.save(null, { 309 | useMasterKey : true 310 | }); 311 | }).then(function(obj) { 312 | // Return the user object. 313 | return Parse.Promise.as(user); 314 | }); 315 | }); 316 | }; 317 | /** 318 | * This function creates a Parse User with a random login and password, and 319 | * associates it with an object in the TokenStorage class. 320 | * Once completed, this will return upsertGitHubUser. This is done to protect 321 | * against a race condition: In the rare event where 2 new users are created 322 | * at the same time, only the first one will actually get used. 323 | */ 324 | var newGitHubUser = function(accessToken, githubData) { 325 | var user = new Parse.User(); 326 | // Generate a random username and password. 327 | var userDetailsPromise = getGitHubUserDetails(accessToken); 328 | var promise = userDetailsPromise.then(function(response) { 329 | var userDetails = response.data; 330 | var username = userDetails.name; 331 | // create a fake password 332 | var password = new Buffer(24); 333 | _.times(24, function(i) { 334 | password.set(i, _.random(0, 255)); 335 | }); 336 | user.set("password", password.toString('base64')); 337 | // write the rest of the github data 338 | user.set("username", userDetails.login); 339 | user.set("name", userDetails.name); 340 | user.set("email", userDetails.email); 341 | user.set("location", userDetails.location); 342 | user.set("avatar_url", userDetails.avatar_url); 343 | 344 | var promise = Parse.Promise.as(null); 345 | if (userDetails.avatar_url) { 346 | var url = userDetails.avatar_url; 347 | var avatarPromise = Parse.Cloud.httpRequest({ 348 | url : url 349 | }); 350 | promise = avatarPromise.then(function(response) { 351 | // Create an Image from the data. 352 | var image = new Image(); 353 | return image.setData(response.buffer); 354 | }).then(function(image) { 355 | // Scale the image to a certain size. 356 | return image.scale({ 357 | width : 460, 358 | height : 460 359 | }); 360 | }).then(function(image) { 361 | // Get the bytes of the new image. 362 | return image.data(); 363 | }).then(function(buffer) { 364 | // Save the bytes to a new file. 365 | var imageName = userDetails.login + ".jpg"; 366 | var file = new Parse.File(imageName, { 367 | base64 : buffer.toString("base64") 368 | }); 369 | return file.save(); 370 | }); 371 | } 372 | 373 | return promise.then(function(image) { 374 | user.set("image", image); 375 | user.set("blog", userDetails.blog); 376 | user.set("company", userDetails.company); 377 | user.set("followers", userDetails.followers); 378 | user.set("following", userDetails.following); 379 | // Sign up the new User 380 | return user.signUp().then(function(user) { 381 | // create a new TokenStorage object to store the user+GitHub association. 382 | var ts = new TokenStorage(); 383 | ts.set('githubId', githubData.id); 384 | ts.set('githubLogin', githubData.login); 385 | ts.set('accessToken', accessToken); 386 | ts.set('user', user); 387 | ts.setACL(restrictedAcl); 388 | // Use the master key because TokenStorage objects should be protected. 389 | return ts.save(null, { 390 | useMasterKey : true 391 | }); 392 | }).then(function(tokenStorage) { 393 | return upsertGitHubUser(accessToken, githubData); 394 | }); 395 | }); 396 | 397 | }); 398 | return promise; 399 | 400 | }; 401 | -------------------------------------------------------------------------------- /cloud/main.js: -------------------------------------------------------------------------------- 1 | var stream = require('cloud/getstream.js'); 2 | var github = require('cloud/github.js'); 3 | var utils = require('cloud/utils.js'); 4 | var settings = require('cloud/settings.js'); 5 | var _ = require('underscore'); 6 | 7 | 8 | // initialize the getstream.io client 9 | var client = stream.connect(settings.streamApiKey, settings.streamApiSecret, settings.streamApp); 10 | 11 | /* 12 | * Listen to the activityModels afterSave and afterDelete 13 | * and send the activities to getstream.io 14 | */ 15 | _.each(settings.activityModels, function(model) { 16 | Parse.Cloud.afterSave(model, function(request) { 17 | // trigger fanout 18 | var activity = utils.parseToActivity(request.object); 19 | var feed = client.feed(activity.feed_slug, activity.feed_user_id); 20 | feed.addActivity(activity, utils.createHandler()); 21 | }); 22 | 23 | Parse.Cloud.afterDelete(model, function(request) { 24 | // trigger fanout to remove 25 | var activity = utils.parseToActivity(request.object); 26 | var feed = client.feed(activity.feed_slug, activity.feed_user_id); 27 | // remove by foreign id 28 | feed.removeActivity({ 29 | foreignId : activity.foreign_id 30 | }, utils.createHandler()); 31 | }); 32 | }); 33 | 34 | /* 35 | * Sync the follow state to getstream.io 36 | */ 37 | Parse.Cloud.afterSave(settings.followModel, function(request) { 38 | // trigger fanout & follow 39 | var parseObject = request.object; 40 | var activity = utils.parseToActivity(parseObject); 41 | var feed = client.feed(activity.feed_slug, activity.feed_user_id); 42 | feed.addActivity(activity, utils.createHandler()); 43 | // flat feed of user will follow user feed of target 44 | var flat = client.feed('flat', parseObject.get('actor').id); 45 | flat.follow('user', parseObject.get('object').id, utils.createHandler()); 46 | }); 47 | 48 | Parse.Cloud.afterDelete(settings.followModel, function(request) { 49 | // trigger fanout & unfollow 50 | var parseObject = request.object; 51 | var activity = utils.parseToActivity(parseObject); 52 | var feed = client.feed(activity.feed_slug, activity.feed_user_id); 53 | feed.removeActivity({ 54 | foreignId : activity.foreign_id 55 | }, utils.createHandler()); 56 | // flat feed of user will follow user feed of target 57 | var flat = client.feed('flat', parseObject.get('actor').id); 58 | flat.unfollow('user', parseObject.get('object').id, utils.createHandler()); 59 | }); 60 | 61 | /* 62 | * View to retrieve the feed, expects feed in the format user:1 63 | * Accepts params 64 | * 65 | * feed: the feed id in the format user:1 66 | * limit: how many activities to get 67 | * id_lte: filter by activity id less than or equal to (for pagination) 68 | * 69 | */ 70 | Parse.Cloud.define("feed", function(request, response) { 71 | var feedIdentifier = request.params.feed; 72 | var feedParts = feedIdentifier.split(':'); 73 | var feedSlug = feedParts[0]; 74 | var userId = feedParts[1]; 75 | var id_lte = request.params.id_lte || undefined; 76 | var limit = request.params.limit || 100; 77 | var params = { 78 | limit : limit 79 | }; 80 | if (id_lte) { 81 | params.id_lte = limit; 82 | } 83 | // initialize the feed class 84 | var feed = client.feed(feedSlug, userId); 85 | feed.get(params, function(httpResponse) { 86 | var activities = httpResponse.data; 87 | // enrich the response with the database values where needed 88 | var promise = utils.enrich(activities.results); 89 | promise.then(function(activities) { 90 | response.success({ 91 | activities : activities, 92 | feed : feedIdentifier, 93 | token : feed.token 94 | }); 95 | }); 96 | }, utils.createHandler(response)); 97 | }); 98 | 99 | /* 100 | * Bit of extra logic for likes 101 | */ 102 | 103 | Parse.Cloud.afterSave("Like", function(request) { 104 | // trigger fanout 105 | var activity = utils.parseToActivity(request.object); 106 | var feed = client.feed(activity.feed_slug, activity.feed_user_id); 107 | feed.addActivity(activity, utils.createHandler()); 108 | // get the related object 109 | var like = request.object; 110 | var activityType = like.get('activity_type'); 111 | var pointer = like.get('activity_' + activityType); 112 | var query = new Parse.Query(pointer.className); 113 | query.get(pointer.id, function(activity){ 114 | // increment the likes 115 | activity.increment('likes'); 116 | activity.save(); 117 | }); 118 | }); 119 | 120 | Parse.Cloud.afterDelete("Like", function(request) { 121 | // trigger fanout to remove 122 | var activity = utils.parseToActivity(request.object); 123 | var feed = client.feed(activity.feed_slug, activity.feed_user_id); 124 | // remove by foreign id 125 | feed.removeActivity({ 126 | foreignId : activity.foreign_id 127 | }, utils.createHandler()); 128 | // get the related object 129 | var like = request.object; 130 | var activityType = like.get('activity_type'); 131 | var pointer = like.get('activity_' + activityType); 132 | var query = new Parse.Query(pointer.className); 133 | query.get(pointer.id, function(activity){ 134 | // decrement the likes 135 | activity.increment('likes', -1); 136 | activity.save(); 137 | }); 138 | }); 139 | 140 | 141 | -------------------------------------------------------------------------------- /cloud/settings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Settings are defined below 3 | */ 4 | 5 | // define which parse models you want to treat as getstream.io activities 6 | exports.activityModels = ['Tweet', 'Picture']; 7 | // define which parse model stores your follow state 8 | exports.followModel = 'Follow'; 9 | 10 | // signup for getstream.io, and visit the dashboard to see your keys 11 | exports.streamApiKey = 'fwd8yb9h9v2y'; 12 | exports.streamApiSecret = 'xsmb5m88q82h7suaefn5tvp4tnafanq4ceqv96ntdqdqz7k957g7424uemxqvgkb'; 13 | exports.streamApp = '1302'; 14 | // create a github app and configure the details 15 | exports.githubClientId = '3143108e65106284af77'; 16 | exports.githubClientSecret = '3d082d0a1483bbbf05276c54c87f9617d17b8c69'; 17 | 18 | -------------------------------------------------------------------------------- /cloud/utils.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | function serializeId(parseObject) { 4 | /* 5 | * Returns ref:className:id 6 | */ 7 | return 'ref:' + parseObject.className + ':' + parseObject.id; 8 | }; 9 | exports.serializeId = serializeId; 10 | 11 | function normalizeModelClass(className) { 12 | /* 13 | * Take a string value of a className and return something we can use in 14 | * A query 15 | */ 16 | var modelClass = className; 17 | var map = {'_User': Parse.User}; 18 | if (className in map) { 19 | modelClass = map[className]; 20 | } 21 | return modelClass; 22 | }; 23 | exports.normalizeModelClass = normalizeModelClass; 24 | 25 | exports.parseToActivity = function parseToActivity(parseObject) { 26 | /* 27 | * Take the parse activity and converts it into the required 28 | * activity information for getstream.io 29 | * The names are based on: 30 | * http://activitystrea.ms/specs/json/1.0/ 31 | * Also see 32 | * http://getstream.io/docs 33 | */ 34 | var activity = {}; 35 | var activityProperties = ["actor", "verb", "object", "target", "to", "time"]; 36 | var arrayLength = activityProperties.length; 37 | for (var i = 0; i < arrayLength; i++) { 38 | var field = activityProperties[i]; 39 | var value = parseObject.get(field); 40 | if (value) { 41 | activity[field] = value; 42 | } 43 | } 44 | activity.actor = serializeId(parseObject.get('actor')); 45 | // default to the activity if object is not specified 46 | activity.object = serializeId(parseObject.get('object') || parseObject); 47 | activity.foreign_id = serializeId(parseObject); 48 | activity.feed_slug = parseObject.get('feedSlug'); 49 | activity.feed_user_id = parseObject.get('feedUserId'); 50 | // time and foreign id together ensure uniqueness 51 | activity.time = parseObject.createdAt.toISOString(); 52 | return activity; 53 | }; 54 | 55 | function enrich(activities) { 56 | /* 57 | * Takes the given activities from getstream.io and looks up the needed 58 | * references from the parse database 59 | */ 60 | // Find all the references and add them to the lookup object 61 | var lookup = {}; 62 | var activityIds = []; 63 | _.each(activities, function(activity) { 64 | activityIds.push(activity.id); 65 | _.each(activity, function(value, field) { 66 | if (value && value.indexOf('ref') === 0) { 67 | var parts = value.split(':'); 68 | if (!(parts[1] in lookup)) { 69 | lookup[parts[1]] = []; 70 | } 71 | lookup[parts[1]].push(parts[2]); 72 | } 73 | }); 74 | }); 75 | 76 | // we add all the neccesary queries to this list of promises 77 | var promises = []; 78 | 79 | // Query which activities the user already likes 80 | var currentUser = Parse.User.current(); 81 | if (currentUser) { 82 | var doILikeQuery = new Parse.Query('Like'); 83 | doILikeQuery.containedIn('activityId', activityIds); 84 | doILikeQuery.equalTo('actor', currentUser); 85 | var likePromise = doILikeQuery.find(); 86 | promises.push(likePromise); 87 | } else { 88 | var doILikeQuery = Parse.Promise.as([]); 89 | promises.push(doILikeQuery); 90 | } 91 | 92 | // Query all the needed data in parallel and wait for results 93 | _.each(lookup, function(ids, className) { 94 | var query = new Parse.Query(normalizeModelClass(className)); 95 | query.containedIn("objectId", ids); 96 | var promise = query.find(); 97 | promises.push(promise); 98 | }); 99 | var all = Parse.Promise.when(promises); 100 | 101 | // Transform the queries into dictionaries 102 | // And add the data to the response 103 | var promise = all.then(function(doILikeResult) { 104 | // convert the do i like into an object 105 | var doILikeHash = {}; 106 | if (doILikeResult.length) { 107 | _.each(doILikeResult, function(like) { 108 | var activityId = like.get('activityId'); 109 | doILikeHash[activityId] = like; 110 | }); 111 | }; 112 | 113 | // create the result hash 114 | var resultSets = _.toArray(arguments).slice(1); 115 | var resultHash = {}; 116 | _.each(resultSets, function(results) { 117 | if (results.length) { 118 | resultHash[results[0].className] = {}; 119 | _.each(results, function(result) { 120 | resultHash[result.className][result.id] = result; 121 | }); 122 | } 123 | }); 124 | 125 | // now we set the data 126 | _.each(activities, function(activity) { 127 | _.each(activity, function(value, field) { 128 | if (value && value.indexOf('ref') === 0) { 129 | var parts = value.split(':'); 130 | var parseModels = resultHash[parts[1]]; 131 | activity[field + '_parse'] = parseModels && parseModels[parts[2]]; 132 | } 133 | }); 134 | // set the liked state 135 | activity.liked = activity.id in doILikeHash; 136 | 137 | }); 138 | return activities; 139 | }, function() { 140 | console.log('failed to query the data needed for enrichment'); 141 | }); 142 | 143 | return promise; 144 | } 145 | 146 | exports.enrich = enrich; 147 | 148 | function createHandler(response) { 149 | /* 150 | * Default error handling behaviour for async requests 151 | */ 152 | function errorHandler(result) { 153 | if (result && result.data && result.data.exception) { 154 | var msg = 'GetStream.io ' + result.data.exception + ':' + result.data.detail; 155 | console.error(msg); 156 | // afterSave doesnt have the response object available 157 | if (response) { 158 | response.error(msg); 159 | } 160 | } 161 | } 162 | return errorHandler; 163 | } 164 | 165 | exports.createHandler = createHandler; 166 | -------------------------------------------------------------------------------- /cloud/views/error.ejs: -------------------------------------------------------------------------------- 1 | <% include head %> 2 |

An error has occurred: <%= errorMessage %>

3 |

Login with GitHub

4 | <% include foot %> -------------------------------------------------------------------------------- /cloud/views/foot.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /cloud/views/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GitHub Authentication Example 8 | 9 | 10 | 11 | 17 | 18 |
19 |

GitHub Authentication Example

20 | -------------------------------------------------------------------------------- /cloud/views/login.ejs: -------------------------------------------------------------------------------- 1 | <% include head %> 2 |

Login with GitHub

3 | 9 | <% include foot %> -------------------------------------------------------------------------------- /cloud/views/main.ejs: -------------------------------------------------------------------------------- 1 | <% include head %> 2 | 35 | 55 | <% include foot %> -------------------------------------------------------------------------------- /cloud/views/store_auth.ejs: -------------------------------------------------------------------------------- 1 | <% include head %> 2 |

Saving your GitHub session...

3 | 13 | <% include foot %> -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | require 'compass/import-once/activate' 2 | # Require any additional compass plugins here. 3 | 4 | # Set this to the root of your project when deployed: 5 | http_path = "/" 6 | 7 | sass_dir = "styles" 8 | css_dir = "public/dist/styles" 9 | 10 | generated_images_dir = 'public/dist//images' 11 | images_dir = "images" 12 | 13 | javascripts_dir = "js" 14 | fonts_dir = 'styles/fonts' 15 | 16 | import_path = 'bower_components' 17 | http_images_path = '/images' 18 | http_generated_images_path = '/images/generated' 19 | http_fonts_path = '/styles/fonts' 20 | relative_assets = false 21 | 22 | 23 | # You can select your preferred output style here (can be overridden via the command line): 24 | # output_style = :expanded or :nested or :compact or :compressed 25 | 26 | # To enable relative paths to assets via compass helper functions. Uncomment: 27 | # relative_assets = true 28 | 29 | # To disable debugging comments that display the original location of your selectors. Uncomment: 30 | # line_comments = false 31 | 32 | 33 | # If you prefer the indented syntax, you might want to regenerate this 34 | # project again passing --syntax sass, or you can uncomment this: 35 | # preferred_syntax = :sass 36 | # and then run: 37 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass 38 | -------------------------------------------------------------------------------- /config/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "applications": { 3 | "_default": { 4 | "link": "stream" 5 | }, 6 | "stream": { 7 | "applicationId": "jybMJ0LPeOwp0dZZPDIeqw7Pjp9qyt6RBH2fekGe", 8 | "masterKey": "8A2aLGDx8EF43hSCGzvETQbopHUe6zKs8LoCL9vU" 9 | } 10 | }, 11 | "global": { 12 | "parseVersion": "1.3.1" 13 | } 14 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | compass = require('gulp-compass'), 3 | watch = require('gulp-watch'), 4 | handlebars = require('gulp-ember-handlebars'), 5 | uglify = require('gulp-uglify'), 6 | minifyCSS = require('gulp-minify-css'), 7 | livereload = require('gulp-livereload'), 8 | open = require('gulp-open'), 9 | plumber = require('gulp-plumber'), 10 | neuter = require('gulp-neuter'), 11 | autoprefixer = require('gulp-autoprefixer'), 12 | gutil = require('gulp-util'), 13 | stripDebug = require('gulp-strip-debug'), 14 | concat = require('gulp-concat'), 15 | es6ModuleTranspiler = require("gulp-es6-module-transpiler"); 16 | 17 | /* 18 | Usage: 19 | 20 | Development: 21 | gulp 22 | 23 | Production 24 | gulp build 25 | */ 26 | 27 | function plumberError(error) { 28 | gutil.log(gutil.colors.red(error.message)); 29 | this.emit('end'); 30 | } 31 | 32 | 33 | gulp.task('css', function() { 34 | gulp.src([ 35 | 'bower_components/bootstrap-sass-official/vendor/stylesheets/bootstrap.scss', 36 | 'styles/*.scss' 37 | ]) 38 | .pipe(plumber()) 39 | .pipe(compass({ 40 | config_file: './config.rb', 41 | sass: 'styles', 42 | css: 'public/dist/styles' 43 | })) 44 | .pipe(minifyCSS()) 45 | .pipe(autoprefixer()) 46 | .pipe(gulp.dest('public/dist/styles')); 47 | }); 48 | 49 | gulp.task('templates', function() { 50 | gulp.src(['templates/**/*.hbs']) 51 | .pipe(plumber()) 52 | .pipe(handlebars({ 53 | outputType: 'browser', 54 | namespace: 'Ember.TEMPLATES' 55 | })) 56 | .pipe(concat('templates.js')) 57 | .pipe(stripDebug()) 58 | .pipe(uglify()) 59 | .pipe(gulp.dest('public/dist/scripts')); 60 | }); 61 | 62 | var scriptSrc = [ 63 | 'js/*.js', 64 | 'js/components/*.js', 65 | 'js/controllers/*.js', 66 | 'js/routes/*.js', 67 | 'js/views/*.js', 68 | ]; 69 | 70 | gulp.task('scripts_dev', function() { 71 | return gulp.src(scriptSrc) 72 | .pipe(plumber({'errorHandler': plumberError})) 73 | // .pipe(es6ModuleTranspiler({ 74 | // type: "amd" 75 | // })) 76 | .pipe(neuter("app.js").on('error', gutil.log)) 77 | .pipe(concat('main.js').on('error', gutil.log)) 78 | .pipe(gulp.dest('public/dist/scripts').on('error', gutil.log)); 79 | }); 80 | 81 | 82 | var libsSrc = [ 83 | 'js/libs/jquery-1.10.2.js', 84 | 'bower_components/momentjs/moment.js', 85 | 'js/libs/underscore.js', 86 | 'js/libs/handlebars-v1.3.0.js', 87 | 'js/libs/ember.min.js', 88 | 'bower_components/ember-data/ember-data.js', 89 | 'js/libs/ember-parse-adapter.js', 90 | 'bower_components/getstream/dist/js/getstream.js', 91 | 'bower_components/ember-simple-auth/simple-auth.js', 92 | ]; 93 | 94 | gulp.task('libs', function() { 95 | return gulp.src(libsSrc) 96 | .pipe(concat('libs.js').on('error', gutil.log)) 97 | .pipe(stripDebug()) 98 | .pipe(uglify()) 99 | .pipe(gulp.dest('public/dist/scripts').on('error', gutil.log)); 100 | }); 101 | 102 | gulp.task('scripts_prod', function() { 103 | return gulp.src(scriptSrc) 104 | .pipe(stripDebug()) 105 | .pipe(neuter("app.js")) 106 | .pipe(uglify()) 107 | .pipe(concat('main.js')) 108 | .pipe(gulp.dest('public/dist/scripts')); 109 | }); 110 | 111 | gulp.task("url", function(){ 112 | var options = { 113 | url: "https://getstream.parseapp.com", 114 | app: "google-chrome" 115 | }; 116 | gulp.src("./app/index.html") 117 | .pipe(open("", options)); 118 | }); 119 | 120 | var signal = 'change'; 121 | function handler() { 122 | setTimeout( 123 | livereload.changed 124 | , 200); 125 | } 126 | 127 | gulp.task('watch', function() { 128 | // start the live reload 129 | livereload.listen(); 130 | //watches SCSS files for changes 131 | gulp.watch('styles/**/*.*', ['css']).on(signal, handler); 132 | //watches handlebars files for changes 133 | gulp.watch('templates/**/*.hbs', ['templates']).on(signal, handler); 134 | //watches JavaScript files for changes 135 | gulp.watch('js/**/*.js', ['scripts_dev']).on(signal, handler); 136 | // open the url 137 | gulp.run("url"); 138 | }); 139 | 140 | gulp.task('default', ['css', 'templates', 'scripts_prod', 'libs', 'watch']); 141 | 142 | gulp.task('build', ['css', 'templates', 'scripts_prod', 'libs']); 143 | 144 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | ParseUser = EmberParseAdapter.ParseUser; 4 | 5 | ParseAuthenticator = SimpleAuth.Authenticators.Base.extend({ 6 | restore: function(data) { 7 | var adapter, sessionToken, store; 8 | if (data == null) { 9 | data = {}; 10 | } 11 | store = this.container.lookup('store:main'); 12 | adapter = store.adapterFor('application'); 13 | 14 | sessionToken = data.sessionToken; 15 | 16 | adapter.set('sessionToken', sessionToken); 17 | var currentUser = Parse.User.current(); 18 | var currentSessionToken = currentUser && currentUser._sessionToken; 19 | 20 | if (sessionToken && currentSessionToken != sessionToken){ 21 | Parse.User.become(sessionToken); 22 | } 23 | adapter.set('sessionToken', user._sessionToken); 24 | 25 | }, 26 | 27 | authenticate: function(data) { 28 | var adapter, store, user; 29 | if (data == null) { 30 | data = {}; 31 | } 32 | store = this.container.lookup('store:main'); 33 | adapter = store.adapterFor('application'); 34 | user = data.user; 35 | 36 | if (user) { 37 | adapter.set('sessionToken', user._sessionToken); 38 | data = { 39 | userId: user.get('id'), 40 | user: user, 41 | sessionToken: user.get('sessionToken') 42 | }; 43 | return Ember.RSVP.resolve(data); 44 | } 45 | }, 46 | invalidate: function() { 47 | var adapter; 48 | Parse.User.logOut(); 49 | adapter = this.container.lookup('adapter:application'); 50 | return new Ember.RSVP.Promise(function(resolve, reject) { 51 | adapter.set('sessionToken', null); 52 | return resolve(); 53 | }); 54 | } 55 | }); 56 | 57 | 58 | Ember.Application.initializer({ 59 | name: 'authentication', 60 | initialize: function(container, application) { 61 | container.register('authenticator:parse', ParseAuthenticator); 62 | } 63 | }); 64 | 65 | var App = window.App = Ember.Application.create({ 66 | //LOG_TRANSITIONS: true, 67 | //LOG_ACTIVE_GENERATION: true, 68 | //LOG_RESOLVER: true 69 | }); 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /js/components/app_activity.js: -------------------------------------------------------------------------------- 1 | App.AppActivityComponent = Ember.Component.extend({ 2 | likeAction: 'like', 3 | unlikeAction: 'unlike', 4 | 5 | loading: false, 6 | isTweet : Ember.computed.equal('activity.verb', 'tweet'), 7 | isUpload : Ember.computed.equal('activity.verb', 'upload'), 8 | isLike : Ember.computed.equal('activity.verb', 'like'), 9 | isFollow : Ember.computed.equal('activity.verb', 'follow'), 10 | 11 | ago : function() { 12 | // small hack to force UTC 13 | var parsedDate = this.get('activity.time') + 'Z'; 14 | var ago = moment(parsedDate).fromNow(); 15 | return ago; 16 | }.property('time'), 17 | 18 | likes: function() { 19 | var likes = this.get('activity.foreign_id_parse.attributes.likes') || 0; 20 | return likes; 21 | }.property('activity'), 22 | 23 | likedActivity: function() { 24 | var likeActivity = this.get('activity.object_parse'); 25 | if (likeActivity) { 26 | var activityType = likeActivity.get('activityType'); 27 | var activity = likeActivity.get('activity_' + activityType); 28 | return activity; 29 | } 30 | }.property('activity.object_parse'), 31 | 32 | username : function() { 33 | var username = this.get('activity.actor_parse.attributes.username'); 34 | return username; 35 | }.property('user'), 36 | 37 | imageUrl : function() { 38 | var parseObject = this.get('activity').object_parse; 39 | if (parseObject) { 40 | var image = parseObject.get('image'); 41 | if (image && image.url) { 42 | return image.url(); 43 | } 44 | } 45 | }.property('activity'), 46 | 47 | userImageUrl: function() { 48 | var parseImage = this.get('activity.actor_parse.attributes.image'); 49 | var defaultImage = 'https://getstream.parseapp.com/images/profile-pic.png'; 50 | var image = (parseImage && parseImage.url) ? parseImage.url() : defaultImage; 51 | return image; 52 | }.property('activity'), 53 | 54 | followImageUrl: function() { 55 | var parseImage = this.get('activity.object_parse.attributes.image'); 56 | var defaultImage = 'https://getstream.parseapp.com/images/profile-pic.png'; 57 | var image = (parseImage && parseImage.url) ? parseImage.url() : defaultImage; 58 | return image; 59 | }.property('activity'), 60 | 61 | likedActivity: function() { 62 | var activityType = this.get('activity.foreign_id_parse.attributes.activityType'); 63 | var activity = this.get('activity.foreign_id_parse.attributes.activity_' + activityType); 64 | return activity; 65 | }.property('activity'), 66 | 67 | actions : { 68 | like : function() { 69 | var component = this; 70 | component.set('loading', true); 71 | var like = new Like(); 72 | var user = Parse.User.current(); 73 | // polymorphism is weird with parse 74 | var activity = component.get('activity'); 75 | var parseActivity = component.get('activity.foreign_id_parse'); 76 | var activityType = parseActivity.className; 77 | var activityField = 'activity_' + activityType; 78 | like.set(activityField, parseActivity); 79 | var streamActivityId = activity.id; 80 | like.set('activityId', streamActivityId); 81 | like.set('to', ['user:all']); 82 | 83 | // configure which feed to write to 84 | like.set('feedSlug', 'user'); 85 | like.set('feedUserId', user.id); 86 | 87 | like.save({ 88 | actor : user, 89 | verb : 'like', 90 | // the activity you like 91 | activity_type : activityType 92 | }, { 93 | success : function(object) { 94 | var newLikes = component.get('likes') + 1; 95 | component.set('activity.foreign_id_parse.attributes.likes', newLikes); 96 | component.set('loading', false); 97 | component.set('activity.liked', true); 98 | console.log('saved like'); 99 | }, 100 | error : function(model, error) { 101 | component.set('loading', false); 102 | console.log('error like'); 103 | } 104 | }); 105 | }, 106 | unlike: function() { 107 | var component = this; 108 | component.set('loading', true); 109 | var activity = component.get('activity'); 110 | var currentUser = Parse.User.current(); 111 | var doILikeQuery = new Parse.Query('Like'); 112 | doILikeQuery.equalTo('activityId', activity.id); 113 | doILikeQuery.equalTo('actor', currentUser); 114 | var likePromise = doILikeQuery.find(); 115 | likePromise.then(function(results) { 116 | var promises = []; 117 | if (results.length) { 118 | _.each(results, function(like) { 119 | promises.push(like.destroy()); 120 | }); 121 | } 122 | var all = Parse.Promise.when(promises); 123 | all.then(function() { 124 | component.set('loading', false); 125 | component.set('activity.liked', false); 126 | }); 127 | }); 128 | } 129 | } 130 | 131 | }); 132 | -------------------------------------------------------------------------------- /js/components/app_user.js: -------------------------------------------------------------------------------- 1 | App.AppUserComponent = Ember.Component.extend({ 2 | loading: false, 3 | followedAction: 'followed', 4 | reloadAction: 'reload', 5 | 6 | actions : { 7 | follow : function(user) { 8 | var follow = new Follow(); 9 | var currentUser = Parse.User.current(); 10 | var controller = this; 11 | controller.set('loading', true); 12 | 13 | // configure which feed to write to 14 | follow.set('feedSlug', 'user'); 15 | follow.set('feedUserId', currentUser.id); 16 | 17 | follow.set('to', ['user:all']); 18 | follow.set('likes', 0); 19 | follow.save({ 20 | actor : currentUser, 21 | verb : 'follow', 22 | object : user 23 | }, { 24 | success : function(object) { 25 | console.log('saved follow'); 26 | controller.set('loading', false); 27 | controller.sendAction('followedAction'); 28 | controller.sendAction('reloadAction'); 29 | }, 30 | error : function(model, error) { 31 | controller.set('loading', false); 32 | console.log('error follow'); 33 | } 34 | }); 35 | } 36 | } 37 | }); 38 | 39 | App.AppUserHeaderComponent = App.AppUserComponent.extend({ 40 | 41 | }); -------------------------------------------------------------------------------- /js/controllers/application_controller.js: -------------------------------------------------------------------------------- 1 | App.ApplicationController = Ember.Controller.extend({ 2 | init: function() { 3 | this._super(); 4 | $('#preember').hide(); 5 | }, 6 | posted: false, 7 | followed: false, 8 | user : Ember.computed.alias('session.content.user'), 9 | username : function() { 10 | var user = this.get('user'); 11 | if (user) { 12 | return user.attributes.username; 13 | } 14 | }.property('user'), 15 | userImageUrl: function() { 16 | var url; 17 | var user = this.get('user'); 18 | var image = user.get('image'); 19 | if (image && image.url) { 20 | url = image.url(); 21 | } else { 22 | url = 'https://getstream.parseapp.com/images/profile-pic.png'; 23 | } 24 | return url; 25 | }.property('user'), 26 | displayName: function() { 27 | var username = this.get('username'); 28 | var name = this.get('user').attributes.name; 29 | var displayName = (name) ? name.split(' ')[0] : username; 30 | return displayName; 31 | }.property('user') 32 | }); -------------------------------------------------------------------------------- /js/controllers/index_controller.js: -------------------------------------------------------------------------------- 1 | App.IndexController = Ember.Controller.extend({ 2 | status : '', 3 | errors: {}, 4 | loading: null, 5 | posting: null, 6 | 7 | feedId : function() { 8 | return 'user:everybody'; 9 | return 'flat:' + this.get('user').id; 10 | }.property('user'), 11 | 12 | newActivities: false, 13 | user: Ember.computed.alias('session.content.user'), 14 | userImageUrl: function() { 15 | var user = this.get('user'); 16 | var image = user.get('image'); 17 | var url; 18 | if (image && image.url) { 19 | url = image.url(); 20 | } else { 21 | url = 'https://getstream.parseapp.com/images/profile-pic.png'; 22 | } 23 | return url; 24 | }.property('user'), 25 | 26 | getFeedData: function(name) { 27 | var data = this.get('model.' + name); 28 | if (data) { 29 | var token = data.token; 30 | var feedSlug = data.feed.split(':')[0]; 31 | var feedId = data.feed.split(':')[1]; 32 | var feed = StreamClient.feed(feedSlug, feedId, token); 33 | return feed; 34 | } 35 | }, 36 | 37 | globalFeed: function() { 38 | return this.getFeedData('globalFeed'); 39 | }.property('model.globalFeed.token'), 40 | 41 | flatFeed: function() { 42 | return this.getFeedData('flatFeed'); 43 | }.property('model.flatFeed.token'), 44 | 45 | listenToChanges: function() { 46 | var controller = this; 47 | _.each(['globalFeed', 'flatFeed'], function(feedName) { 48 | var feed = controller.get(feedName); 49 | if (feed) { 50 | console.log('listening to', feed); 51 | feed.subscribe(function callback(data) { 52 | controller.set('model.globalFeed.new', true); 53 | }); 54 | } 55 | }); 56 | }.observes('model'), 57 | 58 | actions : { 59 | status : function() { 60 | var msg = this.get('status'); 61 | var fileUploadControl = $("#profilePhotoFileUpload")[0]; 62 | var imageUpload = fileUploadControl.files.length > 0; 63 | var controller = this; 64 | 65 | 66 | if (msg || imageUpload) { 67 | controller.set('posting', true); 68 | var update = (imageUpload) ? new Picture() : new Tweet(); 69 | var verb = (imageUpload) ? 'upload' : 'tweet'; 70 | 71 | var user = Parse.User.current(); 72 | // we write to the user feed 73 | update.set('feedSlug', 'user'); 74 | update.set('feedUserId', user.id); 75 | // the feed data 76 | update.set('actor', user); 77 | update.set('verb', verb); 78 | update.set('tweet', msg); 79 | update.set('likes', 0); 80 | // to is also often used for things such as @mentions 81 | // see the docs https://getstream.io/docs/#targetting 82 | // think of it as ccing an email 83 | update.set('to', ['user:all']); 84 | 85 | if (imageUpload) { 86 | var file = fileUploadControl.files[0]; 87 | var name = "photo.jpg"; 88 | 89 | var parseFile = new Parse.File(name, file); 90 | parseFile.save().then(function() { 91 | // The file has been saved to Parse. 92 | }, function(error) { 93 | // The file either could not be read, or could not be saved to Parse. 94 | }); 95 | update.set('image', parseFile); 96 | } 97 | 98 | update.save(null, { 99 | success : function(object) { 100 | controller.set('posting', false); 101 | console.log('saved', verb); 102 | $("form").get(0).reset(); 103 | controller.send('posted'); 104 | }, 105 | error : function(model, error) { 106 | controller.set('posting', false); 107 | var errors = {status: error}; 108 | controller.set('errors', errors); 109 | } 110 | }); 111 | } else { 112 | var errors = {status: 'Please write a status message or select a picture'}; 113 | controller.set('errors', errors); 114 | } 115 | } 116 | } 117 | }); 118 | -------------------------------------------------------------------------------- /js/router.js: -------------------------------------------------------------------------------- 1 | App.Router.map(function () { 2 | this.route('people'); 3 | this.resource('profile', { path: '/profile/:username/' }); 4 | }); 5 | -------------------------------------------------------------------------------- /js/routes/application_route.js: -------------------------------------------------------------------------------- 1 | App.ApplicationRoute = Ember.Route.extend( 2 | SimpleAuth.ApplicationRouteMixin, { 3 | model: function() { 4 | session = this.get('session'); 5 | var user = Parse.User.current(); 6 | if (user) { 7 | session.authenticate('authenticator:parse', {user: user}); 8 | } 9 | }, 10 | 11 | actions: { 12 | logout: function() { 13 | this.send('invalidateSession'); 14 | }, 15 | login: function() { 16 | document.location = '/authorize'; }, 17 | followed: function() { 18 | var controller = this.get('controller'); 19 | controller.set('followed', true); 20 | }, 21 | posted: function() { 22 | var controller = this.get('controller'); 23 | controller.set('posted', true); 24 | } 25 | } 26 | }); -------------------------------------------------------------------------------- /js/routes/index_route.js: -------------------------------------------------------------------------------- 1 | 2 | App.IndexRoute = Ember.Route.extend({ 3 | user: Ember.computed.alias('session.content.user'), 4 | model : function(params) { 5 | var promises = []; 6 | // lookup the global feed 7 | var promise = Parse.Cloud.run('feed', { 8 | feed : 'user:all' 9 | }); 10 | promises.push(promise); 11 | // add the user feed 12 | var user = this.get('user'); 13 | if (user) { 14 | promise = Parse.Cloud.run('feed', { 15 | feed : 'flat:' + user.id 16 | }); 17 | promises.push(promise); 18 | } 19 | return Promise.all(promises).then(function(feeds){ 20 | return {'globalFeed': feeds[0], 'flatFeed': feeds[1]}; 21 | }); 22 | 23 | }, 24 | actions: { 25 | reload: function () { 26 | this.refresh(); 27 | } 28 | } 29 | }); 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /js/routes/people_route.js: -------------------------------------------------------------------------------- 1 | App.PeopleRoute = Ember.Route.extend({ 2 | model : function(params) { 3 | q = new Parse.Query(Parse.User); 4 | q.limit(100); 5 | var promise = q.find(function(users) { 6 | return users; 7 | }); 8 | return promise; 9 | } 10 | }); -------------------------------------------------------------------------------- /js/routes/profile_route.js: -------------------------------------------------------------------------------- 1 | 2 | App.ProfileRoute = Ember.Route.extend( 3 | { 4 | user: Ember.computed.alias('session.content.user'), 5 | model : function(params) { 6 | q = new Parse.Query(Parse.User); 7 | q.equalTo('username', params.username); 8 | var userPromise = q.first(); 9 | var modelPromise = userPromise.then(function(user) { 10 | return feedPromise = Parse.Cloud.run('feed', { 11 | feed : 'user:' + user.id 12 | }).then(function(feed) { 13 | return {profile: user, feed:feed}; 14 | }); 15 | }); 16 | return modelPromise; 17 | } 18 | }); 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /js/views/custom_file_input.js: -------------------------------------------------------------------------------- 1 | App.CustomFileInputView = Ember.View.extend({ 2 | templateName : 'views/custom_file_input', 3 | initialize : function() { 4 | $(document).on('change', '.btn-file :file', function() { 5 | var input = $(this), numFiles = input.get(0).files ? input.get(0).files.length : 1, label = input.val().replace(/\\/g, '/').replace(/.*\//, ''); 6 | input.trigger('fileselect', [numFiles, label]); 7 | }); 8 | $(document).ready(function() { 9 | $('.btn-file :file').on('fileselect', function(event, numFiles, label) { 10 | console.log(numFiles); 11 | console.log(label); 12 | $('#selected-image').html(label); 13 | }); 14 | }); 15 | }.on('didInsertElement'), 16 | }); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-app", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "connect-livereload": "~0.2.0", 7 | "gulp": "^3.8.7", 8 | "gulp-autoprefixer": "0.0.8", 9 | "gulp-compass": "^1.2.0", 10 | "gulp-concat": "^2.3.4", 11 | "gulp-ember-handlebars": "^0.6.0", 12 | "gulp-es6-module-transpiler": "^0.1.3", 13 | "gulp-livereload": "^2.1.0", 14 | "gulp-minify-css": "^0.3.7", 15 | "gulp-neuter": "^0.1.2", 16 | "gulp-open": "^0.2.8", 17 | "gulp-plumber": "^0.6.4", 18 | "gulp-strip-debug": "^1.0.0", 19 | "gulp-uglify": "^0.3.1", 20 | "gulp-util": "^3.0.0", 21 | "gulp-watch": "^0.6.9", 22 | "jshint-stylish": "~0.1.3", 23 | "neuter": "https://github.com/squarewolf/node-neuter/archive/master.tar.gz" 24 | }, 25 | "engines": { 26 | "node": ">=0.8.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/fonts/icomoon.eot -------------------------------------------------------------------------------- /public/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/fonts/icomoon.ttf -------------------------------------------------------------------------------- /public/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/fonts/icomoon.woff -------------------------------------------------------------------------------- /public/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/images/loading.gif -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/images/logo.png -------------------------------------------------------------------------------- /public/images/profile-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/images/profile-pic.png -------------------------------------------------------------------------------- /public/images/refences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/images/refences.png -------------------------------------------------------------------------------- /public/images/temp1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/images/temp1.png -------------------------------------------------------------------------------- /public/images/temp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/images/temp2.png -------------------------------------------------------------------------------- /public/images/temp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/images/temp3.png -------------------------------------------------------------------------------- /public/images/temp4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-Parse/8eebc5382a12dc126f0e28a75be7b83987936a94/public/images/temp4.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Parse App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | GetStream.io, Parse cloud code & EmberJS 2 | ======================================== 3 | 4 | ## Warning 5 | 6 | Support for Parse has been discontinued. Some of this integration might still work, but you're on your own as it's no longer supported. 7 | 8 | ###Activity Streams & Newsfeeds 9 | 10 | This example app helps you create activity streams & newsfeeds with [Parse Cloud Code](https://parse.com/docs/cloud_code_guide) and [GetStream.io](https://getstream.io). 11 | 12 | ![](https://dvqg2dogggmn6.cloudfront.net/images/mood-home.png) 13 | 14 | What you can build: 15 | 16 | * Activity streams such as seen on Github 17 | * A twitter style newsfeed 18 | * A feed like instagram/ pinterest 19 | * Facebook style newsfeeds 20 | * A notification system 21 | 22 | ### Demo 23 | 24 | You should start by checking out [the demo](https://getstream.parseapp.com/) hosted on Parse. 25 | 26 | ### About Parse Cloud & GetStream.io 27 | 28 | Parse Cloud allows you to write server side application logic and extend the functionality of your existing Parse installation. GetStream.io allows you to build scalable newsfeeds and activity streams. This example app shows how you can get them to work together. 29 | 30 | The example app is based on [EmberJS](http://emberjs.com/) and showcases a Twitter/Facebook style community. 31 | 32 | ### Tutorial 33 | 34 | #### Installing the Stream library 35 | The Parse cloud compatible client can be found in [cloud/getstream.js](https://github.com/tschellenbach/Stream-Example-Parse/blob/master/cloud/getstream.js). 36 | The documentation for the client can be found on [getstream.io/docs](https://getstream.io/docs/) 37 | If you haven't tried out getstream.io before I recommend the [getting started](https://getstream.io/get_started/#intro). 38 | The interactive API tutorial will get you up to speed in a few minutes. 39 | 40 | #### Syncing activities to getstream.io 41 | 42 | To build your activity stream you need to notifity getstream.io of 2 things: 43 | 44 | 1. When activities are added/removed 45 | 2. When follow relationships change 46 | 47 | The code to notify GetStream.io of these events can be found in [cloud/main.js](https://github.com/tschellenbach/Stream-Example-Parse/blob/master/cloud/main.js) 48 | For each model defined in settings.activityModels we'll listen to the Parse.Cloud.afterSave and Parse.Cloud.afterDelete methods. 49 | 50 | Furthermore we also listen to the changes in settings.followModel and sync the changes. 51 | 52 | #### Creating activities 53 | 54 | Now that our Parse Cloud code is correctly setup, activities created via Parse will get published to getstream.io 55 | 56 | ``` 57 | // create a new tweet 58 | var tweet = new Tweet(); 59 | // we write to the user feed 60 | tweet.set('feedSlug', 'user'); 61 | tweet.set('feedUserId', user.id); 62 | // the tweet's data 63 | tweet.set('actor', user); 64 | tweet.set('verb', 'tweet'); 65 | tweet.set('tweet', 'hello world'); 66 | tweet.set('likes', 0); 67 | tweet.save(); 68 | ``` 69 | 70 | When you call tweet.save() the Parse object is created. After the tweet is created the Parse.Cloud.afterSave trigger will publish the activity to getstream.io. 71 | 72 | #### Reading feeds 73 | 74 | You can read feeds by using a Parse.Cloud.run('feed') call, for example: 75 | 76 | ``` 77 | var promise = Parse.Cloud.run('feed', { 78 | feed : 'user:1' 79 | }); 80 | ``` 81 | 82 | Will retrieve the user feed for user 1. 83 | 84 | ### Advanced: Running this example app 85 | 86 | #### Settings 87 | 88 | If you want to install this example app on your own parse instance you'll need to update a few setting files 89 | 90 | 0. signup for parse, getstream.io and create a github app (for the github login) 91 | 1. edit cloud/settings.js 92 | 2. edit config/global.json 93 | 3. edit public/index.html (the inline config variable) 94 | 95 | #### Dev tools 96 | 97 | 0. install gulp, compass and bower 98 | 1. bower install 99 | 2. gulp 100 | 3. parse develop 101 | 102 | gulp will monitor for changes and livereload the index.html file 103 | parse develop will upload your cloud code and host it at .parseapp.com. 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /styles/_icons.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('../../fonts/icomoon.eot?wvulje'); 4 | src:url('../../fonts/icomoon.eot?#iefixwvulje') format('embedded-opentype'), 5 | url('../../fonts/icomoon.woff?wvulje') format('woff'), 6 | url('../../fonts/icomoon.ttf?wvulje') format('truetype'), 7 | url('../../fonts/icomoon.svg?wvulje#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | font-family: 'icomoon'; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .icon-home:before { 27 | content: "\e600"; 28 | } 29 | .icon-pen:before { 30 | content: "\e601"; 31 | } 32 | .icon-image:before { 33 | content: "\e602"; 34 | } 35 | .icon-bullhorn:before { 36 | content: "\e603"; 37 | } 38 | .icon-clock:before { 39 | content: "\e604"; 40 | } 41 | .icon-user:before { 42 | content: "\e605"; 43 | } 44 | .icon-search:before { 45 | content: "\e606"; 46 | } 47 | .icon-cog:before { 48 | content: "\e607"; 49 | } 50 | .icon-thumbs-up:before { 51 | content: "\e608"; 52 | } 53 | -------------------------------------------------------------------------------- /styles/_style.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | background-color: #eee; 3 | min-height: 100%; 4 | font-family: 'Raleway', sans-serif; 5 | font-size: 12px; 6 | font-weight: 400; 7 | padding-bottom: 100px; 8 | } 9 | 10 | .navbar-default { 11 | background-color: #8CC63E; 12 | border:none; 13 | box-shadow: -3px 0px 15px -3px #777777; 14 | } 15 | .navbar-default .navbar-nav > li { 16 | color: #fff; 17 | img { 18 | height: 50px; 19 | padding: 9px; 20 | } 21 | } 22 | .navbar-default .navbar-nav > li > a { 23 | color: #FFF !important; 24 | font-weight: 700; 25 | text-transform: uppercase; 26 | } 27 | .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { 28 | color: #FFF !important; 29 | } 30 | .navbar-default .navbar-nav > .active > a { 31 | background-color: #B9DD7F; 32 | } 33 | .tight { 34 | padding-right: 0px; 35 | padding-left: 0px; 36 | } 37 | 38 | .panel { 39 | box-shadow: -3px 0px 15px -3px #777777; 40 | border-radius: 0px; 41 | min-height: 100%; 42 | padding: 15px; 43 | } 44 | .panel.feed { 45 | min-height: 1000px; 46 | } 47 | .centered { 48 | text-align: center; 49 | } 50 | .upper { 51 | text-transform: uppercase; 52 | } 53 | h1 { 54 | font-family: 'Raleway', sans-serif; 55 | font-size: 20px; 56 | padding: 25px; 57 | font-weight: 300; 58 | border-bottom: solid 1px #8CC63E; 59 | margin-top: 0px; 60 | } 61 | h2 { 62 | color: #8CC63E; 63 | } 64 | a { 65 | color: #8CC63E; 66 | cursor: pointer; 67 | } 68 | a:hover, a:active { 69 | color: #8CC63E; 70 | opacity: 0.8; 71 | } 72 | ul { 73 | margin: 0px; 74 | padding: 0px; 75 | list-style-type: none; 76 | } 77 | .logo-container { 78 | padding: 30px; 79 | } 80 | .form-control { 81 | border: none; 82 | background-color: #fff; 83 | border-radius: 0px 84 | } 85 | 86 | .btn { 87 | border: none; 88 | text-transform: uppercase; 89 | font-size: 12px; 90 | font-weight: 400; 91 | border-radius: 3px; 92 | line-height: 12px; 93 | } 94 | 95 | .btn-primary { 96 | background-color: #8CC63E; 97 | color: #fff; 98 | } 99 | .btn-primary:hover, .btn-primary:active { 100 | background-color: #8CC63E; 101 | opacity: 0.8; 102 | } 103 | .btn-default { 104 | background-color: #dddddd; 105 | color: #000; 106 | } 107 | .btn-default:hover, .btn-default:active { 108 | background-color: #dddddd; 109 | opacity: 0.8; 110 | } 111 | .activity-container { 112 | position: relative; 113 | padding-left: 50px; 114 | margin-bottom: 35px; 115 | } 116 | .activity-container .actor { 117 | position: absolute; 118 | left: 0; 119 | top: 0; 120 | width: 35px; 121 | } 122 | .activity-text { 123 | font-size: 10px; 124 | text-transform: uppercase; 125 | font-weight: 700; 126 | padding: 3px 0px 5px; 127 | } 128 | .activity-content { 129 | padding: 15px; 130 | background-color: #eee; 131 | } 132 | .activity-image { 133 | width: 100%; 134 | background-color: #e9e9e9; 135 | } 136 | .activity-time { 137 | text-align: right; 138 | font-weight: 300; 139 | float: right; 140 | font-size: 10px; 141 | padding-top: 3px; 142 | } 143 | .activity-actions { 144 | color: #999; 145 | font-size: 10px; 146 | padding-top: 4px; 147 | } 148 | .activity-actions a { 149 | font-weight: 700; 150 | } 151 | 152 | .form-group .btn { 153 | width: 100%; 154 | } 155 | 156 | .btn-file { 157 | position: relative; 158 | overflow: hidden; 159 | } 160 | .btn-file input[type=file] { 161 | position: absolute; 162 | top: 0; 163 | right: 0; 164 | min-width: 100%; 165 | min-height: 100%; 166 | font-size: 100px; 167 | text-align: right; 168 | filter: alpha(opacity=0); 169 | opacity: 0; 170 | outline: none; 171 | background: white; 172 | cursor: inherit; 173 | display: block; 174 | } 175 | .blank-slate { 176 | padding: 30px; 177 | text-align: center; 178 | } 179 | 180 | .walkthrough-container { 181 | position: fixed; 182 | bottom: 30px; 183 | width: 100%; 184 | z-index: 100; 185 | font-size: 18px; 186 | } 187 | .walkthrough { 188 | border-radius: 5px; 189 | border: 3px solid #8CC63E; 190 | background-color: #fff; 191 | width: 90%; 192 | max-width: 800px; 193 | margin: 0 auto; 194 | box-shadow: 0px 0px 5px #000000; 195 | h1 { 196 | font-weight: 400; 197 | } 198 | } 199 | .wt-header { 200 | color: #fff; 201 | background-color: #8CC63E; 202 | padding: 15px; 203 | } 204 | .wt-header h1 { 205 | color: #fff; 206 | padding: 0px; 207 | margin: 0px; 208 | font-size: 22px; 209 | } 210 | .wt-header a { 211 | color: #fff; 212 | opacity: 1; 213 | text-decoration: underline; 214 | } 215 | .wt-step { 216 | padding: 15px; 217 | } 218 | .profile-header { 219 | text-align: center; 220 | margin-bottom: 30px; 221 | } 222 | .profile-header .profile-image { 223 | border: 3px solid #fff; 224 | border-radius: 100px; 225 | width: 100px; 226 | box-shadow: -3px 0px 15px 0px #777777; 227 | } 228 | 229 | .user-list { 230 | margin-top: 20px; 231 | } 232 | .user-list li { 233 | padding: 10px 0; 234 | border-top: 1px solid #ccc; 235 | } 236 | .user-list button { 237 | margin-top: 16px; 238 | } 239 | .loading-container { 240 | float: right; 241 | padding-top: 20px; 242 | } 243 | .user-list .username { 244 | display: inline-block; 245 | padding-left: 10px; 246 | font-size: 22px; 247 | margin-top: 16px; 248 | } 249 | 250 | .activity-content .user-image { 251 | width: 80px; 252 | border: 3px solid #fff; 253 | border-radius: 100px; 254 | box-shadow: -3px 0px 15px 0px #777777; 255 | margin-top: 5px; 256 | } 257 | .new-notice { 258 | background-color: #F29B00; 259 | color: #fff; 260 | padding: 10px; 261 | margin-bottom: 20px; 262 | text-align: center; 263 | } 264 | .loading { 265 | text-align: center; 266 | } 267 | .loading img { 268 | width: 24px; 269 | height: 24px; 270 | } 271 | #preember { 272 | text-align: center; 273 | padding: 100px; 274 | } 275 | #preember img { 276 | width: 24px; 277 | height: 24px; 278 | } 279 | -------------------------------------------------------------------------------- /styles/bootstrap.scss: -------------------------------------------------------------------------------- 1 | // Core variables and mixins 2 | @import "bootstrap/variables"; 3 | @import "bootstrap/mixins"; 4 | 5 | // Reset 6 | @import "bootstrap/normalize"; 7 | @import "bootstrap/print"; 8 | 9 | // Core CSS 10 | @import "bootstrap/scaffolding"; 11 | @import "bootstrap/type"; 12 | @import "bootstrap/code"; 13 | @import "bootstrap/grid"; 14 | @import "bootstrap/tables"; 15 | @import "bootstrap/forms"; 16 | @import "bootstrap/buttons"; 17 | 18 | // Components 19 | @import "bootstrap/component-animations"; 20 | @import "bootstrap/glyphicons"; 21 | @import "bootstrap/dropdowns"; 22 | @import "bootstrap/button-groups"; 23 | @import "bootstrap/input-groups"; 24 | @import "bootstrap/navs"; 25 | @import "bootstrap/navbar"; 26 | @import "bootstrap/breadcrumbs"; 27 | @import "bootstrap/pagination"; 28 | @import "bootstrap/pager"; 29 | @import "bootstrap/labels"; 30 | @import "bootstrap/badges"; 31 | @import "bootstrap/jumbotron"; 32 | @import "bootstrap/thumbnails"; 33 | @import "bootstrap/alerts"; 34 | @import "bootstrap/progress-bars"; 35 | @import "bootstrap/media"; 36 | @import "bootstrap/list-group"; 37 | @import "bootstrap/panels"; 38 | @import "bootstrap/wells"; 39 | @import "bootstrap/close"; 40 | 41 | // Components w/ JavaScript 42 | @import "bootstrap/modals"; 43 | @import "bootstrap/tooltip"; 44 | @import "bootstrap/popovers"; 45 | @import "bootstrap/carousel"; 46 | 47 | // Utility classes 48 | @import "bootstrap/utilities"; 49 | @import "bootstrap/responsive-utilities"; 50 | -------------------------------------------------------------------------------- /styles/bootstrap/_alerts.scss: -------------------------------------------------------------------------------- 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 | @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text); 58 | } 59 | .alert-info { 60 | @include alert-variant($alert-info-bg, $alert-info-border, $alert-info-text); 61 | } 62 | .alert-warning { 63 | @include alert-variant($alert-warning-bg, $alert-warning-border, $alert-warning-text); 64 | } 65 | .alert-danger { 66 | @include alert-variant($alert-danger-bg, $alert-danger-border, $alert-danger-text); 67 | } 68 | -------------------------------------------------------------------------------- /styles/bootstrap/_badges.scss: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_breadcrumbs.scss: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_button-groups.scss: -------------------------------------------------------------------------------- 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 | @include clearfix(); 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 | @include 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 | @include 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 | @include border-right-radius(0); 82 | } 83 | } 84 | .btn-group > .btn-group:last-child > .btn:first-child { 85 | @include 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 { @extend .btn-xs; } 100 | .btn-group-sm > .btn { @extend .btn-sm; } 101 | .btn-group-lg > .btn { @extend .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 | @include 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 | @include 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 | @include clearfix(); 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 | @include border-bottom-radius(0); 181 | } 182 | &:last-child:not(:first-child) { 183 | border-bottom-left-radius: $border-radius-base; 184 | @include 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 | @include border-bottom-radius(0); 194 | } 195 | } 196 | .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { 197 | @include 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_buttons.scss: -------------------------------------------------------------------------------- 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 | @include button-size($padding-base-vertical, $padding-base-horizontal, $font-size-base, $line-height-base, $border-radius-base); 20 | @include user-select(none); 21 | 22 | &, 23 | &:active, 24 | &.active { 25 | &:focus { 26 | @include tab-focus(); 27 | } 28 | } 29 | 30 | &:hover, 31 | &:focus { 32 | color: $btn-default-color; 33 | text-decoration: none; 34 | } 35 | 36 | &:active, 37 | &.active { 38 | outline: 0; 39 | background-image: none; 40 | @include box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); 41 | } 42 | 43 | &.disabled, 44 | &[disabled], 45 | fieldset[disabled] & { 46 | cursor: not-allowed; 47 | pointer-events: none; // Future-proof disabling of clicks 48 | @include opacity(.65); 49 | @include box-shadow(none); 50 | } 51 | } 52 | 53 | 54 | // Alternate buttons 55 | // -------------------------------------------------- 56 | 57 | .btn-default { 58 | @include button-variant($btn-default-color, $btn-default-bg, $btn-default-border); 59 | } 60 | .btn-primary { 61 | @include button-variant($btn-primary-color, $btn-primary-bg, $btn-primary-border); 62 | } 63 | // Success appears as green 64 | .btn-success { 65 | @include button-variant($btn-success-color, $btn-success-bg, $btn-success-border); 66 | } 67 | // Info appears as blue-green 68 | .btn-info { 69 | @include button-variant($btn-info-color, $btn-info-bg, $btn-info-border); 70 | } 71 | // Warning appears as orange 72 | .btn-warning { 73 | @include button-variant($btn-warning-color, $btn-warning-bg, $btn-warning-border); 74 | } 75 | // Danger and error appear as red 76 | .btn-danger { 77 | @include button-variant($btn-danger-color, $btn-danger-bg, $btn-danger-border); 78 | } 79 | 80 | 81 | // Link buttons 82 | // ------------------------- 83 | 84 | // Make a button look and behave like a link 85 | .btn-link { 86 | color: $link-color; 87 | font-weight: normal; 88 | cursor: pointer; 89 | border-radius: 0; 90 | 91 | &, 92 | &:active, 93 | &[disabled], 94 | fieldset[disabled] & { 95 | background-color: transparent; 96 | @include box-shadow(none); 97 | } 98 | &, 99 | &:hover, 100 | &:focus, 101 | &:active { 102 | border-color: transparent; 103 | } 104 | &:hover, 105 | &:focus { 106 | color: $link-hover-color; 107 | text-decoration: underline; 108 | background-color: transparent; 109 | } 110 | &[disabled], 111 | fieldset[disabled] & { 112 | &:hover, 113 | &:focus { 114 | color: $btn-link-disabled-color; 115 | text-decoration: none; 116 | } 117 | } 118 | } 119 | 120 | 121 | // Button Sizes 122 | // -------------------------------------------------- 123 | 124 | .btn-lg { 125 | // line-height: ensure even-numbered height of button next to large input 126 | @include button-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $border-radius-large); 127 | } 128 | .btn-sm { 129 | // line-height: ensure proper height of button next to small input 130 | @include button-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $border-radius-small); 131 | } 132 | .btn-xs { 133 | @include button-size($padding-xs-vertical, $padding-xs-horizontal, $font-size-small, $line-height-small, $border-radius-small); 134 | } 135 | 136 | 137 | // Block button 138 | // -------------------------------------------------- 139 | 140 | .btn-block { 141 | display: block; 142 | width: 100%; 143 | padding-left: 0; 144 | padding-right: 0; 145 | } 146 | 147 | // Vertically space out multiple block buttons 148 | .btn-block + .btn-block { 149 | margin-top: 5px; 150 | } 151 | 152 | // Specificity overrides 153 | input[type="submit"], 154 | input[type="reset"], 155 | input[type="button"] { 156 | &.btn-block { 157 | width: 100%; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /styles/bootstrap/_carousel.scss: -------------------------------------------------------------------------------- 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 | @include transition(.6s ease-in-out left); 20 | 21 | // Account for jankitude on images 22 | > img, 23 | > a > img { 24 | @include 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 | @include 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 | @include 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 | @include 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 | @include 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 | .glyphicon-chevron-left, 210 | .glyphicon-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 | -------------------------------------------------------------------------------- /styles/bootstrap/_close.scss: -------------------------------------------------------------------------------- 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 | @include opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: $close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | @include opacity(.5); 21 | } 22 | 23 | // [converter] extracted button& to button.close 24 | } 25 | 26 | // Additional properties for button version 27 | // iOS requires the button element instead of an anchor tag. 28 | // If you want the anchor version, it requires `href="#"`. 29 | button.close { 30 | padding: 0; 31 | cursor: pointer; 32 | background: transparent; 33 | border: 0; 34 | -webkit-appearance: none; 35 | } 36 | -------------------------------------------------------------------------------- /styles/bootstrap/_code.scss: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_component-animations.scss: -------------------------------------------------------------------------------- 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 | @include 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 | @include transition(height .35s ease); 29 | } 30 | -------------------------------------------------------------------------------- /styles/bootstrap/_dropdowns.scss: -------------------------------------------------------------------------------- 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 | @include 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 | @include 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 | @include 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 | right: 0; left: auto; 205 | } 206 | // Necessary for overrides of the default right aligned menu. 207 | // Will remove come v4 in all likelihood. 208 | .dropdown-menu-left { 209 | left: 0; right: auto; 210 | } 211 | } 212 | } 213 | 214 | -------------------------------------------------------------------------------- /styles/bootstrap/_forms.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Forms 3 | // -------------------------------------------------- 4 | 5 | 6 | // Normalize non-controls 7 | // 8 | // Restyle and baseline non-control form elements. 9 | 10 | fieldset { 11 | padding: 0; 12 | margin: 0; 13 | border: 0; 14 | // Chrome and Firefox set a `min-width: -webkit-min-content;` on fieldsets, 15 | // so we reset that to ensure it behaves more like a standard block element. 16 | // See https://github.com/twbs/bootstrap/issues/12359. 17 | min-width: 0; 18 | } 19 | 20 | legend { 21 | display: block; 22 | width: 100%; 23 | padding: 0; 24 | margin-bottom: $line-height-computed; 25 | font-size: ($font-size-base * 1.5); 26 | line-height: inherit; 27 | color: $legend-color; 28 | border: 0; 29 | border-bottom: 1px solid $legend-border-color; 30 | } 31 | 32 | label { 33 | display: inline-block; 34 | margin-bottom: 5px; 35 | font-weight: bold; 36 | } 37 | 38 | 39 | // Normalize form controls 40 | // 41 | // While most of our form styles require extra classes, some basic normalization 42 | // is required to ensure optimum display with or without those classes to better 43 | // address browser inconsistencies. 44 | 45 | // Override content-box in Normalize (* isn't specific enough) 46 | input[type="search"] { 47 | @include box-sizing(border-box); 48 | } 49 | 50 | // Position radios and checkboxes better 51 | input[type="radio"], 52 | input[type="checkbox"] { 53 | margin: 4px 0 0; 54 | margin-top: 1px \9; /* IE8-9 */ 55 | line-height: normal; 56 | } 57 | 58 | // Set the height of file controls to match text inputs 59 | input[type="file"] { 60 | display: block; 61 | } 62 | 63 | // Make range inputs behave like textual form controls 64 | input[type="range"] { 65 | display: block; 66 | width: 100%; 67 | } 68 | 69 | // Make multiple select elements height not fixed 70 | select[multiple], 71 | select[size] { 72 | height: auto; 73 | } 74 | 75 | // Focus for file, radio, and checkbox 76 | input[type="file"]:focus, 77 | input[type="radio"]:focus, 78 | input[type="checkbox"]:focus { 79 | @include tab-focus(); 80 | } 81 | 82 | // Adjust output element 83 | output { 84 | display: block; 85 | padding-top: ($padding-base-vertical + 1); 86 | font-size: $font-size-base; 87 | line-height: $line-height-base; 88 | color: $input-color; 89 | } 90 | 91 | 92 | // Common form controls 93 | // 94 | // Shared size and type resets for form controls. Apply `.form-control` to any 95 | // of the following form controls: 96 | // 97 | // select 98 | // textarea 99 | // input[type="text"] 100 | // input[type="password"] 101 | // input[type="datetime"] 102 | // input[type="datetime-local"] 103 | // input[type="date"] 104 | // input[type="month"] 105 | // input[type="time"] 106 | // input[type="week"] 107 | // input[type="number"] 108 | // input[type="email"] 109 | // input[type="url"] 110 | // input[type="search"] 111 | // input[type="tel"] 112 | // input[type="color"] 113 | 114 | .form-control { 115 | display: block; 116 | width: 100%; 117 | height: $input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) 118 | padding: $padding-base-vertical $padding-base-horizontal; 119 | font-size: $font-size-base; 120 | line-height: $line-height-base; 121 | color: $input-color; 122 | background-color: $input-bg; 123 | background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 124 | border: 1px solid $input-border; 125 | border-radius: $input-border-radius; 126 | @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); 127 | @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s); 128 | 129 | // Customize the `:focus` state to imitate native WebKit styles. 130 | @include form-control-focus(); 131 | 132 | // Placeholder 133 | @include placeholder(); 134 | 135 | // Disabled and read-only inputs 136 | // 137 | // HTML5 says that controls under a fieldset > legend:first-child won't be 138 | // disabled if the fieldset is disabled. Due to implementation difficulty, we 139 | // don't honor that edge case; we style them as disabled anyway. 140 | &[disabled], 141 | &[readonly], 142 | fieldset[disabled] & { 143 | cursor: not-allowed; 144 | background-color: $input-bg-disabled; 145 | opacity: 1; // iOS fix for unreadable disabled content 146 | } 147 | 148 | // [converter] extracted textarea& to textarea.form-control 149 | } 150 | 151 | // Reset height for `textarea`s 152 | textarea.form-control { 153 | height: auto; 154 | } 155 | 156 | 157 | // Search inputs in iOS 158 | // 159 | // This overrides the extra rounded corners on search inputs in iOS so that our 160 | // `.form-control` class can properly style them. Note that this cannot simply 161 | // be added to `.form-control` as it's not specific enough. For details, see 162 | // https://github.com/twbs/bootstrap/issues/11586. 163 | 164 | input[type="search"] { 165 | -webkit-appearance: none; 166 | } 167 | 168 | 169 | // Special styles for iOS date input 170 | // 171 | // In Mobile Safari, date inputs require a pixel line-height that matches the 172 | // given height of the input. 173 | 174 | input[type="date"] { 175 | line-height: $input-height-base; 176 | } 177 | 178 | 179 | // Form groups 180 | // 181 | // Designed to help with the organization and spacing of vertical forms. For 182 | // horizontal forms, use the predefined grid classes. 183 | 184 | .form-group { 185 | margin-bottom: 15px; 186 | } 187 | 188 | 189 | // Checkboxes and radios 190 | // 191 | // Indent the labels to position radios/checkboxes as hanging controls. 192 | 193 | .radio, 194 | .checkbox { 195 | display: block; 196 | min-height: $line-height-computed; // clear the floating input if there is no label text 197 | margin-top: 10px; 198 | margin-bottom: 10px; 199 | padding-left: 20px; 200 | label { 201 | display: inline; 202 | font-weight: normal; 203 | cursor: pointer; 204 | } 205 | } 206 | .radio input[type="radio"], 207 | .radio-inline input[type="radio"], 208 | .checkbox input[type="checkbox"], 209 | .checkbox-inline input[type="checkbox"] { 210 | float: left; 211 | margin-left: -20px; 212 | } 213 | .radio + .radio, 214 | .checkbox + .checkbox { 215 | margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing 216 | } 217 | 218 | // Radios and checkboxes on same line 219 | .radio-inline, 220 | .checkbox-inline { 221 | display: inline-block; 222 | padding-left: 20px; 223 | margin-bottom: 0; 224 | vertical-align: middle; 225 | font-weight: normal; 226 | cursor: pointer; 227 | } 228 | .radio-inline + .radio-inline, 229 | .checkbox-inline + .checkbox-inline { 230 | margin-top: 0; 231 | margin-left: 10px; // space out consecutive inline controls 232 | } 233 | 234 | // Apply same disabled cursor tweak as for inputs 235 | // 236 | // Note: Neither radios nor checkboxes can be readonly. 237 | input[type="radio"], 238 | input[type="checkbox"], 239 | .radio, 240 | .radio-inline, 241 | .checkbox, 242 | .checkbox-inline { 243 | &[disabled], 244 | fieldset[disabled] & { 245 | cursor: not-allowed; 246 | } 247 | } 248 | 249 | 250 | // Form control sizing 251 | // 252 | // Build on `.form-control` with modifier classes to decrease or increase the 253 | // height and font-size of form controls. 254 | 255 | @include input-size('.input-sm', $input-height-small, $padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $border-radius-small); 256 | 257 | @include input-size('.input-lg', $input-height-large, $padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $border-radius-large); 258 | 259 | 260 | // Form control feedback states 261 | // 262 | // Apply contextual and semantic states to individual form controls. 263 | 264 | .has-feedback { 265 | // Enable absolute positioning 266 | position: relative; 267 | 268 | // Ensure icons don't overlap text 269 | .form-control { 270 | padding-right: ($input-height-base * 1.25); 271 | } 272 | 273 | // Feedback icon (requires .glyphicon classes) 274 | .form-control-feedback { 275 | position: absolute; 276 | top: ($line-height-computed + 5); // Height of the `label` and its margin 277 | right: 0; 278 | display: block; 279 | width: $input-height-base; 280 | height: $input-height-base; 281 | line-height: $input-height-base; 282 | text-align: center; 283 | } 284 | } 285 | 286 | // Feedback states 287 | .has-success { 288 | @include form-control-validation($state-success-text, $state-success-text, $state-success-bg); 289 | } 290 | .has-warning { 291 | @include form-control-validation($state-warning-text, $state-warning-text, $state-warning-bg); 292 | } 293 | .has-error { 294 | @include form-control-validation($state-danger-text, $state-danger-text, $state-danger-bg); 295 | } 296 | 297 | 298 | // Static form control text 299 | // 300 | // Apply class to a `p` element to make any string of text align with labels in 301 | // a horizontal form layout. 302 | 303 | .form-control-static { 304 | margin-bottom: 0; // Remove default margin from `p` 305 | } 306 | 307 | 308 | // Help text 309 | // 310 | // Apply to any element you wish to create light text for placement immediately 311 | // below a form control. Use for general help, formatting, or instructional text. 312 | 313 | .help-block { 314 | display: block; // account for any element using help-block 315 | margin-top: 5px; 316 | margin-bottom: 10px; 317 | color: lighten($text-color, 25%); // lighten the text some for contrast 318 | } 319 | 320 | 321 | 322 | // Inline forms 323 | // 324 | // Make forms appear inline(-block) by adding the `.form-inline` class. Inline 325 | // forms begin stacked on extra small (mobile) devices and then go inline when 326 | // viewports reach <768px. 327 | // 328 | // Requires wrapping inputs and labels with `.form-group` for proper display of 329 | // default HTML form controls and our custom form controls (e.g., input groups). 330 | // 331 | // Heads up! This is mixin-ed into `.navbar-form` in navbars.less. 332 | 333 | .form-inline { 334 | 335 | // Kick in the inline 336 | @media (min-width: $screen-sm-min) { 337 | // Inline-block all the things for "inline" 338 | .form-group { 339 | display: inline-block; 340 | margin-bottom: 0; 341 | vertical-align: middle; 342 | } 343 | 344 | // In navbar-form, allow folks to *not* use `.form-group` 345 | .form-control { 346 | display: inline-block; 347 | width: auto; // Prevent labels from stacking above inputs in `.form-group` 348 | vertical-align: middle; 349 | } 350 | // Input groups need that 100% width though 351 | .input-group > .form-control { 352 | width: 100%; 353 | } 354 | 355 | .control-label { 356 | margin-bottom: 0; 357 | vertical-align: middle; 358 | } 359 | 360 | // Remove default margin on radios/checkboxes that were used for stacking, and 361 | // then undo the floating of radios and checkboxes to match (which also avoids 362 | // a bug in WebKit: https://github.com/twbs/bootstrap/issues/1969). 363 | .radio, 364 | .checkbox { 365 | display: inline-block; 366 | margin-top: 0; 367 | margin-bottom: 0; 368 | padding-left: 0; 369 | vertical-align: middle; 370 | } 371 | .radio input[type="radio"], 372 | .checkbox input[type="checkbox"] { 373 | float: none; 374 | margin-left: 0; 375 | } 376 | 377 | // Validation states 378 | // 379 | // Reposition the icon because it's now within a grid column and columns have 380 | // `position: relative;` on them. Also accounts for the grid gutter padding. 381 | .has-feedback .form-control-feedback { 382 | top: 0; 383 | } 384 | } 385 | } 386 | 387 | 388 | // Horizontal forms 389 | // 390 | // Horizontal forms are built on grid classes and allow you to create forms with 391 | // labels on the left and inputs on the right. 392 | 393 | .form-horizontal { 394 | 395 | // Consistent vertical alignment of labels, radios, and checkboxes 396 | .control-label, 397 | .radio, 398 | .checkbox, 399 | .radio-inline, 400 | .checkbox-inline { 401 | margin-top: 0; 402 | margin-bottom: 0; 403 | padding-top: ($padding-base-vertical + 1); // Default padding plus a border 404 | } 405 | // Account for padding we're adding to ensure the alignment and of help text 406 | // and other content below items 407 | .radio, 408 | .checkbox { 409 | min-height: ($line-height-computed + ($padding-base-vertical + 1)); 410 | } 411 | 412 | // Make form groups behave like rows 413 | .form-group { 414 | @include make-row(); 415 | } 416 | 417 | .form-control-static { 418 | padding-top: ($padding-base-vertical + 1); 419 | } 420 | 421 | // Only right align form labels here when the columns stop stacking 422 | @media (min-width: $screen-sm-min) { 423 | .control-label { 424 | text-align: right; 425 | } 426 | } 427 | 428 | // Validation states 429 | // 430 | // Reposition the icon because it's now within a grid column and columns have 431 | // `position: relative;` on them. Also accounts for the grid gutter padding. 432 | .has-feedback .form-control-feedback { 433 | top: 0; 434 | right: ($grid-gutter-width / 2); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /styles/bootstrap/_grid.scss: -------------------------------------------------------------------------------- 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 | @include 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 | @include container-fixed(); 32 | } 33 | 34 | 35 | // Row 36 | // 37 | // Rows contain and clear the floats of your columns. 38 | 39 | .row { 40 | @include make-row(); 41 | } 42 | 43 | 44 | // Columns 45 | // 46 | // Common styles for small and large grid columns 47 | 48 | @include 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 | @include make-grid(xs); 57 | 58 | 59 | // Small grid 60 | // 61 | // Columns, offsets, pushes, and pulls for the small device range, from phones 62 | // to tablets. 63 | 64 | @media (min-width: $screen-sm-min) { 65 | @include make-grid(sm); 66 | } 67 | 68 | 69 | // Medium grid 70 | // 71 | // Columns, offsets, pushes, and pulls for the desktop device range. 72 | 73 | @media (min-width: $screen-md-min) { 74 | @include make-grid(md); 75 | } 76 | 77 | 78 | // Large grid 79 | // 80 | // Columns, offsets, pushes, and pulls for the large desktop device range. 81 | 82 | @media (min-width: $screen-lg-min) { 83 | @include make-grid(lg); 84 | } 85 | -------------------------------------------------------------------------------- /styles/bootstrap/_input-groups.scss: -------------------------------------------------------------------------------- 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 | // Ensure that the input is always above the *appended* addon button for 21 | // proper border colors. 22 | position: relative; 23 | z-index: 2; 24 | 25 | // IE9 fubars the placeholder attribute in text inputs and the arrows on 26 | // select elements in input groups. To fix it, we float the input. Details: 27 | // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855 28 | float: left; 29 | 30 | width: 100%; 31 | margin-bottom: 0; 32 | } 33 | } 34 | 35 | // Sizing options 36 | // 37 | // Remix the default form control sizing classes into new ones for easier 38 | // manipulation. 39 | 40 | .input-group-lg > .form-control, 41 | .input-group-lg > .input-group-addon, 42 | .input-group-lg > .input-group-btn > .btn { @extend .input-lg; } 43 | .input-group-sm > .form-control, 44 | .input-group-sm > .input-group-addon, 45 | .input-group-sm > .input-group-btn > .btn { @extend .input-sm; } 46 | 47 | 48 | // Display as table-cell 49 | // ------------------------- 50 | .input-group-addon, 51 | .input-group-btn, 52 | .input-group .form-control { 53 | display: table-cell; 54 | 55 | &:not(:first-child):not(:last-child) { 56 | border-radius: 0; 57 | } 58 | } 59 | // Addon and addon wrapper for buttons 60 | .input-group-addon, 61 | .input-group-btn { 62 | width: 1%; 63 | white-space: nowrap; 64 | vertical-align: middle; // Match the inputs 65 | } 66 | 67 | // Text input groups 68 | // ------------------------- 69 | .input-group-addon { 70 | padding: $padding-base-vertical $padding-base-horizontal; 71 | font-size: $font-size-base; 72 | font-weight: normal; 73 | line-height: 1; 74 | color: $input-color; 75 | text-align: center; 76 | background-color: $input-group-addon-bg; 77 | border: 1px solid $input-group-addon-border-color; 78 | border-radius: $border-radius-base; 79 | 80 | // Sizing 81 | &.input-sm { 82 | padding: $padding-small-vertical $padding-small-horizontal; 83 | font-size: $font-size-small; 84 | border-radius: $border-radius-small; 85 | } 86 | &.input-lg { 87 | padding: $padding-large-vertical $padding-large-horizontal; 88 | font-size: $font-size-large; 89 | border-radius: $border-radius-large; 90 | } 91 | 92 | // Nuke default margins from checkboxes and radios to vertically center within. 93 | input[type="radio"], 94 | input[type="checkbox"] { 95 | margin-top: 0; 96 | } 97 | } 98 | 99 | // Reset rounded corners 100 | .input-group .form-control:first-child, 101 | .input-group-addon:first-child, 102 | .input-group-btn:first-child > .btn, 103 | .input-group-btn:first-child > .btn-group > .btn, 104 | .input-group-btn:first-child > .dropdown-toggle, 105 | .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), 106 | .input-group-btn:last-child > .btn-group:not(:last-child) > .btn { 107 | @include border-right-radius(0); 108 | } 109 | .input-group-addon:first-child { 110 | border-right: 0; 111 | } 112 | .input-group .form-control:last-child, 113 | .input-group-addon:last-child, 114 | .input-group-btn:last-child > .btn, 115 | .input-group-btn:last-child > .btn-group > .btn, 116 | .input-group-btn:last-child > .dropdown-toggle, 117 | .input-group-btn:first-child > .btn:not(:first-child), 118 | .input-group-btn:first-child > .btn-group:not(:first-child) > .btn { 119 | @include border-left-radius(0); 120 | } 121 | .input-group-addon:last-child { 122 | border-left: 0; 123 | } 124 | 125 | // Button input groups 126 | // ------------------------- 127 | .input-group-btn { 128 | position: relative; 129 | // Jankily prevent input button groups from wrapping with `white-space` and 130 | // `font-size` in combination with `inline-block` on buttons. 131 | font-size: 0; 132 | white-space: nowrap; 133 | 134 | // Negative margin for spacing, position for bringing hovered/focused/actived 135 | // element above the siblings. 136 | > .btn { 137 | position: relative; 138 | + .btn { 139 | margin-left: -1px; 140 | } 141 | // Bring the "active" button to the front 142 | &:hover, 143 | &:focus, 144 | &:active { 145 | z-index: 2; 146 | } 147 | } 148 | 149 | // Negative margin to only have a 1px border between the two 150 | &:first-child { 151 | > .btn, 152 | > .btn-group { 153 | margin-right: -1px; 154 | } 155 | } 156 | &:last-child { 157 | > .btn, 158 | > .btn-group { 159 | margin-left: -1px; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /styles/bootstrap/_jumbotron.scss: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_labels.scss: -------------------------------------------------------------------------------- 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 | @include label-variant($label-default-bg); 44 | } 45 | 46 | .label-primary { 47 | @include label-variant($label-primary-bg); 48 | } 49 | 50 | .label-success { 51 | @include label-variant($label-success-bg); 52 | } 53 | 54 | .label-info { 55 | @include label-variant($label-info-bg); 56 | } 57 | 58 | .label-warning { 59 | @include label-variant($label-warning-bg); 60 | } 61 | 62 | .label-danger { 63 | @include label-variant($label-danger-bg); 64 | } 65 | -------------------------------------------------------------------------------- /styles/bootstrap/_list-group.scss: -------------------------------------------------------------------------------- 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 | @include border-top-radius($list-group-border-radius); 33 | } 34 | &:last-child { 35 | margin-bottom: 0; 36 | @include 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 | @include list-group-item-variant(success, $state-success-bg, $state-success-text); 94 | @include list-group-item-variant(info, $state-info-bg, $state-info-text); 95 | @include list-group-item-variant(warning, $state-warning-bg, $state-warning-text); 96 | @include 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_media.scss: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_modals.scss: -------------------------------------------------------------------------------- 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 | @include translate(0, -25%); 35 | @include transition-transform(0.3s ease-out); 36 | } 37 | &.in .modal-dialog { @include 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 | @include 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 { @include opacity(0); } 71 | &.in { @include 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 | @include clearfix(); // 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 | // Automatically set modal's width for larger viewports 125 | .modal-dialog { 126 | width: $modal-md; 127 | margin: 30px auto; 128 | } 129 | .modal-content { 130 | @include box-shadow(0 5px 15px rgba(0,0,0,.5)); 131 | } 132 | 133 | // Modal sizes 134 | .modal-sm { width: $modal-sm; } 135 | } 136 | 137 | @media (min-width: $screen-md-min) { 138 | .modal-lg { width: $modal-lg; } 139 | } 140 | -------------------------------------------------------------------------------- /styles/bootstrap/_navs.scss: -------------------------------------------------------------------------------- 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 | @include clearfix(); 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 | @include 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 | @extend .nav-justified; 110 | @extend .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 | @include border-top-radius(0); 242 | } 243 | -------------------------------------------------------------------------------- /styles/bootstrap/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.0 | MIT License | git.io/normalize */ 2 | 3 | // 4 | // 1. Set default font family to sans-serif. 5 | // 2. Prevent iOS text size adjust after orientation change, without disabling 6 | // user zoom. 7 | // 8 | 9 | html { 10 | font-family: sans-serif; // 1 11 | -ms-text-size-adjust: 100%; // 2 12 | -webkit-text-size-adjust: 100%; // 2 13 | } 14 | 15 | // 16 | // Remove default margin. 17 | // 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | // HTML5 display definitions 24 | // ========================================================================== 25 | 26 | // 27 | // Correct `block` display not defined in IE 8/9. 28 | // 29 | 30 | article, 31 | aside, 32 | details, 33 | figcaption, 34 | figure, 35 | footer, 36 | header, 37 | hgroup, 38 | main, 39 | nav, 40 | section, 41 | summary { 42 | display: block; 43 | } 44 | 45 | // 46 | // 1. Correct `inline-block` display not defined in IE 8/9. 47 | // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 48 | // 49 | 50 | audio, 51 | canvas, 52 | progress, 53 | video { 54 | display: inline-block; // 1 55 | vertical-align: baseline; // 2 56 | } 57 | 58 | // 59 | // Prevent modern browsers from displaying `audio` without controls. 60 | // Remove excess height in iOS 5 devices. 61 | // 62 | 63 | audio:not([controls]) { 64 | display: none; 65 | height: 0; 66 | } 67 | 68 | // 69 | // Address `[hidden]` styling not present in IE 8/9. 70 | // Hide the `template` element in IE, Safari, and Firefox < 22. 71 | // 72 | 73 | [hidden], 74 | template { 75 | display: none; 76 | } 77 | 78 | // Links 79 | // ========================================================================== 80 | 81 | // 82 | // Remove the gray background color from active links in IE 10. 83 | // 84 | 85 | a { 86 | background: transparent; 87 | } 88 | 89 | // 90 | // Improve readability when focused and also mouse hovered in all browsers. 91 | // 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | // Text-level semantics 99 | // ========================================================================== 100 | 101 | // 102 | // Address styling not present in IE 8/9, Safari 5, and Chrome. 103 | // 104 | 105 | abbr[title] { 106 | border-bottom: 1px dotted; 107 | } 108 | 109 | // 110 | // Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 111 | // 112 | 113 | b, 114 | strong { 115 | font-weight: bold; 116 | } 117 | 118 | // 119 | // Address styling not present in Safari 5 and Chrome. 120 | // 121 | 122 | dfn { 123 | font-style: italic; 124 | } 125 | 126 | // 127 | // Address variable `h1` font-size and margin within `section` and `article` 128 | // contexts in Firefox 4+, Safari 5, and Chrome. 129 | // 130 | 131 | h1 { 132 | font-size: 2em; 133 | margin: 0.67em 0; 134 | } 135 | 136 | // 137 | // Address styling not present in IE 8/9. 138 | // 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | // 146 | // Address inconsistent and variable font size in all browsers. 147 | // 148 | 149 | small { 150 | font-size: 80%; 151 | } 152 | 153 | // 154 | // Prevent `sub` and `sup` affecting `line-height` in all browsers. 155 | // 156 | 157 | sub, 158 | sup { 159 | font-size: 75%; 160 | line-height: 0; 161 | position: relative; 162 | vertical-align: baseline; 163 | } 164 | 165 | sup { 166 | top: -0.5em; 167 | } 168 | 169 | sub { 170 | bottom: -0.25em; 171 | } 172 | 173 | // Embedded content 174 | // ========================================================================== 175 | 176 | // 177 | // Remove border when inside `a` element in IE 8/9. 178 | // 179 | 180 | img { 181 | border: 0; 182 | } 183 | 184 | // 185 | // Correct overflow displayed oddly in IE 9. 186 | // 187 | 188 | svg:not(:root) { 189 | overflow: hidden; 190 | } 191 | 192 | // Grouping content 193 | // ========================================================================== 194 | 195 | // 196 | // Address margin not present in IE 8/9 and Safari 5. 197 | // 198 | 199 | figure { 200 | margin: 1em 40px; 201 | } 202 | 203 | // 204 | // Address differences between Firefox and other browsers. 205 | // 206 | 207 | hr { 208 | -moz-box-sizing: content-box; 209 | box-sizing: content-box; 210 | height: 0; 211 | } 212 | 213 | // 214 | // Contain overflow in all browsers. 215 | // 216 | 217 | pre { 218 | overflow: auto; 219 | } 220 | 221 | // 222 | // Address odd `em`-unit font size rendering in all browsers. 223 | // 224 | 225 | code, 226 | kbd, 227 | pre, 228 | samp { 229 | font-family: monospace, monospace; 230 | font-size: 1em; 231 | } 232 | 233 | // Forms 234 | // ========================================================================== 235 | 236 | // 237 | // Known limitation: by default, Chrome and Safari on OS X allow very limited 238 | // styling of `select`, unless a `border` property is set. 239 | // 240 | 241 | // 242 | // 1. Correct color not being inherited. 243 | // Known issue: affects color of disabled elements. 244 | // 2. Correct font properties not being inherited. 245 | // 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 246 | // 247 | 248 | button, 249 | input, 250 | optgroup, 251 | select, 252 | textarea { 253 | color: inherit; // 1 254 | font: inherit; // 2 255 | margin: 0; // 3 256 | } 257 | 258 | // 259 | // Address `overflow` set to `hidden` in IE 8/9/10. 260 | // 261 | 262 | button { 263 | overflow: visible; 264 | } 265 | 266 | // 267 | // Address inconsistent `text-transform` inheritance for `button` and `select`. 268 | // All other form control elements do not inherit `text-transform` values. 269 | // Correct `button` style inheritance in Firefox, IE 8+, and Opera 270 | // Correct `select` style inheritance in Firefox. 271 | // 272 | 273 | button, 274 | select { 275 | text-transform: none; 276 | } 277 | 278 | // 279 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 280 | // and `video` controls. 281 | // 2. Correct inability to style clickable `input` types in iOS. 282 | // 3. Improve usability and consistency of cursor style between image-type 283 | // `input` and others. 284 | // 285 | 286 | button, 287 | html input[type="button"], // 1 288 | input[type="reset"], 289 | input[type="submit"] { 290 | -webkit-appearance: button; // 2 291 | cursor: pointer; // 3 292 | } 293 | 294 | // 295 | // Re-set default cursor for disabled elements. 296 | // 297 | 298 | button[disabled], 299 | html input[disabled] { 300 | cursor: default; 301 | } 302 | 303 | // 304 | // Remove inner padding and border in Firefox 4+. 305 | // 306 | 307 | button::-moz-focus-inner, 308 | input::-moz-focus-inner { 309 | border: 0; 310 | padding: 0; 311 | } 312 | 313 | // 314 | // Address Firefox 4+ setting `line-height` on `input` using `!important` in 315 | // the UA stylesheet. 316 | // 317 | 318 | input { 319 | line-height: normal; 320 | } 321 | 322 | // 323 | // It's recommended that you don't attempt to style these elements. 324 | // Firefox's implementation doesn't respect box-sizing, padding, or width. 325 | // 326 | // 1. Address box sizing set to `content-box` in IE 8/9/10. 327 | // 2. Remove excess padding in IE 8/9/10. 328 | // 329 | 330 | input[type="checkbox"], 331 | input[type="radio"] { 332 | box-sizing: border-box; // 1 333 | padding: 0; // 2 334 | } 335 | 336 | // 337 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain 338 | // `font-size` values of the `input`, it causes the cursor style of the 339 | // decrement button to change from `default` to `text`. 340 | // 341 | 342 | input[type="number"]::-webkit-inner-spin-button, 343 | input[type="number"]::-webkit-outer-spin-button { 344 | height: auto; 345 | } 346 | 347 | // 348 | // 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 349 | // 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 350 | // (include `-moz` to future-proof). 351 | // 352 | 353 | input[type="search"] { 354 | -webkit-appearance: textfield; // 1 355 | -moz-box-sizing: content-box; 356 | -webkit-box-sizing: content-box; // 2 357 | box-sizing: content-box; 358 | } 359 | 360 | // 361 | // Remove inner padding and search cancel button in Safari and Chrome on OS X. 362 | // Safari (but not Chrome) clips the cancel button when the search input has 363 | // padding (and `textfield` appearance). 364 | // 365 | 366 | input[type="search"]::-webkit-search-cancel-button, 367 | input[type="search"]::-webkit-search-decoration { 368 | -webkit-appearance: none; 369 | } 370 | 371 | // 372 | // Define consistent border, margin, and padding. 373 | // 374 | 375 | fieldset { 376 | border: 1px solid #c0c0c0; 377 | margin: 0 2px; 378 | padding: 0.35em 0.625em 0.75em; 379 | } 380 | 381 | // 382 | // 1. Correct `color` not being inherited in IE 8/9. 383 | // 2. Remove padding so people aren't caught out if they zero out fieldsets. 384 | // 385 | 386 | legend { 387 | border: 0; // 1 388 | padding: 0; // 2 389 | } 390 | 391 | // 392 | // Remove default vertical scrollbar in IE 8/9. 393 | // 394 | 395 | textarea { 396 | overflow: auto; 397 | } 398 | 399 | // 400 | // Don't inherit the `font-weight` (applied by a rule above). 401 | // NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 402 | // 403 | 404 | optgroup { 405 | font-weight: bold; 406 | } 407 | 408 | // Tables 409 | // ========================================================================== 410 | 411 | // 412 | // Remove most spacing between table cells. 413 | // 414 | 415 | table { 416 | border-collapse: collapse; 417 | border-spacing: 0; 418 | } 419 | 420 | td, 421 | th { 422 | padding: 0; 423 | } -------------------------------------------------------------------------------- /styles/bootstrap/_pager.scss: -------------------------------------------------------------------------------- 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 | @include clearfix(); 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_pagination.scss: -------------------------------------------------------------------------------- 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 | @include border-left-radius($border-radius-base); 29 | } 30 | } 31 | &:last-child { 32 | > a, 33 | > span { 34 | @include 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 | @include pagination-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $border-radius-large); 83 | } 84 | 85 | // Small 86 | .pagination-sm { 87 | @include pagination-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $border-radius-small); 88 | } 89 | -------------------------------------------------------------------------------- /styles/bootstrap/_panels.scss: -------------------------------------------------------------------------------- 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 | @include box-shadow(0 1px 1px rgba(0,0,0,.05)); 13 | } 14 | 15 | // Panel contents 16 | .panel-body { 17 | padding: $panel-body-padding; 18 | @include clearfix(); 19 | } 20 | 21 | // Optional heading 22 | .panel-heading { 23 | padding: 10px 15px; 24 | border-bottom: 1px solid transparent; 25 | @include border-top-radius(($panel-border-radius - 1)); 26 | 27 | > .dropdown .dropdown-toggle { 28 | color: inherit; 29 | } 30 | } 31 | 32 | // Within heading, strip any `h*` tag of its default margins for spacing. 33 | .panel-title { 34 | margin-top: 0; 35 | margin-bottom: 0; 36 | font-size: ceil(($font-size-base * 1.125)); 37 | color: inherit; 38 | 39 | > a { 40 | color: inherit; 41 | } 42 | } 43 | 44 | // Optional footer (stays gray in every modifier class) 45 | .panel-footer { 46 | padding: 10px 15px; 47 | background-color: $panel-footer-bg; 48 | border-top: 1px solid $panel-inner-border; 49 | @include border-bottom-radius(($panel-border-radius - 1)); 50 | } 51 | 52 | 53 | // List groups in panels 54 | // 55 | // By default, space out list group content from panel headings to account for 56 | // any kind of custom content between the two. 57 | 58 | .panel { 59 | > .list-group { 60 | margin-bottom: 0; 61 | 62 | .list-group-item { 63 | border-width: 1px 0; 64 | border-radius: 0; 65 | } 66 | 67 | // Add border top radius for first one 68 | &:first-child { 69 | .list-group-item:first-child { 70 | border-top: 0; 71 | @include border-top-radius(($panel-border-radius - 1)); 72 | } 73 | } 74 | // Add border bottom radius for last one 75 | &:last-child { 76 | .list-group-item:last-child { 77 | border-bottom: 0; 78 | @include border-bottom-radius(($panel-border-radius - 1)); 79 | } 80 | } 81 | } 82 | } 83 | // Collapse space between when there's no additional content. 84 | .panel-heading + .list-group { 85 | .list-group-item:first-child { 86 | border-top-width: 0; 87 | } 88 | } 89 | 90 | 91 | // Tables in panels 92 | // 93 | // Place a non-bordered `.table` within a panel (not within a `.panel-body`) and 94 | // watch it go full width. 95 | 96 | .panel { 97 | > .table, 98 | > .table-responsive > .table { 99 | margin-bottom: 0; 100 | } 101 | // Add border top radius for first one 102 | > .table:first-child, 103 | > .table-responsive:first-child > .table:first-child { 104 | @include border-top-radius(($panel-border-radius - 1)); 105 | 106 | > thead:first-child, 107 | > tbody:first-child { 108 | > tr:first-child { 109 | td:first-child, 110 | th:first-child { 111 | border-top-left-radius: ($panel-border-radius - 1); 112 | } 113 | td:last-child, 114 | th:last-child { 115 | border-top-right-radius: ($panel-border-radius - 1); 116 | } 117 | } 118 | } 119 | } 120 | // Add border bottom radius for last one 121 | > .table:last-child, 122 | > .table-responsive:last-child > .table:last-child { 123 | @include border-bottom-radius(($panel-border-radius - 1)); 124 | 125 | > tbody:last-child, 126 | > tfoot:last-child { 127 | > tr:last-child { 128 | td:first-child, 129 | th:first-child { 130 | border-bottom-left-radius: ($panel-border-radius - 1); 131 | } 132 | td:last-child, 133 | th:last-child { 134 | border-bottom-right-radius: ($panel-border-radius - 1); 135 | } 136 | } 137 | } 138 | } 139 | > .panel-body + .table, 140 | > .panel-body + .table-responsive { 141 | border-top: 1px solid $table-border-color; 142 | } 143 | > .table > tbody:first-child > tr:first-child th, 144 | > .table > tbody:first-child > tr:first-child td { 145 | border-top: 0; 146 | } 147 | > .table-bordered, 148 | > .table-responsive > .table-bordered { 149 | border: 0; 150 | > thead, 151 | > tbody, 152 | > tfoot { 153 | > tr { 154 | > th:first-child, 155 | > td:first-child { 156 | border-left: 0; 157 | } 158 | > th:last-child, 159 | > td:last-child { 160 | border-right: 0; 161 | } 162 | } 163 | } 164 | > thead, 165 | > tbody { 166 | > tr:first-child { 167 | > td, 168 | > th { 169 | border-bottom: 0; 170 | } 171 | } 172 | } 173 | > tbody, 174 | > tfoot { 175 | > tr:last-child { 176 | > td, 177 | > th { 178 | border-bottom: 0; 179 | } 180 | } 181 | } 182 | } 183 | > .table-responsive { 184 | border: 0; 185 | margin-bottom: 0; 186 | } 187 | } 188 | 189 | 190 | // Collapsable panels (aka, accordion) 191 | // 192 | // Wrap a series of panels in `.panel-group` to turn them into an accordion with 193 | // the help of our collapse JavaScript plugin. 194 | 195 | .panel-group { 196 | margin-bottom: $line-height-computed; 197 | 198 | // Tighten up margin so it's only between panels 199 | .panel { 200 | margin-bottom: 0; 201 | border-radius: $panel-border-radius; 202 | overflow: hidden; // crop contents when collapsed 203 | + .panel { 204 | margin-top: 5px; 205 | } 206 | } 207 | 208 | .panel-heading { 209 | border-bottom: 0; 210 | + .panel-collapse .panel-body { 211 | border-top: 1px solid $panel-inner-border; 212 | } 213 | } 214 | .panel-footer { 215 | border-top: 0; 216 | + .panel-collapse .panel-body { 217 | border-bottom: 1px solid $panel-inner-border; 218 | } 219 | } 220 | } 221 | 222 | 223 | // Contextual variations 224 | .panel-default { 225 | @include panel-variant($panel-default-border, $panel-default-text, $panel-default-heading-bg, $panel-default-border); 226 | } 227 | .panel-primary { 228 | @include panel-variant($panel-primary-border, $panel-primary-text, $panel-primary-heading-bg, $panel-primary-border); 229 | } 230 | .panel-success { 231 | @include panel-variant($panel-success-border, $panel-success-text, $panel-success-heading-bg, $panel-success-border); 232 | } 233 | .panel-info { 234 | @include panel-variant($panel-info-border, $panel-info-text, $panel-info-heading-bg, $panel-info-border); 235 | } 236 | .panel-warning { 237 | @include panel-variant($panel-warning-border, $panel-warning-text, $panel-warning-heading-bg, $panel-warning-border); 238 | } 239 | .panel-danger { 240 | @include panel-variant($panel-danger-border, $panel-danger-text, $panel-danger-heading-bg, $panel-danger-border); 241 | } 242 | -------------------------------------------------------------------------------- /styles/bootstrap/_popovers.scss: -------------------------------------------------------------------------------- 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 | @include 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: -$popover-arrow-width; } 27 | &.right { margin-left: $popover-arrow-width; } 28 | &.bottom { margin-top: $popover-arrow-width; } 29 | &.left { margin-left: -$popover-arrow-width; } 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_print.scss: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_progress-bars.scss: -------------------------------------------------------------------------------- 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 | @include 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 | @include box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); 47 | @include transition(width .6s ease); 48 | } 49 | 50 | // Striped bars 51 | .progress-striped .progress-bar { 52 | @include gradient-striped(); 53 | background-size: 40px 40px; 54 | } 55 | 56 | // Call animation for the active one 57 | .progress.active .progress-bar { 58 | @include animation(progress-bar-stripes 2s linear infinite); 59 | } 60 | 61 | 62 | 63 | // Variations 64 | // ------------------------- 65 | 66 | .progress-bar-success { 67 | @include progress-bar-variant($progress-bar-success-bg); 68 | } 69 | 70 | .progress-bar-info { 71 | @include progress-bar-variant($progress-bar-info-bg); 72 | } 73 | 74 | .progress-bar-warning { 75 | @include progress-bar-variant($progress-bar-warning-bg); 76 | } 77 | 78 | .progress-bar-danger { 79 | @include progress-bar-variant($progress-bar-danger-bg); 80 | } 81 | -------------------------------------------------------------------------------- /styles/bootstrap/_responsive-utilities.scss: -------------------------------------------------------------------------------- 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 | 27 | @include responsive-invisibility('.visible-xs, .visible-sm, .visible-md, .visible-lg'); 28 | 29 | @media (max-width: $screen-xs-max) { 30 | @include responsive-visibility('.visible-xs'); 31 | } 32 | 33 | @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { 34 | @include responsive-visibility('.visible-sm'); 35 | } 36 | 37 | @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { 38 | @include responsive-visibility('.visible-md'); 39 | } 40 | 41 | @media (min-width: $screen-lg-min) { 42 | @include responsive-visibility('.visible-lg'); 43 | } 44 | 45 | @media (max-width: $screen-xs-max) { 46 | @include responsive-invisibility('.hidden-xs'); 47 | } 48 | 49 | @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { 50 | @include responsive-invisibility('.hidden-sm'); 51 | } 52 | 53 | @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { 54 | @include responsive-invisibility('.hidden-md'); 55 | } 56 | 57 | @media (min-width: $screen-lg-min) { 58 | @include responsive-invisibility('.hidden-lg'); 59 | } 60 | 61 | 62 | // Print utilities 63 | // 64 | // Media queries are placed on the inside to be mixin-friendly. 65 | 66 | @include responsive-invisibility('.visible-print'); 67 | 68 | @media print { 69 | @include responsive-visibility('.visible-print'); 70 | } 71 | 72 | @media print { 73 | @include responsive-invisibility('.hidden-print'); 74 | } 75 | -------------------------------------------------------------------------------- /styles/bootstrap/_scaffolding.scss: -------------------------------------------------------------------------------- 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 | @include box-sizing(border-box); 13 | } 14 | *:before, 15 | *:after { 16 | @include 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 | @include 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 | @include 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 | @include transition(all .2s ease-in-out); 100 | 101 | // Keep them at most 100% wide 102 | @include 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_tables.scss: -------------------------------------------------------------------------------- 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 | @include table-row-variant('active', $table-bg-active); 159 | @include table-row-variant('success', $state-success-bg); 160 | @include table-row-variant('info', $state-info-bg); 161 | @include table-row-variant('warning', $state-warning-bg); 162 | @include 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_theme.scss: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // Load core variables and mixins 4 | // -------------------------------------------------- 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | 9 | 10 | 11 | // 12 | // Buttons 13 | // -------------------------------------------------- 14 | 15 | // Common styles 16 | .btn-default, 17 | .btn-primary, 18 | .btn-success, 19 | .btn-info, 20 | .btn-warning, 21 | .btn-danger { 22 | text-shadow: 0 -1px 0 rgba(0,0,0,.2); 23 | $shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075); 24 | @include box-shadow($shadow); 25 | 26 | // Reset the shadow 27 | &:active, 28 | &.active { 29 | @include box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); 30 | } 31 | } 32 | 33 | // Mixin for generating new styles 34 | @mixin btn-styles($btn-color: #555) { 35 | @include gradient-vertical($start-color: $btn-color, $end-color: darken($btn-color, 12%)); 36 | @include reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners 37 | background-repeat: repeat-x; 38 | border-color: darken($btn-color, 14%); 39 | 40 | &:hover, 41 | &:focus { 42 | background-color: darken($btn-color, 12%); 43 | background-position: 0 -15px; 44 | } 45 | 46 | &:active, 47 | &.active { 48 | background-color: darken($btn-color, 12%); 49 | border-color: darken($btn-color, 14%); 50 | } 51 | } 52 | 53 | // Common styles 54 | .btn { 55 | // Remove the gradient for the pressed/active state 56 | &:active, 57 | &.active { 58 | background-image: none; 59 | } 60 | } 61 | 62 | // Apply the mixin to the buttons 63 | .btn-default { @include btn-styles($btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; } 64 | .btn-primary { @include btn-styles($btn-primary-bg); } 65 | .btn-success { @include btn-styles($btn-success-bg); } 66 | .btn-info { @include btn-styles($btn-info-bg); } 67 | .btn-warning { @include btn-styles($btn-warning-bg); } 68 | .btn-danger { @include btn-styles($btn-danger-bg); } 69 | 70 | 71 | 72 | // 73 | // Images 74 | // -------------------------------------------------- 75 | 76 | .thumbnail, 77 | .img-thumbnail { 78 | @include box-shadow(0 1px 2px rgba(0,0,0,.075)); 79 | } 80 | 81 | 82 | 83 | // 84 | // Dropdowns 85 | // -------------------------------------------------- 86 | 87 | .dropdown-menu > li > a:hover, 88 | .dropdown-menu > li > a:focus { 89 | @include gradient-vertical($start-color: $dropdown-link-hover-bg, $end-color: darken($dropdown-link-hover-bg, 5%)); 90 | background-color: darken($dropdown-link-hover-bg, 5%); 91 | } 92 | .dropdown-menu > .active > a, 93 | .dropdown-menu > .active > a:hover, 94 | .dropdown-menu > .active > a:focus { 95 | @include gradient-vertical($start-color: $dropdown-link-active-bg, $end-color: darken($dropdown-link-active-bg, 5%)); 96 | background-color: darken($dropdown-link-active-bg, 5%); 97 | } 98 | 99 | 100 | 101 | // 102 | // Navbar 103 | // -------------------------------------------------- 104 | 105 | // Default navbar 106 | .navbar-default { 107 | @include gradient-vertical($start-color: lighten($navbar-default-bg, 10%), $end-color: $navbar-default-bg); 108 | @include reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered 109 | border-radius: $navbar-border-radius; 110 | $shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075); 111 | @include box-shadow($shadow); 112 | 113 | .navbar-nav > .active > a { 114 | @include gradient-vertical($start-color: darken($navbar-default-bg, 5%), $end-color: darken($navbar-default-bg, 2%)); 115 | @include box-shadow(inset 0 3px 9px rgba(0,0,0,.075)); 116 | } 117 | } 118 | .navbar-brand, 119 | .navbar-nav > li > a { 120 | text-shadow: 0 1px 0 rgba(255,255,255,.25); 121 | } 122 | 123 | // Inverted navbar 124 | .navbar-inverse { 125 | @include gradient-vertical($start-color: lighten($navbar-inverse-bg, 10%), $end-color: $navbar-inverse-bg); 126 | @include reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered 127 | 128 | .navbar-nav > .active > a { 129 | @include gradient-vertical($start-color: $navbar-inverse-bg, $end-color: lighten($navbar-inverse-bg, 2.5%)); 130 | @include box-shadow(inset 0 3px 9px rgba(0,0,0,.25)); 131 | } 132 | 133 | .navbar-brand, 134 | .navbar-nav > li > a { 135 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 136 | } 137 | } 138 | 139 | // Undo rounded corners in static and fixed navbars 140 | .navbar-static-top, 141 | .navbar-fixed-top, 142 | .navbar-fixed-bottom { 143 | border-radius: 0; 144 | } 145 | 146 | 147 | 148 | // 149 | // Alerts 150 | // -------------------------------------------------- 151 | 152 | // Common styles 153 | .alert { 154 | text-shadow: 0 1px 0 rgba(255,255,255,.2); 155 | $shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05); 156 | @include box-shadow($shadow); 157 | } 158 | 159 | // Mixin for generating new styles 160 | @mixin alert-styles($color) { 161 | @include gradient-vertical($start-color: $color, $end-color: darken($color, 7.5%)); 162 | border-color: darken($color, 15%); 163 | } 164 | 165 | // Apply the mixin to the alerts 166 | .alert-success { @include alert-styles($alert-success-bg); } 167 | .alert-info { @include alert-styles($alert-info-bg); } 168 | .alert-warning { @include alert-styles($alert-warning-bg); } 169 | .alert-danger { @include alert-styles($alert-danger-bg); } 170 | 171 | 172 | 173 | // 174 | // Progress bars 175 | // -------------------------------------------------- 176 | 177 | // Give the progress background some depth 178 | .progress { 179 | @include gradient-vertical($start-color: darken($progress-bg, 4%), $end-color: $progress-bg) 180 | } 181 | 182 | // Mixin for generating new styles 183 | @mixin progress-bar-styles($color) { 184 | @include gradient-vertical($start-color: $color, $end-color: darken($color, 10%)); 185 | } 186 | 187 | // Apply the mixin to the progress bars 188 | .progress-bar { @include progress-bar-styles($progress-bar-bg); } 189 | .progress-bar-success { @include progress-bar-styles($progress-bar-success-bg); } 190 | .progress-bar-info { @include progress-bar-styles($progress-bar-info-bg); } 191 | .progress-bar-warning { @include progress-bar-styles($progress-bar-warning-bg); } 192 | .progress-bar-danger { @include progress-bar-styles($progress-bar-danger-bg); } 193 | 194 | 195 | 196 | // 197 | // List groups 198 | // -------------------------------------------------- 199 | 200 | .list-group { 201 | border-radius: $border-radius-base; 202 | @include box-shadow(0 1px 2px rgba(0,0,0,.075)); 203 | } 204 | .list-group-item.active, 205 | .list-group-item.active:hover, 206 | .list-group-item.active:focus { 207 | text-shadow: 0 -1px 0 darken($list-group-active-bg, 10%); 208 | @include gradient-vertical($start-color: $list-group-active-bg, $end-color: darken($list-group-active-bg, 7.5%)); 209 | border-color: darken($list-group-active-border, 7.5%); 210 | } 211 | 212 | 213 | 214 | // 215 | // Panels 216 | // -------------------------------------------------- 217 | 218 | // Common styles 219 | .panel { 220 | @include box-shadow(0 1px 2px rgba(0,0,0,.05)); 221 | } 222 | 223 | // Mixin for generating new styles 224 | @mixin panel-heading-styles($color) { 225 | @include gradient-vertical($start-color: $color, $end-color: darken($color, 5%)); 226 | } 227 | 228 | // Apply the mixin to the panel headings only 229 | .panel-default > .panel-heading { @include panel-heading-styles($panel-default-heading-bg); } 230 | .panel-primary > .panel-heading { @include panel-heading-styles($panel-primary-heading-bg); } 231 | .panel-success > .panel-heading { @include panel-heading-styles($panel-success-heading-bg); } 232 | .panel-info > .panel-heading { @include panel-heading-styles($panel-info-heading-bg); } 233 | .panel-warning > .panel-heading { @include panel-heading-styles($panel-warning-heading-bg); } 234 | .panel-danger > .panel-heading { @include panel-heading-styles($panel-danger-heading-bg); } 235 | 236 | 237 | 238 | // 239 | // Wells 240 | // -------------------------------------------------- 241 | 242 | .well { 243 | @include gradient-vertical($start-color: darken($well-bg, 5%), $end-color: $well-bg); 244 | border-color: darken($well-bg, 10%); 245 | $shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1); 246 | @include box-shadow($shadow); 247 | } 248 | -------------------------------------------------------------------------------- /styles/bootstrap/_thumbnails.scss: -------------------------------------------------------------------------------- 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 | @include transition(all .2s ease-in-out); 16 | 17 | > img, 18 | a > img { 19 | @include img-responsive(); 20 | margin-left: auto; 21 | margin-right: auto; 22 | } 23 | 24 | // [converter] extracted a&:hover, a&:focus, a&.active to a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active 25 | 26 | // Image captions 27 | .caption { 28 | padding: $thumbnail-caption-padding; 29 | color: $thumbnail-caption-color; 30 | } 31 | } 32 | 33 | // Add a hover state for linked versions only 34 | a.thumbnail:hover, 35 | a.thumbnail:focus, 36 | a.thumbnail.active { 37 | border-color: $link-color; 38 | } 39 | -------------------------------------------------------------------------------- /styles/bootstrap/_tooltip.scss: -------------------------------------------------------------------------------- 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 | @include opacity(0); 15 | 16 | &.in { @include 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_type.scss: -------------------------------------------------------------------------------- 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 | 95 | @include text-emphasis-variant('.text-primary', $brand-primary); 96 | 97 | @include text-emphasis-variant('.text-success', $state-success-text); 98 | 99 | @include text-emphasis-variant('.text-info', $state-info-text); 100 | 101 | @include text-emphasis-variant('.text-warning', $state-warning-text); 102 | 103 | @include text-emphasis-variant('.text-danger', $state-danger-text); 104 | 105 | // Contextual backgrounds 106 | // For now we'll leave these alongside the text classes until v4 when we can 107 | // safely shift things around (per SemVer rules). 108 | .bg-primary { 109 | // Given the contrast here, this is the only class to have its color inverted 110 | // automatically. 111 | color: #fff; 112 | } 113 | @include bg-variant('.bg-primary', $brand-primary); 114 | 115 | @include bg-variant('.bg-success', $state-success-bg); 116 | 117 | @include bg-variant('.bg-info', $state-info-bg); 118 | 119 | @include bg-variant('.bg-warning', $state-warning-bg); 120 | 121 | @include bg-variant('.bg-danger', $state-danger-bg); 122 | 123 | 124 | // Page header 125 | // ------------------------- 126 | 127 | .page-header { 128 | padding-bottom: (($line-height-computed / 2) - 1); 129 | margin: ($line-height-computed * 2) 0 $line-height-computed; 130 | border-bottom: 1px solid $page-header-border-color; 131 | } 132 | 133 | 134 | // Lists 135 | // -------------------------------------------------- 136 | 137 | // Unordered and Ordered lists 138 | ul, 139 | ol { 140 | margin-top: 0; 141 | margin-bottom: ($line-height-computed / 2); 142 | ul, 143 | ol { 144 | margin-bottom: 0; 145 | } 146 | } 147 | 148 | // List options 149 | 150 | // Unstyled keeps list items block level, just removes default browser padding and list-style 151 | .list-unstyled { 152 | padding-left: 0; 153 | list-style: none; 154 | } 155 | 156 | // Inline turns list items into inline-block 157 | .list-inline { 158 | @extend .list-unstyled; 159 | margin-left: -5px; 160 | 161 | > li { 162 | display: inline-block; 163 | padding-left: 5px; 164 | padding-right: 5px; 165 | } 166 | } 167 | 168 | // Description Lists 169 | dl { 170 | margin-top: 0; // Remove browser default 171 | margin-bottom: $line-height-computed; 172 | } 173 | dt, 174 | dd { 175 | line-height: $line-height-base; 176 | } 177 | dt { 178 | font-weight: bold; 179 | } 180 | dd { 181 | margin-left: 0; // Undo browser default 182 | } 183 | 184 | // Horizontal description lists 185 | // 186 | // Defaults to being stacked without any of the below styles applied, until the 187 | // grid breakpoint is reached (default of ~768px). 188 | 189 | @media (min-width: $grid-float-breakpoint) { 190 | .dl-horizontal { 191 | dt { 192 | float: left; 193 | width: ($component-offset-horizontal - 20); 194 | clear: left; 195 | text-align: right; 196 | @include text-overflow(); 197 | } 198 | dd { 199 | margin-left: $component-offset-horizontal; 200 | @include clearfix(); // Clear the floated `dt` if an empty `dd` is present 201 | } 202 | } 203 | } 204 | 205 | // MISC 206 | // ---- 207 | 208 | // Abbreviations and acronyms 209 | abbr[title], 210 | // Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 211 | abbr[data-original-title] { 212 | cursor: help; 213 | border-bottom: 1px dotted $abbr-border-color; 214 | } 215 | .initialism { 216 | font-size: 90%; 217 | text-transform: uppercase; 218 | } 219 | 220 | // Blockquotes 221 | blockquote { 222 | padding: ($line-height-computed / 2) $line-height-computed; 223 | margin: 0 0 $line-height-computed; 224 | font-size: $blockquote-font-size; 225 | border-left: 5px solid $blockquote-border-color; 226 | 227 | p, 228 | ul, 229 | ol { 230 | &:last-child { 231 | margin-bottom: 0; 232 | } 233 | } 234 | 235 | // Note: Deprecated small and .small as of v3.1.0 236 | // Context: https://github.com/twbs/bootstrap/issues/11660 237 | footer, 238 | small, 239 | .small { 240 | display: block; 241 | font-size: 80%; // back to default font-size 242 | line-height: $line-height-base; 243 | color: $blockquote-small-color; 244 | 245 | &:before { 246 | content: '\2014 \00A0'; // em dash, nbsp 247 | } 248 | } 249 | } 250 | 251 | // Opposite alignment of blockquote 252 | // 253 | // Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0. 254 | .blockquote-reverse, 255 | blockquote.pull-right { 256 | padding-right: 15px; 257 | padding-left: 0; 258 | border-right: 5px solid $blockquote-border-color; 259 | border-left: 0; 260 | text-align: right; 261 | 262 | // Account for citation 263 | footer, 264 | small, 265 | .small { 266 | &:before { content: ''; } 267 | &:after { 268 | content: '\00A0 \2014'; // nbsp, em dash 269 | } 270 | } 271 | } 272 | 273 | // Quotes 274 | blockquote:before, 275 | blockquote:after { 276 | content: ""; 277 | } 278 | 279 | // Addresses 280 | address { 281 | margin-bottom: $line-height-computed; 282 | font-style: normal; 283 | line-height: $line-height-base; 284 | } 285 | -------------------------------------------------------------------------------- /styles/bootstrap/_utilities.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // Floats 7 | // ------------------------- 8 | 9 | .clearfix { 10 | @include clearfix(); 11 | } 12 | .center-block { 13 | @include 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 | @include 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 | -------------------------------------------------------------------------------- /styles/bootstrap/_wells.scss: -------------------------------------------------------------------------------- 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 | @include 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 | -------------------------------------------------------------------------------- /styles/bootstrap/bootstrap.scss: -------------------------------------------------------------------------------- 1 | // Core variables and mixins 2 | @import "variables"; 3 | @import "mixins"; 4 | 5 | // Reset 6 | @import "normalize"; 7 | @import "print"; 8 | 9 | // Core CSS 10 | @import "scaffolding"; 11 | @import "type"; 12 | @import "code"; 13 | @import "grid"; 14 | @import "tables"; 15 | @import "forms"; 16 | @import "buttons"; 17 | 18 | // Components 19 | @import "component-animations"; 20 | @import "glyphicons"; 21 | @import "dropdowns"; 22 | @import "button-groups"; 23 | @import "input-groups"; 24 | @import "navs"; 25 | @import "navbar"; 26 | @import "breadcrumbs"; 27 | @import "pagination"; 28 | @import "pager"; 29 | @import "labels"; 30 | @import "badges"; 31 | @import "jumbotron"; 32 | @import "thumbnails"; 33 | @import "alerts"; 34 | @import "progress-bars"; 35 | @import "media"; 36 | @import "list-group"; 37 | @import "panels"; 38 | @import "wells"; 39 | @import "close"; 40 | 41 | // Components w/ JavaScript 42 | @import "modals"; 43 | @import "tooltip"; 44 | @import "popovers"; 45 | @import "carousel"; 46 | 47 | // Utility classes 48 | @import "utilities"; 49 | @import "responsive-utilities"; 50 | -------------------------------------------------------------------------------- /styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | @import "style"; 3 | @import "icons"; -------------------------------------------------------------------------------- /templates/_feed.hbs: -------------------------------------------------------------------------------- 1 |
      2 | {{# if feed.activities }} 3 |
        4 | {{# each feed.activities }} 5 | {{app-activity activity=this }} 6 | {{/each }} 7 |
      8 | {{/if }} 9 | {{#unless feed.activities }} 10 |
      11 | Follow some people to get your feed started 12 |
      13 | {{/unless}} 14 |
      -------------------------------------------------------------------------------- /templates/_loading.hbs: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
        4 |
      -------------------------------------------------------------------------------- /templates/application.hbs: -------------------------------------------------------------------------------- 1 |
      2 |
      3 |
      4 |

      Parse cloud code, getstream.io & EmberJS

      5 | This example app shows you how to build scalable newsfeeds using GetStream.io & parse cloud. 6 | The code is on Github. 7 |
      8 | 9 | 10 |
      11 | {{#if session.isAuthenticated}} 12 | {{# if posted }} 13 | {{# if followed }} 14 | Step 4/4
      15 | Great, your {{#link-to 'index'}}feed{{/link-to}} now shows the content from the people you follow. 16 | It works with 3 users and keeps on working with 3 million users. 17 | To understand the API powering this try the getting started for getstream.io. 18 | {{else}} 19 | Step 3/4
      20 | Nice one! Your own feed is still empty though. How about you try following some {{#link-to 'people'}}people{{/link-to}}? 21 | {{/if }} 22 | {{else}} 23 | Step 2/4
      24 | Hi {{ displayName }}, welcome! Now give the example a try by posting a status update about the city you're currently in. 25 | {{/if }} 26 | {{else}} 27 | Step 1/4
      28 | As we're demoing a social app, you'll need to login to give it a try. 29 | Login with Github 30 | {{/if }} 31 |
      32 |
      33 |
      34 | 35 |
      36 |
      37 | 70 |
      71 |
      72 | {{outlet}} 73 |
      74 |
      75 |
      76 |
      77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /templates/components/app-activity.hbs: -------------------------------------------------------------------------------- 1 |
    1. 2 | 3 |
      4 | {{ ago }} 5 |
      6 | 7 |
      8 | {{#link-to 'profile' username}}{{username}}{{/link-to}} {{ activity.verb }} 9 |
      10 | 11 | {{# if activity.object_parse.attributes.tweet }} 12 |
      13 | {{ activity.object_parse.attributes.tweet }} 14 |
      15 | {{/if }} 16 | 17 | {{# if isUpload }} 18 | 19 | {{/if }} 20 | 21 | {{# if isLike }} 22 |
      23 | {{ username }} likes an {{ likedActivity.className }} activity 24 |
      25 | {{/if }} 26 | 27 | {{# if isFollow }} 28 |
      29 | {{ activity.actor_parse.attributes.username }} followed 30 | {{ activity.object_parse.attributes.username }}
      31 | » 32 | 33 |
      34 | {{/if }} 35 | 36 |
      37 | {{# unless isLike }} 38 | {{#if activity.liked }} 39 | 40 | Unlike 41 | 42 | {{else }} 43 | 44 | Like 45 | 46 | {{/if}} 47 | {{#if loading }} 48 | {{ partial 'loading' }} 49 | {{/if}} 50 | {{/unless }} 51 | x {{ likes }} 52 |
      53 |
    2. 54 | 55 | -------------------------------------------------------------------------------- /templates/components/app-user-header.hbs: -------------------------------------------------------------------------------- 1 |
      2 | 3 |

      {{ user.attributes.username }}

      4 | {{#if loading }} 5 | {{ partial 'loading' }} 6 | {{ else }} 7 | FOLLOW 8 | {{/if }} 9 |
      -------------------------------------------------------------------------------- /templates/components/app-user.hbs: -------------------------------------------------------------------------------- 1 |
    3. 2 | 3 | {{#if loading }} 4 |
      {{ partial 'loading' }}
      5 | {{ else }} 6 | 9 | {{/if }} 10 | {{#link-to 'profile' user.attributes.username}} 11 | {{user.attributes.name}} 12 | {{/link-to}} 13 |
    4. -------------------------------------------------------------------------------- /templates/index.hbs: -------------------------------------------------------------------------------- 1 |
      2 |
      3 |
      4 |
      5 | 6 |
      7 |
      8 | 9 |
      10 | 11 |
      12 |
      13 | {{#if session.isAuthenticated}} 14 | {{textarea value=status cols="40" rows="3" placeholder="Which city are you in right now?" class="form-control"}} 15 | {{else }} 16 | {{textarea value=status cols="40" rows="3" placeholder="Which city are you in right now?" class="form-control" disabled=entryNotAllowed }} 17 | {{/if }} 18 |
      19 | 20 | {{#if errors.status }} 21 |
      {{ errors.status }}
      22 | {{/if }} 23 | {{#if session.isAuthenticated}} 24 | {{else}} 25 |
      26 | Login with Github to share your status 27 |
      28 | {{/if}} 29 | 30 | {{#if session.isAuthenticated}} 31 | {{# if posting }} 32 |   33 | {{ partial 'loading' }} 34 | {{ else }} 35 |
      36 |
      37 | 38 |
      39 |
      40 | {{view "customFileInput" }} 41 |
      42 |
      43 | 46 |
      47 |
      48 | {{/if}} 49 | {{/if }} 50 |
      51 |
      52 |
      53 |
      54 |
      55 | {{# if model.globalFeed.new }} 56 |
      57 | Whooooop! There are new activities!
      58 | 59 |
      60 | {{/if }} 61 | {{# if loading }} 62 |   63 | {{ partial 'loading' }} 64 | {{/if }} 65 |
      66 |
      67 |

      People you follow

      68 | {{# if model.flatFeed }} 69 | {{#with model.flatFeed as feed}} 70 | {{ partial 'feed' }} 71 | {{/with}} 72 | {{ else }} 73 |
      74 | Log in and follow some people to see your personalised feed!
       
      75 | Login with Github 76 |
      77 | {{/if}} 78 |
      79 |
      80 |
      81 |
      82 |

      Everyone

      83 | {{#with model.globalFeed as feed}} 84 | {{ partial 'feed' }} 85 | {{/with}} 86 |
      87 |
      88 |
      89 |
      90 |
      -------------------------------------------------------------------------------- /templates/people.hbs: -------------------------------------------------------------------------------- 1 |

      Follow some people

      2 | 3 |
      4 |
      5 |
      6 | {{#unless session.isAuthenticated}} 7 |
      8 | Log in with Github to follow people
      9 | Login 10 |
      11 | {{/unless}} 12 |
        13 | {{# each model }} 14 | {{app-user user=this session=session }} 15 | {{/each }} 16 |
      17 |
      18 |
      19 |
      20 | -------------------------------------------------------------------------------- /templates/profile.hbs: -------------------------------------------------------------------------------- 1 | {{app-user-header user=model.profile session=session }} 2 | 3 |
      4 |
      5 |
      6 | {{#with model.feed as feed}} 7 | {{ partial 'feed' }} 8 | {{/with}} 9 |
      10 |
      11 |
      -------------------------------------------------------------------------------- /templates/views/custom_file_input.hbs: -------------------------------------------------------------------------------- 1 | Add image 2 | 3 | --------------------------------------------------------------------------------