Files
freeCodeCamp/client/commonFramework.js

746 lines
19 KiB
JavaScript
Raw Normal View History

2015-11-13 11:10:23 -08:00
var common = window.common || { init: [] };
2015-09-30 22:32:31 -07:00
2015-08-27 11:23:17 -07:00
var BDDregex = new RegExp(
'(expect(\\s+)?\\(.*\\;)|' +
'(assert(\\s+)?\\(.*\\;)|' +
'(assert\\.\\w.*\\;)|' +
'(.*\\.should\\..*\\;)/'
2015-08-27 11:23:17 -07:00
);
2015-11-04 12:37:25 -08:00
var libraryIncludes =
"<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'/>" +
2015-11-13 11:10:23 -08:00
'<style>body { padding: 0px 3px 0px 3px; }</style>';
var iFrameScript = "<script src='/js/iFrameScripts.js'></script>";
2015-08-27 00:15:13 -07:00
function workerError(error) {
var display = $('.runTimeError');
var housing = $('#testSuite');
2015-11-04 12:37:25 -08:00
2015-11-13 11:10:23 -08:00
if (display.html() === error) {
return null;
2015-08-27 00:15:13 -07:00
}
2015-11-13 11:10:23 -08:00
display.remove();
2015-11-13 11:10:23 -08:00
housing.prepend(`
<div class="runTimeError" style="font-size: 18px;">
<code>
${common.unScopeJQuery(error)}
</code>
</div>
`);
display.hide().fadeIn(function() {
setTimeout(function() {
display.fadeOut(function() {
display.remove();
2015-08-27 11:23:17 -07:00
});
2015-11-13 11:10:23 -08:00
}, 1000);
});
}
2015-11-13 11:10:23 -08:00
common.safeHTMLRun = function safeHTMLRun(shouldTest) {
const codeStorage = common.codeStorage;
if (common.challengeType !== '0') {
return null;
}
const editorValue = common.editor.getValue();
const previewFrame = document.getElementById('preview');
const preview = previewFrame.contentDocument ||
previewFrame.contentWindow.document;
if (!editorValue.match(/\<script\>/gi)) {
preview.open();
preview.write(
libraryIncludes + editorValue + (shouldTest ? iFrameScript : '')
);
codeStorage.updateStorage();
preview.close();
return null;
}
// grab user javaScript
var s = editorValue
.split(/\<\s?script\s?\>/gi)[1]
.split(/\<\s?\/\s?script\s?\>/gi)[0];
// need to add jQuery here
s = `
document = {};
var navigator = function() {
this.geolocation = function() {
this.getCurrentPosition = function() {
this.coords = {latitude: "", longitude: ""};
return this;
};
return this;
};
return this;
};
${s}
`;
return common.detectLoop(s, function(cls, message) {
if (cls) {
console.log(message.error);
workerError(message.error);
}
preview.open();
preview.write(
libraryIncludes + editorValue + (shouldTest ? iFrameScript : '')
);
codeStorage.updateStorage();
preview.close();
});
};
common.updatePreview = function updatePreview() {
var editorValue = common.editor.getValue();
var openingComments = editorValue.match(/\<\!\-\-/gi);
var closingComments = editorValue.match(/\-\-\>/gi);
2015-08-27 11:23:17 -07:00
if (
2015-11-04 12:37:25 -08:00
openingComments &&
(
!closingComments ||
openingComments.length > closingComments.length
)
2015-08-27 11:23:17 -07:00
) {
2015-11-13 11:10:23 -08:00
common.editor.setValue(editorValue + '-->');
editorValue = editorValue + '-->';
2015-08-27 11:23:17 -07:00
}
2015-08-27 11:23:17 -07:00
2015-11-13 11:10:23 -08:00
if (!editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi)) {
common.safeHTMLRun(false);
2015-08-27 11:23:17 -07:00
} else {
workerError('Unsafe $($)');
}
2015-11-13 11:10:23 -08:00
};
2015-08-27 11:23:17 -07:00
2015-11-13 11:10:23 -08:00
common.init.push(() => {
if (common.challengeType === '0') {
common.updatePreview(false);
}
});
2015-08-27 11:23:17 -07:00
/* eslint-disable no-unused-vars */
var testResults = [];
var postSuccess = function(data) {
/* eslint-enable no-unused-vars */
2015-08-27 11:23:17 -07:00
var testDoc = document.createElement('div');
2015-11-13 11:10:23 -08:00
$(testDoc).html(`
<div class='row'>
<div class='col-xs-2 text-center'>
<i class='ion-checkmark-circled big-success-icon'></i>
</div>
<div class='col-xs-10 test-output test-vertical-center wrappable'>
${JSON.parse(data)}
</div>
`);
2015-08-27 11:23:17 -07:00
$('#testSuite').append(testDoc);
2015-11-13 11:10:23 -08:00
2015-08-27 11:23:17 -07:00
testSuccess();
};
2015-11-04 12:37:25 -08:00
/* eslint-disable no-unused-vars */
var postError = function(data) {
2015-11-04 12:37:25 -08:00
/* eslint-enable no-unused-vars */
2015-08-27 11:23:17 -07:00
var testDoc = document.createElement('div');
2015-11-13 11:10:23 -08:00
$(testDoc).html(`
<div class='row'>
<div class='col-xs-2 text-center'>
<i class='ion-close-circled big-error-icon'></i>
</div>
<div class='col-xs-10 test-vertical-center test-output wrappable'>
${JSON.parse(data)}
</div>
`);
2015-08-27 11:23:17 -07:00
$('#testSuite').append(testDoc);
};
2015-08-27 11:23:17 -07:00
var goodTests = 0;
var testSuccess = function() {
2015-08-27 11:23:17 -07:00
goodTests++;
// test successful run show completion
2015-11-13 11:10:23 -08:00
if (goodTests === common.tests.length) {
return showCompletion();
2015-08-27 11:23:17 -07:00
}
};
function ctrlEnterClickHandler(e) {
2015-11-01 22:15:15 -08:00
// ctrl + enter or cmd + enter
2015-11-13 11:10:23 -08:00
if (
e.metaKey && e.keyCode === 13 ||
e.ctrlKey && e.keyCode === 13
) {
2015-09-09 21:33:45 -07:00
$('#complete-courseware-dialog').off('keydown', ctrlEnterClickHandler);
if ($('#submit-challenge').length > 0) {
$('#submit-challenge').click();
} else {
window.location = '/challenges/next-challenge?id=' + common.challengeId;
}
}
}
function showCompletion() {
var time = Math.floor(Date.now()) - window.started;
2015-11-13 11:10:23 -08:00
window.ga(
2015-08-27 11:23:17 -07:00
'send',
'event',
'Challenge',
'solved',
2015-11-13 11:10:23 -08:00
common.challengeName + ', Time: ' + time + ', Attempts: ' + 0
2015-08-27 11:23:17 -07:00
);
2015-11-13 11:10:23 -08:00
var bonfireSolution = common.editor.getValue();
2015-08-27 11:23:17 -07:00
var didCompleteWith = $('#completed-with').val() || null;
2015-09-04 21:51:34 +01:00
$('#complete-courseware-dialog').modal('show');
$('#complete-courseware-dialog .modal-header').click();
2015-08-27 11:23:17 -07:00
$('#submit-challenge').click(function(e) {
e.preventDefault();
$('#submit-challenge')
.attr('disabled', 'true')
.removeClass('btn-primary')
.addClass('btn-warning disabled');
var $checkmarkContainer = $('#checkmark-container');
$checkmarkContainer.css({ height: $checkmarkContainer.innerHeight() });
$('#challenge-checkmark')
.addClass('zoomOutUp')
// .removeClass('zoomInDown')
.delay(1000)
.queue(function(next) {
$(this).replaceWith(
2015-09-30 22:32:31 -07:00
'<div id="challenge-spinner" ' +
'class="animated zoomInUp inner-circles-loader">' +
'submitting...</div>'
2015-08-27 11:23:17 -07:00
);
next();
});
$.post(
'/completed-bonfire/', {
challengeInfo: {
2015-09-27 15:58:59 -07:00
challengeId: common.challengeId,
challengeName: common.challengeName,
2015-08-27 11:23:17 -07:00
completedWith: didCompleteWith,
2015-09-27 15:58:59 -07:00
challengeType: common.challengeType,
2015-08-27 11:23:17 -07:00
solution: bonfireSolution
}
2015-08-27 11:23:17 -07:00
},
function(res) {
if (res) {
2015-09-27 15:58:59 -07:00
window.location =
'/challenges/next-challenge?id=' + common.challengeId;
2015-08-27 11:23:17 -07:00
}
}
);
});
}
2015-11-13 11:10:23 -08:00
common.resetEditor = function resetEditor() {
common.editor.setValue(common.replaceSafeTags(common.seed));
2015-08-27 00:15:13 -07:00
$('#testSuite').empty();
2015-11-13 11:10:23 -08:00
common.executeChallenge(true);
2015-09-27 15:58:59 -07:00
common.codeStorage.updateStorage();
};
2015-11-13 11:10:23 -08:00
common.addTestsToString = function(userJavaScript, userTests = []) {
2015-08-27 11:23:17 -07:00
// insert tests from mongo
2015-11-13 11:10:23 -08:00
for (var i = 0; i < common.tests.length; i++) {
userJavaScript += '\n' + common.tests[i];
2015-08-27 11:23:17 -07:00
}
2015-08-27 11:23:17 -07:00
var counter = 0;
var match = BDDregex.exec(userJavaScript);
while (match) {
2015-11-13 11:10:23 -08:00
var replacement = '//' + counter + common.salt;
2015-08-27 11:23:17 -07:00
userJavaScript = userJavaScript.substring(0, match.index) +
replacement +
userJavaScript.substring(match.index + match[0].length);
userTests.push({
'text': match[0],
'line': counter,
'err': null
});
counter++;
match = BDDregex.exec(userJavaScript);
}
return userJavaScript;
};
function removeComments(userJavaScript) {
var regex = new RegExp(/(\/\*[^(\*\/)]*\*\/)|([ \n]\/\/[^\n]*)/g);
return userJavaScript.replace(regex, '');
}
function removeLogs(userJavaScript) {
return userJavaScript.replace(/(console\.[\w]+\s*\(.*\;)/g, '');
}
var pushed = false;
var createTestDisplay = function() {
2015-08-27 11:23:17 -07:00
if (pushed) {
userTests.pop();
}
for (var i = 0; i < userTests.length; i++) {
var didTestPass = !userTests[i].err;
2015-09-30 22:32:31 -07:00
var testText = userTests[i].text
.split('message: ')
.pop()
.replace(/\'\);/g, '');
2015-08-27 11:23:17 -07:00
var testDoc = document.createElement('div');
var iconClass = didTestPass ?
'"ion-checkmark-circled big-success-icon"' :
'"ion-close-circled big-error-icon"';
$(testDoc).html(
"<div class='row'><div class='col-xs-2 text-center'><i class=" +
iconClass +
"></i></div><div class='col-xs-10 test-output wrappable'>" +
testText +
"</div><div class='ten-pixel-break'/>"
)
.appendTo($('#testSuite'));
2015-08-27 11:23:17 -07:00
}
};
2015-09-27 15:58:59 -07:00
(function(win, chai) {
if (!chai) {
return;
}
win.expect = chai.expect;
win.assert = chai.assert;
win.should = chai.should();
}(window, window.chai));
var reassembleTest = function(test, data) {
2015-08-27 11:23:17 -07:00
var lineNum = test.line;
var regexp = new RegExp('\/\/' + lineNum + testSalt);
return data.input.replace(regexp, test.text);
};
var runTests = function(err, data) {
var head = common.arrayToNewLineString(common.head);
var tail = common.arrayToNewLineString(common.tail);
var editorValue = head + editor.getValue() + tail;
2015-08-27 11:23:17 -07:00
// userTests = userTests ? null : [];
var allTestsPassed = true;
pushed = false;
$('#testSuite').children().remove();
if (err && userTests.length > 0) {
userTests = [{
text: 'Program Execution Failure',
err: 'No user tests were run.'
}];
createTestDisplay();
// Add blocks to test exploits here!
} else if (editorValue.match(/if\s\(null\)\sconsole\.log\(1\);/gi)) {
allTestsPassed = false;
userTests = [{
text: 'Program Execution Failure',
err: 'Invalid if (null) console.log(1); detected'
}];
createTestDisplay();
} else if (userTests) {
userTests.push(false);
pushed = true;
userTests.forEach(function(
chaiTestFromJSON,
indexOfTestArray,
__testArray
) {
try {
if (chaiTestFromJSON) {
2015-09-27 15:58:59 -07:00
/* eslint-disable no-eval, no-unused-vars */
2015-08-27 11:23:17 -07:00
var output = eval(reassembleTest(chaiTestFromJSON, data));
2015-09-27 15:58:59 -07:00
/* eslint-enable no-eval, no-unused-vars */
2015-08-27 11:23:17 -07:00
}
} catch (error) {
allTestsPassed = false;
2015-08-27 11:23:17 -07:00
__testArray[indexOfTestArray].err = error.message;
} finally {
if (!chaiTestFromJSON) {
createTestDisplay();
}
2015-08-27 11:23:17 -07:00
}
});
if (allTestsPassed) {
allTestsPassed = false;
showCompletion();
} else {
2015-09-04 21:34:07 +01:00
isInitRun = false;
}
2015-08-27 11:23:17 -07:00
}
};
2015-09-27 15:58:59 -07:00
// step challenge
common.init.push((function() {
var stepClass = '.challenge-step';
2015-10-31 17:24:05 +04:00
var prevBtnClass = '.challenge-step-btn-prev';
2015-09-27 15:58:59 -07:00
var nextBtnClass = '.challenge-step-btn-next';
var actionBtnClass = '.challenge-step-btn-action';
var finishBtnClass = '.challenge-step-btn-finish';
var submitBtnId = '#challenge-step-btn-submit';
var submitModalId = '#challenge-step-modal';
2015-10-31 17:24:05 +04:00
function getPreviousStep($challengeSteps) {
var $prevStep = false;
var prevStepIndex = 0;
$challengeSteps.each(function(index) {
var $step = $(this);
if (
!$step.hasClass('hidden')
2015-10-31 17:24:05 +04:00
) {
prevStepIndex = index - 1;
}
});
$prevStep = $challengeSteps[prevStepIndex];
return $prevStep;
}
2015-09-27 15:58:59 -07:00
function getNextStep($challengeSteps) {
var length = $challengeSteps.length;
var $nextStep = false;
var nextStepIndex = 0;
$challengeSteps.each(function(index) {
var $step = $(this);
if (
!$step.hasClass('hidden') &&
index + 1 !== length
) {
nextStepIndex = index + 1;
}
});
$nextStep = $challengeSteps[nextStepIndex];
return $nextStep;
}
2015-10-31 17:24:05 +04:00
function handlePrevStepClick(e) {
e.preventDefault();
var prevStep = getPreviousStep($(stepClass));
$(this)
.parent().parent()
.removeClass('slideInLeft slideInRight')
2015-10-31 17:24:05 +04:00
.addClass('animated fadeOutRight fast-animation')
.delay(250)
.queue(function(prev) {
$(this).addClass('hidden');
if (prevStep) {
$(prevStep)
.removeClass('hidden')
.removeClass('fadeOutLeft fadeOutRight')
2015-10-31 17:24:05 +04:00
.addClass('animated slideInLeft fast-animation')
.delay(500)
.queue(function(prev) {
prev();
});
}
prev();
});
}
2015-09-27 15:58:59 -07:00
function handleNextStepClick(e) {
e.preventDefault();
var nextStep = getNextStep($(stepClass));
$(this)
.parent().parent()
.removeClass('slideInRight slideInLeft')
2015-10-05 21:57:15 -07:00
.addClass('animated fadeOutLeft fast-animation')
.delay(250)
2015-09-27 15:58:59 -07:00
.queue(function(next) {
$(this).addClass('hidden');
if (nextStep) {
$(nextStep)
.removeClass('hidden')
.removeClass('fadeOutRight fadeOutLeft')
2015-10-05 21:57:15 -07:00
.addClass('animated slideInRight fast-animation')
.delay(500)
2015-09-27 15:58:59 -07:00
.queue(function(next) {
next();
});
}
next();
});
}
2015-10-02 11:47:36 -07:00
function handleActionClick(e) {
var props = common.challengeSeed[0] ||
{ stepIndex: [] };
var $el = $(this);
var index = +$el.attr('id');
var propIndex = props.stepIndex.indexOf(index);
if (propIndex === -1) {
return $el
.parent()
.find('.disabled')
.removeClass('disabled');
}
// an API action
// prevent link from opening
e.preventDefault();
var prop = props.properties[propIndex];
var api = props.apis[propIndex];
if (common[prop]) {
return $el
.parent()
.find('.disabled')
.removeClass('disabled');
}
$
.post(api)
.done(function(data) {
// assume a boolean indicates passing
if (typeof data === 'boolean') {
return $el
.parent()
.find('.disabled')
.removeClass('disabled');
}
// assume api returns string when fails
$el
.parent()
.find('.disabled')
.replaceWith('<p>' + data + '</p>');
})
.fail(function() {
console.log('failed');
});
2015-09-27 15:58:59 -07:00
}
function handleFinishClick(e) {
e.preventDefault();
$(submitModalId).modal('show');
$(submitModalId + '.modal-header').click();
$(submitBtnId).click(handleSubmitClick);
}
function handleSubmitClick(e) {
e.preventDefault();
$('#submit-challenge')
.attr('disabled', 'true')
.removeClass('btn-primary')
.addClass('btn-warning disabled');
var $checkmarkContainer = $('#checkmark-container');
$checkmarkContainer.css({ height: $checkmarkContainer.innerHeight() });
$('#challenge-checkmark')
.addClass('zoomOutUp')
.delay(1000)
.queue(function(next) {
$(this).replaceWith(
'<div id="challenge-spinner" ' +
'class="animated zoomInUp inner-circles-loader">' +
'submitting...</div>'
);
next();
});
$.post(
'/completed-bonfire/', {
challengeInfo: {
challengeId: common.challengeId,
challengeName: common.challengeName,
challengeType: common.challengeType
}
},
function(res) {
if (res) {
window.location =
'/challenges/next-challenge?id=' + common.challengeId;
}
}
);
}
return function($) {
2015-10-31 17:24:05 +04:00
$(prevBtnClass).click(handlePrevStepClick);
2015-09-27 15:58:59 -07:00
$(nextBtnClass).click(handleNextStepClick);
$(actionBtnClass).click(handleActionClick);
$(finishBtnClass).click(handleFinishClick);
};
}(window.$)));
2015-08-27 11:23:17 -07:00
function bonfireExecute(shouldTest) {
var head = common.arrayToNewLineString(common.head);
var tail = common.arrayToNewLineString(common.tail);
2015-09-27 15:58:59 -07:00
var codeOutput = common.codeOutput;
2015-08-27 11:23:17 -07:00
initPreview = false;
goodTests = 0;
attempts++;
2015-09-27 15:58:59 -07:00
ga('send', 'event', 'Challenge', 'ran-code', common.challengeName);
2015-08-27 11:23:17 -07:00
userTests = null;
$('#testSuite').empty();
if (
2015-09-27 15:58:59 -07:00
common.challengeType !== '0' &&
2015-08-27 11:23:17 -07:00
!editor.getValue().match(/\$\s*?\(\s*?\$\s*?\)/gi)
) {
var userJavaScript = head + editor.getValue() + tail;
2015-08-27 11:23:17 -07:00
var failedCommentTest = false;
2015-11-04 12:37:25 -08:00
var openingComments = userJavaScript.match(/\/\*/gi);
2015-08-27 11:23:17 -07:00
// checks if the number of opening comments(/*) matches the number of
// closing comments(*/)
if (
2015-11-04 12:37:25 -08:00
openingComments &&
openingComments.length > userJavaScript.match(/\*\//gi).length
2015-08-27 11:23:17 -07:00
) {
failedCommentTest = true;
}
userJavaScript = removeComments(userJavaScript);
userJavaScript = scrapeTests(userJavaScript);
// simple fix in case the user forgets to invoke their function
if (userJavaScript.match(/function/gi)) {
if (userJavaScript.match(/function\s*?\(|function\s+\w+\s*?\(/gi)) {
2015-11-10 18:27:58 -08:00
common.sandBox.submit(userJavaScript, function(cls, message) {
2015-08-27 11:23:17 -07:00
if (failedCommentTest) {
2015-09-27 15:58:59 -07:00
editor.setValue(editor.getValue() + '*/');
2015-08-27 11:23:17 -07:00
console.log('Caught Unfinished Comment');
codeOutput.setValue('Unfinished multi-line comment');
failedCommentTest = false;
} else if (cls) {
codeOutput.setValue(message.error);
if (shouldTest) {
2015-08-27 11:23:17 -07:00
runTests('Error', null);
}
2015-08-27 11:23:17 -07:00
} else {
codeOutput.setValue(message.output);
codeOutput.setValue(codeOutput.getValue().replace(/\\\"/gi, ''));
message.input = removeLogs(message.input);
if (shouldTest) {
runTests(null, message);
}
2015-08-27 11:23:17 -07:00
}
});
} else {
codeOutput.setValue('Unsafe or unfinished function declaration');
}
} else {
2015-11-10 18:27:58 -08:00
common.sandBox.submit(userJavaScript, function(cls, message) {
2015-08-27 11:23:17 -07:00
if (failedCommentTest) {
2015-09-27 15:58:59 -07:00
editor.setValue(editor.getValue() + '*/');
2015-08-27 11:23:17 -07:00
console.log('Caught Unfinished Comment');
codeOutput.setValue('Unfinished mulit-line comment');
failedCommentTest = false;
} else if (cls) {
codeOutput.setValue(message.error);
if (shouldTest) {
runTests('Error', null);
}
} else {
codeOutput.setValue(message.output);
codeOutput.setValue(codeOutput.getValue().replace(/\\\"/gi, ''));
message.input = removeLogs(message.input);
if (shouldTest) {
runTests(null, message);
}
2015-08-25 21:07:36 +01:00
}
2015-08-27 11:23:17 -07:00
});
}
2015-08-27 11:23:17 -07:00
} else {
editorValueForIFrame = editor.getValue();
if (failedCommentTest) {
editor.setValue(editor.getValue() + '-->');
editorValueForIFrame = editorValueForIFrame + '-->';
}
if (
!editor.getValue().match(/\$\s*?\(\s*?\$\s*?\)/gi) &&
2015-09-27 15:58:59 -07:00
common.challengeType === '0'
2015-08-27 11:23:17 -07:00
) {
safeHTMLRun(shouldTest);
2015-08-27 11:23:17 -07:00
} else {
workerError('Unsafe $($)');
}
}
setTimeout(function() {
var $marginFix = $('.innerMarginFix');
$marginFix.css('min-height', $marginFix.height());
}, 1000);
}
2015-11-13 11:10:23 -08:00
common.init($ => {
$('#submitButton').on('click', function() {
common.executeChallenge(true);
});
});
2015-08-27 00:02:07 -07:00
$(document).ready(function() {
2015-09-27 15:58:59 -07:00
common.init.forEach(function(init) {
init($);
});
// init modal keybindings on open
$('#complete-courseware-dialog').on('shown.bs.modal', function() {
2015-09-09 21:33:45 -07:00
$('#complete-courseware-dialog').keydown(ctrlEnterClickHandler);
});
// remove modal keybinds on close
$('#complete-courseware-dialog').on('hidden.bs.modal', function() {
2015-09-09 21:33:45 -07:00
$('#complete-courseware-dialog').off('keydown', ctrlEnterClickHandler);
});
2015-08-27 00:15:13 -07:00
var $preview = $('#preview');
if (typeof $preview.html() !== 'undefined') {
$preview.load(function() {
2015-11-13 11:10:23 -08:00
common.executeChallenge(true);
2015-08-27 00:15:13 -07:00
});
2015-11-08 20:04:43 -08:00
} else if (
common.challengeType !== '2' &&
common.challengeType !== '3' &&
common.challengeType !== '4' &&
common.challengeType !== '7'
2015-11-08 20:04:43 -08:00
) {
2015-11-13 11:10:23 -08:00
common.executeChallenge(true);
2015-08-27 00:15:13 -07:00
}
2015-09-27 15:58:59 -07:00
});