diff --git a/bower.json b/bower.json index fe3c9c6a30..ad1dbf82ea 100644 --- a/bower.json +++ b/bower.json @@ -25,7 +25,7 @@ "font-awesome": "~4.3.0", "moment": "~2.10.2", "angular-bootstrap": "~0.13.0", - "jshint": "~2.7.0", + "jshint": "~2.9.0", "lightbox2": "~2.8.1", "rxjs": "~4.0.6", "CodeMirror": "~5.8.0", diff --git a/client/commonFramework.js b/client/commonFramework.js index b5a80bc2f0..e717d4d012 100644 --- a/client/commonFramework.js +++ b/client/commonFramework.js @@ -1,371 +1,5 @@ -var common = (function() { - // common namespace - // all classes should be stored here - var common = window.common || { - // init is an array of functions that are - // called at the beginning of dom ready - init: [] - }; +var common = window.common || { init: [] }; - common.challengeName = common.challengeName || window.challenge_Name || ''; - - common.challengeType = common.challengeType || window.challengeType || 0; - - common.challengeId = common.challengeId || window.challenge_Id; - - common.challengeSeed = common.challengeSeed || window.challengeSeed || []; - - common.head = common.head || ''; - common.tail = common.tail || ''; - - common.arrayToNewLineString = function arrayToNewLineString(seedData) { - seedData = Array.isArray(seedData) ? seedData : [seedData]; - return seedData.reduce(function(seed, line) { - return '' + seed + line + '\n'; - }, ''); - }; - - common.seed = common.arrayToNewLineString(common.challengeSeed); - - common.replaceScriptTags = function replaceScriptTags(value) { - return value - .replace(/'); - }; - - common.replaceFormActionAttr = function replaceFormAction(value) { - return value.replace(/]*>/, function(val) { - return val.replace(/action(\s*?)=/, 'fccfaa$1='); - }); - }; - - common.replaceFccfaaAttr = function replaceFccfaaAttr(value) { - return value.replace(/]*>/, function(val) { - return val.replace(/fccfaa(\s*?)=/, 'action$1='); - }); - }; - - return common; -})(); - -// store code in the URL -common.codeUri = (function(common, encode, decode, location, history) { - var replaceScriptTags = common.replaceScriptTags; - var replaceSafeTags = common.replaceSafeTags; - var replaceFormActionAttr = common.replaceFormActionAttr; - var replaceFccfaaAttr = common.replaceFccfaaAttr; - - function encodeFcc(val) { - return replaceScriptTags(replaceFormActionAttr(val)); - } - - function decodeFcc(val) { - return replaceSafeTags(replaceFccfaaAttr(val)); - } - - var codeUri = { - encode: function(code) { - return encode(code); - }, - decode: function(code) { - try { - return decode(code); - } catch (ignore) { - return null; - } - }, - isInQuery: function(query) { - var decoded = codeUri.decode(query); - if (!decoded || typeof decoded.split !== 'function') { - return false; - } - return decoded - .split('?') - .splice(1) - .reduce(function(found, param) { - var key = param.split('=')[0]; - if (key === 'solution') { - return true; - } - return found; - }, false); - }, - isAlive: function() { - return codeUri.enabled && - codeUri.isInQuery(location.search) || - codeUri.isInQuery(location.hash); - }, - parse: function() { - if (!codeUri.enabled) { - return null; - } - var query; - if (location.search && codeUri.isInQuery(location.search)) { - query = location.search.replace(/^\?/, ''); - if (history && typeof history.replaceState === 'function') { - history.replaceState( - history.state, - null, - location.href.split('?')[0] - ); - location.hash = '#?' + encodeFcc(query); - } - } else { - query = location.hash.replace(/^\#\?/, ''); - } - if (!query) { - return null; - } - - return query - .split('&') - .reduce(function(solution, param) { - var key = param.split('=')[0]; - var value = param.split('=')[1]; - if (key === 'solution') { - return decodeFcc(codeUri.decode(value || '')); - } - return solution; - }, null); - }, - querify: function(solution) { - if (!codeUri.enabled) { - return null; - } - if (history && typeof history.replaceState === 'function') { - history.replaceState( - history.state, - null, - '?solution=' + codeUri.encode(encodeFcc(solution)) - ); - } else { - location.hash = '?solution=' + - codeUri.encode(encodeFcc(solution)); - } - - return solution; - }, - enabled: true - }; - - common.init.push(function() { - codeUri.parse(); - }); - - return codeUri; -}(common, encodeURIComponent, decodeURIComponent, location, history)); - -// codeStorage -common.codeStorageFactory = (function($, localStorage, codeUri) { - - 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 - ); - } - - return codeStorageFactory; -}($, localStorage, common.codeUri)); - -common.codeOutput = (function(CodeMirror, document, challengeType) { - if (!CodeMirror) { - return {}; - } - if ( - challengeType === '0' || - challengeType === '7' - ) { - return {}; - } - 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%'); - - return codeOutput; -}(window.CodeMirror, window.document, common.challengeType || 0)); - -common.sandBox = (function(jailed, codeOutput) { - if (!jailed) { - return {}; - } - var plugin = null; - - var sandBox = { - }; - - var printCallback; - - // sends the input to the plugin for evaluation - function submit(code, callback) { - printCallback = callback; - - // postpone the evaluation until the plugin is initialized - plugin.whenConnected(function() { - if (requests === 0) { - startLoading(); - } - - requests++; - plugin.remote.run(code); - }); - } - - // puts the message on the terminal - var print = function(cls, msg) { - printCallback(cls, msg); - }; - - - // will restart the plugin if it does not respond - var disconnectTimeout = null; - var startLoading = function() { - disconnectTimeout = setTimeout(disconnect, 3000); - }; - - var endLoading = function() { - clearTimeout(disconnectTimeout); - }; - - var disconnect = function() { - plugin.disconnect(); - }; - - - // interface provided to the plugin - var api = { - output: function(data) { - endLoading(); - // print('input', data.input); - - if (data.error) { - print('Error', data); - reset(); - } else { - print(null, data); - reset(); - } - } - }; - - - // obtaining absolute path of this script - var scripts = document.getElementsByTagName('script'); - var path = scripts[scripts.length - 1].src - .split('?')[0] - .split('/') - .slice(0, -1) - .join('/') + '/'; - - var requests; - - // (re)initializes the plugin - var reset = function() { - requests = 0; - plugin = new jailed.Plugin(path + 'plugin.js', api); - plugin.whenDisconnected( function() { - // give some time to handle the last responce - setTimeout( function() { - endLoading(); - console.log('resetting on fatal plugin error'); - - if (common.challengeType === 0) { - codeOutput.setValue( - 'Sorry, your code is either too slow, has a fatal error, ' + - 'or contains an infinite loop.' - ); - } - reset(); - }, 10); - }); - }; - reset(); - sandBox.submit = submit; - return sandBox; -}(window.jailed, common.codeOutput)); var BDDregex = new RegExp( '(expect(\\s+)?\\(.*\\;)|' + @@ -374,485 +8,105 @@ var BDDregex = new RegExp( '(.*\\.should\\..*\\;)/' ); -var isInitRun = false; -var initPreview = true; - -var editor = (function(CodeMirror, emmetCodeMirror, common) { - var codeStorageFactory = common.codeStorageFactory; - if (!CodeMirror) { - return {}; - } - - 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'); - - var codeStorage = common.codeStorage = - codeStorageFactory(editor, common.challengeName); - - editor.on('keyup', function() { - clearTimeout(codeStorage.updateTimeoutId); - codeStorage.updateTimeoutId = setTimeout( - codeStorage.updateStorage.bind(codeStorage), - codeStorage.updateWait - ); - }); - - // Initialize CodeMirror editor with a nice html5 canvas demo. - editor.on('keyup', function() { - clearTimeout(delay); - delay = setTimeout(updatePreview, 300); - }); - - editor.setOption('extraKeys', { - Tab: function(cm) { - if (cm.somethingSelected()) { - cm.indentSelection('add'); - } else { - var spaces = Array(cm.getOption('indentUnit') + 1).join(' '); - cm.replaceSelection(spaces); - } - }, - 'Shift-Tab': function(cm) { - if (cm.somethingSelected()) { - cm.indentSelection('subtract'); - } else { - var spaces = Array(cm.getOption('indentUnit') + 1).join(' '); - cm.replaceSelection(spaces); - } - }, - 'Ctrl-Enter': function() { - isInitRun = false; - bonfireExecute(true); - return false; - }, - 'Cmd-Enter': function() { - isInitRun = false; - bonfireExecute(true); - return false; - } - }); - - - 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); - } - - if (emmetCodeMirror) { - emmetCodeMirror( - editor, - { - 'Cmd-E': 'emmet.expand_abbreviation', - Tab: 'emmet.expand_abbreviation_with_tab', - Enter: 'emmet.insert_formatted_line_break_only' - } - ); - } - common.init.push(function() { - var editorValue; - if (common.codeUri.isAlive()) { - editorValue = common.codeUri.parse(); - } else { - editorValue = codeStorage.isAlive() ? - codeStorage.getStoredValue() : - common.seed; - } - - editor.setValue(common.replaceSafeTags(editorValue)); - editor.refresh(); - }); - - return editor; -}(window.CodeMirror, window.emmetCodeMirror, common)); - - -var tests = common.tests || []; - var libraryIncludes = - "' + - "" + - "" + - "" + "" + "" + "" + - '' + - ''; + ''; -var editorValueForIFrame; var iFrameScript = ""; -var delay; function workerError(error) { var display = $('.runTimeError'); var housing = $('#testSuite'); - if (display.html() !== error) { - display.remove(); - housing.prepend( - '
' + - error - .replace(/j\$/gi, '$') - .replace(/jdocument/gi, 'document') - .replace(/jjQuery/gi, 'jQuery') + - '
' - ); - - display.hide().fadeIn(function() { - setTimeout(function() { - display.fadeOut(function() { - display.remove(); - }); - }, 1000); - }); + if (display.html() === error) { + return null; } -} -function scopejQuery(str) { - return str - .replace(/\$/gi, 'j$') - .replace(/document/gi, 'jdocument') - .replace(/jQuery/gi, 'jjQuery'); -} + display.remove(); -function safeHTMLRun(test) { - var codeStorage = common.codeStorage; - if (common.challengeType === '0') { - var previewFrame = document.getElementById('preview'); + housing.prepend(` +
+ + ${common.unScopeJQuery(error)} + +
+ `); - var preview = previewFrame.contentDocument || - previewFrame.contentWindow.document; - - if (editor.getValue().match(/\/gi) !== null) { - var s = editor - .getValue() - .split(/\<\s?script\s?\>/gi)[1] - .split(/\<\s?\/\s?script\s?\>/gi)[0]; - - // add feuxQuery - s = 'var document = ""; ' + - 'var $ = function() {' + - 'return new function() { this.add=function() { return this; };' + - 'this.addBack=function() {return this; };' + - 'this.addClass=function() {return this; };' + - 'this.after=function() {return this; };' + - 'this.ajaxComplete=function() {return this; };' + - 'this.ajaxError=function() {return this; };' + - 'this.ajaxSend=function() {return this; };' + - 'this.ajaxStart=function() {return this; };' + - 'this.ajaxStop=function() {return this; };' + - 'this.ajaxSuccess=function() {return this; };' + - 'this.andSelf=function() {return this; };' + - 'this.animate=function() {return this; };' + - 'this.append=function() {return this; };' + - 'this.appendTo=function() {return this; };' + - 'this.attr=function() {return this; };' + - 'this.before=function() {return this; };' + - 'this.bind=function() {return this; };' + - 'this.blur=function() {return this; };' + - 'this.callbacksadd=function() {return this; };' + - 'this.callbacksdisable=function() {return this; };' + - 'this.callbacksdisabled=function() {return this; };' + - 'this.callbacksempty=function() {return this; };' + - 'this.callbacksfire=function() {return this; };' + - 'this.callbacksfired=function() {return this; };' + - 'this.callbacksfireWith=function() {return this; };' + - 'this.callbackshas=function() {return this; };' + - 'this.callbackslock=function() {return this; };' + - 'this.callbackslocked=function() {return this; };' + - 'this.callbacksremove=function() {return this; };' + - 'this.change=function() {return this; };' + - 'this.children=function() {return this; };' + - 'this.clearQueue=function() {return this; };' + - 'this.click=function() {return this; };' + - 'this.clone=function() {return this; };' + - 'this.closest=function() {return this; };' + - 'this.contents=function() {return this; };' + - 'this.context=function() {return this; };' + - 'this.css=function() {return this; };' + - 'this.data=function() {return this; };' + - 'this.dblclick=function() {return this; };' + - 'this.delay=function() {return this; };' + - 'this.delegate=function() {return this; };' + - 'this.dequeue=function() {return this; };' + - 'this.detach=function() {return this; };' + - 'this.die=function() {return this; };' + - 'this.each=function() {return this; };' + - 'this.empty=function() {return this; };' + - 'this.end=function() {return this; };' + - 'this.eq=function() {return this; };' + - 'this.error=function() {return this; };' + - 'this.fadeIn=function() {return this; };' + - 'this.fadeOut=function() {return this; };' + - 'this.fadeTo=function() {return this; };' + - 'this.fadeToggle=function() {return this; };' + - 'this.filter=function() {return this; };' + - 'this.find=function() {return this; };' + - 'this.finish=function() {return this; };' + - 'this.first=function() {return this; };' + - 'this.focus=function() {return this; };' + - 'this.focusin=function() {return this; };' + - 'this.focusout=function() {return this; };' + - 'this.get=function() {return this; };' + - 'this.has=function() {return this; };' + - 'this.hasClass=function() {return this; };' + - 'this.height=function() {return this; };' + - 'this.hide=function() {return this; };' + - 'this.hover=function() {return this; };' + - 'this.html=function() {return this; };' + - 'this.index=function() {return this; };' + - 'this.innerHeight=function() {return this; };' + - 'this.innerWidth=function() {return this; };' + - 'this.insertAfter=function() {return this; };' + - 'this.insertBefore=function() {return this; };' + - 'this.is=function() {return this; };' + - 'this.jQuery=function() {return this; };' + - 'this.jquery=function() {return this; };' + - 'this.keydown=function() {return this; };' + - 'this.keypress=function() {return this; };' + - 'this.keyup=function() {return this; };' + - 'this.last=function() {return this; };' + - 'this.length=function() {return this; };' + - 'this.live=function() {return this; };' + - 'this.load=function() {return this; };' + - 'this.load=function() {return this; };' + - 'this.map=function() {return this; };' + - 'this.mousedown=function() {return this; };' + - 'this.mouseenter=function() {return this; };' + - 'this.mouseleave=function() {return this; };' + - 'this.mousemove=function() {return this; };' + - 'this.mouseout=function() {return this; };' + - 'this.mouseover=function() {return this; };' + - 'this.mouseup=function() {return this; };' + - 'this.next=function() {return this; };' + - 'this.nextAll=function() {return this; };' + - 'this.nextUntil=function() {return this; };' + - 'this.not=function() {return this; };' + - 'this.off=function() {return this; };' + - 'this.offset=function() {return this; };' + - 'this.offsetParent=function() {return this; };' + - 'this.on=function() {return this; };' + - 'this.one=function() {return this; };' + - 'this.outerHeight=function() {return this; };' + - 'this.outerWidth=function() {return this; };' + - 'this.parent=function() {return this; };' + - 'this.parents=function() {return this; };' + - 'this.parentsUntil=function() {return this; };' + - 'this.position=function() {return this; };' + - 'this.prepend=function() {return this; };' + - 'this.prependTo=function() {return this; };' + - 'this.prev=function() {return this; };' + - 'this.prevAll=function() {return this; };' + - 'this.prevUntil=function() {return this; };' + - 'this.promise=function() {return this; };' + - 'this.prop=function() {return this; };' + - 'this.pushStack=function() {return this; };' + - 'this.queue=function() {return this; };' + - 'this.ready=function() {return this; };' + - 'this.remove=function() {return this; };' + - 'this.removeAttr=function() {return this; };' + - 'this.removeClass=function() {return this; };' + - 'this.removeData=function() {return this; };' + - 'this.removeProp=function() {return this; };' + - 'this.replaceAll=function() {return this; };' + - 'this.replaceWith=function() {return this; };' + - 'this.resize=function() {return this; };' + - 'this.scroll=function() {return this; };' + - 'this.scrollLeft=function() {return this; };' + - 'this.scrollTop=function() {return this; };' + - 'this.select=function() {return this; };' + - 'this.selector=function() {return this; };' + - 'this.serialize=function() {return this; };' + - 'this.serializeArray=function() {return this; };' + - 'this.show=function() {return this; };' + - 'this.siblings=function() {return this; };' + - 'this.size=function() {return this; };' + - 'this.slice=function() {return this; };' + - 'this.slideDown=function() {return this; };' + - 'this.slideToggle=function() {return this; };' + - 'this.slideUp=function() {return this; };' + - 'this.stop=function() {return this; };' + - 'this.submit=function() {return this; };' + - 'this.text=function() {return this; };' + - 'this.toArray=function() {return this; };' + - 'this.toggle=function() {return this; };' + - 'this.toggle=function() {return this; };' + - 'this.toggleClass=function() {return this; };' + - 'this.trigger=function() {return this; };' + - 'this.triggerHandler=function() {return this; };' + - 'this.unbind=function() {return this; };' + - 'this.undelegate=function() {return this; };' + - 'this.unload=function() {return this; };' + - 'this.unwrap=function() {return this; };' + - 'this.val=function() {return this; };' + - 'this.width=function() {return this; };' + - 'this.wrap=function() {return this; };' + - 'this.wrapAll=function() {return this; };' + - 'this.wrapInner=function() {return this; };};};' + - '$.ajax=function() {return $;};' + - '$.ajaxPrefilter=function() {return $; };' + - '$.ajaxSetup=function() {return $; };' + - '$.ajaxTransport=function() {return $; };' + - '$.boxModel=function() {return $; };' + - '$.browser=function() {return $; };' + - '$.Callbacks=function() {return $; };' + - '$.contains=function() {return $; };' + - '$.cssHooks=function() {return $; };' + - '$.cssNumber=function() {return $; };' + - '$.data=function() {return $; };' + - '$.Deferred=function() {return $; };' + - '$.dequeue=function() {return $; };' + - '$.each=function() {return $; };' + - '$.error=function() {return $; };' + - '$.extend=function() {return $; };' + - '$.fnextend=function() {return $; };' + - '$.fxinterval=function() {return $; };' + - '$.fxoff=function() {return $; };' + - '$.get=function() {return $; };' + - '$.getJSON=function() {return $; };' + - '$.getScript=function() {return $; };' + - '$.globalEval=function() {return $; };' + - '$.grep=function() {return $; };' + - '$.hasData=function() {return $; };' + - '$.holdReady=function() {return $; };' + - '$.inArray=function() {return $; };' + - '$.isArray=function() {return $; };' + - '$.isEmptyObject=function() {return $; };' + - '$.isFunction=function() {return $; };' + - '$.isNumeric=function() {return $; };' + - '$.isPlainObject=function() {return $; };' + - '$.isWindow=function() {return $; };' + - '$.isXMLDoc=function() {return $; };' + - '$.makeArray=function() {return $; };' + - '$.map=function() {return $; };' + - '$.merge=function() {return $; };' + - '$.noConflict=function() {return $; };' + - '$.noop=function() {return $; };' + - '$.now=function() {return $; };' + - '$.param=function() {return $; };' + - '$.parseHTML=function() {return $; };' + - '$.parseJSON=function() {return $; };' + - '$.parseXML=function() {return $; };' + - '$.post=function() {return $; };' + - '$.proxy=function() {return $; };' + - '$.queue=function() {return $; };' + - '$.removeData=function() {return $; };' + - '$.sub=function() {return $; };' + - '$.support=function() {return $; };' + - '$.trim=function() {return $; };' + - '$.type=function() {return $; };' + - '$.unique=function() {return $; };' + - '$.when=function() {return $; };' + - '$.always=function() {return $; };' + - '$.done=function() {return $; };' + - '$.fail=function() {return $; };' + - '$.isRejected=function() {return $; };' + - '$.isResolved=function() {return $; };' + - '$.notify=function() {return $; };' + - '$.notifyWith=function() {return $; };' + - '$.pipe=function() {return $; };' + - '$.progress=function() {return $; };' + - '$.promise=function() {return $; };' + - '$.reject=function() {return $; };' + - '$.rejectWith=function() {return $; };' + - '$.resolve=function() {return $; };' + - '$.resolveWith=function() {return $; };' + - '$.state=function() {return $; };' + - '$.then=function() {return $; };' + - '$.currentTarget=function() {return $; };' + - '$.data=function() {return $; };' + - '$.delegateTarget=function() {return $; };' + - '$.isDefaultPrevented=function() {return $; };' + - '$.isImmediatePropagationStopped=function() {return $; };' + - '$.isPropagationStopped=function() {return $; };' + - '$.metaKey=function() {return $; };' + - '$.namespace=function() {return $; };' + - '$.pageX=function() {return $; };' + - '$.pageY=function() {return $; };' + - '$.preventDefault=function() {return $; };' + - '$.relatedTarget=function() {return $; };' + - '$.result=function() {return $; };' + - '$.stopImmediatePropagation=function() {return $; };' + - '$.stopPropagation=function() {return $; };' + - '$.target=function() {return $; };' + - '$.timeStamp=function() {return $; };' + - '$.type=function() {return $; };' + - '$.which=function() {return $; };' + - '' + s; - - // add spoofigator - - s = ' var navigator = ' + - 'function() {' + - ' this.geolocation=function() {' + - ' this.getCurrentPosition=function() {' + - ' this.coords = {latitude: "", longitude: ""};' + - ' return this;' + - ' };' + - ' return this;' + - ' };' + - ' return this;' + - '};' + s; - - common.sandBox.submit(scopejQuery(s), function(cls, message) { - if (cls) { - console.log(message.error); - workerError(message.error); - } else if (test) { - preview.open(); - preview.write(libraryIncludes + editor.getValue() + iFrameScript); - codeStorage.updateStorage(); - preview.close(); - } else { - preview.open(); - preview.write(libraryIncludes + editor.getValue()); - codeStorage.updateStorage(); - preview.close(); - } + display.hide().fadeIn(function() { + setTimeout(function() { + display.fadeOut(function() { + display.remove(); }); - } else if (test) { - preview.open(); - preview.write(libraryIncludes + editor.getValue() + iFrameScript); - codeStorage.updateStorage(); - preview.close(); - } else { - preview.open(); - preview.write(libraryIncludes + editor.getValue()); - codeStorage.updateStorage(); - preview.close(); - } - } + }, 1000); + }); } -function updatePreview() { - editorValueForIFrame = editor.getValue(); - var failedCommentTest = false; - var openingComments = editorValueForIFrame.match(/\<\!\-\-/gi); - var closingComments = editorValueForIFrame.match(/\-\-\>/gi); +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 && ( @@ -860,28 +114,24 @@ function updatePreview() { openingComments.length > closingComments.length ) ) { - failedCommentTest = true; + common.editor.setValue(editorValue + '-->'); + editorValue = editorValue + '-->'; } - if (failedCommentTest) { - editor.setValue(editor.getValue() + '-->'); - editorValueForIFrame = editorValueForIFrame + '-->'; - } - if (!editor.getValue().match(/\$\s*?\(\s*?\$\s*?\)/gi)) { - safeHTMLRun(false); + if (!editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi)) { + common.safeHTMLRun(false); } else { workerError('Unsafe $($)'); } -} +}; -if (common.challengeType === '0') { - setTimeout(updatePreview, 300); -} +common.init.push(() => { + if (common.challengeType === '0') { + common.updatePreview(false); + } +}); -/** - * "post" methods - */ /* eslint-disable no-unused-vars */ var testResults = []; @@ -889,15 +139,18 @@ var postSuccess = function(data) { /* eslint-enable no-unused-vars */ var testDoc = document.createElement('div'); - $(testDoc).html( - "
" + - "
" + - "
" + - JSON.parse(data) + - '
' - ); + $(testDoc).html(` +
+
+ +
+
+ ${JSON.parse(data)} +
+ `); $('#testSuite').append(testDoc); + testSuccess(); }; @@ -906,13 +159,15 @@ var postError = function(data) { /* eslint-enable no-unused-vars */ var testDoc = document.createElement('div'); - $(testDoc).html( - "
" + - "
" + - "
" + - JSON.parse(data) + - '
' - ); + $(testDoc).html(` +
+
+ +
+
+ ${JSON.parse(data)} +
+ `); $('#testSuite').append(testDoc); }; @@ -921,15 +176,17 @@ var goodTests = 0; var testSuccess = function() { goodTests++; // test successful run show completion - if (goodTests === tests.length) { + 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) { + 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(); @@ -940,21 +197,17 @@ function ctrlEnterClickHandler(e) { } function showCompletion() { - if (isInitRun) { - isInitRun = false; - return; - } - var time = Math.floor(Date.now()) - window.started; - ga( + window.ga( 'send', 'event', 'Challenge', 'solved', - common.challengeName + ', Time: ' + time + ', Attempts: ' + attempts + common.challengeName + ', Time: ' + time + ', Attempts: ' + 0 ); - var bonfireSolution = editor.getValue(); + + var bonfireSolution = common.editor.getValue(); var didCompleteWith = $('#completed-with').val() || null; $('#complete-courseware-dialog').modal('show'); @@ -1004,45 +257,30 @@ function showCompletion() { }); } -/* eslint-disable no-unused-vars */ -var resetEditor = function resetEditor() { -/* eslint-enable no-unused-vars */ - - editor.setValue(common.replaceSafeTags(common.seed)); +common.resetEditor = function resetEditor() { + common.editor.setValue(common.replaceSafeTags(common.seed)); $('#testSuite').empty(); - bonfireExecute(true); + common.executeChallenge(true); common.codeStorage.updateStorage(); }; -var attempts = 0; -if (attempts) { - attempts = 0; -} - -var userTests; -var testSalt = Math.random(); - -var scrapeTests = function(userJavaScript) { +common.addTestsToString = function(userJavaScript, userTests = []) { // insert tests from mongo - for (var i = 0; i < tests.length; i++) { - userJavaScript += '\n' + tests[i]; + 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 + testSalt; + var replacement = '//' + counter + common.salt; userJavaScript = userJavaScript.substring(0, match.index) + replacement + userJavaScript.substring(match.index + match[0].length); - if (!userTests) { - userTests = []; - } - userTests.push({ 'text': match[0], 'line': counter, @@ -1468,9 +706,10 @@ function bonfireExecute(shouldTest) { }, 1000); } -$('#submitButton').on('click', function() { - isInitRun = false; - bonfireExecute(true); +common.init($ => { + $('#submitButton').on('click', function() { + common.executeChallenge(true); + }); }); $(document).ready(function() { @@ -1490,13 +729,9 @@ $(document).ready(function() { }); var $preview = $('#preview'); - isInitRun = true; - if (typeof $preview.html() !== 'undefined') { $preview.load(function() { - if (initPreview) { - bonfireExecute(true); - } + common.executeChallenge(true); }); } else if ( common.challengeType !== '2' && @@ -1504,7 +739,7 @@ $(document).ready(function() { common.challengeType !== '4' && common.challengeType !== '7' ) { - bonfireExecute(true); + common.executeChallenge(true); } }); diff --git a/client/commonFramework/codeStorage.js b/client/commonFramework/codeStorage.js new file mode 100644 index 0000000000..5753c83193 --- /dev/null +++ b/client/commonFramework/codeStorage.js @@ -0,0 +1,82 @@ +// 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/codeUri.js b/client/commonFramework/codeUri.js new file mode 100644 index 0000000000..a0694b892b --- /dev/null +++ b/client/commonFramework/codeUri.js @@ -0,0 +1,118 @@ +// store code in the URL +window.common = (function(global) { + const { + encodeURIComponent: encode, + decodeURIComponent: decode, + location, + history, + common = { init: [] } + } = global; + + const { + replaceScriptTags, + replaceSafeTags, + replaceFormActionAttr, + replaceFccfaaAttr + } = common; + + function encodeFcc(val) { + return replaceScriptTags(replaceFormActionAttr(val)); + } + + function decodeFcc(val) { + return replaceSafeTags(replaceFccfaaAttr(val)); + } + + var codeUri = { + encode: function(code) { + return encode(code); + }, + decode: function(code) { + try { + return decode(code); + } catch (ignore) { + return null; + } + }, + isInQuery: function(query) { + var decoded = codeUri.decode(query); + if (!decoded || typeof decoded.split !== 'function') { + return false; + } + return decoded + .split('?') + .splice(1) + .reduce(function(found, param) { + var key = param.split('=')[0]; + if (key === 'solution') { + return true; + } + return found; + }, false); + }, + isAlive: function() { + return codeUri.enabled && + codeUri.isInQuery(location.search) || + codeUri.isInQuery(location.hash); + }, + parse: function() { + if (!codeUri.enabled) { + return null; + } + var query; + if (location.search && codeUri.isInQuery(location.search)) { + query = location.search.replace(/^\?/, ''); + if (history && typeof history.replaceState === 'function') { + history.replaceState( + history.state, + null, + location.href.split('?')[0] + ); + location.hash = '#?' + encodeFcc(query); + } + } else { + query = location.hash.replace(/^\#\?/, ''); + } + if (!query) { + return null; + } + + return query + .split('&') + .reduce(function(solution, param) { + var key = param.split('=')[0]; + var value = param.split('=')[1]; + if (key === 'solution') { + return decodeFcc(codeUri.decode(value || '')); + } + return solution; + }, null); + }, + querify: function(solution) { + if (!codeUri.enabled) { + return null; + } + if (history && typeof history.replaceState === 'function') { + history.replaceState( + history.state, + null, + '?solution=' + codeUri.encode(encodeFcc(solution)) + ); + } else { + location.hash = '?solution=' + + codeUri.encode(encodeFcc(solution)); + } + + return solution; + }, + enabled: true + }; + + common.init.push(function() { + codeUri.parse(); + }); + + common.codeUri = codeUri; + + return common; +}(window)); diff --git a/client/commonFramework/createEditor.js b/client/commonFramework/createEditor.js new file mode 100644 index 0000000000..2a3c65b698 --- /dev/null +++ b/client/commonFramework/createEditor.js @@ -0,0 +1,113 @@ +window.common = (function(global) { + const { + CodeMirror, + emmetCodeMirror, + common = { init: [] } + } = global; + + if (!CodeMirror) { + return {}; + } + + 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'] + }); + + editor.setSize('100%', 'auto'); + + var codeStorage = common.codeStorage = + codeStorageFactory(editor, common.challengeName); + + editor.on('keyup', function() { + clearTimeout(codeStorage.updateTimeoutId); + codeStorage.updateTimeoutId = setTimeout( + codeStorage.updateStorage.bind(codeStorage), + codeStorage.updateWait + ); + }); + + // Initialize CodeMirror editor with a nice html5 canvas demo. + editor.on('keyup', function() { + clearTimeout(delay); + delay = setTimeout(common.updatePreview, 300); + }); + + editor.setOption('extraKeys', { + Tab: function(cm) { + if (cm.somethingSelected()) { + cm.indentSelection('add'); + } else { + var spaces = Array(cm.getOption('indentUnit') + 1).join(' '); + cm.replaceSelection(spaces); + } + }, + 'Shift-Tab': function(cm) { + if (cm.somethingSelected()) { + cm.indentSelection('subtract'); + } else { + var spaces = Array(cm.getOption('indentUnit') + 1).join(' '); + cm.replaceSelection(spaces); + } + }, + 'Ctrl-Enter': function() { + common.executeChallenge(true); + return false; + }, + 'Cmd-Enter': function() { + common.executeChallenge(true); + return false; + } + }); + + + 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); + } + + if (emmetCodeMirror) { + emmetCodeMirror( + editor, + { + 'Cmd-E': 'emmet.expand_abbreviation', + Tab: 'emmet.expand_abbreviation_with_tab', + Enter: 'emmet.insert_formatted_line_break_only' + } + ); + } + common.init.push(function() { + var editorValue; + if (common.codeUri.isAlive()) { + editorValue = common.codeUri.parse(); + } else { + editorValue = codeStorage.isAlive() ? + codeStorage.getStoredValue() : + common.seed; + } + + editor.setValue(common.replaceSafeTags(editorValue)); + editor.refresh(); + }); + + common.editor = editor; + + return common; +}(window)); diff --git a/client/commonFramework/detectLoops.js b/client/commonFramework/detectLoops.js new file mode 100644 index 0000000000..2f67088801 --- /dev/null +++ b/client/commonFramework/detectLoops.js @@ -0,0 +1,57 @@ +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/displayTestResults.js new file mode 100644 index 0000000000..9b7f84a58c --- /dev/null +++ b/client/commonFramework/displayTestResults.js @@ -0,0 +1,28 @@ +window.common = (function({ $, common = { init: [] }}) { + + common.displayTestResults = function displayTestResults(data = []) { + data.forEach(({ err = false, text = '' }) => { + var iconClass = err ? + '"ion-checkmark-circled big-success-icon"' : + '"ion-close-circled big-error-icon"'; + + $('#testSuite').children().remove(); + $('
').html(` +
+
+ +
+
+ ${text.split('message: ').pop().replace(/\'\);/g, '')} +
+
+
+ `) + .appendTo($('#testSuite')); + }); + + return data; + }; + + return common; +}(window)); diff --git a/client/commonFramework/executeChallenge.js b/client/commonFramework/executeChallenge.js new file mode 100644 index 0000000000..113bfbd6c9 --- /dev/null +++ b/client/commonFramework/executeChallenge.js @@ -0,0 +1,94 @@ +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/outputDisplay.js b/client/commonFramework/outputDisplay.js new file mode 100644 index 0000000000..69f7d055a6 --- /dev/null +++ b/client/commonFramework/outputDisplay.js @@ -0,0 +1,44 @@ +window.common = (function(global) { + const { + CodeMirror, + document: doc, + common = { init: [] } + } = global; + + const { challengeType = '0' } = common; + + if (!CodeMirror) { + return {}; + } + + if ( + challengeType === '0' || + challengeType === '7' + ) { + return {}; + } + + common.codeOutput = CodeMirror.fromTextArea( + doc.getElementById('codeOutput'), + { + lineNumbers: false, + mode: 'text', + theme: 'monokai', + readOnly: 'nocursor', + lineWrapping: true + } + ); + + common.codeOutput.setValue(` + /** + * Your output will go here. + * Console.log() -type statements + * will appear in your browser\'s + * DevTools JavaScript console. + */' + `); + + common.codeOutput.setSize('100%', '100%'); + + return common; +}(window)); diff --git a/client/commonFramework/runTests.js b/client/commonFramework/runTests.js new file mode 100644 index 0000000000..93703552a1 --- /dev/null +++ b/client/commonFramework/runTests.js @@ -0,0 +1,43 @@ +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/stepChallenge.js b/client/commonFramework/stepChallenge.js new file mode 100644 index 0000000000..6b689ffc15 --- /dev/null +++ b/client/commonFramework/stepChallenge.js @@ -0,0 +1,202 @@ +window.common = (function({ $, common = { init: [] }}) { + const stepClass = '.challenge-step'; + const prevBtnClass = '.challenge-step-btn-prev'; + const nextBtnClass = '.challenge-step-btn-next'; + const actionBtnClass = '.challenge-step-btn-action'; + const finishBtnClass = '.challenge-step-btn-finish'; + const submitBtnId = '#challenge-step-btn-submit'; + 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 + ) { + 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() + .removeClass('fadeOutLeft') + .addClass('animated fadeOutRight fast-animation') + .delay(250) + .queue(function(prev) { + $(this).addClass('hidden'); + if (prevStep) { + $(prevStep) + .removeClass('hidden') + .removeClass('slideInRight') + .addClass('animated slideInLeft fast-animation') + .delay(500) + .queue(function(prev) { + prev(); + }); + } + prev(); + }); + } + + function handleNextStepClick(e) { + e.preventDefault(); + var nextStep = getNextStep($(stepClass)); + $(this) + .parent() + .removeClass('fadeOutRight') + .addClass('animated fadeOutLeft fast-animation') + .delay(250) + .queue(function(next) { + $(this).addClass('hidden'); + if (nextStep) { + $(nextStep) + .removeClass('hidden') + .removeClass('slideInLeft') + .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; + } + } + ); + } + + common.init.push(function($) { + if (common.challengeType === '7') { + return null; + } + + $(prevBtnClass).click(handlePrevStepClick); + $(nextBtnClass).click(handleNextStepClick); + $(actionBtnClass).click(handleActionClick); + $(finishBtnClass).click(handleFinishClick); + }); + + return common; +}(window)); diff --git a/client/commonFramework/utils.js b/client/commonFramework/utils.js new file mode 100644 index 0000000000..bf62660bb3 --- /dev/null +++ b/client/commonFramework/utils.js @@ -0,0 +1,79 @@ +window.common = (function(global) { + // common namespace + // all classes should be stored here + // called at the beginning of dom ready + const { + common = { init: [] } + } = global; + + common.head = common.head || []; + common.tail = common.tail || []; + common.salt = Math.random(); + + common.arrayToNewLineString = function arrayToNewLineString(seedData) { + seedData = Array.isArray(seedData) ? seedData : [seedData]; + return seedData.reduce(function(seed, line) { + return '' + seed + line + '\n'; + }, ''); + }; + + common.seed = common.arrayToNewLineString(common.challengeSeed); + + common.replaceScriptTags = function replaceScriptTags(value) { + return value + .replace(/'); + }; + + common.replaceFormActionAttr = function replaceFormAction(value) { + return value.replace(/]*>/, function(val) { + return val.replace(/action(\s*?)=/, 'fccfaa$1='); + }); + }; + + common.replaceFccfaaAttr = function replaceFccfaaAttr(value) { + return value.replace(/]*>/, function(val) { + return val.replace(/fccfaa(\s*?)=/, 'action$1='); + }); + }; + + common.scopejQuery = function scopejQuery(str) { + return str + .replace(/\$/gi, 'j$') + .replace(/document/gi, 'jdocument') + .replace(/jQuery/gi, 'jjQuery'); + }; + + common.unScopeJQuery = function unScopeJQuery(str) { + return str + .replace(/j\$/gi, '$') + .replace(/jdocument/gi, 'document') + .replace(/jjQuery/gi, 'jQuery'); + }; + + const commentRegex = /(\/\*[^(\*\/)]*\*\/)|([ \n]\/\/[^\n]*)/g; + common.removeLogs = function removeComments(str) { + return str.replace(commentRegex, ''); + }; + + const logRegex = /(console\.[\w]+\s*\(.*\;)/g; + common.removeLogs = function removeLogs(str) { + 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); + }; + + + return common; +})(); + diff --git a/client/iFrameScripts.js b/client/iFrameScripts.js index 61c4c66fe5..de012eec7d 100644 --- a/client/iFrameScripts.js +++ b/client/iFrameScripts.js @@ -1,26 +1,26 @@ /* eslint-disable no-undef, no-unused-vars, no-native-reassign */ -(function() { +window.$ = parent.$; +window.$(function() { + var _ = parent._; + var chai = parent.chai; var expect = chai.expect; var tests = parent.tests; - var editor = parent.editorValueForIFrame; + var common = parent.common; + var editorValue = common.editor.getValue(); - setTimeout(function() { - for (var i = 0; i < tests.length; i++) { - var thisTest = true; - try { - /* eslint-disable no-eval */ - eval(parent.tests[i]); - /* eslint-enable no-eval */ - } catch (err) { - allTestsGood = false; - thisTest = false; - parent.postError(JSON.stringify(err.message.split(':').shift())); - } finally { - if (thisTest) { - parent.postSuccess(JSON.stringify(tests[i].split(',').pop().replace( - /\'/g, '').replace(/\)/, ''))); - } - } + common.tests.forEach(test => { + try { + /* eslint-disable no-eval */ + eval(test); + /* eslint-enable no-eval */ + } catch (e) { + parent.postError(JSON.stringify(e.message.split(':').shift())); + } finally { + parent.postSuccess( + JSON.stringify( + test.split(',').pop().replace(/\'/g, '').replace(/\)/, '') + ) + ); } - }, 10); -})(); + }); +}); diff --git a/client/plugin.js b/client/plugin.js index f53d68c71a..7724b08fbf 100644 --- a/client/plugin.js +++ b/client/plugin.js @@ -11,11 +11,11 @@ function importScript(url, error) { return error; } -function run(code) { +function run(code, cb) { + var err = null; var result = { input: code, output: null, - error: null, type: null }; @@ -24,10 +24,15 @@ function run(code) { result.type = typeof codeExec; result.output = stringify(codeExec); } catch (e) { - result.error = e.message; + err = e; + } + + if (err) { + cb(err.message, null); + } else { + cb(null, result); } - application.remote.output(result); self.close(); } diff --git a/gulpfile.js b/gulpfile.js index 890c4cdc35..a19aec572b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -17,6 +17,7 @@ var Rx = require('rx'), concat = require('gulp-concat'), uglify = require('gulp-uglify'), merge = require('merge-stream'), + babel = require('gulp-babel'), // react app webpack = require('webpack-stream'), @@ -375,6 +376,7 @@ gulp.task('js', function() { gulp.src(paths.js) .pipe(plumber({ errorHandler: errorHandler })) + .pipe(babel()) .pipe(__DEV__ ? gutil.noop() : uglify()) ); @@ -395,7 +397,6 @@ gulp.task('js', function() { }); // commonFramework depend on iFrameScripts -// sandbox depends on plugin gulp.task('dependents', ['js'], function() { var manifestName = 'dependents-manifest.json'; var dest = paths.publicJs; @@ -406,6 +407,7 @@ gulp.task('dependents', ['js'], function() { return gulp.src(paths.dependents) .pipe(plumber({ errorHandler: errorHandler })) + .pipe(babel()) .pipe(__DEV__ ? gutil.noop() : uglify()) .pipe(revReplace({ manifest: manifest })) .pipe(gulp.dest(dest)) diff --git a/package.json b/package.json index cd71221d6b..2db376173e 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "forever": "~0.15.1", "frameguard": "~0.2.2", "gulp": "^3.9.0", + "gulp-babel": "^5.3.0", "gulp-concat": "^2.6.0", "gulp-eslint": "^1.1.0", "gulp-inject": "^3.0.0", diff --git a/public/js/fauxJQuery.js b/public/js/fauxJQuery.js new file mode 100644 index 0000000000..c32440051d --- /dev/null +++ b/public/js/fauxJQuery.js @@ -0,0 +1,263 @@ +function $() { + if (!(this instanceof $)) { + return new $(); + } +} +function returnThis() { return this; } +function return$() { return $; } +var methods = [ + 'add', + 'addBack', + 'addClass', + 'after', + 'ajaxComplete', + 'ajaxError', + 'ajaxSend', + 'ajaxStart', + 'ajaxStop', + 'ajaxSuccess', + 'andSelf', + 'animate', + 'append', + 'appendTo', + 'attr', + 'before', + 'bind', + 'blur', + 'callbacksadd', + 'callbacksdisable', + 'callbacksdisabled', + 'callbacksempty', + 'callbacksfire', + 'callbacksfired', + 'callbacksfireWith', + 'callbackshas', + 'callbackslock', + 'callbackslocked', + 'callbacksremove', + 'change', + 'children', + 'clearQueue', + 'click', + 'clone', + 'closest', + 'contents', + 'context', + 'css', + 'data', + 'dblclick', + 'delay', + 'delegate', + 'dequeue', + 'detach', + 'die', + 'each', + 'empty', + 'end', + 'eq', + 'error', + 'fadeIn', + 'fadeOut', + 'fadeTo', + 'fadeToggle', + 'filter', + 'find', + 'finish', + 'first', + 'focus', + 'focusin', + 'focusout', + 'get', + 'has', + 'hasClass', + 'height', + 'hide', + 'hover', + 'html', + 'index', + 'innerHeight', + 'innerWidth', + 'insertAfter', + 'insertBefore', + 'is', + 'jQuery', + 'jquery', + 'keydown', + 'keypress', + 'keyup', + 'last', + 'length', + 'live', + 'load', + 'load', + 'map', + 'mousedown', + 'mouseenter', + 'mouseleave', + 'mousemove', + 'mouseout', + 'mouseover', + 'mouseup', + 'next', + 'nextAll', + 'nextUntil', + 'not', + 'off', + 'offset', + 'offsetParent', + 'on', + 'one', + 'outerHeight', + 'outerWidth', + 'parent', + 'parents', + 'parentsUntil', + 'position', + 'prepend', + 'prependTo', + 'prev', + 'prevAll', + 'prevUntil', + 'promise', + 'prop', + 'pushStack', + 'queue', + 'ready', + 'remove', + 'removeAttr', + 'removeClass', + 'removeData', + 'removeProp', + 'replaceAll', + 'replaceWith', + 'resize', + 'scroll', + 'scrollLeft', + 'scrollTop', + 'select', + 'selector', + 'serialize', + 'serializeArray', + 'show', + 'siblings', + 'size', + 'slice', + 'slideDown', + 'slideToggle', + 'slideUp', + 'stop', + 'submit', + 'text', + 'toArray', + 'toggle', + 'toggle', + 'toggleClass', + 'trigger', + 'triggerHandler', + 'unbind', + 'undelegate', + 'unload', + 'unwrap', + 'val', + 'width', + 'wrap', + 'wrapAll', + 'wrapInner' +]; + +var statics = [ + 'ajax', + 'ajaxPrefilter', + 'ajaxSetup', + 'ajaxTransport', + 'boxModel', + 'browser', + 'Callbacks', + 'contains', + 'cssHooks', + 'cssNumber', + 'data', + 'Deferred', + 'dequeue', + 'each', + 'error', + 'extend', + 'fnextend', + 'fxinterval', + 'fxoff', + 'get', + 'getJSON', + 'getScript', + 'globalEval', + 'grep', + 'hasData', + 'holdReady', + 'inArray', + 'isArray', + 'isEmptyObject', + 'isFunction', + 'isNumeric', + 'isPlainObject', + 'isWindow', + 'isXMLDoc', + 'makeArray', + 'map', + 'merge', + 'noConflict', + 'noop', + 'now', + 'param', + 'parseHTML', + 'parseJSON', + 'parseXML', + 'post', + 'proxy', + 'queue', + 'removeData', + 'sub', + 'support', + 'trim', + 'type', + 'unique', + 'when', + 'always', + 'done', + 'fail', + 'isRejected', + 'isResolved', + 'notify', + 'notifyWith', + 'pipe', + 'progress', + 'promise', + 'reject', + 'rejectWith', + 'resolve', + 'resolveWith', + 'state', + 'then', + 'currentTarget', + 'data', + 'delegateTarget', + 'isDefaultPrevented', + 'isImmediatePropagationStopped', + 'isPropagationStopped', + 'metaKey', + 'namespace', + 'pageX', + 'pageY', + 'preventDefault', + 'relatedTarget', + 'result', + 'stopImmediatePropagation', + 'stopPropagation', + 'target', + 'timeStamp', + 'type', + 'which' +]; + +var $Proto = {}; +methods.forEach(method => $Proto[method] = returnThis); +statics.forEach(staticMeth => $[staticMeth] = return$); +$.prototype = Object.create($); diff --git a/server/views/coursewares/showBonfire.jade b/server/views/coursewares/showBonfire.jade index 10322ea36c..137e247cca 100644 --- a/server/views/coursewares/showBonfire.jade +++ b/server/views/coursewares/showBonfire.jade @@ -1,8 +1,8 @@ extends ../layout-wide block content - link(rel='stylesheet', href='/js/lib/codemirror/lib/codemirror.css') - link(rel='stylesheet', href='/js/lib/codemirror/addon/lint/lint.css') - link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css') + link(rel='stylesheet', href='/bower_components/CodeMirror/lib/codemirror.css') + link(rel='stylesheet', href='/bower_components/CodeMirror/addon/lint/lint.css') + link(rel='stylesheet', href='/bower_components/CodeMirror/theme/monokai.css') link(rel='stylesheet', href='/css/ubuntu.css') .row(ng-controller="pairedWithController")