2
.gitignore
vendored
2
.gitignore
vendored
@ -7,6 +7,8 @@ lib-cov
|
||||
*.pid
|
||||
*.gz
|
||||
*.swp
|
||||
.floo
|
||||
.flooignore
|
||||
|
||||
*.env
|
||||
pids
|
||||
|
@ -151,7 +151,7 @@ Changelog
|
||||
Contributing
|
||||
------------
|
||||
|
||||
We welcome pull requests from Free Code Camp "Code Campers" (our students) and seasoned JavaScript developers alike!
|
||||
We welcome pull requests from Free Code Camp "campers" (our students) and seasoned JavaScript developers alike!
|
||||
1) Check our [public Trello Board](https://trello.com/b/CW5AFr0v/free-code-camp-development)
|
||||
2) If your issue or feature isn't on the board, either open an issue on this GitHub repo or message Quincy Larson to request to be added to the Trello board.
|
||||
3) Once your code is ready, submit the pull request. We'll do a quick code review and give you feedback, and iterate from there.
|
||||
|
84
app.js
84
app.js
@ -32,6 +32,7 @@ var express = require('express'),
|
||||
userController = require('./controllers/user'),
|
||||
contactController = require('./controllers/contact'),
|
||||
bonfireController = require('./controllers/bonfire'),
|
||||
coursewareController = require('./controllers/courseware'),
|
||||
|
||||
/**
|
||||
* User model
|
||||
@ -282,81 +283,18 @@ app.get('/bonfire', function(req, res) {
|
||||
res.redirect(301, '/playground');
|
||||
});
|
||||
|
||||
app.post('/completed-bonfire/', function (req, res) {
|
||||
var isCompletedWith = req.body.bonfireInfo.completedWith || undefined;
|
||||
var isCompletedDate = Math.round(+new Date() / 1000);
|
||||
var bonfireHash = req.body.bonfireInfo.bonfireHash;
|
||||
var isSolution = req.body.bonfireInfo.solution;
|
||||
app.post('/completed-bonfire/', bonfireController.completedBonfire);
|
||||
|
||||
if (isCompletedWith) {
|
||||
var paired = User.find({"profile.username": isCompletedWith}).limit(1);
|
||||
paired.exec(function(err, pairedWith) {
|
||||
if (err) {
|
||||
return err;
|
||||
} else {
|
||||
var index = req.user.uncompletedBonfires.indexOf(bonfireHash);
|
||||
/**
|
||||
* Courseware related routes
|
||||
*/
|
||||
|
||||
if (index > -1) {
|
||||
req.user.uncompletedBonfires.splice(index,1)
|
||||
}
|
||||
pairedWith = pairedWith.pop();
|
||||
|
||||
index = pairedWith.uncompletedBonfires.indexOf(bonfireHash);
|
||||
if (index > -1) {
|
||||
pairedWith.uncompletedBonfires.splice(index,1)
|
||||
}
|
||||
|
||||
pairedWith.completedBonfires.push({
|
||||
_id: bonfireHash,
|
||||
completedWith: req.user._id,
|
||||
completedDate: isCompletedDate,
|
||||
solution: isSolution
|
||||
})
|
||||
|
||||
req.user.completedBonfires.push({
|
||||
_id: bonfireHash,
|
||||
completedWith: pairedWith._id,
|
||||
completedDate: isCompletedDate,
|
||||
solution: isSolution
|
||||
})
|
||||
|
||||
req.user.save(function(err, user) {
|
||||
pairedWith.save(function(err, paired) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
if (user && paired) {
|
||||
res.send(true);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
||||
req.user.completedBonfires.push({
|
||||
_id: bonfireHash,
|
||||
completedWith: null,
|
||||
completedDate: isCompletedDate,
|
||||
solution: isSolution
|
||||
})
|
||||
|
||||
var index = req.user.uncompletedBonfires.indexOf(bonfireHash);
|
||||
if (index > -1) {
|
||||
req.user.uncompletedBonfires.splice(index,1)
|
||||
}
|
||||
|
||||
req.user.save(function(err, user) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
if (user) {
|
||||
debug('Saving user');
|
||||
res.send(true)
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
app.get('/coursewares/', coursewareController.returnNextCourseware);
|
||||
app.get(
|
||||
'/coursewares/:coursewareName',
|
||||
coursewareController.returnIndividualCourseware
|
||||
);
|
||||
app.post('/completed-courseware/', coursewareController.completedCourseware);
|
||||
|
||||
// Unique Check API route
|
||||
app.get('/api/checkUniqueUsername/:username', userController.checkUniqueUsername);
|
||||
|
@ -8,7 +8,11 @@ var _ = require('lodash'),
|
||||
* Bonfire controller
|
||||
*/
|
||||
|
||||
var highestBonfireNumber = resources.numberOfBonfires();
|
||||
exports.bonfireNames = function(req, res) {
|
||||
res.render('bonfires/showList', {
|
||||
bonfireList: resources.allBonfireNames()
|
||||
});
|
||||
};
|
||||
|
||||
exports.index = function(req, res) {
|
||||
res.render('bonfire/show.jade', {
|
||||
@ -32,37 +36,37 @@ exports.index = function(req, res) {
|
||||
});
|
||||
};
|
||||
|
||||
exports.returnNextBonfire = function(req, res, next) {
|
||||
exports.returnNextBonfire = function(req, res) {
|
||||
if (!req.user) {
|
||||
return res.redirect('../bonfires/meet-bonfire');
|
||||
}
|
||||
var currentTime = parseInt(+new Date() / 1000);
|
||||
if (currentTime - req.user.lastContentSync > 10) {
|
||||
req.user.lastContentSync = currentTime;
|
||||
var completed = req.user.completedBonfires.map(function (elem) {
|
||||
return elem._id;
|
||||
});
|
||||
|
||||
req.user.uncompletedBonfires = resources.allBonfireIds().filter(function (elem) {
|
||||
if (completed.indexOf(elem) === -1) {
|
||||
return elem;
|
||||
}
|
||||
});
|
||||
req.user.save();
|
||||
}
|
||||
var completed = req.user.completedBonfires.map(function (elem) {
|
||||
return elem._id;
|
||||
});
|
||||
|
||||
req.user.uncompletedBonfires = resources.allBonfireIds().filter(function (elem) {
|
||||
if (completed.indexOf(elem) === -1) {
|
||||
return elem;
|
||||
}
|
||||
});
|
||||
req.user.save();
|
||||
|
||||
var uncompletedBonfires = req.user.uncompletedBonfires;
|
||||
|
||||
|
||||
var displayedBonfires = Bonfire.find({'_id': uncompletedBonfires[0]});
|
||||
displayedBonfires.exec(function(err, bonfire) {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
nameString = bonfire[0].name.toLowerCase().replace(/\s/g, '-');
|
||||
return res.redirect('/bonfires/' + nameString);
|
||||
bonfire = bonfire.pop();
|
||||
if (bonfire === undefined) {
|
||||
req.flash('errors', {
|
||||
msg: "It looks like you've completed all the bonfires we have available. Good job!"
|
||||
});
|
||||
return res.redirect('../bonfires/meet-bonfire');
|
||||
}
|
||||
nameString = bonfire.name.toLowerCase().replace(/\s/g, '-');
|
||||
return res.redirect('../bonfires/' + nameString);
|
||||
});
|
||||
};
|
||||
|
||||
@ -70,19 +74,22 @@ exports.returnIndividualBonfire = function(req, res, next) {
|
||||
var dashedName = req.params.bonfireName;
|
||||
|
||||
bonfireName = dashedName.replace(/\-/g, ' ');
|
||||
var bonfireNumber = 0;
|
||||
|
||||
Bonfire.find({"name" : new RegExp(bonfireName, 'i')}, function(err, bonfire) {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
|
||||
if (bonfire.length < 1) {
|
||||
req.flash('errors', {
|
||||
msg: "404: We couldn't find a bonfire with that name. Please double check the name."
|
||||
});
|
||||
return res.redirect('/bonfires/meet-bonfire');
|
||||
|
||||
return res.redirect('/bonfires');
|
||||
}
|
||||
bonfire = bonfire.pop();
|
||||
|
||||
bonfire = bonfire.pop()
|
||||
var dashedNameFull = bonfire.name.toLowerCase().replace(/\s/g, '-');
|
||||
if (dashedNameFull != dashedName) {
|
||||
return res.redirect('../bonfires/' + dashedNameFull);
|
||||
@ -142,7 +149,7 @@ function randomString() {
|
||||
randomstring += chars.substring(rnum,rnum+1);
|
||||
}
|
||||
return randomstring;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
@ -184,11 +191,11 @@ function getRidOfEmpties(elem) {
|
||||
if (elem.length > 0) {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.publicGenerator = function(req, res) {
|
||||
res.render('bonfire/public-generator');
|
||||
}
|
||||
};
|
||||
|
||||
exports.generateChallenge = function(req, res) {
|
||||
var bonfireName = req.body.name,
|
||||
@ -214,4 +221,83 @@ exports.generateChallenge = function(req, res) {
|
||||
tests: bonfireTests
|
||||
};
|
||||
res.send(response);
|
||||
}
|
||||
};
|
||||
|
||||
exports.completedBonfire = function (req, res) {
|
||||
var isCompletedWith = req.body.bonfireInfo.completedWith || undefined;
|
||||
var isCompletedDate = Math.round(+new Date() / 1000);
|
||||
var bonfireHash = req.body.bonfireInfo.bonfireHash;
|
||||
var isSolution = req.body.bonfireInfo.solution;
|
||||
|
||||
if (isCompletedWith) {
|
||||
var paired = User.find({"profile.username": isCompletedWith}).limit(1);
|
||||
paired.exec(function (err, pairedWith) {
|
||||
if (err) {
|
||||
return err;
|
||||
} else {
|
||||
var index = req.user.uncompletedBonfires.indexOf(bonfireHash);
|
||||
if (index > -1) {
|
||||
req.user.points++;
|
||||
req.user.uncompletedBonfires.splice(index, 1)
|
||||
}
|
||||
pairedWith = pairedWith.pop();
|
||||
|
||||
index = pairedWith.uncompletedBonfires.indexOf(bonfireHash);
|
||||
if (index > -1) {
|
||||
pairedWith.points++;
|
||||
pairedWith.uncompletedBonfires.splice(index, 1);
|
||||
|
||||
}
|
||||
|
||||
pairedWith.completedBonfires.push({
|
||||
_id: bonfireHash,
|
||||
completedWith: req.user._id,
|
||||
completedDate: isCompletedDate,
|
||||
solution: isSolution
|
||||
});
|
||||
|
||||
req.user.completedBonfires.push({
|
||||
_id: bonfireHash,
|
||||
completedWith: pairedWith._id,
|
||||
completedDate: isCompletedDate,
|
||||
solution: isSolution
|
||||
})
|
||||
|
||||
req.user.save(function (err, user) {
|
||||
pairedWith.save(function (err, paired) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
if (user && paired) {
|
||||
res.send(true);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
||||
req.user.completedBonfires.push({
|
||||
_id: bonfireHash,
|
||||
completedWith: null,
|
||||
completedDate: isCompletedDate,
|
||||
solution: isSolution
|
||||
});
|
||||
|
||||
var index = req.user.uncompletedBonfires.indexOf(bonfireHash);
|
||||
if (index > -1) {
|
||||
req.user.points++;
|
||||
req.user.uncompletedBonfires.splice(index, 1)
|
||||
}
|
||||
|
||||
req.user.save(function (err, user) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
if (user) {
|
||||
debug('Saving user');
|
||||
res.send(true)
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -47,7 +47,6 @@ module.exports = {
|
||||
},
|
||||
|
||||
getDoneWithFirst100Hours: function(req, res) {
|
||||
console.log(req.user.points)
|
||||
if (req.user.points >= 53) {
|
||||
res.render('contact/done-with-first-100-hours', {
|
||||
title: 'Congratulations on finishing the first 100 hours of Free Code Camp!'
|
||||
@ -63,7 +62,7 @@ module.exports = {
|
||||
to: 'team@freecodecamp.com',
|
||||
name: 'Completionist',
|
||||
from: req.body.email,
|
||||
subject: 'Code Camper at ' + req.body.email + ' has completed the first 100 hours',
|
||||
subject: 'Camper at ' + req.body.email + ' has completed the first 100 hours',
|
||||
text: ''
|
||||
};
|
||||
|
||||
|
189
controllers/courseware.js
Normal file
189
controllers/courseware.js
Normal file
@ -0,0 +1,189 @@
|
||||
var _ = require('lodash'),
|
||||
debug = require('debug')('freecc:cntr:courseware'),
|
||||
Courseware = require('./../models/Courseware'),
|
||||
User = require('./../models/User'),
|
||||
resources = require('./resources');
|
||||
|
||||
/**
|
||||
* Courseware controller
|
||||
*/
|
||||
|
||||
exports.coursewareNames = function(req, res) {
|
||||
res.render('coursewares/showList', {
|
||||
coursewareList: resources.allCoursewareNames()
|
||||
});
|
||||
};
|
||||
|
||||
exports.returnNextCourseware = function(req, res) {
|
||||
if (!req.user) {
|
||||
return res.redirect('coursewares/intro');
|
||||
}
|
||||
var completed = req.user.completedCoursewares.map(function (elem) {
|
||||
return elem._id;
|
||||
});
|
||||
|
||||
req.user.uncompletedCoursewares = resources.allCoursewareIds().filter(function (elem) {
|
||||
if (completed.indexOf(elem) === -1) {
|
||||
return elem;
|
||||
}
|
||||
});
|
||||
req.user.save();
|
||||
|
||||
var uncompletedCoursewares = req.user.uncompletedCoursewares;
|
||||
|
||||
var displayedCoursewares = Courseware.find({'_id': uncompletedCoursewares[0]});
|
||||
displayedCoursewares.exec(function(err, courseware) {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
courseware = courseware.pop();
|
||||
if (courseware === undefined) {
|
||||
req.flash('errors', {
|
||||
msg: "It looks like you've completed all the courses we have available. Good job!"
|
||||
})
|
||||
return res.redirect('../coursewares/intro');
|
||||
}
|
||||
nameString = courseware.name.toLowerCase().replace(/\s/g, '-');
|
||||
return res.redirect('../coursewares/' + nameString);
|
||||
});
|
||||
};
|
||||
|
||||
exports.returnIndividualCourseware = function(req, res, next) {
|
||||
var dashedName = req.params.coursewareName;
|
||||
|
||||
coursewareName = dashedName.replace(/\-/g, ' ');
|
||||
|
||||
Courseware.find({"name" : new RegExp(coursewareName, 'i')}, function(err, courseware) {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
// Handle not found
|
||||
if (courseware.length < 1) {
|
||||
req.flash('errors', {
|
||||
msg: "404: We couldn't find a challenge with that name. Please double check the name."
|
||||
});
|
||||
return res.redirect('/coursewares')
|
||||
}
|
||||
courseware = courseware.pop();
|
||||
|
||||
// Redirect to full name if the user only entered a partial
|
||||
var dashedNameFull = courseware.name.toLowerCase().replace(/\s/g, '-');
|
||||
if (dashedNameFull != dashedName) {
|
||||
return res.redirect('../coursewares/' + dashedNameFull);
|
||||
}
|
||||
|
||||
// Render the view for the user
|
||||
res.render('coursewares/show', {
|
||||
title: courseware.name,
|
||||
dashedName: dashedName,
|
||||
name: courseware.name,
|
||||
brief: courseware.description[0],
|
||||
details: courseware.description.slice(1),
|
||||
tests: courseware.tests,
|
||||
challengeSeed: courseware.challengeSeed,
|
||||
cc: !!req.user,
|
||||
points: req.user ? req.user.points : undefined,
|
||||
verb: resources.randomVerb(),
|
||||
phrase: resources.randomPhrase(),
|
||||
compliment: resources.randomCompliment(),
|
||||
coursewareHash: courseware._id,
|
||||
environment: resources.whichEnvironment()
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
exports.testCourseware = function(req, res) {
|
||||
var coursewareName = req.body.name,
|
||||
coursewareTests = req.body.tests,
|
||||
coursewareDifficulty = req.body.difficulty,
|
||||
coursewareDescription = req.body.description,
|
||||
coursewareEntryPoint = req.body.challengeEntryPoint,
|
||||
coursewareChallengeSeed = req.body.challengeSeed;
|
||||
coursewareTests = coursewareTests.split('\r\n');
|
||||
coursewareDescription = coursewareDescription.split('\r\n');
|
||||
coursewareTests.filter(getRidOfEmpties);
|
||||
coursewareDescription.filter(getRidOfEmpties);
|
||||
coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', '');
|
||||
res.render('courseware/show', {
|
||||
completedWith: null,
|
||||
title: coursewareName,
|
||||
name: coursewareName,
|
||||
difficulty: +coursewareDifficulty,
|
||||
brief: coursewareDescription[0],
|
||||
details: coursewareDescription.slice(1),
|
||||
tests: coursewareTests,
|
||||
challengeSeed: coursewareChallengeSeed,
|
||||
challengeEntryPoint: coursewareEntryPoint,
|
||||
cc: req.user ? req.user.coursewaresHash : undefined,
|
||||
points: req.user ? req.user.points : undefined,
|
||||
verb: resources.randomVerb(),
|
||||
phrase: resources.randomPhrase(),
|
||||
compliment: resources.randomCompliment(),
|
||||
coursewares: [],
|
||||
coursewareHash: "test"
|
||||
});
|
||||
};
|
||||
|
||||
function getRidOfEmpties(elem) {
|
||||
if (elem.length > 0) {
|
||||
return elem;
|
||||
}
|
||||
};
|
||||
|
||||
exports.publicGenerator = function(req, res) {
|
||||
res.render('courseware/public-generator');
|
||||
};
|
||||
|
||||
exports.generateChallenge = function(req, res) {
|
||||
var coursewareName = req.body.name,
|
||||
coursewareTests = req.body.tests,
|
||||
coursewareDifficulty = req.body.difficulty,
|
||||
coursewareDescription = req.body.description,
|
||||
coursewareEntryPoint = req.body.challengeEntryPoint,
|
||||
coursewareChallengeSeed = req.body.challengeSeed;
|
||||
coursewareTests = coursewareTests.split('\r\n');
|
||||
coursewareDescription = coursewareDescription.split('\r\n');
|
||||
coursewareTests.filter(getRidOfEmpties);
|
||||
coursewareDescription.filter(getRidOfEmpties);
|
||||
coursewareChallengeSeed = coursewareChallengeSeed.replace('\r', '');
|
||||
|
||||
var response = {
|
||||
_id: randomString(),
|
||||
name: coursewareName,
|
||||
difficulty: coursewareDifficulty,
|
||||
description: coursewareDescription,
|
||||
challengeEntryPoint: coursewareEntryPoint,
|
||||
challengeSeed: coursewareChallengeSeed,
|
||||
tests: coursewareTests
|
||||
};
|
||||
res.send(response);
|
||||
};
|
||||
|
||||
exports.completedCourseware = function (req, res) {
|
||||
debug('In post call with data from req', req);
|
||||
|
||||
var isCompletedDate = Math.round(+new Date() / 1000);
|
||||
var coursewareHash = req.body.coursewareInfo.coursewareHash;
|
||||
|
||||
req.user.completedCoursewares.push({
|
||||
_id: coursewareHash,
|
||||
completedDate: isCompletedDate
|
||||
});
|
||||
|
||||
var index = req.user.uncompletedCoursewares.indexOf(coursewareHash);
|
||||
if (index > -1) {
|
||||
req.user.points++;
|
||||
req.user.uncompletedCoursewares.splice(index, 1)
|
||||
}
|
||||
|
||||
req.user.save(function (err, user) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
if (user) {
|
||||
debug('Saving user');
|
||||
res.send(true)
|
||||
}
|
||||
});
|
||||
};
|
@ -5,6 +5,7 @@ var User = require('../models/User'),
|
||||
secrets = require('./../config/secrets'),
|
||||
Challenge = require('./../models/Challenge'),
|
||||
bonfires = require('../seed_data/bonfires.json');
|
||||
coursewares = require('../seed_data/coursewares.json');
|
||||
Client = require('node-rest-client').Client,
|
||||
client = new Client(),
|
||||
debug = require('debug')('freecc:cntr:bonfires');
|
||||
@ -202,8 +203,55 @@ module.exports = {
|
||||
})
|
||||
.map(function(elem) {
|
||||
return elem._id;
|
||||
});
|
||||
},
|
||||
allBonfireNames: function() {
|
||||
return bonfires.map(function(elem) {
|
||||
return {
|
||||
name: elem.name,
|
||||
difficulty: elem.difficulty
|
||||
}
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.difficulty - b.difficulty;
|
||||
})
|
||||
.map(function(elem) {
|
||||
return elem.name;
|
||||
});
|
||||
},
|
||||
|
||||
allCoursewareIds: function() {
|
||||
return coursewares.map(function(elem) {
|
||||
return {
|
||||
_id: elem._id,
|
||||
difficulty: elem.difficulty
|
||||
}
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.difficulty - b.difficulty;
|
||||
})
|
||||
.map(function(elem) {
|
||||
return elem._id;
|
||||
});
|
||||
},
|
||||
allCoursewareNames: function() {
|
||||
return coursewares.map(function(elem) {
|
||||
return {
|
||||
name: elem.name,
|
||||
difficulty: elem.difficulty
|
||||
}
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.difficulty - b.difficulty;
|
||||
})
|
||||
.map(function(elem) {
|
||||
return elem.name;
|
||||
});
|
||||
},
|
||||
whichEnvironment: function() {
|
||||
return process.env.NODE_ENV;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -140,7 +140,7 @@
|
||||
"Now go to <a href='http://coderbyte.com/CodingArea/Challenges/#easyChals' target='_blank'>http://coderbyte.com/CodingArea/Challenges/#easyChals</a> and start working through Coderbyte's easy algorithm scripting challenges using JavaScript.",
|
||||
"When you are finished pair programming, click the X to end the session.",
|
||||
"Congratulations! You have completed your first pair programming session.",
|
||||
"You should pair program with different Code Campers until you've completed all the Easy, Medium and Hard CoderByte challenges. This is a big time investment, but the JavaScript practice you'll get, along with the scripting and algorithm experience, are well worth it!",
|
||||
"You should pair program with different campers until you've completed all the Easy, Medium and Hard CoderByte challenges. This is a big time investment, but the JavaScript practice you'll get, along with the scripting and algorithm experience, are well worth it!",
|
||||
"You can complete CoderByte problems while you continue to work through Free Code Camp's challenges.",
|
||||
"Be sure to pair program on these challenges, and remember to apply the RSAP methodology.",
|
||||
"Click the button below to return to the Pair Programming challenge, then mark it complete."
|
||||
@ -162,6 +162,8 @@
|
||||
"compliments": [
|
||||
"Over the top!",
|
||||
"Down the rabbit hole we go!",
|
||||
"Well, isn't that special!",
|
||||
"Somewhere over the rainbow!",
|
||||
"Follow the white rabbit!",
|
||||
"Eye of the tiger!",
|
||||
"Run, Forest, run!",
|
||||
|
@ -229,7 +229,7 @@ exports.returnUser = function(req, res, next) {
|
||||
var user = user[0];
|
||||
Challenge.find({}, null, {sort: {challengeNumber: 1}}, function (err, c) {
|
||||
res.render('account/show', {
|
||||
title: 'Code Camper: ',
|
||||
title: 'Camper: ',
|
||||
username: user.profile.username,
|
||||
name: user.profile.name,
|
||||
location: user.profile.location,
|
||||
|
@ -17,7 +17,6 @@ var bonfireSchema = new mongoose.Schema({
|
||||
tests: Array,
|
||||
challengeSeed: String,
|
||||
challengeEntryPoint: String,
|
||||
challengeEntryPointNegate: String
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Bonfire', bonfireSchema);
|
||||
|
21
models/Courseware.js
Normal file
21
models/Courseware.js
Normal file
@ -0,0 +1,21 @@
|
||||
var mongoose = require('mongoose');
|
||||
var secrets = require('../config/secrets');
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {exports.Schema}
|
||||
*/
|
||||
|
||||
var coursewareSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
difficulty: String,
|
||||
description: Array,
|
||||
tests: Array,
|
||||
challengeSeed: Array,
|
||||
challengeType: Number // 0 = html, 1 = javascript only, 2 = video
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Courseware', coursewareSchema);
|
@ -355,10 +355,8 @@ var userSchema = new mongoose.Schema({
|
||||
resetPasswordExpires: Date,
|
||||
uncompletedBonfires: Array,
|
||||
completedBonfires: Array,
|
||||
lastContentSync: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
uncompletedCoursewares: Array,
|
||||
completedCoursewares: Array
|
||||
});
|
||||
|
||||
/**
|
||||
|
11
public/css/lib/ionicons/ionicons.min.css
vendored
Executable file
11
public/css/lib/ionicons/ionicons.min.css
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -530,7 +530,7 @@ thead {
|
||||
.challenge-list-header {
|
||||
background-color: #215f1e;
|
||||
color: #eee;
|
||||
font-size: 40px;
|
||||
font-size: 36px;
|
||||
text-align: center;
|
||||
margin-bottom: -30px;
|
||||
border-radius: 5px 5px 0px 0px;
|
||||
@ -539,7 +539,7 @@ thead {
|
||||
|
||||
.closing-x {
|
||||
color: #eee;
|
||||
font-size: 60px;
|
||||
font-size: 50px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@ -654,6 +654,35 @@ div.CodeMirror-scroll {
|
||||
margin-top: -30px;
|
||||
}
|
||||
|
||||
iframe.iphone {
|
||||
border: none;
|
||||
@media(min-width: 992px) {
|
||||
width: 280px;
|
||||
height: 500px;
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
right: 25px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
@media(max-width: 991px) {
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
overflow-y: visible;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// To adjust right margin, negative values bring the image closer to the edge of the screen
|
||||
.iphone-position {
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
right: -205px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.courseware-height {
|
||||
min-height: 650px;
|
||||
}
|
||||
|
||||
|
||||
//uncomment this to see the dimensions of all elements outlined in red
|
||||
|
@ -10,17 +10,24 @@ var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor")
|
||||
scrollbarStyle: 'null',
|
||||
lineWrapping: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
onKeyEvent: doLinting,
|
||||
extraKeys : {
|
||||
"Ctrl-Enter" : function() {
|
||||
bonfireExecute();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
onKeyEvent: doLinting
|
||||
});
|
||||
var editor = myCodeMirror;
|
||||
editor.setSize("100%", "auto");
|
||||
|
||||
// Hijack tab key to enter two spaces intead
|
||||
editor.setOption("extraKeys", {
|
||||
Tab: function(cm) {
|
||||
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
|
||||
cm.replaceSelection(spaces);
|
||||
},
|
||||
"Ctrl-Enter": function() {
|
||||
bonfireExecute();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
var attempts = 0;
|
||||
if (attempts) {
|
||||
@ -50,6 +57,7 @@ var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"),
|
||||
readOnly: 'nocursor',
|
||||
lineWrapping: true
|
||||
});
|
||||
|
||||
codeOutput.setValue('/**\n' +
|
||||
' * Your output will go here.\n' + ' * Console.log() -type statements\n' +
|
||||
' * will appear in your browser\'s\n' + ' * DevTools JavaScript console.\n' +
|
||||
@ -234,4 +242,9 @@ function showCompletion() {
|
||||
console.log(time);
|
||||
ga('send', 'event', 'Bonfire', 'solved', bonfireName + ', Time: ' + time +', Attempts: ' + attempts);
|
||||
$('#complete-bonfire-dialog').modal('show');
|
||||
$('#complete-bonfire-dialog').keydown(function(e) {
|
||||
if (e.ctrlKey && e.keyCode == 13) {
|
||||
$('.next-bonfire-button').click();
|
||||
}
|
||||
});
|
||||
}
|
231
public/js/lib/chai/chai-jquery.js
Normal file
231
public/js/lib/chai/chai-jquery.js
Normal file
@ -0,0 +1,231 @@
|
||||
(function (chaiJquery) {
|
||||
// Module systems magic dance.
|
||||
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
||||
// NodeJS
|
||||
module.exports = chaiJquery;
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define(['jquery'], function ($) {
|
||||
return function (chai, utils) {
|
||||
return chaiJquery(chai, utils, $);
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// Other environment (usually <script> tag): plug in to global chai instance directly.
|
||||
chai.use(function (chai, utils) {
|
||||
return chaiJquery(chai, utils, jQuery);
|
||||
});
|
||||
}
|
||||
}(function (chai, utils, $) {
|
||||
var inspect = utils.inspect,
|
||||
flag = utils.flag;
|
||||
$ = $ || jQuery;
|
||||
|
||||
var setPrototypeOf = '__proto__' in Object ?
|
||||
function (object, prototype) {
|
||||
object.__proto__ = prototype;
|
||||
} :
|
||||
function (object, prototype) {
|
||||
var excludeNames = /^(?:length|name|arguments|caller)$/;
|
||||
|
||||
function copyProperties(dst, src) {
|
||||
Object.getOwnPropertyNames(src).forEach(function (name) {
|
||||
if (!excludeNames.test(name)) {
|
||||
Object.defineProperty(dst, name,
|
||||
Object.getOwnPropertyDescriptor(src, name));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
copyProperties(object, prototype);
|
||||
copyProperties(object, Object.getPrototypeOf(prototype));
|
||||
};
|
||||
|
||||
$.fn.inspect = function (depth) {
|
||||
var el = $('<div />').append(this.clone());
|
||||
if (depth !== undefined) {
|
||||
var children = el.children();
|
||||
while (depth-- > 0)
|
||||
children = children.children();
|
||||
children.html('...');
|
||||
}
|
||||
return el.html();
|
||||
};
|
||||
|
||||
var props = {attr: 'attribute', css: 'CSS property', prop: 'property'};
|
||||
for (var prop in props) {
|
||||
(function (prop, description) {
|
||||
chai.Assertion.addMethod(prop, function (name, val) {
|
||||
var actual = flag(this, 'object')[prop](name);
|
||||
|
||||
if (!flag(this, 'negate') || undefined === val) {
|
||||
this.assert(
|
||||
undefined !== actual
|
||||
, 'expected #{this} to have a #{exp} ' + description
|
||||
, 'expected #{this} not to have a #{exp} ' + description
|
||||
, name
|
||||
);
|
||||
}
|
||||
|
||||
if (undefined !== val) {
|
||||
this.assert(
|
||||
val === actual
|
||||
, 'expected #{this} to have a ' + inspect(name) + ' ' + description + ' with the value #{exp}, but the value was #{act}'
|
||||
, 'expected #{this} not to have a ' + inspect(name) + ' ' + description + ' with the value #{act}'
|
||||
, val
|
||||
, actual
|
||||
);
|
||||
}
|
||||
|
||||
flag(this, 'object', actual);
|
||||
});
|
||||
})(prop, props[prop]);
|
||||
}
|
||||
|
||||
chai.Assertion.addMethod('data', function (name, val) {
|
||||
// Work around a chai bug (https://github.com/logicalparadox/chai/issues/16)
|
||||
if (flag(this, 'negate') && undefined !== val && undefined === flag(this, 'object').data(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var assertion = new chai.Assertion(flag(this, 'object').data());
|
||||
if (flag(this, 'negate'))
|
||||
assertion = assertion.not;
|
||||
return assertion.property(name, val);
|
||||
});
|
||||
|
||||
chai.Assertion.addMethod('class', function (className) {
|
||||
this.assert(
|
||||
flag(this, 'object').hasClass(className)
|
||||
, 'expected #{this} to have class #{exp}'
|
||||
, 'expected #{this} not to have class #{exp}'
|
||||
, className
|
||||
);
|
||||
});
|
||||
|
||||
chai.Assertion.addMethod('id', function (id) {
|
||||
this.assert(
|
||||
flag(this, 'object').attr('id') === id
|
||||
, 'expected #{this} to have id #{exp}'
|
||||
, 'expected #{this} not to have id #{exp}'
|
||||
, id
|
||||
);
|
||||
});
|
||||
|
||||
chai.Assertion.addMethod('html', function (html) {
|
||||
var actual = flag(this, 'object').html();
|
||||
this.assert(
|
||||
actual === html
|
||||
, 'expected #{this} to have HTML #{exp}, but the HTML was #{act}'
|
||||
, 'expected #{this} not to have HTML #{exp}'
|
||||
, html
|
||||
, actual
|
||||
);
|
||||
});
|
||||
|
||||
chai.Assertion.addMethod('text', function (text) {
|
||||
var actual = flag(this, 'object').text();
|
||||
this.assert(
|
||||
actual === text
|
||||
, 'expected #{this} to have text #{exp}, but the text was #{act}'
|
||||
, 'expected #{this} not to have text #{exp}'
|
||||
, text
|
||||
, actual
|
||||
);
|
||||
});
|
||||
|
||||
chai.Assertion.addMethod('value', function (value) {
|
||||
var actual = flag(this, 'object').val();
|
||||
this.assert(
|
||||
flag(this, 'object').val() === value
|
||||
, 'expected #{this} to have value #{exp}, but the value was #{act}'
|
||||
, 'expected #{this} not to have value #{exp}'
|
||||
, value
|
||||
, actual
|
||||
);
|
||||
});
|
||||
|
||||
chai.Assertion.addMethod('descendants', function (selector) {
|
||||
this.assert(
|
||||
flag(this, 'object').has(selector).length > 0
|
||||
, 'expected #{this} to have #{exp}'
|
||||
, 'expected #{this} not to have #{exp}'
|
||||
, selector
|
||||
);
|
||||
});
|
||||
|
||||
$.each(['visible', 'hidden', 'selected', 'checked', 'enabled', 'disabled'], function (i, attr) {
|
||||
chai.Assertion.addProperty(attr, function () {
|
||||
this.assert(
|
||||
flag(this, 'object').is(':' + attr)
|
||||
, 'expected #{this} to be ' + attr
|
||||
, 'expected #{this} not to be ' + attr);
|
||||
});
|
||||
});
|
||||
|
||||
chai.Assertion.overwriteProperty('exist', function (_super) {
|
||||
return function () {
|
||||
var obj = flag(this, 'object');
|
||||
if (obj instanceof $) {
|
||||
this.assert(
|
||||
obj.length > 0
|
||||
, 'expected ' + inspect(obj.selector) + ' to exist'
|
||||
, 'expected ' + inspect(obj.selector) + ' not to exist');
|
||||
} else {
|
||||
_super.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
chai.Assertion.overwriteProperty('empty', function (_super) {
|
||||
return function () {
|
||||
var obj = flag(this, 'object');
|
||||
if (obj instanceof $) {
|
||||
this.assert(
|
||||
obj.is(':empty')
|
||||
, 'expected #{this} to be empty'
|
||||
, 'expected #{this} not to be empty');
|
||||
} else {
|
||||
_super.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
chai.Assertion.overwriteMethod('match', function (_super) {
|
||||
return function (selector) {
|
||||
var obj = flag(this, 'object');
|
||||
if (obj instanceof $) {
|
||||
this.assert(
|
||||
obj.is(selector)
|
||||
, 'expected #{this} to match #{exp}'
|
||||
, 'expected #{this} not to match #{exp}'
|
||||
, selector
|
||||
);
|
||||
} else {
|
||||
_super.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chai.Assertion.overwriteChainableMethod('contain',
|
||||
function (_super) {
|
||||
return function (text) {
|
||||
var obj = flag(this, 'object');
|
||||
if (obj instanceof $) {
|
||||
this.assert(
|
||||
obj.is(':contains(\'' + text + '\')')
|
||||
, 'expected #{this} to contain #{exp}'
|
||||
, 'expected #{this} not to contain #{exp}'
|
||||
, text);
|
||||
} else {
|
||||
_super.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
function(_super) {
|
||||
return function() {
|
||||
_super.call(this);
|
||||
};
|
||||
}
|
||||
);
|
||||
}));
|
1
public/js/lib/codemirror-accessible/.gitignore
vendored
Executable file
1
public/js/lib/codemirror-accessible/.gitignore
vendored
Executable file
@ -0,0 +1 @@
|
||||
.DS_Store
|
3
public/js/lib/codemirror-accessible/README.md
Executable file
3
public/js/lib/codemirror-accessible/README.md
Executable file
@ -0,0 +1,3 @@
|
||||
# CodeMirror Accessible
|
||||
|
||||
Read more at the project page: http://bgrins.github.io/codemirror-accessible/
|
80
public/js/lib/codemirror-accessible/accessible.js
Executable file
80
public/js/lib/codemirror-accessible/accessible.js
Executable file
@ -0,0 +1,80 @@
|
||||
window.onload=function(){
|
||||
if (!window.waitToLoad) {
|
||||
load();
|
||||
}
|
||||
};
|
||||
|
||||
function load() {
|
||||
if (window.loaded) {
|
||||
return;
|
||||
}
|
||||
window.loaded = true;
|
||||
|
||||
var myTextArea = document.getElementById("editor");
|
||||
var textareaEditor = document.getElementById('textarea-editor');
|
||||
var clonedTextArea = document.getElementById('editor2');
|
||||
|
||||
textareaEditor.value = clonedTextArea.value = myTextArea.value;
|
||||
|
||||
document.getElementById("original-demo").appendChild(clonedTextArea);
|
||||
var myScreenreader = document.getElementById("editor-screenreader");
|
||||
|
||||
var myContentEditable = document.getElementById("content-editable");
|
||||
var button = document.getElementById("toggle-textarea-visibility");
|
||||
|
||||
|
||||
var editor = window.editor = CodeMirror.fromTextArea(myTextArea, {
|
||||
lineNumbers: true,
|
||||
matchBrackets: true,
|
||||
continueComments: "Enter",
|
||||
extraKeys: {"Ctrl-Q": "toggleComment"}
|
||||
});
|
||||
|
||||
var editor2 = window.editor2 = CodeMirrorOriginal.fromTextArea(clonedTextArea, {
|
||||
lineNumbers: true,
|
||||
matchBrackets: true,
|
||||
continueComments: "Enter",
|
||||
extraKeys: {"Ctrl-Q": "toggleComment"}
|
||||
});
|
||||
|
||||
|
||||
var textareaVisible = false;
|
||||
function toggleTextareaDisplay() {
|
||||
|
||||
if (!editor.display.input.getAttribute("backup-style")) {
|
||||
editor.display.input.setAttribute("backup-style", editor.display.input.getAttribute("style"));
|
||||
editor.display.input.parentNode.setAttribute("backup-style", editor.display.input.parentNode.getAttribute("style"));
|
||||
}
|
||||
|
||||
if (textareaVisible) {
|
||||
editor.display.input.setAttribute("style", editor.display.input.getAttribute("backup-style"));
|
||||
editor.display.input.parentNode.setAttribute("style", editor.display.input.parentNode.getAttribute("backup-style"));
|
||||
editor2.display.input.setAttribute("style", editor.display.input.getAttribute("backup-style"));
|
||||
editor2.display.input.parentNode.setAttribute("style", editor.display.input.parentNode.getAttribute("backup-style"));
|
||||
}
|
||||
else {
|
||||
editor.display.input.setAttribute("style", "height: 100px; width: 95%; margin-left: 3%; ");
|
||||
editor.display.input.parentNode.setAttribute("style", "");
|
||||
editor2.display.input.setAttribute("style", "height: 100px; width: 95%; margin-left: 3%; ");
|
||||
editor2.display.input.parentNode.setAttribute("style", "");
|
||||
}
|
||||
|
||||
textareaVisible = !textareaVisible;
|
||||
|
||||
if (window.localStorage) {
|
||||
if (textareaVisible) {
|
||||
window.localStorage["toggleTextareaDisplay"] = "1";
|
||||
}
|
||||
else {
|
||||
window.localStorage.removeKey("toggleTextareaDisplay");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.onclick = toggleTextareaDisplay;
|
||||
|
||||
if (window.localStorage && window.localStorage["toggleTextareaDisplay"]) {
|
||||
toggleTextareaDisplay();
|
||||
}
|
||||
|
||||
}
|
5828
public/js/lib/codemirror-accessible/codemirror-accessible.js
Executable file
5828
public/js/lib/codemirror-accessible/codemirror-accessible.js
Executable file
File diff suppressed because it is too large
Load Diff
5848
public/js/lib/codemirror-accessible/codemirror-accessible2.js
Executable file
5848
public/js/lib/codemirror-accessible/codemirror-accessible2.js
Executable file
File diff suppressed because it is too large
Load Diff
5799
public/js/lib/codemirror-accessible/codemirror-original.js
Executable file
5799
public/js/lib/codemirror-accessible/codemirror-original.js
Executable file
File diff suppressed because it is too large
Load Diff
258
public/js/lib/codemirror-accessible/codemirror.css
Executable file
258
public/js/lib/codemirror-accessible/codemirror.css
Executable file
@ -0,0 +1,258 @@
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
/* Set scrolling behaviour here */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
z-index: 3;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
background: #7e7;
|
||||
z-index: 1;
|
||||
}
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
|
||||
|
||||
.cm-tab { display: inline-block; }
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable {color: black;}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3 {color: #085;}
|
||||
.cm-s-default .cm-property {color: black;}
|
||||
.cm-s-default .cm-operator {color: black;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px; margin-right: -30px;
|
||||
padding-bottom: 30px; padding-right: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actuall scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
padding-bottom: 30px;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: -32px;
|
||||
display: inline-block;
|
||||
/* Hack to make IE7 behave */
|
||||
*zoom:1;
|
||||
*display:inline;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
}
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
.CodeMirror-code pre {
|
||||
border-right: 30px solid transparent;
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
.CodeMirror-wrap .CodeMirror-code pre {
|
||||
border-right: none;
|
||||
width: auto;
|
||||
}
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-widget {
|
||||
}
|
||||
|
||||
.CodeMirror-wrap .CodeMirror-scroll {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%; height: 0px;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
.CodeMirror-focused div.CodeMirror-cursor {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background: #ffa;
|
||||
background: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
|
||||
.CodeMirror span { *vertical-align: text-bottom; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
9941
public/js/lib/codemirror-accessible/index.html
Executable file
9941
public/js/lib/codemirror-accessible/index.html
Executable file
File diff suppressed because it is too large
Load Diff
482
public/js/lib/codemirror-accessible/javascript.js
Executable file
482
public/js/lib/codemirror-accessible/javascript.js
Executable file
@ -0,0 +1,482 @@
|
||||
// TODO actually recognize syntax of TypeScript constructs
|
||||
|
||||
|
||||
var opts= function(config, parserConfig) {
|
||||
var indentUnit = config.indentUnit;
|
||||
var statementIndent = parserConfig.statementIndent;
|
||||
var jsonMode = parserConfig.json;
|
||||
var isTS = parserConfig.typescript;
|
||||
|
||||
// Tokenizer
|
||||
|
||||
var keywords = function(){
|
||||
function kw(type) {return {type: type, style: "keyword"};}
|
||||
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
|
||||
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
|
||||
|
||||
var jsKeywords = {
|
||||
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
|
||||
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
|
||||
"var": kw("var"), "const": kw("var"), "let": kw("var"),
|
||||
"function": kw("function"), "catch": kw("catch"),
|
||||
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
|
||||
"in": operator, "typeof": operator, "instanceof": operator,
|
||||
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
|
||||
"this": kw("this")
|
||||
};
|
||||
|
||||
// Extend the 'normal' keywords with the TypeScript language extensions
|
||||
if (isTS) {
|
||||
var type = {type: "variable", style: "variable-3"};
|
||||
var tsKeywords = {
|
||||
// object-like things
|
||||
"interface": kw("interface"),
|
||||
"class": kw("class"),
|
||||
"extends": kw("extends"),
|
||||
"constructor": kw("constructor"),
|
||||
|
||||
// scope modifiers
|
||||
"public": kw("public"),
|
||||
"private": kw("private"),
|
||||
"protected": kw("protected"),
|
||||
"static": kw("static"),
|
||||
|
||||
"super": kw("super"),
|
||||
|
||||
// types
|
||||
"string": type, "number": type, "bool": type, "any": type
|
||||
};
|
||||
|
||||
for (var attr in tsKeywords) {
|
||||
jsKeywords[attr] = tsKeywords[attr];
|
||||
}
|
||||
}
|
||||
|
||||
return jsKeywords;
|
||||
}();
|
||||
|
||||
var isOperatorChar = /[+\-*&%=<>!?|~^]/;
|
||||
|
||||
function chain(stream, state, f) {
|
||||
state.tokenize = f;
|
||||
return f(stream, state);
|
||||
}
|
||||
|
||||
function nextUntilUnescaped(stream, end) {
|
||||
var escaped = false, next;
|
||||
while ((next = stream.next()) != null) {
|
||||
if (next == end && !escaped)
|
||||
return false;
|
||||
escaped = !escaped && next == "\\";
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Used as scratch variables to communicate multiple values without
|
||||
// consing up tons of objects.
|
||||
var type, content;
|
||||
function ret(tp, style, cont) {
|
||||
type = tp; content = cont;
|
||||
return style;
|
||||
}
|
||||
|
||||
function jsTokenBase(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (ch == '"' || ch == "'")
|
||||
return chain(stream, state, jsTokenString(ch));
|
||||
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
|
||||
return ret(ch);
|
||||
else if (ch == "0" && stream.eat(/x/i)) {
|
||||
stream.eatWhile(/[\da-f]/i);
|
||||
return ret("number", "number");
|
||||
}
|
||||
else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
|
||||
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
|
||||
return ret("number", "number");
|
||||
}
|
||||
else if (ch == "/") {
|
||||
if (stream.eat("*")) {
|
||||
return chain(stream, state, jsTokenComment);
|
||||
}
|
||||
else if (stream.eat("/")) {
|
||||
stream.skipToEnd();
|
||||
return ret("comment", "comment");
|
||||
}
|
||||
else if (state.lastType == "operator" || state.lastType == "keyword c" ||
|
||||
/^[\[{}\(,;:]$/.test(state.lastType)) {
|
||||
nextUntilUnescaped(stream, "/");
|
||||
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
|
||||
return ret("regexp", "string-2");
|
||||
}
|
||||
else {
|
||||
stream.eatWhile(isOperatorChar);
|
||||
return ret("operator", null, stream.current());
|
||||
}
|
||||
}
|
||||
else if (ch == "#") {
|
||||
stream.skipToEnd();
|
||||
return ret("error", "error");
|
||||
}
|
||||
else if (isOperatorChar.test(ch)) {
|
||||
stream.eatWhile(isOperatorChar);
|
||||
return ret("operator", null, stream.current());
|
||||
}
|
||||
else {
|
||||
stream.eatWhile(/[\w\$_]/);
|
||||
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
|
||||
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
|
||||
ret("variable", "variable", word);
|
||||
}
|
||||
}
|
||||
|
||||
function jsTokenString(quote) {
|
||||
return function(stream, state) {
|
||||
if (!nextUntilUnescaped(stream, quote))
|
||||
state.tokenize = jsTokenBase;
|
||||
return ret("string", "string");
|
||||
};
|
||||
}
|
||||
|
||||
function jsTokenComment(stream, state) {
|
||||
var maybeEnd = false, ch;
|
||||
while (ch = stream.next()) {
|
||||
if (ch == "/" && maybeEnd) {
|
||||
state.tokenize = jsTokenBase;
|
||||
break;
|
||||
}
|
||||
maybeEnd = (ch == "*");
|
||||
}
|
||||
return ret("comment", "comment");
|
||||
}
|
||||
|
||||
// Parser
|
||||
|
||||
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
|
||||
|
||||
function JSLexical(indented, column, type, align, prev, info) {
|
||||
this.indented = indented;
|
||||
this.column = column;
|
||||
this.type = type;
|
||||
this.prev = prev;
|
||||
this.info = info;
|
||||
if (align != null) this.align = align;
|
||||
}
|
||||
|
||||
function inScope(state, varname) {
|
||||
for (var v = state.localVars; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
}
|
||||
|
||||
function parseJS(state, style, type, content, stream) {
|
||||
var cc = state.cc;
|
||||
// Communicate our context to the combinators.
|
||||
// (Less wasteful than consing up a hundred closures on every call.)
|
||||
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
|
||||
|
||||
if (!state.lexical.hasOwnProperty("align"))
|
||||
state.lexical.align = true;
|
||||
|
||||
while(true) {
|
||||
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
|
||||
if (combinator(type, content)) {
|
||||
while(cc.length && cc[cc.length - 1].lex)
|
||||
cc.pop()();
|
||||
if (cx.marked) return cx.marked;
|
||||
if (type == "variable" && inScope(state, content)) return "variable-2";
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combinator utils
|
||||
|
||||
var cx = {state: null, column: null, marked: null, cc: null};
|
||||
function pass() {
|
||||
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
|
||||
}
|
||||
function cont() {
|
||||
pass.apply(null, arguments);
|
||||
return true;
|
||||
}
|
||||
function register(varname) {
|
||||
function inList(list) {
|
||||
for (var v = list; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
return false;
|
||||
}
|
||||
var state = cx.state;
|
||||
if (state.context) {
|
||||
cx.marked = "def";
|
||||
if (inList(state.localVars)) return;
|
||||
state.localVars = {name: varname, next: state.localVars};
|
||||
} else {
|
||||
if (inList(state.globalVars)) return;
|
||||
state.globalVars = {name: varname, next: state.globalVars};
|
||||
}
|
||||
}
|
||||
|
||||
// Combinators
|
||||
|
||||
var defaultVars = {name: "this", next: {name: "arguments"}};
|
||||
function pushcontext() {
|
||||
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
|
||||
cx.state.localVars = defaultVars;
|
||||
}
|
||||
function popcontext() {
|
||||
cx.state.localVars = cx.state.context.vars;
|
||||
cx.state.context = cx.state.context.prev;
|
||||
}
|
||||
function pushlex(type, info) {
|
||||
var result = function() {
|
||||
var state = cx.state, indent = state.indented;
|
||||
if (state.lexical.type == "stat") indent = state.lexical.indented;
|
||||
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
|
||||
};
|
||||
result.lex = true;
|
||||
return result;
|
||||
}
|
||||
function poplex() {
|
||||
var state = cx.state;
|
||||
if (state.lexical.prev) {
|
||||
if (state.lexical.type == ")")
|
||||
state.indented = state.lexical.indented;
|
||||
state.lexical = state.lexical.prev;
|
||||
}
|
||||
}
|
||||
poplex.lex = true;
|
||||
|
||||
function expect(wanted) {
|
||||
return function(type) {
|
||||
if (type == wanted) return cont();
|
||||
else if (wanted == ";") return pass();
|
||||
else return cont(arguments.callee);
|
||||
};
|
||||
}
|
||||
|
||||
function statement(type) {
|
||||
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
|
||||
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
|
||||
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
|
||||
if (type == "{") return cont(pushlex("}"), block, poplex);
|
||||
if (type == ";") return cont();
|
||||
if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
|
||||
if (type == "function") return cont(functiondef);
|
||||
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
|
||||
poplex, statement, poplex);
|
||||
if (type == "variable") return cont(pushlex("stat"), maybelabel);
|
||||
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
|
||||
block, poplex, poplex);
|
||||
if (type == "case") return cont(expression, expect(":"));
|
||||
if (type == "default") return cont(expect(":"));
|
||||
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
|
||||
statement, poplex, popcontext);
|
||||
return pass(pushlex("stat"), expression, expect(";"), poplex);
|
||||
}
|
||||
function expression(type) {
|
||||
return expressionInner(type, false);
|
||||
}
|
||||
function expressionNoComma(type) {
|
||||
return expressionInner(type, true);
|
||||
}
|
||||
function expressionInner(type, noComma) {
|
||||
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
|
||||
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
|
||||
if (type == "function") return cont(functiondef);
|
||||
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
|
||||
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
|
||||
if (type == "operator") return cont(noComma ? expressionNoComma : expression);
|
||||
if (type == "[") return cont(pushlex("]"), commasep(expressionNoComma, "]"), poplex, maybeop);
|
||||
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeop);
|
||||
return cont();
|
||||
}
|
||||
function maybeexpression(type) {
|
||||
if (type.match(/[;\}\)\],]/)) return pass();
|
||||
return pass(expression);
|
||||
}
|
||||
function maybeexpressionNoComma(type) {
|
||||
if (type.match(/[;\}\)\],]/)) return pass();
|
||||
return pass(expressionNoComma);
|
||||
}
|
||||
|
||||
function maybeoperatorComma(type, value) {
|
||||
if (type == ",") return cont(expression);
|
||||
return maybeoperatorNoComma(type, value, false);
|
||||
}
|
||||
function maybeoperatorNoComma(type, value, noComma) {
|
||||
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
|
||||
var expr = noComma == false ? expression : expressionNoComma;
|
||||
if (type == "operator") {
|
||||
if (/\+\+|--/.test(value)) return cont(me);
|
||||
if (value == "?") return cont(expression, expect(":"), expr);
|
||||
return cont(expr);
|
||||
}
|
||||
if (type == ";") return;
|
||||
if (type == "(") return cont(pushlex(")", "call"), commasep(expressionNoComma, ")"), poplex, me);
|
||||
if (type == ".") return cont(property, me);
|
||||
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
|
||||
}
|
||||
function maybelabel(type) {
|
||||
if (type == ":") return cont(poplex, statement);
|
||||
return pass(maybeoperatorComma, expect(";"), poplex);
|
||||
}
|
||||
function property(type) {
|
||||
if (type == "variable") {cx.marked = "property"; return cont();}
|
||||
}
|
||||
function objprop(type, value) {
|
||||
if (type == "variable") {
|
||||
cx.marked = "property";
|
||||
if (value == "get" || value == "set") return cont(getterSetter);
|
||||
} else if (type == "number" || type == "string") {
|
||||
cx.marked = type + " property";
|
||||
}
|
||||
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expressionNoComma);
|
||||
}
|
||||
function getterSetter(type) {
|
||||
if (type == ":") return cont(expression);
|
||||
if (type != "variable") return cont(expect(":"), expression);
|
||||
cx.marked = "property";
|
||||
return cont(functiondef);
|
||||
}
|
||||
function commasep(what, end) {
|
||||
function proceed(type) {
|
||||
if (type == ",") {
|
||||
var lex = cx.state.lexical;
|
||||
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
|
||||
return cont(what, proceed);
|
||||
}
|
||||
if (type == end) return cont();
|
||||
return cont(expect(end));
|
||||
}
|
||||
return function(type) {
|
||||
if (type == end) return cont();
|
||||
else return pass(what, proceed);
|
||||
};
|
||||
}
|
||||
function block(type) {
|
||||
if (type == "}") return cont();
|
||||
return pass(statement, block);
|
||||
}
|
||||
function maybetype(type) {
|
||||
if (type == ":") return cont(typedef);
|
||||
return pass();
|
||||
}
|
||||
function typedef(type) {
|
||||
if (type == "variable"){cx.marked = "variable-3"; return cont();}
|
||||
return pass();
|
||||
}
|
||||
function vardef1(type, value) {
|
||||
if (type == "variable") {
|
||||
register(value);
|
||||
return isTS ? cont(maybetype, vardef2) : cont(vardef2);
|
||||
}
|
||||
return pass();
|
||||
}
|
||||
function vardef2(type, value) {
|
||||
if (value == "=") return cont(expressionNoComma, vardef2);
|
||||
if (type == ",") return cont(vardef1);
|
||||
}
|
||||
function maybeelse(type, value) {
|
||||
if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
|
||||
}
|
||||
function forspec1(type) {
|
||||
if (type == "var") return cont(vardef1, expect(";"), forspec2);
|
||||
if (type == ";") return cont(forspec2);
|
||||
if (type == "variable") return cont(formaybein);
|
||||
return pass(expression, expect(";"), forspec2);
|
||||
}
|
||||
function formaybein(_type, value) {
|
||||
if (value == "in") return cont(expression);
|
||||
return cont(maybeoperatorComma, forspec2);
|
||||
}
|
||||
function forspec2(type, value) {
|
||||
if (type == ";") return cont(forspec3);
|
||||
if (value == "in") return cont(expression);
|
||||
return pass(expression, expect(";"), forspec3);
|
||||
}
|
||||
function forspec3(type) {
|
||||
if (type != ")") cont(expression);
|
||||
}
|
||||
function functiondef(type, value) {
|
||||
if (type == "variable") {register(value); return cont(functiondef);}
|
||||
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
|
||||
}
|
||||
function funarg(type, value) {
|
||||
if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
|
||||
}
|
||||
|
||||
// Interface
|
||||
|
||||
return {
|
||||
startState: function(basecolumn) {
|
||||
return {
|
||||
tokenize: jsTokenBase,
|
||||
lastType: null,
|
||||
cc: [],
|
||||
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
|
||||
localVars: parserConfig.localVars,
|
||||
globalVars: parserConfig.globalVars,
|
||||
context: parserConfig.localVars && {vars: parserConfig.localVars},
|
||||
indented: 0
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (stream.sol()) {
|
||||
if (!state.lexical.hasOwnProperty("align"))
|
||||
state.lexical.align = false;
|
||||
state.indented = stream.indentation();
|
||||
}
|
||||
if (state.tokenize != jsTokenComment && stream.eatSpace()) return null;
|
||||
var style = state.tokenize(stream, state);
|
||||
if (type == "comment") return style;
|
||||
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
|
||||
return parseJS(state, style, type, content, stream);
|
||||
},
|
||||
|
||||
indent: function(state, textAfter) {
|
||||
if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
|
||||
if (state.tokenize != jsTokenBase) return 0;
|
||||
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
|
||||
// Kludge to prevent 'maybelse' from blocking lexical scope pops
|
||||
for (var i = state.cc.length - 1; i >= 0; --i) {
|
||||
var c = state.cc[i];
|
||||
if (c == poplex) lexical = lexical.prev;
|
||||
else if (c != maybeelse || /^else\b/.test(textAfter)) break;
|
||||
}
|
||||
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
|
||||
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
|
||||
lexical = lexical.prev;
|
||||
var type = lexical.type, closing = firstChar == type;
|
||||
|
||||
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
|
||||
else if (type == "form" && firstChar == "{") return lexical.indented;
|
||||
else if (type == "form") return lexical.indented + indentUnit;
|
||||
else if (type == "stat")
|
||||
return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
|
||||
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
|
||||
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
|
||||
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
|
||||
else return lexical.indented + (closing ? 0 : indentUnit);
|
||||
},
|
||||
|
||||
electricChars: ":{}",
|
||||
blockCommentStart: jsonMode ? null : "/*",
|
||||
blockCommentEnd: jsonMode ? null : "*/",
|
||||
lineComment: jsonMode ? null : "//",
|
||||
fold: "brace",
|
||||
|
||||
helperType: jsonMode ? "json" : "javascript",
|
||||
jsonMode: jsonMode
|
||||
};
|
||||
};
|
||||
|
||||
CodeMirror.defineMode("javascript", opts );
|
||||
CodeMirrorOriginal.defineMode("javascript", opts );
|
||||
CodeMirror.defineMIME("text/javascript", "javascript");
|
||||
CodeMirror.defineMIME("text/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/javascript", "javascript");
|
||||
CodeMirror.defineMIME("application/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
|
||||
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
|
||||
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
|
||||
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
|
749
public/js/lib/codemirror-accessible/stress.html
Executable file
749
public/js/lib/codemirror-accessible/stress.html
Executable file
File diff suppressed because one or more lines are too long
129
public/js/lib/coursewares/coursewaresFramework.js
Normal file
129
public/js/lib/coursewares/coursewaresFramework.js
Normal file
@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Created by nathanleniz on 2/2/15.
|
||||
*/
|
||||
|
||||
var widgets = [];
|
||||
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
|
||||
lineNumbers: true,
|
||||
mode: "text/html",
|
||||
theme: 'monokai',
|
||||
runnable: true,
|
||||
//lint: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
scrollbarStyle: 'null',
|
||||
lineWrapping: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
onKeyEvent: doLinting
|
||||
});
|
||||
var editor = myCodeMirror;
|
||||
|
||||
|
||||
// Hijack tab key to insert two spaces instead
|
||||
editor.setOption("extraKeys", {
|
||||
Tab: function(cm) {
|
||||
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
|
||||
cm.replaceSelection(spaces);
|
||||
},
|
||||
"Ctrl-Enter": function() {
|
||||
bonfireExecute();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
editor.setSize("100%", "auto");
|
||||
|
||||
var libraryIncludes = "<script src='//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>" +
|
||||
"<script>document.domain = 'localhost'</script>" +
|
||||
"<script src='/js/lib/chai/chai.js'></script>" +
|
||||
"<script src='/js/lib/chai/chai-jquery.js'></script>" +
|
||||
"<script src='//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js'></script>" +
|
||||
"<link rel='stylesheet' href='//cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.0/animate.min.css'/>" +
|
||||
"<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'/>" +
|
||||
"<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css'/>" +
|
||||
"<style>body { padding: 0px 3px 0px 3px; }</style>";
|
||||
|
||||
var allTests = '';
|
||||
(function() {
|
||||
tests.forEach(function(elem) {
|
||||
allTests += elem + ' ';
|
||||
});
|
||||
})();
|
||||
|
||||
var otherTestsForNow = "<script src='/js/lib/coursewares/iFrameScripts.js'></script>";
|
||||
|
||||
var delay;
|
||||
// Initialize CodeMirror editor with a nice html5 canvas demo.
|
||||
editor.on("change", function () {
|
||||
clearTimeout(delay);
|
||||
delay = setTimeout(updatePreview, 300);
|
||||
});
|
||||
var nodeEnv = prodOrDev === 'production' ? 'http://www.freecodecamp.com' : 'http://localhost:3001';
|
||||
function updatePreview() {
|
||||
var previewFrame = document.getElementById('preview');
|
||||
var preview = previewFrame.contentDocument || previewFrame.contentWindow.document;
|
||||
preview.open();
|
||||
preview.write(libraryIncludes + editor.getValue() + otherTestsForNow);
|
||||
preview.close();
|
||||
}
|
||||
setTimeout(updatePreview, 300);
|
||||
|
||||
/**
|
||||
* Window postMessage receiving funtionality
|
||||
*/
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
|
||||
|
||||
// Listen to message from child window
|
||||
eventer(messageEvent,function(e) {
|
||||
if (e.data === 'CompleteAwesomeSauce') {
|
||||
showCompletion();
|
||||
}
|
||||
},false);
|
||||
|
||||
var challengeSeed = challengeSeed || null;
|
||||
var tests = tests || [];
|
||||
var allSeeds = '';
|
||||
(function() {
|
||||
challengeSeed.forEach(function(elem) {
|
||||
allSeeds += elem + '\n';
|
||||
});
|
||||
})();
|
||||
|
||||
myCodeMirror.setValue(allSeeds);
|
||||
|
||||
function doLinting () {
|
||||
editor.operation(function () {
|
||||
for (var i = 0; i < widgets.length; ++i)
|
||||
editor.removeLineWidget(widgets[i]);
|
||||
widgets.length = 0;
|
||||
JSHINT(editor.getValue());
|
||||
for (var i = 0; i < JSHINT.errors.length; ++i) {
|
||||
var err = JSHINT.errors[i];
|
||||
if (!err) continue;
|
||||
var msg = document.createElement("div");
|
||||
var icon = msg.appendChild(document.createElement("span"));
|
||||
icon.innerHTML = "!!";
|
||||
icon.className = "lint-error-icon";
|
||||
msg.appendChild(document.createTextNode(err.reason));
|
||||
msg.className = "lint-error";
|
||||
widgets.push(editor.addLineWidget(err.line - 1, msg, {
|
||||
coverGutter: false,
|
||||
noHScroll: true
|
||||
}));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function showCompletion() {
|
||||
$('#complete-courseware-dialog').modal('show');
|
||||
$('#complete-courseware-dialog').keydown(function(e) {
|
||||
if (e.ctrlKey && e.keyCode == 13) {
|
||||
$('.next-courseware-button').click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.domain = 'localhost';
|
15
public/js/lib/coursewares/iFrameScripts.js
Normal file
15
public/js/lib/coursewares/iFrameScripts.js
Normal file
@ -0,0 +1,15 @@
|
||||
(function() {
|
||||
var allTestsGood = true;
|
||||
var expect = chai.expect;
|
||||
|
||||
try {
|
||||
eval(parent.allTests);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
allTestsGood = false;
|
||||
} finally {
|
||||
if (allTestsGood) {
|
||||
parent.postMessage('CompleteAwesomeSauce', parent.nodeEnv);
|
||||
}
|
||||
}
|
||||
})();
|
@ -36,8 +36,6 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
function completedBonfire(didCompleteWith, bonfireSolution, thisBonfireHash) {
|
||||
$('#complete-bonfire-dialog').modal('show');
|
||||
// Only post to server if there is an authenticated user
|
||||
@ -68,6 +66,30 @@ $(document).ready(function() {
|
||||
|
||||
});
|
||||
|
||||
$('#complete-bonfire-dialog').on('hidden.bs.modal', function() {
|
||||
editor.focus();
|
||||
});
|
||||
|
||||
$('#complete-courseware-dialog').on('hidden.bs.modal', function() {
|
||||
editor.focus();
|
||||
});
|
||||
$('.next-courseware-button').on('click', function() {
|
||||
if ($('.signup-btn-nav').length < 1) {
|
||||
$.post(
|
||||
'/completed-courseware',
|
||||
{
|
||||
coursewareInfo: {
|
||||
coursewareHash: passedCoursewareHash
|
||||
}
|
||||
},
|
||||
function(res) {
|
||||
if (res) {
|
||||
window.location.href = '/coursewares'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
$('.all-challenges').on('click', function() {
|
||||
$('#all-challenges-dialog').modal('show');
|
||||
});
|
||||
@ -161,7 +183,7 @@ profileValidation.directive('uniqueUsername', function($http) {
|
||||
}
|
||||
}
|
||||
});
|
||||
// TODO: FIX THIS
|
||||
|
||||
profileValidation.directive('existingUsername', function($http) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
@ -174,7 +196,7 @@ profileValidation.directive('existingUsername', function($http) {
|
||||
ngModel.$setPristine();
|
||||
}
|
||||
if (element.val()) {
|
||||
$http.get("/api/checkExistingUsername/" + element.val() + ' ').success(function (data) {
|
||||
$http.get("/api/checkExistingUsername/" + element.val()).success(function (data) {
|
||||
if (element.val() == scope.existingUsername) {
|
||||
ngModel.$setValidity('exists', false);
|
||||
} else if (data) {
|
||||
|
@ -6,7 +6,7 @@
|
||||
"description": [
|
||||
"Click the button below for further instructions.",
|
||||
"Your goal is to fix the failing test.",
|
||||
"First, run all the tests by clickin \"Run code\" or by pressing Control + Enter",
|
||||
"First, run all the tests by clicking \"Run code\" or by pressing Control + Enter",
|
||||
"The failing test is in red. Fix the code so that all tests pass. Then you can move on to the next Bonfire."
|
||||
],
|
||||
"tests": [
|
||||
@ -59,7 +59,7 @@
|
||||
"difficulty": "1.03",
|
||||
"description": [
|
||||
"Return 'true' if a given string is a palindrome.",
|
||||
"A palindrome is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation and case.",
|
||||
"A palindrome is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation, case, and spacing.",
|
||||
"You'll need to remove punctuation and turn everything lower case in order to check for palindromes.",
|
||||
"We'll pass strings with varying formats, such as \"racecar\", \"RaceCar\", and \"race CAR\" among others.",
|
||||
"Return true if the string is a palindrome. Otherwise, return false."
|
||||
|
@ -24,12 +24,12 @@
|
||||
"video": "114627322",
|
||||
"challengeNumber": 1,
|
||||
"steps": [
|
||||
"Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another Code Camper who's on the same challenge as you and wants to pair program.",
|
||||
"Now we're going to join the Free Code Camp chat room. You can come here any time of day to hang out, ask questions, or find another camper who's on the same challenge as you and wants to pair program.",
|
||||
"If you don't already have a GitHub account, create one real quick at <a href='https://www.github.com' target='_blank'>https://www.github.com</a>.",
|
||||
"Be sure to update your biographical information and upload an image. A picture of your face works best. This is how people will see you in the chat room, so put your best foot forward.",
|
||||
"Now enter the chat room by going to <a href='https://gitter.im/FreeCodeCamp/FreeCodeCamp' target='_blank'>https://gitter.im/FreeCodeCamp/FreeCodeCamp</a> and clicking the \"sign in with GitHub\" button.",
|
||||
"Introduce yourself to our chat room by typing: \"hello world!\".",
|
||||
"Tell your fellow Code Campers how you found Free Code Camp. Also tell us why you want to learn to code.",
|
||||
"Tell your fellow campers how you found Free Code Camp. Also tell us why you want to learn to code.",
|
||||
"Keep the chat room open while you work through the other challenges. That way you ask for help If you get stuck on a challenge. You can also socialize when you feel like taking a break.",
|
||||
"Now that you've completed this challenge, you can go directly your most-recently visited chat room by clicking the \"Chat\" button in the navigation bar above."
|
||||
]
|
||||
@ -47,7 +47,7 @@
|
||||
"Click on the \"Introduce yourself here\" discussion.",
|
||||
"Here you can read through other Free Code Camp community members' self introductions.",
|
||||
"Go ahead and type a brief self introduction of your own.",
|
||||
"Click on the \"Categories\" drop-down menu. You should see a category called \"Local Chapters\". Click that. If your city isn't already on the list, create a topic for it. Otherwise, introduce yourself to the other Code Campers from your city.",
|
||||
"Click on the \"Categories\" drop-down menu. You should see a category called \"Local Chapters\". Click that. If your city isn't already on the list, create a topic for it. Otherwise, introduce yourself to the other campers from your city.",
|
||||
"Come back here daily to ask questions, engage in discussions, and share links to helpful coding tools.",
|
||||
"Now that you've completed this challenge, you can go directly to the forum by clicking the \"Forum\" button in the navigation bar above."
|
||||
]
|
||||
@ -433,7 +433,7 @@
|
||||
"Now go to <a href='http://coderbyte.com/CodingArea/Challenges/#easyChals' target='_blank'>http://coderbyte.com/CodingArea/Challenges/#easyChals</a> and start working through Coderbyte's easy algorithm scripting challenges using JavaScript.",
|
||||
"When you are finished pair programming, end the session in Screen Hero session.",
|
||||
"Congratulations! You have completed your first pair programming session.",
|
||||
"You should pair program with different Code Campers until you've completed all the Easy, Medium and Hard CoderByte challenges. This is a big time investment, but the JavaScript practice you'll get, along with the scripting and algorithm experience, are well worth it!",
|
||||
"You should pair program with different campers until you've completed all the Easy, Medium and Hard CoderByte challenges. This is a big time investment, but the JavaScript practice you'll get, along with the scripting and algorithm experience, are well worth it!",
|
||||
"You can complete CoderByte problems while you continue to work through Free Code Camp's challenges.",
|
||||
"Be sure to pair program on these challenges, and remember to apply the RSAP methodology.",
|
||||
"Mark this challenge as complete and move on."
|
||||
|
1833
seed_data/coursewares.json
Normal file
1833
seed_data/coursewares.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,20 +1,23 @@
|
||||
require('dotenv').load();
|
||||
var Challenge = require('../models/Challenge.js'),
|
||||
Bonfire = require('../models/Bonfire.js'),
|
||||
Courseware = require('../models/Courseware.js'),
|
||||
mongoose = require('mongoose'),
|
||||
secrets = require('../config/secrets'),
|
||||
challenges = require('./challenges.json'),
|
||||
coursewares = require('./coursewares.json'),
|
||||
bonfires = require('./bonfires.json');
|
||||
|
||||
mongoose.connect(secrets.db);
|
||||
|
||||
var counter = 0;
|
||||
var offerings = 3;
|
||||
|
||||
var CompletionMonitor = function() {
|
||||
counter++;
|
||||
console.log('call ' + counter);
|
||||
|
||||
if (counter < 2) {
|
||||
if (counter < offerings) {
|
||||
return;
|
||||
} else {
|
||||
process.exit(0);
|
||||
@ -53,7 +56,21 @@ Bonfire.remove({}, function(err, data) {
|
||||
CompletionMonitor();
|
||||
});
|
||||
console.log('bonfires');
|
||||
|
||||
});
|
||||
|
||||
|
||||
Courseware.remove({}, function(err, data) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
console.log('Deleted ', data);
|
||||
}
|
||||
Courseware.create(coursewares, function(err, data) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log('Saved ', data);
|
||||
}
|
||||
CompletionMonitor();
|
||||
});
|
||||
console.log('coursewares');
|
||||
});
|
@ -27,7 +27,7 @@ block content
|
||||
.form-group
|
||||
label.col-sm-2.control-label.wrappable(for='description') description:
|
||||
.col-sm-10
|
||||
textarea#description.form-control(name="description", rows=5, placeholder="Separate sentences by exactly one space only. Do not add in line breaks.")
|
||||
textarea#description.form-control(name="description", rows=5, placeholder="Each \"paragraph\" needs to be separated by a line break(return key).")
|
||||
.form-group
|
||||
label.col-sm-2.control-label.wrappable(for='challengeSeed') challengeSeed:
|
||||
.col-sm-10
|
||||
|
@ -26,7 +26,7 @@ block content
|
||||
.form-group
|
||||
label.col-sm-2.control-label.wrappable(for='description') description:
|
||||
.col-sm-10
|
||||
textarea#description.form-control(name="description", placeholder="Separate sentences by exactly one space only. Do not add in line breaks.")
|
||||
textarea#description.form-control(name="description", rows=5, placeholder="Each \"paragraph\" needs to be separated by a line break(return key).")
|
||||
.form-group
|
||||
label.col-sm-2.control-label.wrappable(for='challengeSeed') challengeSeed:
|
||||
.col-sm-10
|
||||
|
@ -106,7 +106,6 @@ block content
|
||||
.modal-header.challenge-list-header= compliment
|
||||
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
|
||||
.modal-body(ng-controller="pairedWithController")
|
||||
|
||||
.text-center
|
||||
.animated.zoomInDown.delay-half
|
||||
span.completion-icon.ion-checkmark-circled.text-primary
|
||||
@ -122,7 +121,7 @@ block content
|
||||
span.ion-close-circled
|
||||
| Username not found
|
||||
|
||||
a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Take me to my next challenge
|
||||
a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-bonfire-button(name='_csrf', value=_csrf, ng-disabled='completedWithForm.$invalid && existingUser.length > 0') Go to my next bonfire (ctrl + enter)
|
||||
|
||||
|
||||
- if (points && points > 2)
|
||||
@ -132,11 +131,11 @@ block content
|
||||
- else
|
||||
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
|
||||
|
||||
#all-bonfires-dialog.modal(tabindex='-1')
|
||||
.modal-dialog.animated.fadeInUp.fast-animation
|
||||
.modal-content
|
||||
.modal-header.challenge-list-header Bonfires
|
||||
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
|
||||
.modal-body
|
||||
//
|
||||
include ../partials/bonfires
|
||||
//#all-bonfires-dialog.modal(tabindex='-1')
|
||||
// .modal-dialog.animated.fadeInUp.fast-animation
|
||||
// .modal-content
|
||||
// .modal-header.challenge-list-header Bonfires
|
||||
// a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
|
||||
// .modal-body
|
||||
// include ../partials/bonfires
|
||||
|
||||
|
86
views/coursewares/show.jade
Normal file
86
views/coursewares/show.jade
Normal file
@ -0,0 +1,86 @@
|
||||
extends ../layout-wide
|
||||
block content
|
||||
script(src='/js/lib/codemirror/lib/codemirror.js')
|
||||
script(src='/js/lib/codemirror/addon/edit/closebrackets.js')
|
||||
script(src='/js/lib/codemirror/addon/edit/matchbrackets.js')
|
||||
script(src='/js/lib/codemirror/addon/lint/lint.js')
|
||||
script(src='/js/lib/codemirror/addon/lint/javascript-lint.js')
|
||||
script(src='//ajax.aspnetcdn.com/ajax/jshint/r07/jshint.js')
|
||||
script(src='/js/lib/chai/chai.js')
|
||||
script(src='/js/lib/chai/chai-jquery.js')
|
||||
link(rel='stylesheet', href='/js/lib/codemirror/lib/codemirror.css')
|
||||
link(rel='stylesheet', href='/js/lib/codemirror/addon/lint/lint.css')
|
||||
link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css')
|
||||
link(rel="stylesheet", href="http://fonts.googleapis.com/css?family=Ubuntu+Mono")
|
||||
script(src='/js/lib/codemirror/mode/javascript/javascript.js')
|
||||
script(src='/js/lib/jailed/jailed.js')
|
||||
script(src='/js/lib/bonfire/bonfireInit.js')
|
||||
script(src='/js/lib/codemirror/mode/xml/xml.js')
|
||||
script(src='/js/lib/codemirror/mode/css/css.js')
|
||||
script(src='/js/lib/codemirror/mode/htmlmixed/htmlmixed.js')
|
||||
.row.courseware-height
|
||||
.col-xs-12.col-sm-12.col-md-3.col-lg-3
|
||||
.well
|
||||
.row
|
||||
.col-xs-12
|
||||
h2.text-center= name
|
||||
.bonfire-instructions
|
||||
p= brief
|
||||
#brief-instructions
|
||||
.text-center
|
||||
button#more-info.btn.btn-info
|
||||
span.ion-help-circled
|
||||
| More information
|
||||
#long-instructions.row.hide
|
||||
.col-xs-12
|
||||
for sentence in details
|
||||
p!= sentence
|
||||
.text-center
|
||||
button#less-info.btn.btn-info
|
||||
span.ion-help-circled
|
||||
| Less information
|
||||
div.hidden
|
||||
#submitButton.btn.btn-primary.btn-big.btn-block Run code (ctrl + enter)
|
||||
br
|
||||
form.code
|
||||
.form-group.codeMirrorView
|
||||
textarea#codeOutput
|
||||
br
|
||||
#testSuite
|
||||
br
|
||||
script(type="text/javascript").
|
||||
var tests = !{JSON.stringify(tests)};
|
||||
var challengeSeed = !{JSON.stringify(challengeSeed)};
|
||||
var passedCoursewareHash = !{JSON.stringify(coursewareHash)};
|
||||
var bonfireName = 'Testing Courseware';
|
||||
var prodOrDev = !{JSON.stringify(environment)};
|
||||
.col-xs-12.col-sm-12.col-md-5.col-lg-6
|
||||
#mainEditorPanel
|
||||
form.code
|
||||
.form-group.codeMirrorView
|
||||
textarea#codeEditor(autofocus=true)
|
||||
script(src='/js/lib/coursewares/coursewaresFramework.js')
|
||||
.col-md-4.col-lg-3
|
||||
.hidden-xs.hidden-sm
|
||||
img.iphone-position(src="https://s3.amazonaws.com/freecodecamp/iphone6-frame.png")
|
||||
iframe.iphone#preview
|
||||
|
||||
|
||||
#complete-courseware-dialog.modal(tabindex='-1')
|
||||
.modal-dialog.animated.zoomIn.fast-animation
|
||||
.modal-content
|
||||
.modal-header.challenge-list-header
|
||||
= compliment
|
||||
a.close.closing-x(href='#', data-dismiss='modal', aria-hidden='true') ×
|
||||
.modal-body
|
||||
.text-center
|
||||
.animated.zoomInDown.delay-half
|
||||
span.completion-icon.ion-checkmark-circled.text-primary
|
||||
- if (cc)
|
||||
a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block.next-courseware-button(name='_csrf', value=_csrf, aria-hidden='true') Go to my next challenge (ctrl + enter)
|
||||
- if (points && points > 2)
|
||||
a.animated.fadeIn.btn.btn-lg.btn-block.btn-twitter(href="https://twitter.com/intent/tweet?text=I%20just%20#{verb}%20%40FreeCodeCamp%20Challenge%20%23#{number}:%20#{name}&url=http%3A%2F%2Ffreecodecamp.com/challenges/#{number}&hashtags=LearnToCode, JavaScript" target="_blank")
|
||||
i.fa.fa-twitter
|
||||
= phrase
|
||||
- else
|
||||
a.animated.fadeIn.btn.btn-lg.signup-btn.btn-block(href='/login') Sign in so you can save your progress
|
@ -18,7 +18,7 @@
|
||||
a(href="https://trello.com/b/BA3xVpz9/nonprofit-projects") (view)
|
||||
.row
|
||||
.col-xs-6.text-right
|
||||
h2 Code Campers with at least...
|
||||
h2 Campers with at least...
|
||||
.col-xs-6
|
||||
.row
|
||||
.col-xs-6.text-right
|
||||
|
Reference in New Issue
Block a user