From a558777382a2cb3fb6773d22ea42c9e67f764573 Mon Sep 17 00:00:00 2001 From: Nathan Leniz Date: Mon, 2 Feb 2015 20:39:05 -0500 Subject: [PATCH] Cleaning up angular, preparing to make new editor framework for coursewares --- .../lib/coursewares/coursewaresFramework.js | 232 ++++++++++++++++++ public/js/main.js | 4 +- seed_data/bonfires.json | 2 +- 3 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 public/js/lib/coursewares/coursewaresFramework.js diff --git a/public/js/lib/coursewares/coursewaresFramework.js b/public/js/lib/coursewares/coursewaresFramework.js new file mode 100644 index 0000000000..344d1ac335 --- /dev/null +++ b/public/js/lib/coursewares/coursewaresFramework.js @@ -0,0 +1,232 @@ +/** + * Created by nathanleniz on 2/2/15. + */ + +var widgets = []; +var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), { + lineNumbers: true, + mode: "html-mixed", + theme: 'monokai', + runnable: true, + lint: true, + matchBrackets: true, + autoCloseBrackets: true, + scrollbarStyle: 'null', + lineWrapping: true, + gutters: ["CodeMirror-lint-markers"], + onKeyEvent: doLinting, + extraKeys : { + "Ctrl-Enter" : function() { + bonfireExecute(); + return false; + } + } +}); +var editor = myCodeMirror; +editor.setSize("100%", "auto"); + + +// Default value for editor if one isn't provided in (i.e. a challenge) +var nonChallengeValue = '/*Welcome to Bonfire, Free Code Camp\'s future CoderByte replacement.\n' + + 'Please feel free to use Bonfire as an in-browser playground and linting tool.\n' + + 'Note that you can also write tests using Chai.js by using the keywords assert and expect */\n\n' + + 'function test() {\n' + + ' assert(2 !== 3, "2 is not equal to 3");\n' + + ' return [1,2,3].map(function(elem) {\n' + + ' return elem * elem;\n' + + ' });\n' + + '}\n' + + 'expect(test()).to.be.a("array");\n\n' + + 'assert.deepEqual(test(), [1,4,9]);\n\n' + + 'var foo = test();\n' + + 'foo.should.be.a("array");\n\n' + + 'test();\n'; + +var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), { + lineNumbers: false, + mode: "text", + theme: 'monokai', + readOnly: 'nocursor', + lineWrapping: true +}); +codeOutput.setValue('/**\n' + +' * Your output will go here.\n' + ' * Console.log() -type statements\n' + +' * will appear in your browser\'s\n' + ' * DevTools JavaScript console.\n' + +' */'); +codeOutput.setSize("100%", "100%"); +var info = editor.getScrollInfo(); +var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top; +if (info.top + info.clientHeight < after) + editor.scrollTo(null, after - info.clientHeight + 3); + +var editorValue; + + +var challengeSeed = challengeSeed || null; +var tests = tests || []; +var challengeEntryPoint = challengeEntryPoint || null; + + +if (challengeSeed !== null) { + editorValue = challengeSeed + '\n\n' + challengeEntryPoint; +} else { + editorValue = nonChallengeValue; +} + + +myCodeMirror.setValue(editorValue); + +function doLinting () { + editor.operation(function () { + for (var i = 0; i < widgets.length; ++i) + editor.removeLineWidget(widgets[i]); + widgets.length = 0; + JSHINT(editor.getValue()); + for (var i = 0; i < JSHINT.errors.length; ++i) { + var err = JSHINT.errors[i]; + if (!err) continue; + var msg = document.createElement("div"); + var icon = msg.appendChild(document.createElement("span")); + icon.innerHTML = "!!"; + icon.className = "lint-error-icon"; + msg.appendChild(document.createTextNode(err.reason)); + msg.className = "lint-error"; + widgets.push(editor.addLineWidget(err.line - 1, msg, { + coverGutter: false, + noHScroll: true + })); + } + }); +}; + +$('#submitButton').on('click', function () { + bonfireExecute(); +}); + +function bonfireExecute() { + userTests= null; + $('#codeOutput').empty(); + var userJavaScript = myCodeMirror.getValue(); + userJavaScript = removeComments(userJavaScript); + userJavaScript = scrapeTests(userJavaScript); + // simple fix in case the user forgets to invoke their function + if (challengeEntryPoint) { + userJavaScript = challengeEntryPoint + ' ' + userJavaScript; + } + submit(userJavaScript, function(cls, message) { + if (cls) { + codeOutput.setValue(message.error); + runTests('Error', null); + } else { + codeOutput.setValue(message.output); + message.input = removeLogs(message.input); + runTests(null, message); + } + }); +} + + +var userTests; +var testSalt = Math.random(); + + +var scrapeTests = function(userJavaScript) { + + // insert tests from mongo + for (var i = 0; i < tests.length; i++) { + userJavaScript += '\n' + tests[i]; + } + + var counter = 0; + var regex = new RegExp(/(expect(\s+)?\(.*\;)|(assert(\s+)?\(.*\;)|(assert\.\w.*\;)|(.*\.should\..*\;)/); + var match = regex.exec(userJavaScript); + while (match != null) { + var replacement = '//' + counter + testSalt; + userJavaScript = userJavaScript.substring(0, match.index) + replacement + userJavaScript.substring(match.index + match[0].length); + + if (!userTests) { + userTests= []; + } + userTests.push({"text": match[0], "line": counter, "err": null}); + counter++; + match = regex.exec(userJavaScript); + } + + return userJavaScript; +}; + +function removeComments(userJavaScript) { + var regex = new RegExp(/(\/\*[^(\*\/)]*\*\/)|\/\/[^\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 test = userTests[i]; + var testDoc = document.createElement("div"); + if (test.err != null) { + $(testDoc) + .html("
" + test.text + "
" + test.err + "
") + .prependTo($('#testSuite')) + } else { + $(testDoc) + .html("
" + test.text + "
") + .appendTo($('#testSuite')); + } + }; +}; +var assert = chai.assert; +var expect = chai.expect; +var should = chai.should(); + +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 allTestsPassed = true; + pushed = false; + $('#testSuite').children().remove(); + if (err && userTests.length > 0) { + userTests= [{text:"Program Execution Failure", err: "No user tests were run."}]; + createTestDisplay(); + } else if (userTests) { + userTests.push(false); + pushed = true; + userTests.forEach(function(test, ix, arr){ + try { + if (test) { + console.log(); + var output = eval(reassembleTest(test, data)); + } + } catch(error) { + allTestsPassed = false; + arr[ix].err = error.name + ":" + error.message; + } finally { + if (!test) { + createTestDisplay(); + } + } + }); + + if (allTestsPassed) { + allTestsPassed = false; + showCompletion(); + } + + } +}; + +function showCompletion() { + $('#complete-bonfire-dialog').modal('show'); +} \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index 8c9b83aa31..cbc679a41e 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -158,7 +158,7 @@ profileValidation.directive('uniqueUsername', function($http) { } } }); -// TODO: FIX THIS + profileValidation.directive('existingUsername', function($http) { return { restrict: 'A', @@ -171,7 +171,7 @@ profileValidation.directive('existingUsername', function($http) { ngModel.$setPristine(); } if (element.val()) { - $http.get("/api/checkExistingUsername/" + element.val() + ' ').success(function (data) { + $http.get("/api/checkExistingUsername/" + element.val()).success(function (data) { if (element.val() == scope.existingUsername) { ngModel.$setValidity('exists', false); } else if (data) { diff --git a/seed_data/bonfires.json b/seed_data/bonfires.json index b2d1181b82..d26ceda17b 100644 --- a/seed_data/bonfires.json +++ b/seed_data/bonfires.json @@ -59,7 +59,7 @@ "difficulty": "1.03", "description": [ "Return 'true' if a given string is a palindrome.", - "A palindrome is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation and case.", + "A palindrome is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation, case, and spacing.", "You'll need to remove punctuation and turn everything lower case in order to check for palindromes.", "We'll pass strings with varying formats, such as \"racecar\", \"RaceCar\", and \"race CAR\" among others.", "Return true if the string is a palindrome. Otherwise, return false."