diff --git a/.travis.yml b/.travis.yml index 26804033f5..b6bd11e2c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js -services: - - mongodb - node_js: - - '0.10' \ No newline at end of file + - 'node' + - '1.6.4' + +sudo: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba4e245107..6830f00dc5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,2 +1,12 @@ We're getting a lot of duplicate issues and bug reports that just aren't reporting actual bugs. So, before you submit your issue, please read the [Help I've Found a Bug](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Help-I've-Found-a-Bug) wiki page. + +We welcome pull requests from Free Code Camp campers (our students) and seasoned JavaScript developers alike! Follow these steps to contribute: + +1. Check our [public Waffle Board](https://waffle.io/freecodecamp/freecodecamp). +2. Pick an issue that nobody has claimed and start working on it. If your issue isn't on the board, open an issue. If you think you can fix it yourself, start working on it. Feel free to ask for help in our [Gitter](https://gitter.im/FreeCodeCamp/FreeCodeCamp). +3. Fork the project ([Need help with forking a project?](https://help.github.com/articles/fork-a-repo/)). You'll do all of your work on your forked copy. +4. Create a branch specific to the issue or feature you are working on. Push your work to that branch. ([Need help with branching?](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)) +5. Name the branch something like `fix/xxx` or `feature/xxx` where `xxx` is a short description of the changes or feature you are attempting to add. For example `fix/email-login` would be a branch where I fix something specific to email login. +6. You should have [ESLint running in your editor](http://eslint.org/docs/user-guide/integrations.html), and it will highlight anything doesn't conform to [Free Code Camp's JavaScript Style Guide](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Free-Code-Camp-JavaScript-Style-Guide) (you can find a summary of those rules [here](https://github.com/FreeCodeCamp/FreeCodeCamp/blob/staging/.eslintrc). Please do not ignore any linting errors, as they are meant to **help** you and to ensure a clean and simple code base. Make sure none of your JavaScript is longer than 80 characters per line. +7. Once your code is ready, submit a [pull request](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Pull-Request-Contribute) from your branch to Free Code Camp's `staging` branch. We'll do a quick code review and give you feedback, then iterate from there. It may also be helpful to read about git [rebasing](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/git-rebase). diff --git a/README.md b/README.md index dba5013540..0611cd1816 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - +![](https://s3.amazonaws.com/freecodecamp/wide-social-banner.png) [![Throughput Graph](https://graphs.waffle.io/freecodecamp/freecodecamp/throughput.svg)](https://waffle.io/freecodecamp/freecodecamp/metrics) @@ -7,7 +7,7 @@ Welcome to Free Code Camp's open source codebase! ======================= -Free Code Camp is an open-source community of busy people who learn to code, then build projects for nonprofits. +Free Code Camp is an open-source community of busy people who learn to code and build projects for nonprofits. Our campers (students) start by working through our free, self-paced, browser-based curriculum. Next, they build several practice projects. Finally, we pair two campers together with a stakeholder from a nonprofit organization, and help them build the solution the nonprofit has requested. @@ -15,7 +15,7 @@ Our campers (students) start by working through our free, self-paced, browser-ba 80% of our campers are over 25, and nearly a fifth of our campers are women. -This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We also have [Gitter](https://gitter.im/FreeCodeCamp/FreeCodeCamp), a [blog](http://blog.freecodecamp.com), and even a [Twitch.tv channel](http://twitch.tv/freecodecamp). +This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We also have [Gitter](https://gitter.im/FreeCodeCamp/FreeCodeCamp), a [blog](http://medium.freecodecamp.com), and even a [Twitch.tv channel](http://twitch.tv/freecodecamp). [Join our community here](http://www.freecodecamp.com/signin). @@ -24,7 +24,7 @@ This code is running live at [FreeCodeCamp.com](http://www.FreeCodeCamp.com). We Wiki ------------ -We would love your help expanding our [wiki](https://github.com/freecodecamp/freecodecamp/wiki) with more information about learning to code and getting a coding job. +We would love your help expanding our [wiki](https://github.com/freecodecamp/freecodecamp/wiki). Our goal is to become a great resource for people learning to code, building local coding communities, and applying for coding jobs. Contributing ------------ @@ -35,10 +35,9 @@ We welcome pull requests from Free Code Camp campers (our students) and seasoned 2. Pick an issue that nobody has claimed and start working on it. If your issue isn't on the board, open an issue. If you think you can fix it yourself, start working on it. Feel free to ask for help in our [Gitter](https://gitter.im/FreeCodeCamp/FreeCodeCamp). 3. Fork the project ([Need help with forking a project?](https://help.github.com/articles/fork-a-repo/)). You'll do all of your work on your forked copy. 4. Create a branch specific to the issue or feature you are working on. Push your work to that branch. ([Need help with branching?](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)) -5. Name the branch something like `user-xxx` where user is your username and xxx is the issue number you are addressing. -6. You should have [ESLint running in your editor](http://eslint.org/docs/user-guide/integrations.html), and it will highlight anything doesn't conform to [AirBnB's JavaScript Style Guide](https://github.com/airbnb/javascript). Please do not ignore any linting errors, as they are meant to **help** you. Make sure none of your JavaScript is longer than 80 characters per line. -7. Once your code is ready, submit a pull request from your branch to Free Code Camp's `staging` branch. We'll do a quick code review and give you feedback, then iterate from there. -8. Once we accept one of your pull requests, one of the project owners (currently @quincylarson, @terakilobyte, and @berkeleytrue) will add you to our camper contributor group. +5. Name the branch something like `fix/xxx` or `feature/xxx` where `xxx` is a short description of the changes or feature you are attempting to add. For example `fix/email-login` would be a branch where I fix something specific to email login. +6. You should have [ESLint running in your editor](http://eslint.org/docs/user-guide/integrations.html), and it will highlight anything doesn't conform to [Free Code Camp's JavaScript Style Guide](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Free-Code-Camp-JavaScript-Style-Guide) (you can find a summary of those rules [here](https://github.com/FreeCodeCamp/FreeCodeCamp/blob/staging/.eslintrc). Please do not ignore any linting errors, as they are meant to **help** you and to ensure a clean and simple code base. Make sure none of your JavaScript is longer than 80 characters per line. +7. Once your code is ready, submit a [pull request](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Pull-Request-Contribute) from your branch to Free Code Camp's `staging` branch. We'll do a quick code review and give you feedback, then iterate from there. It may also be helpful to read about git [rebasing](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/git-rebase). Prerequisites ------------- @@ -55,25 +54,28 @@ The easiest way to get started is to clone the repository: # Get the latest snapshot git clone --depth=1 https://github.com/freecodecamp/freecodecamp.git freecodecamp +# Change directory cd freecodecamp # Install NPM dependencies npm install +# Install Gulp globally +npm install -g gulp + +# Install Bower globally +npm install -g bower + # Install Bower dependencies bower install # Create a .env file and populate it with the necessary API keys and secrets: touch .env - -# Install Gulp globally -npm install -g gulp ``` -Edit your .env file with the following API keys accordingly (if you only use email login, only the MONGOHQ_URL, SESSION_SECRET, MANDRILL_USER and MANDRILL_PASSWORD fields are necessary. Keep in mind if you want to use more services you'll have to get your own API keys for those services. +Edit your `.env` file with the following API keys accordingly. If you only use email login, only the `MONGOHQ_URL`, `SESSION_SECRET`, `MANDRILL_USER` and `MANDRILL_PASSWORD` fields are necessary. Keep in mind if you want to use more services you'll have to get your own API keys for those services. If you only use a subset or no OAuth2 authentication methods, you may want to remove them from ```server/passport-providers.js``` - otherwise the server will complain about missing clientIDs at launch. ``` - MONGOHQ_URL='mongodb://localhost:27017/freecodecamp' FACEBOOK_ID=stuff @@ -106,26 +108,23 @@ COOKIE_SECRET='this is a secret' PEER=stuff DEBUG=true - ``` ```bash - -# Start the mongo server +# Start the mongo server in a seperate terminal mongod -# Create your mongo database. -# Type "mongo" in your terminal to access the mongo shell -use freecodecamp -# Exit the mongo shell with control + d - -# Seed your database with the challenges -node seed/ +# Initialize Free Code Camp +# This will seed the database for the first time. +# This command should only be run once. +npm run first-time # start the application gulp - ``` +Now navigate to your browser and open http://localhost:3001 +If the app loads, congratulations - you're all set. Otherwise, let us know by opening a GitHub issue and with your error. + License ------- diff --git a/bower.json b/bower.json index cd8cfd5cf3..1f833167fc 100644 --- a/bower.json +++ b/bower.json @@ -26,6 +26,7 @@ "moment": "~2.10.2", "angular-bootstrap": "~0.13.0", "ramda": "~0.13.0", - "jshint": "~2.7.0" + "jshint": "~2.7.0", + "lightbox2": "~2.8.1" } } diff --git a/client/commonFramework.js b/client/commonFramework.js index dd91756e06..2505fe08f4 100644 --- a/client/commonFramework.js +++ b/client/commonFramework.js @@ -1,6 +1,158 @@ -/* globals jailed, CodeMirror, challenge_Id, challenge_Name, challengeType */ +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: [] + }; + + common.challengeName = common.challengeName || window.challenge_Name ? + window.challenge_Name : + ''; + + common.challengeType = common.challengeType || window.challengeType ? + window.challengeType : + 0; + + common.challengeId = common.challengeId || window.challenge_Id; + + common.challengeSeed = common.challengeSeed || window.challengeSeed ? + window.challengeSeed : + []; + + common.seed = common.challengeSeed.reduce(function(seed, line) { + return seed + line + '\n'; + }, ''); + + 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; + } + 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 -var codeStorageFactory = (function($, localStorage) { +common.codeStorageFactory = (function($, localStorage, codeUri) { var CodeStorageProps = { version: 0.01, @@ -41,7 +193,10 @@ var codeStorageFactory = (function($, localStorage) { 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'); } @@ -66,10 +221,47 @@ var codeStorageFactory = (function($, localStorage) { } return codeStorageFactory; -}($, localStorage)); +}($, localStorage, common.codeUri)); -var sandBox = (function() { +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)); + +var sandBox = (function(jailed, codeOutput) { + if (!jailed) { + return {}; + } var plugin = null; var sandBox = { @@ -150,7 +342,7 @@ var sandBox = (function() { endLoading(); console.log('resetting on fatal plugin error'); - if (challengeType === 0) { + if (common.challengeType === 0) { codeOutput.setValue( 'Sorry, your code is either too slow, has a fatal error, ' + 'or contains an infinite loop.' @@ -163,13 +355,7 @@ var sandBox = (function() { reset(); sandBox.submit = submit; return sandBox; -}()); - -function replaceSafeTags(value) { - return value - .replace(/fccss/gi, ''); -} +}(window.jailed, common.codeOutput)); var BDDregex = new RegExp( '(expect(\\s+)?\\(.*\\;)|' + @@ -180,66 +366,110 @@ var BDDregex = new RegExp( var isInitRun = false; var initPreview = true; -var editor; -editor = CodeMirror.fromTextArea(document.getElementById('codeEditor'), { - lineNumbers: true, - mode: 'text', - theme: 'monokai', - runnable: true, - matchBrackets: true, - autoCloseBrackets: true, - scrollbarStyle: 'null', - lineWrapping: true, - gutters: ['CodeMirror-lint-markers'] -}); - -var codeStorage = codeStorageFactory(editor, challenge_Name); -var myCodeMirror = editor; - -editor.on('keyup', function() { - clearTimeout(codeStorage.updateTimeoutId); - codeStorage.updateTimeoutId = setTimeout( - codeStorage.updateStorage, - codeStorage.updateWait - ); -}); - -var editorValue; -var challengeSeed = challengeSeed || null; -var tests = tests || []; -var allSeeds = ''; - -(function() { - challengeSeed.forEach(function(elem) { - allSeeds += elem + '\n'; - }); -})(); - -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() { - bonfireExecute(true); - return false; +var editor = (function(CodeMirror, emmetCodeMirror, common) { + var codeStorageFactory = common.codeStorageFactory; + if (!CodeMirror) { + return {}; } -}); -editor.setSize('100%', 'auto'); + 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; + } + }); + + + 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 = tests || []; var libraryIncludes = "" + "" + @@ -260,6 +490,7 @@ function workerError(error) { var housing = $('#testSuite'); if (display.html() !== error) { display.remove(); + housing.prepend( '
' + error.replace(/j\$/gi, '$').replace(/jdocument/gi, 'document').replace(/jjQuery/gi, 'jQuery') + @@ -283,9 +514,13 @@ function scopejQuery(str) { } function safeHTMLRun(test) { - if (challengeType === '0') { + var codeStorage = common.codeStorage; + if (common.challengeType === '0') { var previewFrame = document.getElementById('preview'); - var preview = previewFrame.contentDocument || previewFrame.contentWindow.document; + + var preview = previewFrame.contentDocument || + previewFrame.contentWindow.document; + if (editor.getValue().match(/\/gi) !== null) { var s = editor .getValue() @@ -293,7 +528,21 @@ function safeHTMLRun(test) { .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; + 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; sandBox.submit(scopejQuery(s), function(cls, message) { if (cls) { @@ -354,27 +603,26 @@ function updatePreview() { if (typeof prodOrDev !== 'undefined') { - var nodeEnv = prodOrDev === 'production' ? + /* eslint-disable no-unused-vars */ + var nodeEnv = window.prodOrDev === 'production' ? 'http://www.freecodecamp.com' : 'http://localhost:3001'; + /* eslint-enable no-unused-vars */ - if (challengeType === '0') { + if (common.challengeType === '0') { setTimeout(updatePreview, 300); } } -// Initialize CodeMirror editor with a nice html5 canvas demo. -editor.on('keyup', function() { - clearTimeout(delay); - delay = setTimeout(updatePreview, 300); -}); - /** * "post" methods */ +/* eslint-disable no-unused-vars */ var testResults = []; var postSuccess = function(data) { +/* eslint-enable no-unused-vars */ + var testDoc = document.createElement('div'); $(testDoc).html( "
" + @@ -401,27 +649,41 @@ var postError = function(data) { var goodTests = 0; var testSuccess = function() { goodTests++; + // test successful run show completion if (goodTests === tests.length) { - showCompletion(); + return showCompletion(); } }; +function ctrlEnterClickHandler(e) { + // ctrl + enter + if (e.ctrlKey && e.keyCode === 13) { + $('#complete-courseware-dialog').off('keydown', ctrlEnterClickHandler); + $('#submit-challenge').click(); + } +} + function showCompletion() { if (isInitRun) { isInitRun = false; return; } - var time = Math.floor(Date.now()) - started; + + var time = Math.floor(Date.now()) - window.started; + ga( 'send', 'event', 'Challenge', 'solved', - challenge_Name + ', Time: ' + time + ', Attempts: ' + attempts + common.challengeName + ', Time: ' + time + ', Attempts: ' + attempts ); - var bonfireSolution = myCodeMirror.getValue(); + var bonfireSolution = 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(); @@ -439,7 +701,9 @@ function showCompletion() { .delay(1000) .queue(function(next) { $(this).replaceWith( - '
submitting...
' + '
' + + 'submitting...
' ); next(); }); @@ -447,27 +711,31 @@ function showCompletion() { $.post( '/completed-bonfire/', { challengeInfo: { - challengeId: challenge_Id, - challengeName: challenge_Name, + challengeId: common.challengeId, + challengeName: common.challengeName, completedWith: didCompleteWith, - challengeType: challengeType, + challengeType: common.challengeType, solution: bonfireSolution } }, function(res) { if (res) { - window.location = '/challenges/next-challenge'; + window.location = + '/challenges/next-challenge?id=' + common.challengeId; } } ); }); } +/* eslint-disable no-unused-vars */ var resetEditor = function resetEditor() { - editor.setValue(replaceSafeTags(allSeeds)); +/* eslint-enable no-unused-vars */ + + editor.setValue(common.replaceSafeTags(common.seed)); $('#testSuite').empty(); bonfireExecute(true); - codeStorage.updateStorage(); + common.codeStorage.updateStorage(); }; var attempts = 0; @@ -475,40 +743,10 @@ if (attempts) { attempts = 0; } -if (challengeType !== '0') { - 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 userTests; var testSalt = Math.random(); - var scrapeTests = function(userJavaScript) { // insert tests from mongo @@ -557,34 +795,38 @@ var createTestDisplay = function() { userTests.pop(); } for (var i = 0; i < userTests.length; i++) { - var test = userTests[i]; + var didTestPass = !userTests[i].err; + var testText = userTests[i].text + .split('message: ') + .pop() + .replace(/\'\);/g, ''); + var testDoc = document.createElement('div'); - if (test.err) { - console.log('Should be displaying bad tests'); + var iconClass = didTestPass ? + '"ion-checkmark-circled big-success-icon"' : + '"ion-close-circled big-error-icon"'; - $(testDoc).html( - "
" + - test.text + "
" + - test.err + "
" - ) - .appendTo($('#testSuite')); - - } else { - - $(testDoc).html( - "
" + - test.text + - "
" - ) - .appendTo($('#testSuite')); - } + $(testDoc).html( + "
" + + testText + + "
" + ) + .appendTo($('#testSuite')); } }; -var expect = chai.expect; -var assert = chai.assert; -var should = chai.should(); +(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) { @@ -594,6 +836,7 @@ var reassembleTest = function(test, data) { }; var runTests = function(err, data) { + var editorValue = editor.getValue(); // userTests = userTests ? null : []; var allTestsPassed = true; pushed = false; @@ -623,7 +866,9 @@ var runTests = function(err, data) { ) { 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; @@ -638,23 +883,178 @@ var runTests = function(err, data) { if (allTestsPassed) { allTestsPassed = false; showCompletion(); + } else { + isInitRun = false; } } }; +// step challenge +common.init.push((function() { + var stepClass = '.challenge-step'; + 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 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 handleNextStepClick(e) { + e.preventDefault(); + var nextStep = getNextStep($(stepClass)); + $(this) + .parent() + .addClass('animated fadeOutLeft fast-animation') + .delay(250) + .queue(function(next) { + $(this).addClass('hidden'); + if (nextStep) { + $(nextStep) + .removeClass('hidden') + .addClass('animated slideInRight fast-animation') + .delay(500) + .queue(function(next) { + $(this).removeClass('slideInRight'); + 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($) { + $(nextBtnClass).click(handleNextStepClick); + $(actionBtnClass).click(handleActionClick); + $(finishBtnClass).click(handleFinishClick); + }; +}(window.$))); + function bonfireExecute(shouldTest) { + var codeOutput = common.codeOutput; initPreview = false; goodTests = 0; attempts++; - ga('send', 'event', 'Challenge', 'ran-code', challenge_Name); + ga('send', 'event', 'Challenge', 'ran-code', common.challengeName); userTests = null; $('#testSuite').empty(); if ( - challengeType !== '0' && + common.challengeType !== '0' && !editor.getValue().match(/\$\s*?\(\s*?\$\s*?\)/gi) ) { - var userJavaScript = myCodeMirror.getValue(); + var userJavaScript = editor.getValue(); var failedCommentTest = false; // checks if the number of opening comments(/*) matches the number of @@ -674,7 +1074,7 @@ function bonfireExecute(shouldTest) { if (userJavaScript.match(/function\s*?\(|function\s+\w+\s*?\(/gi)) { sandBox.submit(userJavaScript, function(cls, message) { if (failedCommentTest) { - myCodeMirror.setValue(myCodeMirror.getValue() + '*/'); + editor.setValue(editor.getValue() + '*/'); console.log('Caught Unfinished Comment'); codeOutput.setValue('Unfinished multi-line comment'); failedCommentTest = false; @@ -699,7 +1099,7 @@ function bonfireExecute(shouldTest) { sandBox.submit(userJavaScript, function(cls, message) { if (failedCommentTest) { - myCodeMirror.setValue(myCodeMirror.getValue() + '*/'); + editor.setValue(editor.getValue() + '*/'); console.log('Caught Unfinished Comment'); codeOutput.setValue('Unfinished mulit-line comment'); failedCommentTest = false; @@ -729,7 +1129,7 @@ function bonfireExecute(shouldTest) { } if ( !editor.getValue().match(/\$\s*?\(\s*?\$\s*?\)/gi) && - challengeType === '0' + common.challengeType === '0' ) { safeHTMLRun(shouldTest); } else { @@ -743,26 +1143,37 @@ function bonfireExecute(shouldTest) { } $('#submitButton').on('click', function() { + isInitRun = false; bonfireExecute(true); }); $(document).ready(function() { + + 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'); isInitRun = true; - editorValue = codeStorage.isAlive() ? - codeStorage.getStoredValue() : - allSeeds; - - myCodeMirror.setValue(replaceSafeTags(editorValue)); - if (typeof $preview.html() !== 'undefined') { $preview.load(function() { if (initPreview) { bonfireExecute(true); } }); - } else { + } else if (common.challengeType !== 7) { bonfireExecute(true); } + }); diff --git a/client/es6-shims.js b/client/es6-shims.js new file mode 100644 index 0000000000..43abd2ed1a --- /dev/null +++ b/client/es6-shims.js @@ -0,0 +1,2 @@ +require('object.assign').shim(); +require('es6-map/implement'); diff --git a/client/index.js b/client/index.js index 95be7ca9ae..44bdb0a82c 100644 --- a/client/index.js +++ b/client/index.js @@ -1,9 +1,10 @@ +import unused from './es6-shims'; // eslint-disable-line import Rx from 'rx'; import React from 'react'; import Fetchr from 'fetchr'; import debugFactory from 'debug'; import { Router } from 'react-router'; -import { history } from 'react-router/lib/BrowserHistory'; +import { createLocation, createHistory } from 'history'; import { hydrate } from 'thundercats'; import { Render } from 'thundercats-react'; @@ -17,21 +18,29 @@ const services = new Fetchr({ }); Rx.config.longStackSupport = !!debug.enabled; - +const history = createHistory(); +const appLocation = createLocation( + location.pathname + location.search +); // returns an observable -app$(history) +app$({ history, location: appLocation }) .flatMap( ({ AppCat }) => { + // instantiate the cat with service const appCat = AppCat(null, services); + // hydrate the stores return hydrate(appCat, catState) .map(() => appCat); }, - ({ initialState }, appCat) => ({ initialState, appCat }) + // not using nextLocation at the moment but will be used for + // redirects in the future + ({ nextLocation, props }, appCat) => ({ nextLocation, props, appCat }) ) - .flatMap(({ initialState, appCat }) => { + .flatMap(({ props, appCat }) => { + props.history = history; return Render( appCat, - React.createElement(Router, initialState), + React.createElement(Router, props), DOMContianer ); }) diff --git a/client/less/chat.less b/client/less/chat.less new file mode 100644 index 0000000000..a6f83171ca --- /dev/null +++ b/client/less/chat.less @@ -0,0 +1,24 @@ +.chat-embed-main-title { + display: flex; + flex-grow: 1; + padding-left: 31px; + padding-top: 7px; +} + +.gitter-chat-embed { + z-index: 100; + position: fixed; + + top: 0; + left: 60%; + bottom: 0; + right: 0; + + display: flex; + flex-direction: row; + transition: transform 0.3s cubic-bezier(0.16, 0.22, 0.22, 1.7); +} + +.gitter-chat-embed.is-collapsed:not(.is-loading) { + transform: translateX(110%); +} diff --git a/client/less/lib/bootstrap/variables.less b/client/less/lib/bootstrap/variables.less index 01e26f3433..8b8fefa81d 100755 --- a/client/less/lib/bootstrap/variables.less +++ b/client/less/lib/bootstrap/variables.less @@ -107,7 +107,7 @@ @border-radius-small: 3px; //** Global color for active items (e.g., navs or dropdowns). -@component-active-color: #eee; +@component-active-color: @gray-lighter; //** Global background color for active items (e.g., navs or dropdowns). @component-active-bg: @brand-primary; @@ -145,26 +145,26 @@ @btn-font-weight: normal; @btn-default-color: #333; -@btn-default-bg: #eee; +@btn-default-bg: @gray-lighter; @btn-default-border: #ccc; -@btn-primary-color: #eee; +@btn-primary-color: @gray-lighter; @btn-primary-bg: @brand-primary; @btn-primary-border: darken(@btn-primary-bg, 5%); -@btn-success-color: #eee; +@btn-success-color: @gray-lighter; @btn-success-bg: @brand-success; @btn-success-border: darken(@btn-success-bg, 5%); -@btn-info-color: #eee; +@btn-info-color: @gray-lighter; @btn-info-bg: @brand-info; @btn-info-border: darken(@btn-info-bg, 5%); -@btn-warning-color: #eee; +@btn-warning-color: @gray-lighter; @btn-warning-bg: @brand-warning; @btn-warning-border: darken(@btn-warning-bg, 5%); -@btn-danger-color: #eee; +@btn-danger-color: @gray-lighter; @btn-danger-bg: @brand-danger; @btn-danger-border: darken(@btn-danger-bg, 5%); @@ -176,7 +176,7 @@ //## //** `` background color -@input-bg: #eee; +@input-bg: @gray-lighter; //** `` background color @input-bg-disabled: @gray-lighter; @@ -223,7 +223,7 @@ //## Dropdown menu container and contents. //** Background for the dropdown menu. -@dropdown-bg: #eee; +@dropdown-bg: @gray-lighter; //** Dropdown menu `border-color`. @dropdown-border: rgba(0,0,0,.15); //** Dropdown menu `border-color` **for IE8**. @@ -359,10 +359,10 @@ @navbar-default-border: darken(@navbar-default-bg, 6.5%); // Navbar links -@navbar-default-link-color: #eee; +@navbar-default-link-color: @gray-lighter; @navbar-default-link-hover-color: #4a2b0f; -@navbar-default-link-hover-bg: #eee; -@navbar-default-link-active-color: #eee; +@navbar-default-link-hover-bg: @gray-lighter; +@navbar-default-link-active-color: @gray-lighter; @navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%); @navbar-default-link-disabled-color: #ccc; @navbar-default-link-disabled-bg: transparent; @@ -386,7 +386,7 @@ // Inverted navbar links @navbar-inverse-link-color: @gray-light; -@navbar-inverse-link-hover-color: #eee; +@navbar-inverse-link-hover-color: @gray-lighter; @navbar-inverse-link-hover-bg: transparent; @navbar-inverse-link-active-color: @navbar-inverse-link-hover-color; @navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%); @@ -395,12 +395,12 @@ // Inverted navbar brand label @navbar-inverse-brand-color: @navbar-inverse-link-color; -@navbar-inverse-brand-hover-color: #eee; +@navbar-inverse-brand-hover-color: @gray-lighter; @navbar-inverse-brand-hover-bg: transparent; // Inverted navbar toggle @navbar-inverse-toggle-hover-bg: #333; -@navbar-inverse-toggle-icon-bar-bg: #eee; +@navbar-inverse-toggle-icon-bar-bg: @gray-lighter; @navbar-inverse-toggle-border-color: #333; @@ -415,7 +415,7 @@ @nav-disabled-link-color: @gray-light; @nav-disabled-link-hover-color: @gray-light; -@nav-open-link-hover-color: #eee; +@nav-open-link-hover-color: @gray-lighter; //== Tabs @nav-tabs-border-color: #ddd; @@ -440,19 +440,19 @@ //## @pagination-color: @link-color; -@pagination-bg: #eee; +@pagination-bg: @gray-lighter; @pagination-border: #ddd; @pagination-hover-color: @link-hover-color; @pagination-hover-bg: @gray-lighter; @pagination-hover-border: #ddd; -@pagination-active-color: #eee; +@pagination-active-color: @gray-lighter; @pagination-active-bg: @brand-primary; @pagination-active-border: @brand-primary; @pagination-disabled-color: @gray-light; -@pagination-disabled-bg: #eee; +@pagination-disabled-bg: @gray-lighter; @pagination-disabled-border: #ddd; @@ -511,7 +511,7 @@ //** Tooltip max width @tooltip-max-width: 200px; //** Tooltip text color -@tooltip-color: #eee; +@tooltip-color: @gray-lighter; //** Tooltip background color @tooltip-bg: #000; @tooltip-opacity: .9; @@ -527,7 +527,7 @@ //## //** Popover body background color -@popover-bg: #eee; +@popover-bg: @gray-lighter; //** Popover maximum width @popover-max-width: 276px; //** Popover border color @@ -541,7 +541,7 @@ //** Popover arrow width @popover-arrow-width: 10px; //** Popover arrow color -@popover-arrow-color: #eee; +@popover-arrow-color: @gray-lighter; @popover-arrow-color: @popover-bg; //** Popover outer arrow width @@ -570,9 +570,9 @@ @label-danger-bg: @brand-danger; //** Default label text color -@label-color: #eee; +@label-color: @gray-lighter; //** Default text color of a linked label -@label-link-hover-color: #eee; +@label-link-hover-color: @gray-lighter; //== Modals @@ -588,7 +588,7 @@ @modal-title-line-height: @line-height-base; //** Background color of modal content area -@modal-content-bg: #eee; +@modal-content-bg: @gray-lighter; //** Modal content border color @modal-content-border-color: rgba(0,0,0,.2); //** Modal content border color **for IE8** @@ -640,7 +640,7 @@ //** Background color of the whole progress component @progress-bg: #f5f5f5; //** Progress bar text color -@progress-bar-color: #eee; +@progress-bar-color: @gray-lighter; //** Variable for setting rounded corners on progress bar. @progress-border-radius: @border-radius-base; @@ -662,7 +662,7 @@ //## //** Background color on `.list-group-item` -@list-group-bg: #eee; +@list-group-bg: @gray-lighter; //** `.list-group-item` border color @list-group-border: #ddd; //** List group border radius @@ -695,7 +695,7 @@ // //## -@panel-bg: #eee; +@panel-bg: @gray-lighter; @panel-body-padding: 15px; @panel-heading-padding: 10px 15px; @panel-footer-padding: @panel-heading-padding; @@ -709,7 +709,7 @@ @panel-default-border: #ddd; @panel-default-heading-bg: #f5f5f5; -@panel-primary-text: #eee; +@panel-primary-text: @gray-lighter; @panel-primary-border: @brand-primary; @panel-primary-heading-bg: @brand-primary; @@ -717,7 +717,7 @@ @panel-success-border: @state-success-border; @panel-success-heading-bg: @state-success-bg; -@panel-info-text: #eee; +@panel-info-text: @gray-lighter; @panel-info-border: darken(#4a2b0f, 5%); @panel-info-heading-bg: #4a2b0f; @@ -761,15 +761,15 @@ // //## -@badge-color: #eee; +@badge-color: @gray-lighter; //** Linked badge text color on hover -@badge-link-hover-color: #eee; +@badge-link-hover-color: @gray-lighter; @badge-bg: @gray-light; //** Badge text color in active nav link @badge-active-color: @link-color; //** Badge background color in active nav link -@badge-active-bg: #eee; +@badge-active-bg: @gray-lighter; @badge-font-weight: bold; @badge-line-height: 1; @@ -798,15 +798,15 @@ @carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6); -@carousel-control-color: #eee; +@carousel-control-color: @gray-lighter; @carousel-control-width: 15%; @carousel-control-opacity: .5; @carousel-control-font-size: 20px; -@carousel-indicator-active-bg: #eee; -@carousel-indicator-border-color: #eee; +@carousel-indicator-active-bg: @gray-lighter; +@carousel-indicator-border-color: @gray-lighter; -@carousel-caption-color: #eee; +@carousel-caption-color: @gray-lighter; //== Close @@ -815,7 +815,7 @@ @close-font-weight: bold; @close-color: #000; -@close-text-shadow: 0 1px 0 #eee; +@close-text-shadow: 0 1px 0 @gray-lighter; //== Code @@ -825,7 +825,7 @@ @code-color: #c7254e; @code-bg: #f9f2f4; -@kbd-color: #eee; +@kbd-color: @gray-lighter; @kbd-bg: #333; @pre-bg: #f5f5f5; diff --git a/client/less/main.less b/client/less/main.less index 814e99cb54..aa70867894 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -37,8 +37,7 @@ html { overflow-x: hidden; } -input[type=checkbox] -{ +input[type=checkbox] { /* Double-sized Checkboxes */ -ms-transform: scale(2); /* IE */ -moz-transform: scale(2); /* FF */ @@ -48,7 +47,7 @@ input[type=checkbox] } body.full-screen-body-background { - background-color: #eeeeee; + background-color: @gray-lighter; } @@ -97,28 +96,18 @@ h1, h2, h3, h4, h5, h6, p, li { margin-right: 5px; } +.fa:hover { + text-decoration: none; +} + .img-center { margin: 0 auto; } -.three-by-three { - height: 100px; -} - -.darker-background { - background-color: #dedede; -} - -/**/ - .btn-cta { font-size: 40px; } -.nonprofit-cta { - font-size: 28px; -} - .btn, .shadow { white-space: normal; -webkit-box-shadow: 2px 4px 1px rgba(0, 0, 0, 0.3); @@ -142,25 +131,6 @@ ul { word-wrap: break-word; } -.img-center { - margin:0 auto; -} - -.centered-iframe { - display:block; -} - -@media (min-width: 767px) { - .landing-panel-body { - padding-left: 40px; - padding-right: 40px; - } -} - -.landing-panel-heading { - font-size: 40px; -} - .panel-heading { font-size: 25px; } @@ -175,14 +145,6 @@ ul { font-size: 26px; } -.five-pixel-break { - height: 5px; -} - -.fifteen-pixel-break { - height: 15px; -} - .nav-height { height: 50px; border: none; @@ -193,55 +155,34 @@ ul { width: 200px; } -.completion-icon{ +.completion-icon { font-size: 150px; } -.responsive-container { position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden; } -.responsive-container iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } - -.positive-10 { - margin-top: 10px; -} - -.positive-15-bottom { - margin-bottom: 15px; -} - .positive-15 { margin-top: 15px; } -.negative-45 { - margin-top: -45px; - margin-bottom: -45px; +.positive-15-bottom { + margin-bottom: 15px; } -.negative-55 { - margin-top: -55px; - margin-bottom: -55px; +.positive-10 { + margin-top: 10px; } -.negative-10 { - margin-top: -10px; -} - -.negative-28 { - margin-top: -28px; -} - -.negative-35 { - margin-top: -35px; -} - -.negative-30 { - margin-top: -30px; +.positive-5 { + margin-top: 5px; } .negative-5 { margin-top: -5px; } +.negative-10 { + margin-top: -10px; +} + .negative-15 { margin-top: -15px; } @@ -250,14 +191,35 @@ ul { margin-top: -20px; } -.negative-bottom-margin-30 { +.negative-28 { + margin-top: -28px; +} + +.negative-30 { + margin-top: -30px; +} + +.negative-30-bottom { margin-bottom: -30px; } +.negative-35 { + margin-top: -35px; +} + +.negative-55 { + margin-top: -55px; + margin-bottom: -55px; +} + .large-p { font-size: 24px; } +.map-p { + font-size: 20px; +} + .large-li { font-size: 24px; } @@ -270,36 +232,11 @@ ul { color: @brand-success; } -.delay-1 { - -webkit-animation-delay: 1s; - animation-delay: 1s; -} - -.delay-2 { - -webkit-animation-delay: 2s; - animation-delay: 2s; -} - -.delay-4 { - -webkit-animation-delay: 4s; - animation-delay: 4s; -} - -.delay-10 { - -webkit-animation-delay: 10s; - animation-delay: 10s; -} - .fast-animation { -webkit-animation-duration: 0.5s; animation-duration: 0.5s; } -.slow-animation { - -webkit-animation-duration: 1.5s; - animation-duration: 1.5s; -} - .disabled { pointer-events: none; cursor: default; @@ -310,10 +247,6 @@ ul { display: none; } -.button-container { - height: 120px; -} - .nav-logo { height: 40px; margin-top: -10px; @@ -329,7 +262,7 @@ ul { } @media (max-width: 991px) and (min-width: 768px) { position: absolute; - right:0; + right: 0; margin-right: 10px; white-space: nowrap; } @@ -346,8 +279,8 @@ ul { .thin-progress-bar { height: 8px; - margin-top:3px; - margin-bottom:0px; + margin-top: 3px; + margin-bottom: 0px; width: 60%; margin-right: auto; margin-left: auto; @@ -357,8 +290,8 @@ ul { margin-bottom: -6px; } -.strikethrough { - text-decoration: line-through; +.lb-container { + padding: 0px; } .btn-social { @@ -377,7 +310,7 @@ ul { } .navbar-nav > li > a { - color: #eee; + color: @gray-lighter; &:hover { color: #4a2b0f; } @@ -396,11 +329,6 @@ ul { font-size: 63px; } -.scroll-lock { - overflow: hidden; - height: 100%; -} - .signup-btn.btn { background-color: #ffac33; background-image: linear-gradient(#ffcc4d, #ffac33); @@ -421,41 +349,9 @@ ul { background-image: none; box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); } -.profile-image { - border-radius: 5px; - width: 200px; - height: 200px; - padding-left: 5px; - padding-right: 5px; -} -.team-member { - height: 420px; -} - -*, *:before, *:after {box-sizing: border-box !important;} - -.masonry-row { - -moz-column-width: 18em; - -webkit-column-width: 18em; - -moz-column-gap: 1em; - -webkit-column-gap:1em; -} - -.masonry-block { - display: inline-block; - padding: .25rem; - width: 100%; -} - -.masonry-relative { - position:relative; - display: block; -} - -.next-challenge-button { - max-width: 1500px; - margin:0 auto; +*, *:before, *:after { + box-sizing: border-box !important; } .btn-big { @@ -505,13 +401,13 @@ thead { } .navbar-nav a { - color: #eee; + color: @gray-lighter; font-size: 20px; margin-top: -5px; margin-bottom: -5px; } .navbar-toggle { - color: #eee; + color: @gray-lighter; } .navbar-right { @@ -520,16 +416,11 @@ thead { } .signup-btn-nav { - margin-top:-2px !important; + margin-top: -2px !important; padding-top: 10px !important; padding-bottom: 10px !important; } -.nameline { - margin-top: -5px; - font-size: 40px; -} - .public-profile-img { height: 200px; width: 200px; @@ -543,23 +434,6 @@ thead { border-color: #78FA89; } -.desktop-narrow { - @media (min-width: 767px) { - marign: 0 auto; - width: 80%; - } -} - -.min650 { - min-height: 630px; -} - -.portfolio-image { - height: 225px; - width: 300px; - border-radius: 5px; -} - .flat-top { margin-top: -5px; } @@ -573,7 +447,7 @@ thead { } .points-on-top { - color: #eee; + color: @gray-lighter; font-size: 35px; z-index: 2; width: 60%; @@ -620,11 +494,17 @@ thead { border-radius: 5px; } +.story-section { + height: 500px; +} + .testimonial-copy { - font-size: 20px; - text-align: center; + text-align: justify; + font-size: 18px !important; + margin-left: 20px; + margin-right: 20px; @media (min-width: 991px) and (max-width: 1199px) { - height: 120px; + height: 140px; } @media (min-width: 1200px) { height: 90px; @@ -642,7 +522,7 @@ thead { .challenge-list-header { background-color: #215f1e; - color: #eee; + color: @gray-lighter; font-size: 36px; text-align: center; margin-bottom: -30px; @@ -650,19 +530,8 @@ thead { padding-left: 50px; } -.all-list-header { - background-color: #4A2B0F; - color: #eee; - font-size: 36px; - text-align: center; - margin-bottom: -30px; - border-radius: 5px 5px 0px 0px; - padding-left: 50px; - -} - .closing-x { - color: #eee; + color: @gray-lighter; font-size: 50px; text-align: right; } @@ -678,7 +547,7 @@ thead { position: absolute; a { font-size: 20px; - color: #eee; + color: @gray-lighter; margin-left: 0px; margin-right: 0px; padding-left: 10px; @@ -687,7 +556,7 @@ thead { padding-bottom: 12px; &:hover { color: #4a2b0f; - background-color: #eee; + background-color: @gray-lighter; text-decoration: none; } } @@ -702,19 +571,6 @@ thead { font-size: 15px; } -.jquery-exercises-well { - text-align: left; - height: 200px; -} - -#exercise-directory { - font-size: 20px; -} - -#current-exercise { - text-size: 250px; -} - .bonfire-instructions { margin-bottom: 5px; } @@ -764,16 +620,13 @@ form.code span { .test-output { font-size: 15px; font-family: "Ubuntu Mono"; + margin-top: 8px; } #mainEditorPanel .panel-body { padding-bottom: 0px; } -.panel-bonfire { - height: 100% -} - div.CodeMirror-scroll { padding-bottom: 30px; } @@ -812,11 +665,6 @@ iframe.iphone { } } -.nonprofit-help-select-text-height { - font-size: 40px; - padding-top: 20px; -} - // To adjust right margin, negative values bring the image closer to the edge of the screen .iphone-position { position: absolute; @@ -829,25 +677,6 @@ iframe.iphone { min-height: 650px; } -// This is used to give icons text for screen readers to read out, without needing the text to actually appear. -.icon-lock{ - font-size: 0px; -} - -.stats-text { - font-size: 26px; - line-height: 150%; -} - -.github-and-twitter-button-text { - padding-top: 10px; -} - -.gitter-imbed { - height: 100%; - margin-bottom: 50px; -} - .btn-primary-ghost { background: transparent; color: @brand-primary; @@ -908,7 +737,7 @@ iframe.iphone { padding-right: 8px; margin-left: 0px; margin-right: 2px; - text-align:left; + text-align: left; font-size: 10px; } @@ -918,38 +747,11 @@ iframe.iphone { font-size: 18px; } -.tight-h3 { - margin-top: -4px; - margin-bottom: -4px; -} - .story-list { padding-bottom: 30px; margin-bottom: 30px; } -.big-ion-up-arrow { - font-size: 45px; - margin-top: -10px; - margin-bottom: -15px; - text-align: center; -} - -.big-ion-up-arrow #upvote, #reply-to-main-post { - cursor: pointer; -} - -.story-up-votes { - padding-top: 0px; - margin-left: -5px; - text-align: center; -} - -.img-story-post { - max-width: 110px; - max-height: 110px; -} - .button-spacer { padding: 5px 0 2px 0; } @@ -1057,11 +859,7 @@ hr { } .cal-heatmap-container { - background-color: #EEEEEE; -} - -.checkbox-table label { - margin-left: 10px; + background-color: @gray-lighter; } .interested-camper-image { @@ -1070,16 +868,6 @@ hr { padding: 5px; } -.svg-challenge-map { - fill: #333; - height: 40px; -} - -.news-number { - font-size: 30px; - text-align: center; -} - .mobile-story-image { border-radius: 5px; width: 100%; @@ -1101,10 +889,6 @@ hr { opacity: 0.5; } -.same-line { - display: inline-block; -} - .padded-ionic-icon { padding-top: 5px; } @@ -1126,6 +910,10 @@ hr { color: @gray-light; } +code { + padding: 0; +} + @media only screen and (min-width: 993px) { .iframe-scroll { position: fixed !important; @@ -1142,10 +930,6 @@ hr { // Calculator styles -.initially-hidden { - display: none; -} - .chart rect { fill: steelblue; } @@ -1171,6 +955,11 @@ hr { margin: 0!important; } +// gitter chat +.gitter-chat-embed { + z-index: 20000 !important; +} + //uncomment this to see the dimensions of all elements outlined in red //* { // border-color: red; @@ -1278,3 +1067,5 @@ hr { transform: rotate(0deg); } } + +@import "chat.less"; diff --git a/client/main.js b/client/main.js index 8a72664bbf..6f147abcf7 100644 --- a/client/main.js +++ b/client/main.js @@ -1,14 +1,154 @@ +var main = window.main || {}; + +main.mapShareKey = 'map-shares'; + +main.ga = window.ga || function() {}; + +main = (function(main) { + + // should be set before gitter script loads + ((window.gitter = {}).chat = {}).options = { + disableDefaultChat: true + }; + // wait for sidecar to load + + main.chat = {}; + main.chat.isOpen = false; + main.chat.createHelpChat = function createHelpChat() { + throw new Error('Sidecar chat has not initialized'); + }; + + document.addEventListener('gitter-sidecar-ready', function(e) { + main.chat.GitterChat = e.detail.Chat; + + main.chat.createHelpChat = function(room, helpChatBtnClass, roomTitle) { + roomTitle = roomTitle || 'Waypoint Help'; + + $('body').append( + '