Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Cory Shaw 2013-04-10 12:20:18 -10:00
Родитель 9e7fedf5e2 bc8d6336d0
Коммит bfc37b1a17
10 изменённых файлов: 268 добавлений и 28 удалений

Просмотреть файл

@ -1,3 +1,12 @@
# Chicago Summer of Learning
The website for the Chicago Summer of Learning, developed by Ocupop and Mozilla.
## Environment
Property | Default | Description
-------|-------|----------
`CSOL_DB_NAME` | `"csol"` | Name of the database.
`CSOL_DB_USER` | `"root"` | Database username.
`CSOL_DB_PASS` | `null` | Database password.

12
db.js Normal file
Просмотреть файл

@ -0,0 +1,12 @@
const Sequelize = require('sequelize');
const DB_NAME = process.env['CSOL_DB_NAME'] || 'csol';
const USERNAME = process.env['CSOL_DB_USER'] || 'root';
const PASSWORD = process.env['CSOL_DB_PASS'];
const db = new Sequelize(DB_NAME, USERNAME, PASSWORD, {
define: { charset: 'utf8' }
});
db.type = Sequelize;
module.exports = db;

14
models/organization.js Normal file
Просмотреть файл

@ -0,0 +1,14 @@
const db = require('../db.js');
const Organization = db.define('Organization', {
id: { type: db.type.INTEGER, primaryKey: true, autoIncrement: true },
name: { type: db.type.STRING, allowNull: false, unique: true },
description: { type: db.type.STRING, allowNull: false, unique: true },
url: { type: db.type.STRING, allowNull: false, validate: { isUrl: true }},
imageUrl: { type: db.type.TEXT, allowNull: false, validate: { isUrl: true }},
address: { type: db.type.TEXT, allowNull: true },
phone: { type: db.type.STRING, allowNull: true },
email: { type: db.type.STRING, allowNull: true, validate: { isEmail: true }},
});
module.exports = Organization;

Просмотреть файл

@ -5,7 +5,11 @@
"main": "app.js",
"dependencies": {
"express": "~3.1.0",
"nunjucks": "~0.1.8a"
"nunjucks": "~0.1.8a",
"mysql": "~2.0.0-alpha7",
"sequelize": "~1.6.0",
"tap": "~0.4.1",
"async": "~0.2.6"
},
"devDependencies": {},
"scripts": {

Просмотреть файл

@ -66,11 +66,11 @@ body {
text-align: center;
line-height: .8em;
}
#navigation ul {
#navigation .nav {
margin-bottom: 0;
display: inline-block;
}
#navigation ul li a {
#navigation .nav li > a {
text-transform: uppercase;
margin: 0;
padding: 10px 30px 10px 30px;
@ -82,16 +82,35 @@ body {
-webkit-background-clip: padding-box;
background-clip: padding-box;
color: #404041;
border-right: 1px solid #404041;
border: none;
border-left: 1px solid #404041;
}
#navigation ul li a:hover {
#navigation .nav li > a:hover {
background: #eee;
}
#navigation ul li.active a {
#navigation .nav li.active a {
background: #eee;
}
#navigation ul li:last-child a {
border-right: none;
#navigation .nav li:first-child a {
border-left: none;
}
#navigation .nav ul li a {
border: none;
}
#navigation .nav form {
padding: 10px;
text-align: left;
}
#navigation .nav form .btn {
display: block;
width: 100%;
}
#navigation .nav form small {
margin-top: 10px;
border-top: solid 1px #CCC;
display: block;
clear: both;
text-align: center;
}
ul.row {
list-style: none;

32
static/media/css/core.min.css поставляемый
Просмотреть файл

@ -1,13 +1,33 @@
html,body{margin:0;padding:0;background:#EEE;}
html,body{margin:0;padding:0;background-image:url('../images/billie_holiday.png');}@media all and (-webkit-min-device-pixel-ratio:1.5){html,body{background-image:url('../images/billie_holiday@2x.png');background-size:100px 100px;}}
#notes{position:fixed;bottom:1em;right:1em;max-width:300px;padding:1em;background:rgba(255, 255, 255, 0.75);color:#333;border:solid 1px #999;box-shadow:2px 2px 5px rgba(0, 0, 0, 0.5);}
#header{background:#EEE;border-bottom:solid 1px #CCC;position:relative;}#header::after{content:"";position:absolute;top:100%;left:0;width:100%;height:5px;background:-webkit-linear-gradient(top, rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0));}
#header h1{margin:0;overflow:hidden;font-size:2em;line-height:2em;}#header h1 a{display:block;float:left;width:220px;height:100px;background:rgba(0, 0, 0, 0.05);text-indent:-999em;overflow:hidden;position:relative;border-right:solid 1px rgba(255, 255, 255, 0.75);box-shadow:inset 2px 2px 5px rgba(0, 0, 0, 0.125);}#header h1 a::after{content:"CSOL";display:block;position:absolute;top:50%;left:0;width:100%;height:2em;margin-top:-1em;text-align:center;text-indent:0;color:rgba(0, 0, 0, 0.25);text-shadow:1px 1px 1px rgba(255, 255, 255, 0.5);}
#navigation{line-height:3em;margin:-3em 0 0;}#navigation ul{margin-bottom:0;}
#content{padding:50px 0;min-height:200px;background:#FFF;}
#footer{border-top:solid 1px #CCC;height:100px;}
#header{background:url(../images/chalkboard_bg_green.jpg) center;position:relative;-webkit-box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);-moz-box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);box-shadow:inset 0px -10px 20px 0px rgba(0, 0, 0, 0.25);}#header::after{position:absolute;top:100%;left:0;width:100%;height:5px;background:-webkit-linear-gradient(top, rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0));}
#header h1{margin:0;overflow:hidden;font-size:2em;line-height:2em;display:block;}#header h1 a{display:block;max-width:671px;height:220px;background:url(../images/csol_logo_illustration.png) center no-repeat;background-size:100%;text-indent:-999em;position:relative;margin:23px auto 0 auto;}
#navigation{margin:0 auto;position:relative;bottom:-16px;margin-top:-12px;-webkit-box-shadow:0 0 8px 3px rgba(0, 0, 0, 0.25);-moz-box-shadow:0 0 8px 3px rgba(0, 0, 0, 0.25);box-shadow:0 0 8px 3px rgba(0, 0, 0, 0.25);background:white;text-align:center;line-height:.8em;}#navigation .nav{margin-bottom:0;display:inline-block;}#navigation .nav li>a{text-transform:uppercase;margin:0;padding:10px 30px 10px 30px;font-size:1.2em;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;color:#404041;border:none;border-left:1px solid #404041;}#navigation .nav li>a:hover{background:#eee;}
#navigation .nav li.active a{background:#eee;}
#navigation .nav li:first-child a{border-left:none;}
#navigation .nav ul li a{border:none;}
#navigation .nav form{padding:10px;text-align:left;}#navigation .nav form .btn{display:block;width:100%;}
#navigation .nav form small{margin-top:10px;border-top:solid 1px #CCC;display:block;clear:both;text-align:center;}
ul.row{list-style:none;}
figure.thumbnail{margin:0;}
.thumbnail img{margin-top:5px;}
.thumbnail>a{display:block;text-align:center;}
form .divider{display:block;margin:2em 0;text-align:center;position:relative;}form .divider::before{content:"";position:absolute;left:0;top:50%;width:100%;border-top:solid 1px #CCC;}
form .divider em{background:#FFF;padding:0 1em;position:relative;}
#content{text-shadow:1px 1px 0px rgba(255, 255, 255, 0.4);padding:50px 0;min-height:200px;}#content h2{text-transform:uppercase;font-family:"ff-meta-web-pro";font-weight:bold;display:block;margin:0 auto;text-align:center;margin-bottom:40px;padding-bottom:8px;padding-top:8px;position:relative;}#content h2::before{content:"";position:absolute;left:0;top:50%;width:35%;border-top:solid 1px black;}
#content h2::after{content:"";position:absolute;right:0;top:50%;width:35%;border-top:solid 1px black;}
.navbar-form{color:white;text-shadow:1px 1px 0px black;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;border:none;background:url(../images/chalkboard_bg_grey.jpg) center;}.navbar-form .brand{text-transform:uppercase;padding-left:12px;padding-top:9px;}
.thumbnails figure{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;background:white;-webkit-box-shadow:0 0 5px 1px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 0 5px 1px rgba(0, 0, 0, 0.3);box-shadow:0 0 5px 1px rgba(0, 0, 0, 0.3);padding:0;margin:0;border:none;}.thumbnails figure figcaption{background:#555555 url(../images/chalkboard_bg_grey.jpg) center;color:white !important;text-shadow:none;}
.thumbnails figure img{padding:15px ;}
.btn{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;}
#footer{border-top:solid 1px #CCC;min-height:100px;background:url(../images/chalkboard_bg_green.jpg) center;position:relative;-webkit-box-shadow:inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);-moz-box-shadow:inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);box-shadow:inset 0px 10px 20px 0px rgba(0, 0, 0, 0.25);color:white;font-size:1.1em;}#footer .toprow{border-bottom:1px solid white;padding-bottom:20px;margin-bottom:25px;}#footer .toprow .pull-left{font-size:1.1em;padding-top:10px;}
#footer a{color:white;}#footer a:hover{background-color:none;}
#footer .nav{font-size:1.2em;text-transform:uppercase;margin:0;padding:0;}#footer .nav li{margin-bottom:0;padding:0;}#footer .nav li a{display:inline-block;}#footer .nav li a:hover{background:none;}
#footer .container{padding:40px 0;}
#footer .logo-csol{width:100%;height:131px;background:url(../images/csol_logo_footer.png) center no-repeat;text-indent:-999em;display:block;margin-bottom:20px;margin-right:15px;}
#footer .logos{text-align:center;}
#footer .description{margin-bottom:20px;}
#footer .logo{text-indent:-999em;display:inline-block;padding:10px;margin:5px;background-repeat:no-repeat;}#footer .logo.macarthur{width:78px;height:78px;background-image:url(../images/footer_logo_macarthur.png);}
#footer .logo.city{width:78px;height:78px;background-image:url(../images/footer_logo_citiofchi.png);}
#footer .logo.mozilla{width:82px;height:78px;background-image:url(../images/footer_logo_mozilla.png);background-position:10px 20px;}
@media all and (max-width:767px){#header h1{padding:10px 20px;min-height:130px;}#header h1 a{height:150px;} #content{padding:20px 20px;}#content h2:before,#content h2:after{border:none;} #footer{padding:0 20px;}#footer .pull-left{float:none;text-align:center;margin-bottom:20px;} #footer nav{float:none;text-align:center;}#footer nav li{display:inline-block;}}

Просмотреть файл

@ -71,20 +71,21 @@ body {
text-align: center;
line-height: .8em;
ul {
.nav {
margin-bottom: 0;
display: inline-block;
li {
a {
& > a {
text-transform: uppercase;
margin: 0;
padding: 10px 30px 10px 30px;
font-size: 1.2em;
.border-radius(0);
color: #404041;
border-right: 1px solid #404041;
border: none;
border-left: 1px solid #404041;
&:hover {
background: #eee;
@ -94,17 +95,39 @@ body {
&.active {
a {
background:#eee;
background: #eee;
}
}
&:last-child{
&:first-child{
a {
border-right: none;
border-left: none;
}
}
}
ul li a {
border: none;
}
form {
padding: 10px;
text-align: left;
.btn {
display: block;
width: 100%;
}
small {
margin-top: 10px;
border-top: solid 1px #CCC;
display: block;
clear: both;
text-align: center;
}
}
}

80
test/index.js Normal file
Просмотреть файл

@ -0,0 +1,80 @@
const ENV = process.env['NODE_ENV'];
if (ENV !== 'test' && ENV !== 'travis') {
console.log('Tests must be run with NODE_ENV=test');
console.log('Also ensure that CSOL_DB_NAME is set to the test database');
process.exit(1);
}
if (!process.env['CSOL_DB_NAME']) {
console.log('No test database name set, defaulting to "test_csol"');
process.env['CSOL_DB_NAME'] = 'test_csol';
}
const path = require('path');
const async = require('async');
/**
* Prepare the database by syncing all known models and optionally
* saving some test data.
*
* If only one argument is passed, it is treated as the callback. When
* there are no fixtures passed, the only tables that will get synced
* are those that are known about (through `require`ing the models in
* in the test file) before `prepare` is called.
*
* @param fixtures Array of fixture objects. A fixture object is
* expected to have the following properties:
* - model: Name of the model. Should correspond to a filein models/ dir
* - name: Name to give the resulting instance
* - values: Object with the values to save
*/
exports.prepare = function (fixtures, callback) {
if (typeof fixtures == 'function')
callback = fixtures, fixtures = [];
const db = require('../db');
const models = fixtures.reduce(function (models, fixture) {
const filename = fixture.model.toLowerCase();
models[fixture.model] = requireModel(filename);
return models;
}, {});
const save = saveFixture.bind(null, models);
db.sync({force: true})
.success(function () {
async.mapSeries(fixtures, save, function (err, instances) {
if (err) throw err;
instances.forEach(function (instance, idx) {
const name = fixtures[idx].name;
if (name)
instances[name] = instance;
});
return callback(instances);
});
})
.error(function (error) {
console.log('Could not sync database models:');
console.dir(error);
process.exit(1);
});
};
function requireModel (filename) {
return require(path.join(__dirname, '..', 'models', filename));
}
function saveFixture(models, fixture, callback) {
const Model = models[fixture.model];
Model.create(fixture.values)
.success(function (instance) {
console.log('success');
return callback(null, instance);
})
.error(function (error) {
return callback(error);
});
}

Просмотреть файл

@ -0,0 +1,27 @@
const $ = require('./');
const test = require('tap').test;
const Organization = require('../models/organization');
$.prepare([
{ model: 'Organization',
name: 'wbez',
values: {
id: 10,
name: 'WBEZ 91.5',
description: 'Chicago Public Radio',
url: 'http://www.wbez.org/',
imageUrl: 'https://twimg0-a.akamaihd.net/profile_images/858641792/WBEZ915_LOGO.jpg',
address: '848 East Grand Ave, Navy Pier, Chicago, Illinois 60611',
phone: '312.948.4600',
email: 'admin@wbez.org'
}
}
], function (fixtures) {
test('Finding an organization', function (t) {
const expect = fixtures['wbez'];
Organization.find(expect.id).success(function (instance) {
t.same(instance.rawAttributes, expect.rawAttributes);
t.end();
});
});
});

Просмотреть файл

@ -17,16 +17,46 @@
<h1><a href="/">Chicago Summer of Learning</a></h1>
<nav id="navigation">
<ul class="nav nav-pills">
<li class="programs{% if navItem == 'programs' %} active{% endif %}"><a href="/programs">Programs</a></li>
<li class="orgs{% if navItem == 'orgs' %} active{% endif %}"><a href="/orgs">Organisations</a></li>
<li class="claim{% if navItem == 'claim' %} active{% endif %}"><a href="/claim">Claim</a></li>
<li class="learn{% if navItem == 'learn' %} active{% endif %}">
<a href="/learn">Learn</a>
</li>
<li class="badges{% if navItem == 'badges' %} active{% endif %}">
<a href="/badges">Badges</a>
</li>
<li class="challenges{% if navItem == 'challenges' %} active{% endif %}">
<a href="/challenges">Challenges</a>
</li>
{% if user %}
<li class="backpack{% if navItem == 'backpack' %} active{% endif %}"><a href="/backpack">My Badges</a></li>
<li class="bookmarks{% if navItem == 'bookmarks' %} active{% endif %}"><a href="/favorites">My Favorites</a></li>
<li class="log-out"><a href="/logout">Log Out</a></li>
<li class="dropdown dashboard{% if navItem == 'dashboard' %} active{% endif %}">
<a href="/dashboard" class="dropdown-toggle" role="button" data-toggle="dropdown" id="dashboard-menu-toggle">My Dashboard <i class="caret"></i></a>
<ul class="dropdown-menu" role="menu" id="dashboard-menu" aria-labelledby="dashboard-menu-toggle">
<li role="presentation"{% if subNavItem == 'badges' %} class="active"{% endif %}>
<a role="menuitem" tabindex="-1" href="/dashboard/badges">My Badges</a>
</li>
<li role="presentation"{% if subNavItem == 'badges' %} class="active"{% endif %}>
<a role="menuitem" tabindex="-1" href="/dashboard/favorites">My Favorites</a>
</li>
<li role="presentation"{% if subNavItem == 'badges' %} class="active"{% endif %}>
<a role="menuitem" tabindex="-1" href="/dashboard/applications">My Applications</a>
</li>
<li class="divider" role="presentation" aria-hidden="true"></li>
<li role="presentation">
<a role="menuitem" tabindex="-1" href="/logout">Log Out</a>
</li>
</ul>
</li>
{% else %}
<li class="log-in{% if navItem == 'log-in' %} active{% endif %}"><a href="/login">Log In</a></li>
<li class="sign-up{% if navItem == 'sign-up' %} active{% endif %}"><a href="/signup">Sign Up</a></li>
<li class="dropdown log-in{% if navItem == 'log-in' %} active{% endif %}">
<a href="/login" class="dropdown-toggle" role="button" data-toggle="dropdown" id="menu-login-form-toggle">Log In / Sign Up <i class="caret"></i></a>
<form class="dropdown-menu" id="menu-login-form" aria-label="Log In" method="post" action="/login">
<label for="menu-login-username">Email or username:</label>
<input type="text" id="menu-login-username" name="username" placeholder="e.g. user@example.com">
<label for="menu-login-password">Password:</label>
<input type="password" id="menu-login-password" name="password">
<button type="submit" class="btn">Submit</button>
<small>Don't have an account? <a href="/signup">Sign up.</a></small>
</form>
</li>
{% endif %}
</ul>
</nav>
@ -72,5 +102,7 @@
</div><!-- .row-fluid -->
</div>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="/media/bootstrap/js/bootstrap.js"></script>
</body>
</html>