From 29f90505b7885ea004b3cf0e2fe0268518f58af6 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 17 Nov 2015 21:25:16 -0800 Subject: [PATCH] More refactoring --- client/commonFramework.js | 726 +----------------- client/commonFramework/add-faux-stream.js | 37 + client/commonFramework/add-test-to-string.js | 41 + client/commonFramework/bindings.js | 262 +++++++ client/commonFramework/code-storage.js | 37 + .../{codeUri.js => code-uri.js} | 0 client/commonFramework/codeStorage.js | 82 -- .../{createEditor.js => create-editor.js} | 38 +- client/commonFramework/detect-loops-stream.js | 79 ++ client/commonFramework/detectLoops.js | 57 -- ...TestResults.js => display-test-results.js} | 0 .../execute-challenge-stream.js | 119 +++ client/commonFramework/executeChallenge.js | 94 --- client/commonFramework/{utils.js => init.js} | 20 +- .../{outputDisplay.js => output-display.js} | 25 +- client/commonFramework/phone-scroll-lock.js | 132 ++++ client/commonFramework/report-issue.js | 56 ++ client/commonFramework/run-tests-stream.js | 35 + client/commonFramework/runTests.js | 43 -- client/commonFramework/show-completion.js | 70 ++ .../{stepChallenge.js => step-challenge.js} | 16 +- client/commonFramework/test-script-stream.js | 0 client/commonFramework/update-preview.js | 40 + public/js/fauxJQuery.js => client/faux.js | 11 + client/iFrameScripts.js | 16 +- client/main.js | 493 +----------- client/plugin.js | 20 +- server/views/coursewares/showBonfire.jade | 2 +- server/views/coursewares/showVideo.jade | 56 +- .../coursewares/showZiplineOrBasejump.jade | 5 + 30 files changed, 1043 insertions(+), 1569 deletions(-) create mode 100644 client/commonFramework/add-faux-stream.js create mode 100644 client/commonFramework/add-test-to-string.js create mode 100644 client/commonFramework/bindings.js create mode 100644 client/commonFramework/code-storage.js rename client/commonFramework/{codeUri.js => code-uri.js} (100%) delete mode 100644 client/commonFramework/codeStorage.js rename client/commonFramework/{createEditor.js => create-editor.js} (81%) create mode 100644 client/commonFramework/detect-loops-stream.js delete mode 100644 client/commonFramework/detectLoops.js rename client/commonFramework/{displayTestResults.js => display-test-results.js} (100%) create mode 100644 client/commonFramework/execute-challenge-stream.js delete mode 100644 client/commonFramework/executeChallenge.js rename client/commonFramework/{utils.js => init.js} (85%) rename client/commonFramework/{outputDisplay.js => output-display.js} (55%) create mode 100644 client/commonFramework/phone-scroll-lock.js create mode 100644 client/commonFramework/report-issue.js create mode 100644 client/commonFramework/run-tests-stream.js delete mode 100644 client/commonFramework/runTests.js create mode 100644 client/commonFramework/show-completion.js rename client/commonFramework/{stepChallenge.js => step-challenge.js} (94%) create mode 100644 client/commonFramework/test-script-stream.js create mode 100644 client/commonFramework/update-preview.js rename public/js/fauxJQuery.js => client/faux.js (93%) diff --git a/client/commonFramework.js b/client/commonFramework.js index e717d4d012..42e2b2832c 100644 --- a/client/commonFramework.js +++ b/client/commonFramework.js @@ -1,733 +1,10 @@ -var common = window.common || { init: [] }; - - -var BDDregex = new RegExp( - '(expect(\\s+)?\\(.*\\;)|' + - '(assert(\\s+)?\\(.*\\;)|' + - '(assert\\.\\w.*\\;)|' + - '(.*\\.should\\..*\\;)/' -); - -var libraryIncludes = - "" + - "" + - "" + - ''; - -var iFrameScript = ""; - -function workerError(error) { - var display = $('.runTimeError'); - var housing = $('#testSuite'); - - if (display.html() === error) { - return null; - } - - display.remove(); - - housing.prepend(` -
- - ${common.unScopeJQuery(error)} - -
- `); - - display.hide().fadeIn(function() { - setTimeout(function() { - display.fadeOut(function() { - display.remove(); - }); - }, 1000); - }); -} - -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(/\/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); - if ( - openingComments && - ( - !closingComments || - openingComments.length > closingComments.length - ) - ) { - common.editor.setValue(editorValue + '-->'); - editorValue = editorValue + '-->'; - } - - - if (!editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi)) { - common.safeHTMLRun(false); - } else { - workerError('Unsafe $($)'); - } -}; - -common.init.push(() => { - if (common.challengeType === '0') { - common.updatePreview(false); - } -}); - - -/* eslint-disable no-unused-vars */ -var testResults = []; -var postSuccess = function(data) { -/* eslint-enable no-unused-vars */ - - var testDoc = document.createElement('div'); - $(testDoc).html(` -
-
- -
-
- ${JSON.parse(data)} -
- `); - - $('#testSuite').append(testDoc); - - testSuccess(); -}; - -/* eslint-disable no-unused-vars */ -var postError = function(data) { -/* eslint-enable no-unused-vars */ - var testDoc = document.createElement('div'); - - $(testDoc).html(` -
-
- -
-
- ${JSON.parse(data)} -
- `); - - $('#testSuite').append(testDoc); -}; - -var goodTests = 0; -var testSuccess = function() { - goodTests++; - // test successful run show completion - if (goodTests === common.tests.length) { - return showCompletion(); - } -}; - -function ctrlEnterClickHandler(e) { - // ctrl + enter or cmd + enter - if ( - e.metaKey && e.keyCode === 13 || - e.ctrlKey && e.keyCode === 13 - ) { - $('#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; - - window.ga( - 'send', - 'event', - 'Challenge', - 'solved', - common.challengeName + ', Time: ' + time + ', Attempts: ' + 0 - ); - - var bonfireSolution = common.editor.getValue(); - var didCompleteWith = $('#completed-with').val() || null; - - $('#complete-courseware-dialog').modal('show'); - $('#complete-courseware-dialog .modal-header').click(); - - $('#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( - '
' + - 'submitting...
' - ); - next(); - }); - - $.post( - '/completed-bonfire/', { - challengeInfo: { - challengeId: common.challengeId, - challengeName: common.challengeName, - completedWith: didCompleteWith, - challengeType: common.challengeType, - solution: bonfireSolution - } - }, - function(res) { - if (res) { - window.location = - '/challenges/next-challenge?id=' + common.challengeId; - } - } - ); - }); -} - -common.resetEditor = function resetEditor() { - common.editor.setValue(common.replaceSafeTags(common.seed)); - $('#testSuite').empty(); - common.executeChallenge(true); - common.codeStorage.updateStorage(); -}; - - -common.addTestsToString = function(userJavaScript, userTests = []) { - - // insert tests from mongo - for (var i = 0; i < common.tests.length; i++) { - userJavaScript += '\n' + common.tests[i]; - } - - var counter = 0; - var match = BDDregex.exec(userJavaScript); - - while (match) { - var replacement = '//' + counter + common.salt; - 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() { - if (pushed) { - userTests.pop(); - } - for (var i = 0; i < userTests.length; i++) { - var didTestPass = !userTests[i].err; - var testText = userTests[i].text - .split('message: ') - .pop() - .replace(/\'\);/g, ''); - - var testDoc = document.createElement('div'); - - var iconClass = didTestPass ? - '"ion-checkmark-circled big-success-icon"' : - '"ion-close-circled big-error-icon"'; - - $(testDoc).html( - "
" + - testText + - "
" - ) - .appendTo($('#testSuite')); - } -}; - -(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) { - 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; - // 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) { - /* eslint-disable no-eval, no-unused-vars */ - var output = eval(reassembleTest(chaiTestFromJSON, data)); - /* eslint-enable no-eval, no-unused-vars */ - } - } catch (error) { - allTestsPassed = false; - __testArray[indexOfTestArray].err = error.message; - } finally { - if (!chaiTestFromJSON) { - createTestDisplay(); - } - } - }); - - if (allTestsPassed) { - allTestsPassed = false; - showCompletion(); - } else { - isInitRun = false; - } - } -}; - -// step challenge -common.init.push((function() { - var stepClass = '.challenge-step'; - var prevBtnClass = '.challenge-step-btn-prev'; - 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'; - - function getPreviousStep($challengeSteps) { - var $prevStep = false; - var prevStepIndex = 0; - $challengeSteps.each(function(index) { - var $step = $(this); - if ( - !$step.hasClass('hidden') - ) { - prevStepIndex = index - 1; - } - }); - - $prevStep = $challengeSteps[prevStepIndex]; - - return $prevStep; - } - - 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; - } - - function handlePrevStepClick(e) { - e.preventDefault(); - var prevStep = getPreviousStep($(stepClass)); - $(this) - .parent().parent() - .removeClass('slideInLeft slideInRight') - .addClass('animated fadeOutRight fast-animation') - .delay(250) - .queue(function(prev) { - $(this).addClass('hidden'); - if (prevStep) { - $(prevStep) - .removeClass('hidden') - .removeClass('fadeOutLeft fadeOutRight') - .addClass('animated slideInLeft fast-animation') - .delay(500) - .queue(function(prev) { - prev(); - }); - } - prev(); - }); - } - - function handleNextStepClick(e) { - e.preventDefault(); - var nextStep = getNextStep($(stepClass)); - $(this) - .parent().parent() - .removeClass('slideInRight slideInLeft') - .addClass('animated fadeOutLeft fast-animation') - .delay(250) - .queue(function(next) { - $(this).addClass('hidden'); - if (nextStep) { - $(nextStep) - .removeClass('hidden') - .removeClass('fadeOutRight fadeOutLeft') - .addClass('animated slideInRight fast-animation') - .delay(500) - .queue(function(next) { - next(); - }); - } - next(); - }); - } - - 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('

' + data + '

'); - }) - .fail(function() { - console.log('failed'); - }); - } - - 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( - '
' + - 'submitting...
' - ); - 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($) { - $(prevBtnClass).click(handlePrevStepClick); - $(nextBtnClass).click(handleNextStepClick); - $(actionBtnClass).click(handleActionClick); - $(finishBtnClass).click(handleFinishClick); - }; -}(window.$))); - -function bonfireExecute(shouldTest) { - var head = common.arrayToNewLineString(common.head); - var tail = common.arrayToNewLineString(common.tail); - var codeOutput = common.codeOutput; - initPreview = false; - goodTests = 0; - attempts++; - ga('send', 'event', 'Challenge', 'ran-code', common.challengeName); - userTests = null; - $('#testSuite').empty(); - - if ( - common.challengeType !== '0' && - !editor.getValue().match(/\$\s*?\(\s*?\$\s*?\)/gi) - ) { - var userJavaScript = head + editor.getValue() + tail; - var failedCommentTest = false; - - var openingComments = userJavaScript.match(/\/\*/gi); - // checks if the number of opening comments(/*) matches the number of - // closing comments(*/) - if ( - openingComments && - openingComments.length > userJavaScript.match(/\*\//gi).length - ) { - 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)) { - common.sandBox.submit(userJavaScript, function(cls, message) { - if (failedCommentTest) { - editor.setValue(editor.getValue() + '*/'); - console.log('Caught Unfinished Comment'); - codeOutput.setValue('Unfinished multi-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); - } - } - }); - } else { - codeOutput.setValue('Unsafe or unfinished function declaration'); - } - } else { - common.sandBox.submit(userJavaScript, function(cls, message) { - - if (failedCommentTest) { - editor.setValue(editor.getValue() + '*/'); - 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); - } - } - }); - } - } else { - - editorValueForIFrame = editor.getValue(); - - if (failedCommentTest) { - editor.setValue(editor.getValue() + '-->'); - editorValueForIFrame = editorValueForIFrame + '-->'; - } - if ( - !editor.getValue().match(/\$\s*?\(\s*?\$\s*?\)/gi) && - common.challengeType === '0' - ) { - safeHTMLRun(shouldTest); - } else { - workerError('Unsafe $($)'); - } - } - setTimeout(function() { - var $marginFix = $('.innerMarginFix'); - $marginFix.css('min-height', $marginFix.height()); - }, 1000); -} - -common.init($ => { - $('#submitButton').on('click', function() { - common.executeChallenge(true); - }); -}); - $(document).ready(function() { + var common = window.common; common.init.forEach(function(init) { init($); }); - // init modal keybindings on open - $('#complete-courseware-dialog').on('shown.bs.modal', function() { - $('#complete-courseware-dialog').keydown(ctrlEnterClickHandler); - }); - - // remove modal keybinds on close - $('#complete-courseware-dialog').on('hidden.bs.modal', function() { - $('#complete-courseware-dialog').off('keydown', ctrlEnterClickHandler); - }); - var $preview = $('#preview'); if (typeof $preview.html() !== 'undefined') { $preview.load(function() { @@ -741,5 +18,4 @@ $(document).ready(function() { ) { common.executeChallenge(true); } - }); diff --git a/client/commonFramework/add-faux-stream.js b/client/commonFramework/add-faux-stream.js new file mode 100644 index 0000000000..c25f69f75b --- /dev/null +++ b/client/commonFramework/add-faux-stream.js @@ -0,0 +1,37 @@ +window.common = (function(global) { + const { + $, + Rx: { Observable, Disposable }, + common = { init: [] } + } = global; + + + function getFaux() { + return new Observable(function(observer) { + const jqXHR = $.get('/js/faux.js') + .success(data => observer.onNext(data)) + .fail(e => observer.onError(e)) + .always(() => observer.onCompleted()); + + return new Disposable(() => { + jqXHR.abort(); + }); + }); + } + + const faux$ = getFaux().shareReplay(); + + common.safeHTMLRun = function safeHTMLRun(code) { + if (!code.match(/\/gi)) { + return Observable.just(code); + } + + // grab user javaScript + var scriptCode = code + .split(/\<\s?script\s?\>/gi)[1] + .split(/\<\s?\/\s?script\s?\>/gi)[0]; + return faux$.map(faux => faux + scriptCode); + }; + + return common; +}(window)); diff --git a/client/commonFramework/add-test-to-string.js b/client/commonFramework/add-test-to-string.js new file mode 100644 index 0000000000..b649e14678 --- /dev/null +++ b/client/commonFramework/add-test-to-string.js @@ -0,0 +1,41 @@ +window.common = (function({ common = { init: [] }}) { + + var BDDregex = new RegExp( + '(expect(\\s+)?\\(.*\\;)|' + + '(assert(\\s+)?\\(.*\\;)|' + + '(assert\\.\\w.*\\;)|' + + '(.*\\.should\\..*\\;)/' + ); + + common.addTestsToString = function(code) { + const userTests = []; + + // insert tests from mongo + for (var i = 0; i < common.tests.length; i++) { + code += '\n' + common.tests[i]; + } + + var counter = 0; + var match = BDDregex.exec(code); + + while (match) { + var replacement = '//' + counter + common.salt; + code = code.substring(0, match.index) + + replacement + + code.substring(match.index + match[0].length); + + userTests.push({ + err: null, + text: match[0], + line: counter + }); + + counter++; + match = BDDregex.exec(code); + } + + return { code, userTests }; + }; + + return common; +}(window)); diff --git a/client/commonFramework/bindings.js b/client/commonFramework/bindings.js new file mode 100644 index 0000000000..805913b6a1 --- /dev/null +++ b/client/commonFramework/bindings.js @@ -0,0 +1,262 @@ +window.common = (function({ $, common = { init: [] }}) { + + common.ctrlEnterClickHandler = function ctrlEnterClickHandler(e) { + // ctrl + enter or cmd + enter + if ( + e.metaKey && e.keyCode === 13 || + e.ctrlKey && e.keyCode === 13 + ) { + $('#complete-courseware-dialog').off('keydown', ctrlEnterClickHandler); + if ($('#submit-challenge').length > 0) { + $('#submit-challenge').click(); + } else { + window.location = '/challenges/next-challenge?id=' + common.challengeId; + } + } + }; + + common.resetEditor = function resetEditor() { + common.editor.setValue(common.replaceSafeTags(common.seed)); + common.executeChallenge(true); + common.codeStorage.updateStorage(); + }; + + common.init.push(function($) { + + var $marginFix = $('.innerMarginFix'); + $marginFix.css('min-height', $marginFix.height()); + + // init modal keybindings on open + $('#complete-courseware-dialog').on('shown.bs.modal', function() { + $('#complete-courseware-dialog').keydown(common.ctrlEnterClickHandler); + }); + + // remove modal keybinds on close + $('#complete-courseware-dialog').on('hidden.bs.modal', function() { + $('#complete-courseware-dialog').off( + 'keydown', + common.ctrlEnterClickHandler + ); + }); + + // video checklist binding + $('.challenge-list-checkbox').on('change', function() { + var checkboxId = $(this).parent().parent().attr('id'); + if ($(this).is(':checked')) { + $(this).parent().siblings().children().addClass('faded'); + if (!localStorage || !localStorage[checkboxId]) { + localStorage[checkboxId] = true; + } + } + + if (!$(this).is(':checked')) { + $(this).parent().siblings().children().removeClass('faded'); + if (localStorage[checkboxId]) { + localStorage.removeItem(checkboxId); + } + } + }); + + $('.checklist-element').each(function() { + var checklistElementId = $(this).attr('id'); + if (localStorage[checklistElementId]) { + $(this).children().children('li').addClass('faded'); + $(this).children().children('input').trigger('click'); + } + }); + + + // video challenge submit + $('#next-courseware-button').on('click', function() { + $('#next-courseware-button').unbind('click'); + if ($('.signup-btn-nav').length < 1) { + var data; + var completedWith = $('#completed-with').val() || null; + var publicURL = $('#public-url').val() || null; + var githubURL = $('#github-url').val() || null; + switch (common.challengeType) { + case common.challengeTypes.HTML: + case common.challengeTypes.JS: + case common.challengeTypes.VIDEO: + data = { + challengeInfo: { + challengeId: common.challengeId, + challengeName: common.challengeName + } + }; + $.post('/completed-challenge/', data) + .success(function(res) { + if (!res) { + return; + } + window.location.href = '/challenges/next-challenge?id=' + + common.challengeId; + }) + .fail(function() { + window.location.href = '/challenges'; + }); + + break; + case common.challengeTypes.BASEJUMP: + case common.challengeTypes.ZIPLINE: + data = { + challengeInfo: { + challengeId: common.challengeId, + challengeName: common.challengeName, + completedWith: completedWith, + publicURL: publicURL, + githubURL: githubURL, + challengeType: common.challengeType, + verified: false + } + }; + + $.post('/completed-zipline-or-basejump/', data) + .success(function() { + window.location.href = '/challenges/next-challenge?id=' + + common.challengeId; + }) + .fail(function() { + window.location.replace(window.location.href); + }); + break; + + case common.challengeTypes.BONFIRE: + window.location.href = '/challenges/next-challenge?id=' + + common.challengeId; + break; + + default: + console.log('Happy Coding!'); + break; + } + } + }); + + $('#submitButton').on('click', function() { + common.executeChallenge(true); + }); + + if (common.editor) { + $('#reset-button').on('click', common.resetEditor); + } + + if (common.challengeName) { + window.ga('send', 'event', 'Challenge', 'load', common.challengeName); + } + + $('#complete-courseware-dialog').on('hidden.bs.modal', function() { + common.editor.focus(); + }); + + $('#trigger-issue-modal').on('click', function() { + $('#issue-modal').modal('show'); + }); + + $('#trigger-help-modal').on('click', function() { + $('#help-modal').modal('show'); + }); + + $('#trigger-reset-modal').on('click', function() { + $('#reset-modal').modal('show'); + }); + + $('#trigger-pair-modal').on('click', function() { + $('#pair-modal').modal('show'); + }); + + $('#completed-courseware').on('click', function() { + $('#complete-courseware-dialog').modal('show'); + }); + + $('#help-ive-found-a-bug-wiki-article').on('click', function() { + window.open( + 'https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/' + + "Help-I've-Found-a-Bug", + '_blank' + ); + }); + + $('#search-issue').on('click', function() { + var queryIssue = window.location.href.toString(); + window.open( + 'https://github.com/FreeCodeCamp/FreeCodeCamp/issues?q=' + + 'is:issue is:all ' + + (common.challengeName) + + ' OR ' + + queryIssue + .substr(queryIssue.lastIndexOf('challenges/') + 11) + .replace('/', ''), '_blank'); + }); + + $('#gist-share').on('click', function() { + var gistWindow = window.open('', '_blank'); + + $('#gist-share') + .attr('disabled', 'true') + .removeClass('btn-danger') + .addClass('btn-warning disabled'); + + function createCORSRequest(method, url) { + var xhr = new XMLHttpRequest(); + if ('withCredentials' in xhr) { + xhr.open(method, url, true); + } else if (typeof XDomainRequest !== 'undefined') { + xhr = new XDomainRequest(); + xhr.open(method, url); + } else { + xhr = null; + } + xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); + return xhr; + } + + var request = createCORSRequest('post', 'https://api.github.com/gists'); + if (!request) { + return null; + } + + request.onload = function() { + if ( + request.readyState === 4 && + request.status === 201 && + request.statusText === 'Created' + ) { + gistWindow.location.href = + JSON.parse(request.responseText)['html_url']; + } + }; + + var description = common.username ? + 'http://www.freecodecamp.com/' + common.username + ' \'s s' : + 'S'; + + var data = { + description: description + 'olution for ' + common.challengeName, + public: true, + files: {} + }; + var queryIssue = window.location.href.toString().split('#?')[0]; + var filename = queryIssue + .substr(queryIssue.lastIndexOf('challenges/') + 11) + .replace('/', '') + '.js'; + + data.files[filename] = { + content: '// ' + + common.challengeName + + '\n' + + (common.username ? '// Author: @' + common.username + '\n' : '') + + '// Challenge: ' + + queryIssue + + '\n' + + '// Learn to Code at Free Code Camp (www.freecodecamp.com)' + + '\n\n' + + window.editor.getValue().trim() + }; + + request.send(JSON.stringify(data)); + }); + }); + + return common; +}(window)); diff --git a/client/commonFramework/code-storage.js b/client/commonFramework/code-storage.js new file mode 100644 index 0000000000..df3de11308 --- /dev/null +++ b/client/commonFramework/code-storage.js @@ -0,0 +1,37 @@ +// depends on: codeUri +window.common = (function(global) { + const { + localStorage, + common = { init: [] } + } = global; + + var codeStorage = { + getStoredValue(key) { + return '' + localStorage.getItem(key + 'Val'); + }, + + isAlive: function() { + var val = this.getStoredValue(); + return val !== 'null' && + val !== 'undefined' && + (val && val.length > 0); + }, + + updateStorage(key, code) { + if ( + !localStorage || + typeof localStorage !== 'function' || + !key || + typeof key !== 'string' + ) { + console.log('unable to save to storage'); + return code; + } + localStorage.setItem(key + 'Val', code); + } + }; + + common.codeStorage = codeStorage; + + return common; +}(window, window.common)); diff --git a/client/commonFramework/codeUri.js b/client/commonFramework/code-uri.js similarity index 100% rename from client/commonFramework/codeUri.js rename to client/commonFramework/code-uri.js diff --git a/client/commonFramework/codeStorage.js b/client/commonFramework/codeStorage.js deleted file mode 100644 index 5753c83193..0000000000 --- a/client/commonFramework/codeStorage.js +++ /dev/null @@ -1,82 +0,0 @@ -// depends on: codeUri -window.common = (function(global ) { - const { - $, - localStorage, - common = { init: [] } - } = global; - - const { - codeUri - } = common; - - var CodeStorageProps = { - version: 0.01, - keyVersion: 'saveVersion', - keyValue: null, - updateWait: 2000, - updateTimeoutId: null - }; - - var CodeStorage = { - hasSaved: function() { - return this.updateTimeoutId === null; - }, - - onSave: function(func) { - this.eventArray.push(func); - }, - - setSaveKey: function(key) { - this.keyValue = key + 'Val'; - }, - - getStoredValue: function() { - return '' + localStorage.getItem(this.keyValue); - }, - - setEditor: function(editor) { - this.editor = editor; - }, - - isAlive: function() { - var val = this.getStoredValue(); - return val !== 'null' && - val !== 'undefined' && - (val && val.length > 0); - }, - - updateStorage: function() { - if (typeof localStorage !== 'undefined') { - var value = this.editor.getValue(); - // store in localStorage - localStorage.setItem(this.keyValue, value); - // also store code in URL - codeUri.querify(value); - } else { - console.log('no web storage'); - } - this.updateTimeoutId = null; - } - }; - - function codeStorageFactory(editor, challengeName) { - var codeStorage = Object.create(CodeStorage); - $.extend(codeStorage, CodeStorageProps); - codeStorage.setEditor(editor); - codeStorage.setSaveKey(challengeName); - return codeStorage; - } - - var savedVersion = localStorage.getItem(CodeStorageProps.keyVersion); - if (savedVersion === null) { - localStorage.setItem( - CodeStorageProps.keyVersion, - CodeStorageProps.version - ); - } - - common.codeStorageFactory = codeStorageFactory; - - return common; -}(window, window.common)); diff --git a/client/commonFramework/createEditor.js b/client/commonFramework/create-editor.js similarity index 81% rename from client/commonFramework/createEditor.js rename to client/commonFramework/create-editor.js index 2a3c65b698..d2fa94212e 100644 --- a/client/commonFramework/createEditor.js +++ b/client/commonFramework/create-editor.js @@ -5,25 +5,35 @@ window.common = (function(global) { common = { init: [] } } = global; - if (!CodeMirror) { - return {}; + const { challengeType = '0' } = common; + + if ( + !CodeMirror || + challengeType === '0' || + challengeType === '7' + ) { + common.editor = {}; + return common; } var delay; var codeStorageFactory = common.codeStorageFactory; - var editor = CodeMirror.fromTextArea(document.getElementById('codeEditor'), { - lint: true, - lineNumbers: true, - mode: 'javascript', - theme: 'monokai', - runnable: true, - matchBrackets: true, - autoCloseBrackets: true, - scrollbarStyle: 'null', - lineWrapping: true, - gutters: ['CodeMirror-lint-markers'] - }); + var editor = CodeMirror.fromTextArea( + document.getElementById('codeEditor'), + { + lint: true, + lineNumbers: true, + mode: 'javascript', + theme: 'monokai', + runnable: true, + matchBrackets: true, + autoCloseBrackets: true, + scrollbarStyle: 'null', + lineWrapping: true, + gutters: ['CodeMirror-lint-markers'] + } + ); editor.setSize('100%', 'auto'); diff --git a/client/commonFramework/detect-loops-stream.js b/client/commonFramework/detect-loops-stream.js new file mode 100644 index 0000000000..193b28165d --- /dev/null +++ b/client/commonFramework/detect-loops-stream.js @@ -0,0 +1,79 @@ +window.common = (function(global) { + const { + jailed, + document: doc, + Rx: { Observable, Disposable }, + common = { init: [] } + } = global; + + if (!jailed) { + return (code, cb) => cb(new Error('Could not load jailed plugin')); + } + + // obtaining absolute path of this script + var scripts = doc.getElementsByTagName('script'); + var path = scripts[scripts.length - 1].src + .split('?')[0] + .split('/') + .slice(0, -1) + .join('/') + '/'; + + var Sandbox = { + startTimeout() { + this.timeoutId = setTimeout(() => { + this.error = new Error('Plugin failed to initialize'); + this.destroyPlugin(); + }, 3000); + }, + cancelTimout() { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + this.timeoutId = null; + } + }, + createPlugin() { + this.plugin = new jailed.Plugin(path + 'plugin.js'); + this.startTimeout(); + this.plugin.whenConnected(() => { + this.endTimeout(); + }); + }, + destroyPlugin() { + this.plugin.disconnect(); + } + }; + + + // sends the input to the plugin for evaluation + common.detectLoops = function detectLoops({ code = '', ...rest }) { + return new Observable(function(observer) { + const sandbox = Object.create(Sandbox); + + sandbox.createPlugin(); + sandbox.plugin.whenConnected(() => { + sandbox.plugin.remote.run(code, (err, data) => { + observer.onNext({ ...rest, err, code, data }); + observer.onCompleted(); + }); + }); + + sandbox.plugin.whenDisconnected(() => { + if (sandbox.disposed) { + return null; + } + + if (sandbox.error) { + observer.onNext({ ...rest, err: sandbox.error, code, data: {} }); + } + observer.onCompleted(); + }); + + return new Disposable(() => { + sandbox.disposed = true; + sandbox.destroyPlugin(); + }); + }); + }; + + return common; +}(window)); diff --git a/client/commonFramework/detectLoops.js b/client/commonFramework/detectLoops.js deleted file mode 100644 index 2f67088801..0000000000 --- a/client/commonFramework/detectLoops.js +++ /dev/null @@ -1,57 +0,0 @@ -window.common = (function(global) { - const { - jailed, - document: doc, - common = { init: [] } - } = global; - - if (!jailed) { - return (code, cb) => cb(new Error('Could not load jailed plugin')); - } - - // obtaining absolute path of this script - var scripts = doc.getElementsByTagName('script'); - var path = scripts[scripts.length - 1].src - .split('?')[0] - .split('/') - .slice(0, -1) - .join('/') + '/'; - - var sandbox = { - timeoutId: null, - - startTimeout() { - this.timeoutId = setTimeout(() => { - this.disconnect(); - }, 3000); - }, - endTimeout() { - if (this.timeoutId) { - clearTimeout(this.timeoutId); - this.timeoutId = null; - } - }, - createPlugin() { - this.plugin = new jailed.Plugin(path + 'plugin.js'); - this.plugin.whenDisconnected(() => { - this.endTimeout(); - }); - }, - destroyPlugin() { - this.plugin.disconnect(); - } - }; - - - // sends the input to the plugin for evaluation - common.detectLoops = function detectLoops(code, callback) { - sandbox.createPlugin(); - sandbox.plugin.whenConnected(() => { - this.endTimeout(); - - sandbox.plugin.remote.run(code, callback); - }); - }; - - return common; -}(window)); diff --git a/client/commonFramework/displayTestResults.js b/client/commonFramework/display-test-results.js similarity index 100% rename from client/commonFramework/displayTestResults.js rename to client/commonFramework/display-test-results.js diff --git a/client/commonFramework/execute-challenge-stream.js b/client/commonFramework/execute-challenge-stream.js new file mode 100644 index 0000000000..cfc2b48b28 --- /dev/null +++ b/client/commonFramework/execute-challenge-stream.js @@ -0,0 +1,119 @@ +// what are the executeChallenge functions? +// Should be responsible for starting after a submit action +// Should not be responsible for displaying results +// Should return results +// should grab editor value +// depends on main editor +window.common = (function(global) { + const { + ga, + Rx: { Observable }, + common = { init: [] } + } = global; + + let attempts = 0; + const detectFunctionCall = /function\s*?\(|function\s+\w+\s*?\(/gi; + const detectUnsafeJQ = /\$\s*?\(\s*?\$\s*?\)/gi; + const detectUnsafeConsoleCall = /if\s\(null\)\sconsole\.log\(1\);/gi; + + common.executeChallenge$ = function executeChallenge$() { + const code = common.editor.getValue(); + const head = common.arrayToNewLineString(common.head); + const tail = common.arrayToNewLineString(common.tail); + + attempts++; + + ga('send', 'event', 'Challenge', 'ran-code', common.challengeName); + + let openingComments = code.match(/\/\*/gi); + + // checks if the number of opening comments(/*) matches the number of + // closing comments(*/) + Observable.just({ code }) + .flatMap(code => { + if ( + code.match(/\$\s*?\(\s*?\$\s*?\)/gi) && + openingComments && + openingComments.length > code.match(/\*\//gi).length + ) { + + return Observable.just({ + err: 'SyntaxError: Unfinished multi-line comment', + code: code + }); + } + + if (code.match(detectUnsafeJQ)) { + return Observable.just({ + err: 'Unsafe $($)', + output: 'Unsafe $($)', + code: code + }); + } + + if ( + code.match(/function/g) && + !code.match(detectFunctionCall) + ) { + return Observable.just({ + err: 'SyntaxError: Unsafe or unfinished function declaration', + code: code + }); + } + + if (common.challengeType === '0') { + let openingComments = code.match(/\<\!\-\-/gi); + let closingComments = code.match(/\-\-\>/gi) || []; + if ( + openingComments && + openingComments.length > closingComments.length + ) { + return Observable.just({ + err: 'SyntaxError: Unfinished HTML comment', + code: code + }); + } + } + + if (code.match(detectUnsafeConsoleCall)) { + return Observable.just({ + err: 'Invalid if (null) console.log(1); detected', + code: code + }); + } + + // add head and tail and detect loops + return Observable.just({ code: head + code + tail }) + .map(code => { + if (common.challengeType === common.challengeTypes.HTML) { + return common.getScriptCode(code); + } + + return common.addTestsToString( + common.removeComments(code), + common.tests.slice() + ); + }) + .flatMap(common.detectLoops) + .flatMap(({ err, code, data, userTests }) => { + if (err) { + return Observable.just({ + err, + code, + data + }); + } + + return common.runTests$({ + output: data.output.replace(/\\\"/gi, ''), + data, + code, + userTests + }); + }); + + }); + }; + + return common; +}(window)); diff --git a/client/commonFramework/executeChallenge.js b/client/commonFramework/executeChallenge.js deleted file mode 100644 index 113bfbd6c9..0000000000 --- a/client/commonFramework/executeChallenge.js +++ /dev/null @@ -1,94 +0,0 @@ -window.common = (function(global) { - const { - ga, - common = { init: [] } - } = global; - - let attempts = 0; - common.executeChallenge = function executeChallenge(shouldTest) { - const editorValue = common.editor.getValue(); - const head = common.arrayToNewLineString(common.head); - const tail = common.arrayToNewLineString(common.tail); - const codeOutput = common.codeOutput; - - attempts++; - ga('send', 'event', 'Challenge', 'ran-code', common.challengeName); - $('#testSuite').empty(); - - const openingComments = editorValue.match(/\/\*/gi); - - // checks if the number of opening comments(/*) matches the number of - // closing comments(*/) - if ( - editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi) && - openingComments && - openingComments.length > editorValue.match(/\*\//gi).length - ) { - - common.editor.setValue(common.editor.getValue() + '*/'); - codeOutput.setValue('Unfinished multi-line comment'); - return null; - } - - if (common.challengeType !== '0') { - let userJavaScript = head + common.editor.getValue() + tail; - - userJavaScript = common.removeComments(userJavaScript); - userJavaScript = common.addTests(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)) { - common.runInSand(userJavaScript, function(err, message) { - if (err) { - codeOutput.setValue(err); - if (shouldTest) { - common.runTests('Error', null); - } - } else { - codeOutput.setValue(message.output); - codeOutput.setValue(codeOutput.getValue().replace(/\\\"/gi, '')); - message.input = common.removeLogs(message.input); - if (shouldTest) { - common.runTests(null, message); - } - } - }); - } else { - codeOutput.setValue('Unsafe or unfinished function declaration'); - } - } else { - common.runInSand(userJavaScript, function(cls, message) { - - if (cls) { - codeOutput.setValue(message.error); - if (shouldTest) { - common.runTests('Error', null); - } - } else { - codeOutput.setValue(message.output); - codeOutput.setValue(codeOutput.getValue().replace(/\\\"/gi, '')); - message.input = common.removeLogs(message.input); - - if (shouldTest) { - common.runTests(null, message); - } - } - }); - } - } else if ( - !editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi) && - common.challengeType === '0' - ) { - common.safeHTMLRun(shouldTest); - } else { - common.workerError('Unsafe $($)'); - } - - setTimeout(function() { - var $marginFix = $('.innerMarginFix'); - $marginFix.css('min-height', $marginFix.height()); - }, 1000); - }; - return common; -}(window)); diff --git a/client/commonFramework/utils.js b/client/commonFramework/init.js similarity index 85% rename from client/commonFramework/utils.js rename to client/commonFramework/init.js index bf62660bb3..c82ff4de49 100644 --- a/client/commonFramework/utils.js +++ b/client/commonFramework/init.js @@ -10,6 +10,18 @@ window.common = (function(global) { common.tail = common.tail || []; common.salt = Math.random(); + common.challengeTypes = { + HTML: '0', + JS: '1', + VIDEO: '2', + ZIPLINE: '3', + BASEJUMP: '4', + BONFIRE: '5', + HIKES: '6', + STEP: '7' + }; + + common.arrayToNewLineString = function arrayToNewLineString(seedData) { seedData = Array.isArray(seedData) ? seedData : [seedData]; return seedData.reduce(function(seed, line) { @@ -67,13 +79,11 @@ window.common = (function(global) { return str.replace(logRegex, ''); }; - common.reassembleTest = function reassembleTest(test, data) { - var lineNum = test.line; - var regexp = new RegExp('\/\/' + lineNum + common.salt); - return data.input.replace(regexp, test.text); + common.reassembleTest = function reassembleTest(code = '', { line, text }) { + var regexp = new RegExp('\/\/' + line + common.salt); + return code.replace(regexp, text); }; - return common; })(); diff --git a/client/commonFramework/outputDisplay.js b/client/commonFramework/output-display.js similarity index 55% rename from client/commonFramework/outputDisplay.js rename to client/commonFramework/output-display.js index 69f7d055a6..1fea84fee9 100644 --- a/client/commonFramework/outputDisplay.js +++ b/client/commonFramework/output-display.js @@ -7,18 +7,17 @@ window.common = (function(global) { const { challengeType = '0' } = common; - if (!CodeMirror) { - return {}; - } - if ( + !CodeMirror || challengeType === '0' || challengeType === '7' ) { - return {}; + common.updateOutputDisplay = () => {}; + common.appendToOutputDisplay = () => {}; + return common; } - common.codeOutput = CodeMirror.fromTextArea( + var codeOutput = CodeMirror.fromTextArea( doc.getElementById('codeOutput'), { lineNumbers: false, @@ -29,7 +28,7 @@ window.common = (function(global) { } ); - common.codeOutput.setValue(` + codeOutput.setValue(` /** * Your output will go here. * Console.log() -type statements @@ -38,7 +37,17 @@ window.common = (function(global) { */' `); - common.codeOutput.setSize('100%', '100%'); + codeOutput.setSize('100%', '100%'); + + common.updateOutputDisplay = function updateOutputDisplay(str) { + codeOutput.setValue(str); + return str; + }; + + common.appendToOutputDisplay = function appendToOutputDisplay(str) { + codeOutput.setValue(codeOutput.getValue() + str); + return str; + }; return common; }(window)); diff --git a/client/commonFramework/phone-scroll-lock.js b/client/commonFramework/phone-scroll-lock.js new file mode 100644 index 0000000000..f3c7741267 --- /dev/null +++ b/client/commonFramework/phone-scroll-lock.js @@ -0,0 +1,132 @@ +window.common = (function({ common = { init: [] }}) { + + common.lockTop = function lockTop() { + var magiVal; + + if ($(window).width() >= 990) { + if ($('.editorScrollDiv').html()) { + + magiVal = $(window).height() - $('.navbar').height(); + + if (magiVal < 0) { + magiVal = 0; + } + $('.editorScrollDiv').css('height', magiVal - 85 + 'px'); + } + + magiVal = $(window).height() - $('.navbar').height(); + + if (magiVal < 0) { + magiVal = 0; + } + + $('.scroll-locker') + .css('min-height', $('.editorScrollDiv').height()) + .css('height', magiVal - 185); + } else { + $('.editorScrollDiv').css('max-height', 500 + 'px'); + + $('.scroll-locker') + .css('position', 'inherit') + .css('top', 'inherit') + .css('width', '100%') + .css('max-height', '85%'); + } + }; + + common.init.push(function($) { + // fakeiphone positioning hotfix + if ( + $('.iphone-position').html() || + $('.iphone').html() + ) { + var startIphonePosition = parseInt( + $('.iphone-position') + .css('top') + .replace('px', ''), + 10 + ); + + var startIphone = parseInt( + $('.iphone') + .css('top') + .replace('px', ''), + 10 + ); + + $(window).on('scroll', function() { + var courseHeight = $('.courseware-height').height(); + var courseTop = $('.courseware-height').offset().top; + var windowScrollTop = $(window).scrollTop(); + var phoneHeight = $('.iphone-position').height(); + + if (courseHeight + courseTop - windowScrollTop - phoneHeight <= 0) { + $('.iphone-position').css( + 'top', + startIphonePosition + + courseHeight + + courseTop - + windowScrollTop - + phoneHeight + ); + + $('.iphone').css( + 'top', + startIphonePosition + + courseHeight + + courseTop - + windowScrollTop - + phoneHeight + + 120 + ); + } else { + $('.iphone-position').css('top', startIphonePosition); + $('.iphone').css('top', startIphone); + } + }); + } + + if ($('.scroll-locker').html()) { + + if ($('.scroll-locker').html()) { + common.lockTop(); + $(window).on('resize', function() { + common.lockTop(); + }); + $(window).on('scroll', function() { + common.lockTop(); + }); + } + + var execInProgress = false; + + // why is this not $??? + document + .getElementById('scroll-locker') + .addEventListener( + 'previewUpdateSpy', + function(e) { + if (execInProgress) { + return null; + } + execInProgress = true; + setTimeout(function() { + if ( + $($('.scroll-locker').children()[0]).height() - 800 > e.detail + ) { + $('.scroll-locker').scrollTop(e.detail); + } else { + var scrollTop = $($('.scroll-locker').children()[0]).height(); + + $('.scroll-locker').animate({ scrollTop: scrollTop }, 175); + } + execInProgress = false; + }, 750); + }, + false + ); + } + }); + + return common; +}()); diff --git a/client/commonFramework/report-issue.js b/client/commonFramework/report-issue.js new file mode 100644 index 0000000000..93f5a4d784 --- /dev/null +++ b/client/commonFramework/report-issue.js @@ -0,0 +1,56 @@ +window.common = (function({ common = { init: [] } }) { + common.init.push(function($) { + $('#report-issue').on('click', function() { + var textMessage = [ + 'Challenge [', + (common.challengeName || window.location.pathname), + '](', + window.location.href, + ') has an issue.\n', + 'User Agent is: ', + navigator.userAgent, + '.\n', + 'Please describe how to reproduce this issue, and include ', + 'links to screenshots if possible.\n\n' + ].join(''); + + if ( + common.editor && + typeof common.editor.getValue === 'function' && + common.editor.getValue().trim() + ) { + var type; + switch (common.challengeType) { + case common.challengeTypes.html: + type = 'html'; + break; + case common.challengeTypes.js: + case common.challengeTypes.bonfire: + type = 'javascript'; + break; + default: + type = ''; + } + + textMessage += [ + 'My code:\n```', + type, + '\n', + common.editor.getValue(), + '\n```\n\n' + ].join(''); + } + + textMessage = encodeURIComponent(textMessage); + + $('#issue-modal').modal('hide'); + window.open( + 'https://github.com/freecodecamp/freecodecamp/issues/new?&body=' + + textMessage, + '_blank' + ); + }); + }); + + return common; +}(window)); diff --git a/client/commonFramework/run-tests-stream.js b/client/commonFramework/run-tests-stream.js new file mode 100644 index 0000000000..50cfc57320 --- /dev/null +++ b/client/commonFramework/run-tests-stream.js @@ -0,0 +1,35 @@ +window.common = (function(global) { + const { + Rx: { Observable }, + chai, + common = { init: [] } + } = global; + + common.runTests$ = function runTests$({ code, userTests, ...rest }) { + + return Observable.from(userTests) + .map(function(test) { + + /* eslint-disable no-unused-vars */ + const assert = chai.assert; + const editor = { getValue() { return code; }}; + /* eslint-enable no-unused-vars */ + + try { + if (test) { + /* eslint-disable no-eval */ + eval(common.reassembleTest(test, code)); + /* eslint-enable no-eval */ + } + } catch (e) { + test.err = e.message; + } + + return test; + }) + .toArray() + .map(tests => ({ ...rest, tests })); + }; + + return common; +}(window)); diff --git a/client/commonFramework/runTests.js b/client/commonFramework/runTests.js deleted file mode 100644 index 93703552a1..0000000000 --- a/client/commonFramework/runTests.js +++ /dev/null @@ -1,43 +0,0 @@ -window.common = (function({ common = { init: [] }}) { - common.runTests = function runTests(err, data) { - var head = common.arrayToNewLineString(common.head); - var tail = common.arrayToNewLineString(common.tail); - var userTests = Array.isArray(userTests) ? userTests.slice() : []; - - var editorValue = head + common.editor.getValue() + tail; - - if (err) { - userTests = [{ - text: 'Program Execution Failure', - err - }]; - return userTests; - } - - // Add blocks to test exploits here! - if (editorValue.match(/if\s\(null\)\sconsole\.log\(1\);/gi)) { - userTests = [{ - text: 'Program Execution Failure', - err: 'Invalid if (null) console.log(1); detected' - }]; - - return userTests; - } - - return userTests.map(function(test) { - try { - if (test) { - /* eslint-disable no-eval, no-unused-vars */ - var output = eval(common.reassembleTest(test, data)); - /* eslint-enable no-eval, no-unused-vars */ - } - } catch (e) { - test.err = e.message; - } - - return test; - }); - }; - - return common; -}(window)); diff --git a/client/commonFramework/show-completion.js b/client/commonFramework/show-completion.js new file mode 100644 index 0000000000..b4c4998e8e --- /dev/null +++ b/client/commonFramework/show-completion.js @@ -0,0 +1,70 @@ +window.common = (function(global) { + const { + $, + ga = (() => {}), + common = { init: [] } + } = global; + + common.showCompletion = function showCompletion() { + var time = Math.floor(Date.now()) - window.started; + + ga( + 'send', + 'event', + 'Challenge', + 'solved', + common.challengeName + ', Time: ' + time + ', Attempts: ' + 0 + ); + + var bonfireSolution = common.editor.getValue(); + var didCompleteWith = $('#completed-with').val() || null; + + $('#complete-courseware-dialog').modal('show'); + $('#complete-courseware-dialog .modal-header').click(); + + $('#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( + '
' + + 'submitting...
' + ); + next(); + }); + + $.post( + '/completed-bonfire/', { + challengeInfo: { + challengeId: common.challengeId, + challengeName: common.challengeName, + completedWith: didCompleteWith, + challengeType: common.challengeType, + solution: bonfireSolution + } + }, + function(res) { + if (res) { + window.location = + '/challenges/next-challenge?id=' + common.challengeId; + } + } + ); + }); + }; + + return common; +}(window)); diff --git a/client/commonFramework/stepChallenge.js b/client/commonFramework/step-challenge.js similarity index 94% rename from client/commonFramework/stepChallenge.js rename to client/commonFramework/step-challenge.js index 6b689ffc15..6bee81c0b7 100644 --- a/client/commonFramework/stepChallenge.js +++ b/client/commonFramework/step-challenge.js @@ -8,15 +8,11 @@ window.common = (function({ $, common = { init: [] }}) { const submitModalId = '#challenge-step-modal'; function getPreviousStep($challengeSteps) { - var length = $challengeSteps.length; var $prevStep = false; var prevStepIndex = 0; $challengeSteps.each(function(index) { var $step = $(this); - if ( - !$step.hasClass('hidden') && - index + 1 !== length - ) { + if (!$step.hasClass('hidden')) { prevStepIndex = index - 1; } }); @@ -50,7 +46,8 @@ window.common = (function({ $, common = { init: [] }}) { var prevStep = getPreviousStep($(stepClass)); $(this) .parent() - .removeClass('fadeOutLeft') + .parent() + .removeClass('slideInLeft slideInRight') .addClass('animated fadeOutRight fast-animation') .delay(250) .queue(function(prev) { @@ -58,7 +55,7 @@ window.common = (function({ $, common = { init: [] }}) { if (prevStep) { $(prevStep) .removeClass('hidden') - .removeClass('slideInRight') + .removeClass('fadeOutLeft fadeOutRight') .addClass('animated slideInLeft fast-animation') .delay(500) .queue(function(prev) { @@ -74,7 +71,8 @@ window.common = (function({ $, common = { init: [] }}) { var nextStep = getNextStep($(stepClass)); $(this) .parent() - .removeClass('fadeOutRight') + .parent() + .removeClass('slideInRight slideInLeft') .addClass('animated fadeOutLeft fast-animation') .delay(250) .queue(function(next) { @@ -82,7 +80,7 @@ window.common = (function({ $, common = { init: [] }}) { if (nextStep) { $(nextStep) .removeClass('hidden') - .removeClass('slideInLeft') + .removeClass('fadeOutRight fadeOutLeft') .addClass('animated slideInRight fast-animation') .delay(500) .queue(function(next) { diff --git a/client/commonFramework/test-script-stream.js b/client/commonFramework/test-script-stream.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/commonFramework/update-preview.js b/client/commonFramework/update-preview.js new file mode 100644 index 0000000000..3608a65bd8 --- /dev/null +++ b/client/commonFramework/update-preview.js @@ -0,0 +1,40 @@ +window.common = (function({ common = { init: [] } }) { + var libraryIncludes = ` + + + + + + + `; + + var iFrameScript = ""; + + + common.updatePreview = function updatePreview(code = '', shouldTest = false) { + const previewFrame = document.getElementById('preview'); + const preview = previewFrame.contentDocument || + previewFrame.contentWindow.document; + if (!preview) { + return code; + } + preview.open(); + preview.write(libraryIncludes + code + (shouldTest ? iFrameScript : '')); + preview.close(); + + return code; + }; + + return common; +}(window)); diff --git a/public/js/fauxJQuery.js b/client/faux.js similarity index 93% rename from public/js/fauxJQuery.js rename to client/faux.js index c32440051d..f1eb4e44e6 100644 --- a/public/js/fauxJQuery.js +++ b/client/faux.js @@ -1,3 +1,14 @@ +document = {}; +var navigator = function() { + this.geolocation = function() { + this.getCurrentPosition = function() { + this.coords = {latitude: "", longitude: ""}; + return this; + }; + return this; + }; + return this; +}; function $() { if (!(this instanceof $)) { return new $(); diff --git a/client/iFrameScripts.js b/client/iFrameScripts.js index de012eec7d..66cb8554ba 100644 --- a/client/iFrameScripts.js +++ b/client/iFrameScripts.js @@ -7,20 +7,22 @@ window.$(function() { var tests = parent.tests; var common = parent.common; var editorValue = common.editor.getValue(); + var editor = common.editor; - common.tests.forEach(test => { + var userTests = common.tests.map(test => { + var userTest = {}; try { /* eslint-disable no-eval */ eval(test); /* eslint-enable no-eval */ } catch (e) { - parent.postError(JSON.stringify(e.message.split(':').shift())); + userTest.err = e.message.split(':').shift(); } finally { - parent.postSuccess( - JSON.stringify( - test.split(',').pop().replace(/\'/g, '').replace(/\)/, '') - ) - ); + userTest.text = test + .split(',') + .pop() + .replace(/\'/g, '') + .replace(/\)/, ''); } }); }); diff --git a/client/main.js b/client/main.js index 4905bc8e28..50e4449d85 100644 --- a/client/main.js +++ b/client/main.js @@ -4,15 +4,6 @@ main.mapShareKey = 'map-shares'; main.ga = window.ga || function() {}; -main.challengeTypes = { - 'HTML_CSS_JQ': '0', - 'JAVASCRIPT': '1', - 'VIDEO': '2', - 'ZIPLINE': '3', - 'BASEJUMP': '4', - 'BONFIRE': '5' -}; - main = (function(main) { // should be set before gitter script loads @@ -112,47 +103,11 @@ main = (function(main) { return main; }(main)); -main.lockTop = function lockTop() { - var magiVal; - - if ($(window).width() >= 990) { - if ($('.editorScrollDiv').html()) { - - magiVal = $(window).height() - - $('.navbar').height(); - - if (magiVal < 0) { - magiVal = 0; - } - $('.editorScrollDiv').css('height', magiVal - 35 + 'px'); - } - - magiVal = $(window).height() - - $('.navbar').height(); - - if (magiVal < 0) { - magiVal = 0; - } - - $('.scroll-locker') - .css('min-height', $('.editorScrollDiv').height()) - .css('height', magiVal - 185); - } else { - $('.editorScrollDiv').css('max-height', 500 + 'px'); - - $('.scroll-locker') - .css('position', 'inherit') - .css('top', 'inherit') - .css('width', '100%') - .css('max-height', '85%'); - } -}; - var lastCompleted = typeof lastCompleted !== 'undefined' ? lastCompleted : ''; -function getMapShares() { +main.getMapShares = function getMapShares() { var alreadyShared = JSON.parse( localStorage.getItem(main.mapShareKey) || '[]' @@ -163,10 +118,10 @@ function getMapShares() { alreadyShared = []; } return alreadyShared; -} +}; -function setMapShare(id) { - var alreadyShared = getMapShares(); +main.setMapShare = function setMapShare(id) { + var alreadyShared = main.getMapShares(); var found = false; alreadyShared.forEach(function(_id) { if (_id === id) { @@ -178,23 +133,10 @@ function setMapShare(id) { } localStorage.setItem(main.mapShareKey, JSON.stringify(alreadyShared)); return alreadyShared; -} +}; $(document).ready(function() { - - var challengeName = typeof challengeName !== 'undefined' ? - challengeName : - ''; - - if (challengeName) { - ga('send', 'event', 'Challenge', 'load', challengeName); - } - - if (typeof editor !== 'undefined') { - $('#reset-button').on('click', window.resetEditor); - } - var CSRF_HEADER = 'X-CSRF-Token'; var setCSRFToken = function(securityToken) { @@ -207,38 +149,6 @@ $(document).ready(function() { setCSRFToken($('meta[name="csrf-token"]').attr('content')); - $('.checklist-element').each(function() { - var checklistElementId = $(this).attr('id'); - if (localStorage[checklistElementId]) { - $(this).children().children('li').addClass('faded'); - $(this).children().children('input').trigger('click'); - } - }); - - $('.start-challenge').on('click', function() { - $(this).parent().remove(); - $('.challenge-content') - .removeClass('hidden-element') - .addClass('animated fadeInDown'); - }); - - $('.challenge-list-checkbox').on('change', function() { - var checkboxId = $(this).parent().parent().attr('id'); - if ($(this).is(':checked')) { - $(this).parent().siblings().children().addClass('faded'); - if (!localStorage || !localStorage[checkboxId]) { - localStorage[checkboxId] = true; - } - } - - if (!$(this).is(':checked')) { - $(this).parent().siblings().children().removeClass('faded'); - if (localStorage[checkboxId]) { - localStorage.removeItem(checkboxId); - } - } - }); - $('img').error(function() { $(this) .unbind('error') @@ -248,301 +158,6 @@ $(document).ready(function() { ); }); - function reBindModals() { - if (!window.common) { - return; - } - var common = window.common; - - $('.close-modal').unbind('click'); - $('.close-modal').on('click', function() { - setTimeout(function() { - $('.close-modal').parent().parent().parent().parent().modal('hide'); - }, 200); - }); - - $('#search-issue').unbind('click'); - $('#search-issue').on('click', function() { - var queryIssue = window.location.href.toString(); - window.open( - 'https://github.com/FreeCodeCamp/FreeCodeCamp/issues?q=' + - 'is:issue is:all ' + - (common.challengeName) + - ' OR ' + - queryIssue - .substr(queryIssue.lastIndexOf('challenges/') + 11) - .replace('/', ''), '_blank'); - }); - - $('#gist-share').unbind('click'); - $('#gist-share').on('click', function() { - var gistWindow = window.open('', '_blank'); - - $('#gist-share') - .attr('disabled', 'true') - .removeClass('btn-danger') - .addClass('btn-warning disabled'); - - function createCORSRequest(method, url) { - var xhr = new XMLHttpRequest(); - if ('withCredentials' in xhr) { - xhr.open(method, url, true); - } else if (typeof XDomainRequest !== 'undefined') { - xhr = new XDomainRequest(); - xhr.open(method, url); - } else { - xhr = null; - } - xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); - return xhr; - } - - var request = createCORSRequest('post', 'https://api.github.com/gists'); - if (!request) { - return null; - } - - request.onload = function() { - if ( - request.readyState === 4 && - request.status === 201 && - request.statusText === 'Created' - ) { - gistWindow.location.href = - JSON.parse(request.responseText)['html_url']; - } - }; - - var description = common.username ? - 'http://www.freecodecamp.com/' + common.username + ' \'s s' : - 'S'; - - var data = { - description: description + 'olution for ' + common.challengeName, - public: true, - files: {} - }; - var queryIssue = window.location.href.toString().split('#?')[0]; - var filename = queryIssue - .substr(queryIssue.lastIndexOf('challenges/') + 11) - .replace('/', '') + '.js'; - - data.files[filename] = { - content: '// ' + - common.challengeName + - '\n' + - (common.username ? '// Author: @' + common.username + '\n' : '') + - '// Challenge: ' + - queryIssue + - '\n' + - '// Learn to Code at Free Code Camp (www.freecodecamp.com)' + - '\n\n' + - window.editor.getValue().trim() - }; - - request.send(JSON.stringify(data)); - }); - - $('#help-ive-found-a-bug-wiki-article').unbind('click'); - $('#help-ive-found-a-bug-wiki-article').on('click', function() { - window.open( - 'https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/' + - "Help-I've-Found-a-Bug", - '_blank' - ); - }); - - $('#report-issue').unbind('click'); - $('#report-issue').on('click', function() { - var textMessage = [ - 'Challenge [', - (common.challengeName || window.location.href), - '](', - window.location.href, - ') has an issue.\n', - 'User Agent is: ', - navigator.userAgent, - '.\n', - 'Please describe how to reproduce this issue, and include ', - 'links to screenshots if possible.\n\n' - ].join(''); - - if ( - window.editor && - typeof window.editor.getValue === 'function' && - window.editor.getValue().trim() - ) { - var type; - switch (common.challengeType) { - case main.challengeTypes.HTML_CSS_JQ: - type = 'html'; - break; - case main.challengeTypes.JAVASCRIPT: - case main.challengeTypes.BONFIRE: - type = 'javascript'; - break; - default: - type = ''; - } - - textMessage += [ - 'My code:\n```', - type, - '\n', - window.editor.getValue(), - '\n```\n\n' - ].join(''); - } - - textMessage = encodeURIComponent(textMessage); - - $('#issue-modal').modal('hide'); - window.open( - 'https://github.com/freecodecamp/freecodecamp/issues/new?&body=' + - textMessage, '_blank' - ); - }); - - $('#completed-courseware').unbind('click'); - $('#completed-courseware').on('click', function() { - $('#complete-courseware-dialog').modal('show'); - }); - - $('#completed-courseware-editorless').unbind('click'); - $('#completed-courseware-editorless').on('click', function() { - $('#complete-courseware-editorless-dialog').modal('show'); - }); - - $('#trigger-pair-modal').unbind('click'); - $('#trigger-pair-modal').on('click', function() { - $('#pair-modal').modal('show'); - }); - - $('#trigger-reset-modal').unbind('click'); - $('#trigger-reset-modal').on('click', function() { - $('#reset-modal').modal('show'); - }); - - $('#trigger-help-modal').unbind('click'); - $('#trigger-help-modal').on('click', function() { - $('#help-modal').modal('show'); - }); - - $('#trigger-issue-modal').unbind('click'); - $('#trigger-issue-modal').on('click', function() { - $('#issue-modal').modal('show'); - }); - - $('#completed-zipline-or-basejump').unbind('click'); - $('#completed-zipline-or-basejump').on('click', function() { - $('#complete-zipline-or-basejump-dialog').modal('show'); - }); - - $('#next-courseware-button').unbind('click'); - $('#next-courseware-button').on('click', function() { - $('#next-courseware-button').unbind('click'); - if ($('.signup-btn-nav').length < 1) { - var data; - var completedWith; - var publicURL; - switch (common.challengeType) { - case main.challengeTypes.HTML_CSS_JQ: - case main.challengeTypes.JAVASCRIPT: - case main.challengeTypes.VIDEO: - data = { - challengeInfo: { - challengeId: common.challengeId, - challengeName: common.challengeName - } - }; - $.post('/completed-challenge/', data) - .success(function(res) { - if (!res) { - return; - } - window.location.href = '/challenges/next-challenge?id=' + - common.challengeId; - }) - .fail(function() { - window.location.href = '/challenges'; - }); - - break; - case main.challengeTypes.ZIPLINE: - completedWith = $('#completed-with').val() || null; - publicURL = $('#public-url').val() || null; - data = { - challengeInfo: { - challengeId: common.challengeId, - challengeName: common.challengeName, - completedWith: completedWith, - publicURL: publicURL, - challengeType: common.challengeType - } - }; - - $.post('/completed-zipline-or-basejump/', data) - .success(function() { - window.location.href = '/challenges/next-challenge?id=' + - common.challengeId; - }) - .fail(function() { - window.location.href = '/challenges'; - }); - break; - - case main.challengeTypes.BASEJUMP: - completedWith = $('#completed-with').val() || null; - publicURL = $('#public-url').val() || null; - var githubURL = $('#github-url').val() || null; - data = { - challengeInfo: { - challengeId: common.challengeId, - challengeName: common.challengeName, - completedWith: completedWith, - publicURL: publicURL, - githubURL: githubURL, - challengeType: common.challengeType, - verified: false - } - }; - $.post('/completed-zipline-or-basejump/', data) - .success(function() { - window.location.href = '/challenges/next-challenge?id=' + - common.challengeId; - }) - .fail(function() { - window.location.replace(window.location.href); - }); - break; - - case main.challengeTypes.BONFIRE: - window.location.href = '/challenges/next-challenge?id=' + - common.challengeId; - break; - - default: - console.log('Happy Coding!'); - break; - } - } - }); - - $('#complete-courseware-dialog').on('hidden.bs.modal', function() { - window.editor.focus(); - }); - - $('#complete-zipline-or-basejump').on('hidden.bs.modal', function() { - window.editor.focus(); - }); - } - - $(window).resize(function() { - reBindModals(); - }); - - reBindModals(); - function upvoteHandler(e) { e.preventDefault(); var upvoteBtn = this; @@ -602,101 +217,9 @@ $(document).ready(function() { $('#story-submit').on('click', storySubmitButtonHandler); - // fakeiphone positioning hotfix - if ( - $('.iphone-position').html() || - $('.iphone').html() - ) { - var startIphonePosition = parseInt( - $('.iphone-position') - .css('top') - .replace('px', ''), - 10 - ); - - var startIphone = parseInt( - $('.iphone') - .css('top') - .replace('px', ''), - 10 - ); - - $(window).on('scroll', function() { - var courseHeight = $('.courseware-height').height(); - var courseTop = $('.courseware-height').offset().top; - var windowScrollTop = $(window).scrollTop(); - var phoneHeight = $('.iphone-position').height(); - - if (courseHeight + courseTop - windowScrollTop - phoneHeight <= 0) { - $('.iphone-position').css( - 'top', - startIphonePosition + - courseHeight + - courseTop - - windowScrollTop - - phoneHeight - ); - - $('.iphone').css( - 'top', - startIphonePosition + - courseHeight + - courseTop - - windowScrollTop - - phoneHeight + - 120 - ); - } else { - $('.iphone-position').css('top', startIphonePosition); - $('.iphone').css('top', startIphone); - } - }); - } - - if ($('.scroll-locker').html()) { - - if ($('.scroll-locker').html()) { - main.lockTop(); - $(window).on('resize', function() { - main.lockTop(); - }); - $(window).on('scroll', function() { - main.lockTop(); - }); - } - - var execInProgress = false; - - // why is this not $??? - document - .getElementById('scroll-locker') - .addEventListener( - 'previewUpdateSpy', - function(e) { - if (execInProgress) { - return null; - } - execInProgress = true; - setTimeout(function() { - if ( - $($('.scroll-locker').children()[0]).height() - 800 > e.detail - ) { - $('.scroll-locker').scrollTop(e.detail); - } else { - var scrollTop = $($('.scroll-locker').children()[0]).height(); - - $('.scroll-locker').animate({ scrollTop: scrollTop }, 175); - } - execInProgress = false; - }, 750); - }, - false - ); - } - // map sharing - var alreadyShared = getMapShares(); + var alreadyShared = main.getMapShares(); if (lastCompleted && alreadyShared.indexOf(lastCompleted) === -1) { $('div[id="' + lastCompleted + '"]') @@ -724,8 +247,8 @@ $(document).ready(function() { username + '&redirect_uri=http%3A%2F%2Ffreecodecamp%2Ecom%2Fmap'; - setMapShare(challengeBlockName); - main.ga('send', 'event', 'FB_LINK', 'SHARE', 'Facebook map share'); + main.setMapShare(challengeBlockName); + window.ga('send', 'event', 'FB_LINK', 'SHARE', 'Facebook map share'); window.location.href = link; }); }); diff --git a/client/plugin.js b/client/plugin.js index 7724b08fbf..2cc3c54904 100644 --- a/client/plugin.js +++ b/client/plugin.js @@ -13,22 +13,18 @@ function importScript(url, error) { function run(code, cb) { var err = null; - var result = { - input: code, - output: null, - type: null - }; + var result = {}; try { var codeExec = runHidden(code); result.type = typeof codeExec; result.output = stringify(codeExec); } catch (e) { - err = e; + err = e.mesage; } if (err) { - cb(err.message, null); + cb(err, null); } else { cb(null, result); } @@ -40,7 +36,7 @@ function run(code, cb) { // protects even the worker scope from being accessed function runHidden(code) { - /* eslint-disable */ + /* eslint-disable no-unused-vars */ var indexedDB = null; var location = null; var navigator = null; @@ -63,21 +59,15 @@ function runHidden(code) { var dump = null; var onoffline = null; var ononline = null; - /* eslint-enable */ + /* eslint-enable no-unused-vars */ var error = null; - error = importScript( - error, - 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min.js' - ); - error = importScript( 'https://cdnjs.cloudflare.com/ajax/libs/chai/2.2.0/chai.min.js' ); /* eslint-disable*/ - var expect = chai.expect; var assert = chai.assert; /* eslint-enable */ diff --git a/server/views/coursewares/showBonfire.jade b/server/views/coursewares/showBonfire.jade index 137e247cca..3357573bee 100644 --- a/server/views/coursewares/showBonfire.jade +++ b/server/views/coursewares/showBonfire.jade @@ -102,7 +102,7 @@ block content a.btn.btn-lg.btn-primary.btn-block(href='#', data-dismiss='modal', aria-hidden='true') Cancel include ../partials/challenge-modals script(type="text/javascript"). - var common = window.common = { init: [] }; + var common = window.common = window.common || { init: [] }; common.tests = !{JSON.stringify(tests)}; common.head = !{JSON.stringify(head)}; diff --git a/server/views/coursewares/showVideo.jade b/server/views/coursewares/showVideo.jade index 8f95c5c73b..ed06486f89 100644 --- a/server/views/coursewares/showVideo.jade +++ b/server/views/coursewares/showVideo.jade @@ -39,25 +39,6 @@ block content script(type="text/javascript"). - var controlEnterHandler = function(e) { - $('body').unbind('keydown'); - if (e.metaKey && e.keyCode === 13 || - e.ctrlKey && e.keyCode === 13) { - $('#complete-courseware-editorless-dialog').modal('show'); - } else { - $('body').bind('keydown', controlEnterHandler); - } - }; - var modalControlEnterHandler = function(e) { - $('#complete-courseware-editorless-dialog').unbind('keydown'); - if (e.metaKey && e.keyCode === 13 || - e.ctrlKey && e.keyCode === 13) { - $('#next-courseware-button').click(); - } else { - $('#complete-courseware-editorless-dialog').bind('keydown', modalControlEnterHandler); - } - }; - #complete-courseware-editorless-dialog.modal(tabindex='-1') .modal-dialog.animated.fadeIn.fast-animation .modal-content @@ -69,21 +50,48 @@ block content span.completion-icon.ion-checkmark-circled.text-primary if (user) a.btn.btn-lg.btn-primary.btn-block#next-courseware-button(name='_csrf', value=_csrf) I've completed this challenge (ctrl + enter) - script. - $('#complete-courseware-editorless-dialog').bind('keydown', modalControlEnterHandler); else a.animated.fadeIn.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) I've completed this challenge (ctrl + enter) - script. - $('body').bind('keydown', controlEnterHandler); include ../partials/challenge-modals script. - var common = window.common = { init: [] }; + var common = window.common || { init: [] }; common.challengeId = !{JSON.stringify(challengeId)}; common.challengeName = !{JSON.stringify(name)}; common.challengeType = !{JSON.stringify(challengeType)}; common.dashedName = !{JSON.stringify(dashedName)}; + common.init.push(function($) { + function controlEnterHandler(e) { + if ( + e.keyCode === 13 && + e.ctrlKey || + e.metaKey + ) { + $('body').unbind('keydown'); + $('#complete-courseware-editorless-dialog').modal('show'); + } + }; + + function modalControlEnterHandler(e) { + if ( + e.keyCode === 13 && + e.ctrlKey || + e.metaKey + ) { + $('#complete-courseware-editorless-dialog').unbind('keydown'); + $('#next-courseware-button').click(); + } + }; + if (!{JSON.stringify(user && user.username ? true : false)}) { + $('#complete-courseware-editorless-dialog').bind('keydown', modalControlEnterHandler); + } + $('body').bind('keydown', controlEnterHandler); + + $('#completed-courseware-editorless').on('click', function() { + $('#complete-courseware-editorless-dialog').modal('show'); + }); + }); document.addEventListener('gitter-sidecar-ready', function(e) { if (window.main) { diff --git a/server/views/coursewares/showZiplineOrBasejump.jade b/server/views/coursewares/showZiplineOrBasejump.jade index 9a2f3e0acc..54d67cb844 100644 --- a/server/views/coursewares/showZiplineOrBasejump.jade +++ b/server/views/coursewares/showZiplineOrBasejump.jade @@ -105,9 +105,14 @@ block content if (!!{ JSON.stringify(user ? true : false)}) { $('#complete-zipline-or-basejump-dialog').on('keydown', common.modalControlEnterHandler); } + + $('#completed-zipline-or-basejump').on('click', function() { + $('#complete-zipline-or-basejump-dialog').modal('show'); + }); }); + document.addEventListener('gitter-sidecar-ready', function(e) { var room = 'freecodecamp/help'; var title;