mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-10-25 10:47:00 +08:00
More refactoring
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
$,
|
||||
Rx: { Observable, Disposable },
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
|
||||
function getFaux() {
|
||||
return new Observable(function(observer) {
|
||||
const jqXHR = $.get('/js/faux.js')
|
||||
.success(data => observer.onNext(data))
|
||||
.fail(e => observer.onError(e))
|
||||
.always(() => observer.onCompleted());
|
||||
|
||||
return new Disposable(() => {
|
||||
jqXHR.abort();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const faux$ = getFaux().shareReplay();
|
||||
|
||||
common.safeHTMLRun = function safeHTMLRun(code) {
|
||||
if (!code.match(/\<script\>/gi)) {
|
||||
return Observable.just(code);
|
||||
}
|
||||
|
||||
// grab user javaScript
|
||||
var scriptCode = code
|
||||
.split(/\<\s?script\s?\>/gi)[1]
|
||||
.split(/\<\s?\/\s?script\s?\>/gi)[0];
|
||||
return faux$.map(faux => faux + scriptCode);
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -0,0 +1,41 @@
|
||||
window.common = (function({ common = { init: [] }}) {
|
||||
|
||||
var BDDregex = new RegExp(
|
||||
'(expect(\\s+)?\\(.*\\;)|' +
|
||||
'(assert(\\s+)?\\(.*\\;)|' +
|
||||
'(assert\\.\\w.*\\;)|' +
|
||||
'(.*\\.should\\..*\\;)/'
|
||||
);
|
||||
|
||||
common.addTestsToString = function(code) {
|
||||
const userTests = [];
|
||||
|
||||
// insert tests from mongo
|
||||
for (var i = 0; i < common.tests.length; i++) {
|
||||
code += '\n' + common.tests[i];
|
||||
}
|
||||
|
||||
var counter = 0;
|
||||
var match = BDDregex.exec(code);
|
||||
|
||||
while (match) {
|
||||
var replacement = '//' + counter + common.salt;
|
||||
code = code.substring(0, match.index) +
|
||||
replacement +
|
||||
code.substring(match.index + match[0].length);
|
||||
|
||||
userTests.push({
|
||||
err: null,
|
||||
text: match[0],
|
||||
line: counter
|
||||
});
|
||||
|
||||
counter++;
|
||||
match = BDDregex.exec(code);
|
||||
}
|
||||
|
||||
return { code, userTests };
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -0,0 +1,262 @@
|
||||
window.common = (function({ $, common = { init: [] }}) {
|
||||
|
||||
common.ctrlEnterClickHandler = function ctrlEnterClickHandler(e) {
|
||||
// ctrl + enter or cmd + enter
|
||||
if (
|
||||
e.metaKey && e.keyCode === 13 ||
|
||||
e.ctrlKey && e.keyCode === 13
|
||||
) {
|
||||
$('#complete-courseware-dialog').off('keydown', ctrlEnterClickHandler);
|
||||
if ($('#submit-challenge').length > 0) {
|
||||
$('#submit-challenge').click();
|
||||
} else {
|
||||
window.location = '/challenges/next-challenge?id=' + common.challengeId;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
common.resetEditor = function resetEditor() {
|
||||
common.editor.setValue(common.replaceSafeTags(common.seed));
|
||||
common.executeChallenge(true);
|
||||
common.codeStorage.updateStorage();
|
||||
};
|
||||
|
||||
common.init.push(function($) {
|
||||
|
||||
var $marginFix = $('.innerMarginFix');
|
||||
$marginFix.css('min-height', $marginFix.height());
|
||||
|
||||
// init modal keybindings on open
|
||||
$('#complete-courseware-dialog').on('shown.bs.modal', function() {
|
||||
$('#complete-courseware-dialog').keydown(common.ctrlEnterClickHandler);
|
||||
});
|
||||
|
||||
// remove modal keybinds on close
|
||||
$('#complete-courseware-dialog').on('hidden.bs.modal', function() {
|
||||
$('#complete-courseware-dialog').off(
|
||||
'keydown',
|
||||
common.ctrlEnterClickHandler
|
||||
);
|
||||
});
|
||||
|
||||
// video checklist binding
|
||||
$('.challenge-list-checkbox').on('change', function() {
|
||||
var checkboxId = $(this).parent().parent().attr('id');
|
||||
if ($(this).is(':checked')) {
|
||||
$(this).parent().siblings().children().addClass('faded');
|
||||
if (!localStorage || !localStorage[checkboxId]) {
|
||||
localStorage[checkboxId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$(this).is(':checked')) {
|
||||
$(this).parent().siblings().children().removeClass('faded');
|
||||
if (localStorage[checkboxId]) {
|
||||
localStorage.removeItem(checkboxId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.checklist-element').each(function() {
|
||||
var checklistElementId = $(this).attr('id');
|
||||
if (localStorage[checklistElementId]) {
|
||||
$(this).children().children('li').addClass('faded');
|
||||
$(this).children().children('input').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// video challenge submit
|
||||
$('#next-courseware-button').on('click', function() {
|
||||
$('#next-courseware-button').unbind('click');
|
||||
if ($('.signup-btn-nav').length < 1) {
|
||||
var data;
|
||||
var completedWith = $('#completed-with').val() || null;
|
||||
var publicURL = $('#public-url').val() || null;
|
||||
var githubURL = $('#github-url').val() || null;
|
||||
switch (common.challengeType) {
|
||||
case common.challengeTypes.HTML:
|
||||
case common.challengeTypes.JS:
|
||||
case common.challengeTypes.VIDEO:
|
||||
data = {
|
||||
challengeInfo: {
|
||||
challengeId: common.challengeId,
|
||||
challengeName: common.challengeName
|
||||
}
|
||||
};
|
||||
$.post('/completed-challenge/', data)
|
||||
.success(function(res) {
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
window.location.href = '/challenges/next-challenge?id=' +
|
||||
common.challengeId;
|
||||
})
|
||||
.fail(function() {
|
||||
window.location.href = '/challenges';
|
||||
});
|
||||
|
||||
break;
|
||||
case common.challengeTypes.BASEJUMP:
|
||||
case common.challengeTypes.ZIPLINE:
|
||||
data = {
|
||||
challengeInfo: {
|
||||
challengeId: common.challengeId,
|
||||
challengeName: common.challengeName,
|
||||
completedWith: completedWith,
|
||||
publicURL: publicURL,
|
||||
githubURL: githubURL,
|
||||
challengeType: common.challengeType,
|
||||
verified: false
|
||||
}
|
||||
};
|
||||
|
||||
$.post('/completed-zipline-or-basejump/', data)
|
||||
.success(function() {
|
||||
window.location.href = '/challenges/next-challenge?id=' +
|
||||
common.challengeId;
|
||||
})
|
||||
.fail(function() {
|
||||
window.location.replace(window.location.href);
|
||||
});
|
||||
break;
|
||||
|
||||
case common.challengeTypes.BONFIRE:
|
||||
window.location.href = '/challenges/next-challenge?id=' +
|
||||
common.challengeId;
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Happy Coding!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#submitButton').on('click', function() {
|
||||
common.executeChallenge(true);
|
||||
});
|
||||
|
||||
if (common.editor) {
|
||||
$('#reset-button').on('click', common.resetEditor);
|
||||
}
|
||||
|
||||
if (common.challengeName) {
|
||||
window.ga('send', 'event', 'Challenge', 'load', common.challengeName);
|
||||
}
|
||||
|
||||
$('#complete-courseware-dialog').on('hidden.bs.modal', function() {
|
||||
common.editor.focus();
|
||||
});
|
||||
|
||||
$('#trigger-issue-modal').on('click', function() {
|
||||
$('#issue-modal').modal('show');
|
||||
});
|
||||
|
||||
$('#trigger-help-modal').on('click', function() {
|
||||
$('#help-modal').modal('show');
|
||||
});
|
||||
|
||||
$('#trigger-reset-modal').on('click', function() {
|
||||
$('#reset-modal').modal('show');
|
||||
});
|
||||
|
||||
$('#trigger-pair-modal').on('click', function() {
|
||||
$('#pair-modal').modal('show');
|
||||
});
|
||||
|
||||
$('#completed-courseware').on('click', function() {
|
||||
$('#complete-courseware-dialog').modal('show');
|
||||
});
|
||||
|
||||
$('#help-ive-found-a-bug-wiki-article').on('click', function() {
|
||||
window.open(
|
||||
'https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/' +
|
||||
"Help-I've-Found-a-Bug",
|
||||
'_blank'
|
||||
);
|
||||
});
|
||||
|
||||
$('#search-issue').on('click', function() {
|
||||
var queryIssue = window.location.href.toString();
|
||||
window.open(
|
||||
'https://github.com/FreeCodeCamp/FreeCodeCamp/issues?q=' +
|
||||
'is:issue is:all ' +
|
||||
(common.challengeName) +
|
||||
' OR ' +
|
||||
queryIssue
|
||||
.substr(queryIssue.lastIndexOf('challenges/') + 11)
|
||||
.replace('/', ''), '_blank');
|
||||
});
|
||||
|
||||
$('#gist-share').on('click', function() {
|
||||
var gistWindow = window.open('', '_blank');
|
||||
|
||||
$('#gist-share')
|
||||
.attr('disabled', 'true')
|
||||
.removeClass('btn-danger')
|
||||
.addClass('btn-warning disabled');
|
||||
|
||||
function createCORSRequest(method, url) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
if ('withCredentials' in xhr) {
|
||||
xhr.open(method, url, true);
|
||||
} else if (typeof XDomainRequest !== 'undefined') {
|
||||
xhr = new XDomainRequest();
|
||||
xhr.open(method, url);
|
||||
} else {
|
||||
xhr = null;
|
||||
}
|
||||
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
|
||||
return xhr;
|
||||
}
|
||||
|
||||
var request = createCORSRequest('post', 'https://api.github.com/gists');
|
||||
if (!request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
request.onload = function() {
|
||||
if (
|
||||
request.readyState === 4 &&
|
||||
request.status === 201 &&
|
||||
request.statusText === 'Created'
|
||||
) {
|
||||
gistWindow.location.href =
|
||||
JSON.parse(request.responseText)['html_url'];
|
||||
}
|
||||
};
|
||||
|
||||
var description = common.username ?
|
||||
'http://www.freecodecamp.com/' + common.username + ' \'s s' :
|
||||
'S';
|
||||
|
||||
var data = {
|
||||
description: description + 'olution for ' + common.challengeName,
|
||||
public: true,
|
||||
files: {}
|
||||
};
|
||||
var queryIssue = window.location.href.toString().split('#?')[0];
|
||||
var filename = queryIssue
|
||||
.substr(queryIssue.lastIndexOf('challenges/') + 11)
|
||||
.replace('/', '') + '.js';
|
||||
|
||||
data.files[filename] = {
|
||||
content: '// ' +
|
||||
common.challengeName +
|
||||
'\n' +
|
||||
(common.username ? '// Author: @' + common.username + '\n' : '') +
|
||||
'// Challenge: ' +
|
||||
queryIssue +
|
||||
'\n' +
|
||||
'// Learn to Code at Free Code Camp (www.freecodecamp.com)' +
|
||||
'\n\n' +
|
||||
window.editor.getValue().trim()
|
||||
};
|
||||
|
||||
request.send(JSON.stringify(data));
|
||||
});
|
||||
});
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -0,0 +1,37 @@
|
||||
// depends on: codeUri
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
localStorage,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
var codeStorage = {
|
||||
getStoredValue(key) {
|
||||
return '' + localStorage.getItem(key + 'Val');
|
||||
},
|
||||
|
||||
isAlive: function() {
|
||||
var val = this.getStoredValue();
|
||||
return val !== 'null' &&
|
||||
val !== 'undefined' &&
|
||||
(val && val.length > 0);
|
||||
},
|
||||
|
||||
updateStorage(key, code) {
|
||||
if (
|
||||
!localStorage ||
|
||||
typeof localStorage !== 'function' ||
|
||||
!key ||
|
||||
typeof key !== 'string'
|
||||
) {
|
||||
console.log('unable to save to storage');
|
||||
return code;
|
||||
}
|
||||
localStorage.setItem(key + 'Val', code);
|
||||
}
|
||||
};
|
||||
|
||||
common.codeStorage = codeStorage;
|
||||
|
||||
return common;
|
||||
}(window, window.common));
|
||||
@@ -1,82 +0,0 @@
|
||||
// depends on: codeUri
|
||||
window.common = (function(global ) {
|
||||
const {
|
||||
$,
|
||||
localStorage,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
const {
|
||||
codeUri
|
||||
} = common;
|
||||
|
||||
var CodeStorageProps = {
|
||||
version: 0.01,
|
||||
keyVersion: 'saveVersion',
|
||||
keyValue: null,
|
||||
updateWait: 2000,
|
||||
updateTimeoutId: null
|
||||
};
|
||||
|
||||
var CodeStorage = {
|
||||
hasSaved: function() {
|
||||
return this.updateTimeoutId === null;
|
||||
},
|
||||
|
||||
onSave: function(func) {
|
||||
this.eventArray.push(func);
|
||||
},
|
||||
|
||||
setSaveKey: function(key) {
|
||||
this.keyValue = key + 'Val';
|
||||
},
|
||||
|
||||
getStoredValue: function() {
|
||||
return '' + localStorage.getItem(this.keyValue);
|
||||
},
|
||||
|
||||
setEditor: function(editor) {
|
||||
this.editor = editor;
|
||||
},
|
||||
|
||||
isAlive: function() {
|
||||
var val = this.getStoredValue();
|
||||
return val !== 'null' &&
|
||||
val !== 'undefined' &&
|
||||
(val && val.length > 0);
|
||||
},
|
||||
|
||||
updateStorage: function() {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
var value = this.editor.getValue();
|
||||
// store in localStorage
|
||||
localStorage.setItem(this.keyValue, value);
|
||||
// also store code in URL
|
||||
codeUri.querify(value);
|
||||
} else {
|
||||
console.log('no web storage');
|
||||
}
|
||||
this.updateTimeoutId = null;
|
||||
}
|
||||
};
|
||||
|
||||
function codeStorageFactory(editor, challengeName) {
|
||||
var codeStorage = Object.create(CodeStorage);
|
||||
$.extend(codeStorage, CodeStorageProps);
|
||||
codeStorage.setEditor(editor);
|
||||
codeStorage.setSaveKey(challengeName);
|
||||
return codeStorage;
|
||||
}
|
||||
|
||||
var savedVersion = localStorage.getItem(CodeStorageProps.keyVersion);
|
||||
if (savedVersion === null) {
|
||||
localStorage.setItem(
|
||||
CodeStorageProps.keyVersion,
|
||||
CodeStorageProps.version
|
||||
);
|
||||
}
|
||||
|
||||
common.codeStorageFactory = codeStorageFactory;
|
||||
|
||||
return common;
|
||||
}(window, window.common));
|
||||
@@ -5,25 +5,35 @@ window.common = (function(global) {
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
if (!CodeMirror) {
|
||||
return {};
|
||||
const { challengeType = '0' } = common;
|
||||
|
||||
if (
|
||||
!CodeMirror ||
|
||||
challengeType === '0' ||
|
||||
challengeType === '7'
|
||||
) {
|
||||
common.editor = {};
|
||||
return common;
|
||||
}
|
||||
|
||||
var delay;
|
||||
var codeStorageFactory = common.codeStorageFactory;
|
||||
|
||||
var editor = CodeMirror.fromTextArea(document.getElementById('codeEditor'), {
|
||||
lint: true,
|
||||
lineNumbers: true,
|
||||
mode: 'javascript',
|
||||
theme: 'monokai',
|
||||
runnable: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
scrollbarStyle: 'null',
|
||||
lineWrapping: true,
|
||||
gutters: ['CodeMirror-lint-markers']
|
||||
});
|
||||
var editor = CodeMirror.fromTextArea(
|
||||
document.getElementById('codeEditor'),
|
||||
{
|
||||
lint: true,
|
||||
lineNumbers: true,
|
||||
mode: 'javascript',
|
||||
theme: 'monokai',
|
||||
runnable: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
scrollbarStyle: 'null',
|
||||
lineWrapping: true,
|
||||
gutters: ['CodeMirror-lint-markers']
|
||||
}
|
||||
);
|
||||
|
||||
editor.setSize('100%', 'auto');
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
jailed,
|
||||
document: doc,
|
||||
Rx: { Observable, Disposable },
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
if (!jailed) {
|
||||
return (code, cb) => cb(new Error('Could not load jailed plugin'));
|
||||
}
|
||||
|
||||
// obtaining absolute path of this script
|
||||
var scripts = doc.getElementsByTagName('script');
|
||||
var path = scripts[scripts.length - 1].src
|
||||
.split('?')[0]
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.join('/') + '/';
|
||||
|
||||
var Sandbox = {
|
||||
startTimeout() {
|
||||
this.timeoutId = setTimeout(() => {
|
||||
this.error = new Error('Plugin failed to initialize');
|
||||
this.destroyPlugin();
|
||||
}, 3000);
|
||||
},
|
||||
cancelTimout() {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
},
|
||||
createPlugin() {
|
||||
this.plugin = new jailed.Plugin(path + 'plugin.js');
|
||||
this.startTimeout();
|
||||
this.plugin.whenConnected(() => {
|
||||
this.endTimeout();
|
||||
});
|
||||
},
|
||||
destroyPlugin() {
|
||||
this.plugin.disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// sends the input to the plugin for evaluation
|
||||
common.detectLoops = function detectLoops({ code = '', ...rest }) {
|
||||
return new Observable(function(observer) {
|
||||
const sandbox = Object.create(Sandbox);
|
||||
|
||||
sandbox.createPlugin();
|
||||
sandbox.plugin.whenConnected(() => {
|
||||
sandbox.plugin.remote.run(code, (err, data) => {
|
||||
observer.onNext({ ...rest, err, code, data });
|
||||
observer.onCompleted();
|
||||
});
|
||||
});
|
||||
|
||||
sandbox.plugin.whenDisconnected(() => {
|
||||
if (sandbox.disposed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sandbox.error) {
|
||||
observer.onNext({ ...rest, err: sandbox.error, code, data: {} });
|
||||
}
|
||||
observer.onCompleted();
|
||||
});
|
||||
|
||||
return new Disposable(() => {
|
||||
sandbox.disposed = true;
|
||||
sandbox.destroyPlugin();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -1,57 +0,0 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
jailed,
|
||||
document: doc,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
if (!jailed) {
|
||||
return (code, cb) => cb(new Error('Could not load jailed plugin'));
|
||||
}
|
||||
|
||||
// obtaining absolute path of this script
|
||||
var scripts = doc.getElementsByTagName('script');
|
||||
var path = scripts[scripts.length - 1].src
|
||||
.split('?')[0]
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.join('/') + '/';
|
||||
|
||||
var sandbox = {
|
||||
timeoutId: null,
|
||||
|
||||
startTimeout() {
|
||||
this.timeoutId = setTimeout(() => {
|
||||
this.disconnect();
|
||||
}, 3000);
|
||||
},
|
||||
endTimeout() {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
},
|
||||
createPlugin() {
|
||||
this.plugin = new jailed.Plugin(path + 'plugin.js');
|
||||
this.plugin.whenDisconnected(() => {
|
||||
this.endTimeout();
|
||||
});
|
||||
},
|
||||
destroyPlugin() {
|
||||
this.plugin.disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// sends the input to the plugin for evaluation
|
||||
common.detectLoops = function detectLoops(code, callback) {
|
||||
sandbox.createPlugin();
|
||||
sandbox.plugin.whenConnected(() => {
|
||||
this.endTimeout();
|
||||
|
||||
sandbox.plugin.remote.run(code, callback);
|
||||
});
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -0,0 +1,119 @@
|
||||
// what are the executeChallenge functions?
|
||||
// Should be responsible for starting after a submit action
|
||||
// Should not be responsible for displaying results
|
||||
// Should return results
|
||||
// should grab editor value
|
||||
// depends on main editor
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
ga,
|
||||
Rx: { Observable },
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
let attempts = 0;
|
||||
const detectFunctionCall = /function\s*?\(|function\s+\w+\s*?\(/gi;
|
||||
const detectUnsafeJQ = /\$\s*?\(\s*?\$\s*?\)/gi;
|
||||
const detectUnsafeConsoleCall = /if\s\(null\)\sconsole\.log\(1\);/gi;
|
||||
|
||||
common.executeChallenge$ = function executeChallenge$() {
|
||||
const code = common.editor.getValue();
|
||||
const head = common.arrayToNewLineString(common.head);
|
||||
const tail = common.arrayToNewLineString(common.tail);
|
||||
|
||||
attempts++;
|
||||
|
||||
ga('send', 'event', 'Challenge', 'ran-code', common.challengeName);
|
||||
|
||||
let openingComments = code.match(/\/\*/gi);
|
||||
|
||||
// checks if the number of opening comments(/*) matches the number of
|
||||
// closing comments(*/)
|
||||
Observable.just({ code })
|
||||
.flatMap(code => {
|
||||
if (
|
||||
code.match(/\$\s*?\(\s*?\$\s*?\)/gi) &&
|
||||
openingComments &&
|
||||
openingComments.length > code.match(/\*\//gi).length
|
||||
) {
|
||||
|
||||
return Observable.just({
|
||||
err: 'SyntaxError: Unfinished multi-line comment',
|
||||
code: code
|
||||
});
|
||||
}
|
||||
|
||||
if (code.match(detectUnsafeJQ)) {
|
||||
return Observable.just({
|
||||
err: 'Unsafe $($)',
|
||||
output: 'Unsafe $($)',
|
||||
code: code
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
code.match(/function/g) &&
|
||||
!code.match(detectFunctionCall)
|
||||
) {
|
||||
return Observable.just({
|
||||
err: 'SyntaxError: Unsafe or unfinished function declaration',
|
||||
code: code
|
||||
});
|
||||
}
|
||||
|
||||
if (common.challengeType === '0') {
|
||||
let openingComments = code.match(/\<\!\-\-/gi);
|
||||
let closingComments = code.match(/\-\-\>/gi) || [];
|
||||
if (
|
||||
openingComments &&
|
||||
openingComments.length > closingComments.length
|
||||
) {
|
||||
return Observable.just({
|
||||
err: 'SyntaxError: Unfinished HTML comment',
|
||||
code: code
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (code.match(detectUnsafeConsoleCall)) {
|
||||
return Observable.just({
|
||||
err: 'Invalid if (null) console.log(1); detected',
|
||||
code: code
|
||||
});
|
||||
}
|
||||
|
||||
// add head and tail and detect loops
|
||||
return Observable.just({ code: head + code + tail })
|
||||
.map(code => {
|
||||
if (common.challengeType === common.challengeTypes.HTML) {
|
||||
return common.getScriptCode(code);
|
||||
}
|
||||
|
||||
return common.addTestsToString(
|
||||
common.removeComments(code),
|
||||
common.tests.slice()
|
||||
);
|
||||
})
|
||||
.flatMap(common.detectLoops)
|
||||
.flatMap(({ err, code, data, userTests }) => {
|
||||
if (err) {
|
||||
return Observable.just({
|
||||
err,
|
||||
code,
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
return common.runTests$({
|
||||
output: data.output.replace(/\\\"/gi, ''),
|
||||
data,
|
||||
code,
|
||||
userTests
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -1,94 +0,0 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
ga,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
let attempts = 0;
|
||||
common.executeChallenge = function executeChallenge(shouldTest) {
|
||||
const editorValue = common.editor.getValue();
|
||||
const head = common.arrayToNewLineString(common.head);
|
||||
const tail = common.arrayToNewLineString(common.tail);
|
||||
const codeOutput = common.codeOutput;
|
||||
|
||||
attempts++;
|
||||
ga('send', 'event', 'Challenge', 'ran-code', common.challengeName);
|
||||
$('#testSuite').empty();
|
||||
|
||||
const openingComments = editorValue.match(/\/\*/gi);
|
||||
|
||||
// checks if the number of opening comments(/*) matches the number of
|
||||
// closing comments(*/)
|
||||
if (
|
||||
editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi) &&
|
||||
openingComments &&
|
||||
openingComments.length > editorValue.match(/\*\//gi).length
|
||||
) {
|
||||
|
||||
common.editor.setValue(common.editor.getValue() + '*/');
|
||||
codeOutput.setValue('Unfinished multi-line comment');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (common.challengeType !== '0') {
|
||||
let userJavaScript = head + common.editor.getValue() + tail;
|
||||
|
||||
userJavaScript = common.removeComments(userJavaScript);
|
||||
userJavaScript = common.addTests(userJavaScript);
|
||||
// simple fix in case the user forgets to invoke their function
|
||||
|
||||
if (userJavaScript.match(/function/gi)) {
|
||||
if (userJavaScript.match(/function\s*?\(|function\s+\w+\s*?\(/gi)) {
|
||||
common.runInSand(userJavaScript, function(err, message) {
|
||||
if (err) {
|
||||
codeOutput.setValue(err);
|
||||
if (shouldTest) {
|
||||
common.runTests('Error', null);
|
||||
}
|
||||
} else {
|
||||
codeOutput.setValue(message.output);
|
||||
codeOutput.setValue(codeOutput.getValue().replace(/\\\"/gi, ''));
|
||||
message.input = common.removeLogs(message.input);
|
||||
if (shouldTest) {
|
||||
common.runTests(null, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
codeOutput.setValue('Unsafe or unfinished function declaration');
|
||||
}
|
||||
} else {
|
||||
common.runInSand(userJavaScript, function(cls, message) {
|
||||
|
||||
if (cls) {
|
||||
codeOutput.setValue(message.error);
|
||||
if (shouldTest) {
|
||||
common.runTests('Error', null);
|
||||
}
|
||||
} else {
|
||||
codeOutput.setValue(message.output);
|
||||
codeOutput.setValue(codeOutput.getValue().replace(/\\\"/gi, ''));
|
||||
message.input = common.removeLogs(message.input);
|
||||
|
||||
if (shouldTest) {
|
||||
common.runTests(null, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
!editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi) &&
|
||||
common.challengeType === '0'
|
||||
) {
|
||||
common.safeHTMLRun(shouldTest);
|
||||
} else {
|
||||
common.workerError('Unsafe $($)');
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
var $marginFix = $('.innerMarginFix');
|
||||
$marginFix.css('min-height', $marginFix.height());
|
||||
}, 1000);
|
||||
};
|
||||
return common;
|
||||
}(window));
|
||||
@@ -10,6 +10,18 @@ window.common = (function(global) {
|
||||
common.tail = common.tail || [];
|
||||
common.salt = Math.random();
|
||||
|
||||
common.challengeTypes = {
|
||||
HTML: '0',
|
||||
JS: '1',
|
||||
VIDEO: '2',
|
||||
ZIPLINE: '3',
|
||||
BASEJUMP: '4',
|
||||
BONFIRE: '5',
|
||||
HIKES: '6',
|
||||
STEP: '7'
|
||||
};
|
||||
|
||||
|
||||
common.arrayToNewLineString = function arrayToNewLineString(seedData) {
|
||||
seedData = Array.isArray(seedData) ? seedData : [seedData];
|
||||
return seedData.reduce(function(seed, line) {
|
||||
@@ -67,13 +79,11 @@ window.common = (function(global) {
|
||||
return str.replace(logRegex, '');
|
||||
};
|
||||
|
||||
common.reassembleTest = function reassembleTest(test, data) {
|
||||
var lineNum = test.line;
|
||||
var regexp = new RegExp('\/\/' + lineNum + common.salt);
|
||||
return data.input.replace(regexp, test.text);
|
||||
common.reassembleTest = function reassembleTest(code = '', { line, text }) {
|
||||
var regexp = new RegExp('\/\/' + line + common.salt);
|
||||
return code.replace(regexp, text);
|
||||
};
|
||||
|
||||
|
||||
return common;
|
||||
})();
|
||||
|
||||
@@ -7,18 +7,17 @@ window.common = (function(global) {
|
||||
|
||||
const { challengeType = '0' } = common;
|
||||
|
||||
if (!CodeMirror) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (
|
||||
!CodeMirror ||
|
||||
challengeType === '0' ||
|
||||
challengeType === '7'
|
||||
) {
|
||||
return {};
|
||||
common.updateOutputDisplay = () => {};
|
||||
common.appendToOutputDisplay = () => {};
|
||||
return common;
|
||||
}
|
||||
|
||||
common.codeOutput = CodeMirror.fromTextArea(
|
||||
var codeOutput = CodeMirror.fromTextArea(
|
||||
doc.getElementById('codeOutput'),
|
||||
{
|
||||
lineNumbers: false,
|
||||
@@ -29,7 +28,7 @@ window.common = (function(global) {
|
||||
}
|
||||
);
|
||||
|
||||
common.codeOutput.setValue(`
|
||||
codeOutput.setValue(`
|
||||
/**
|
||||
* Your output will go here.
|
||||
* Console.log() -type statements
|
||||
@@ -38,7 +37,17 @@ window.common = (function(global) {
|
||||
*/'
|
||||
`);
|
||||
|
||||
common.codeOutput.setSize('100%', '100%');
|
||||
codeOutput.setSize('100%', '100%');
|
||||
|
||||
common.updateOutputDisplay = function updateOutputDisplay(str) {
|
||||
codeOutput.setValue(str);
|
||||
return str;
|
||||
};
|
||||
|
||||
common.appendToOutputDisplay = function appendToOutputDisplay(str) {
|
||||
codeOutput.setValue(codeOutput.getValue() + str);
|
||||
return str;
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -0,0 +1,132 @@
|
||||
window.common = (function({ common = { init: [] }}) {
|
||||
|
||||
common.lockTop = function lockTop() {
|
||||
var magiVal;
|
||||
|
||||
if ($(window).width() >= 990) {
|
||||
if ($('.editorScrollDiv').html()) {
|
||||
|
||||
magiVal = $(window).height() - $('.navbar').height();
|
||||
|
||||
if (magiVal < 0) {
|
||||
magiVal = 0;
|
||||
}
|
||||
$('.editorScrollDiv').css('height', magiVal - 85 + 'px');
|
||||
}
|
||||
|
||||
magiVal = $(window).height() - $('.navbar').height();
|
||||
|
||||
if (magiVal < 0) {
|
||||
magiVal = 0;
|
||||
}
|
||||
|
||||
$('.scroll-locker')
|
||||
.css('min-height', $('.editorScrollDiv').height())
|
||||
.css('height', magiVal - 185);
|
||||
} else {
|
||||
$('.editorScrollDiv').css('max-height', 500 + 'px');
|
||||
|
||||
$('.scroll-locker')
|
||||
.css('position', 'inherit')
|
||||
.css('top', 'inherit')
|
||||
.css('width', '100%')
|
||||
.css('max-height', '85%');
|
||||
}
|
||||
};
|
||||
|
||||
common.init.push(function($) {
|
||||
// fakeiphone positioning hotfix
|
||||
if (
|
||||
$('.iphone-position').html() ||
|
||||
$('.iphone').html()
|
||||
) {
|
||||
var startIphonePosition = parseInt(
|
||||
$('.iphone-position')
|
||||
.css('top')
|
||||
.replace('px', ''),
|
||||
10
|
||||
);
|
||||
|
||||
var startIphone = parseInt(
|
||||
$('.iphone')
|
||||
.css('top')
|
||||
.replace('px', ''),
|
||||
10
|
||||
);
|
||||
|
||||
$(window).on('scroll', function() {
|
||||
var courseHeight = $('.courseware-height').height();
|
||||
var courseTop = $('.courseware-height').offset().top;
|
||||
var windowScrollTop = $(window).scrollTop();
|
||||
var phoneHeight = $('.iphone-position').height();
|
||||
|
||||
if (courseHeight + courseTop - windowScrollTop - phoneHeight <= 0) {
|
||||
$('.iphone-position').css(
|
||||
'top',
|
||||
startIphonePosition +
|
||||
courseHeight +
|
||||
courseTop -
|
||||
windowScrollTop -
|
||||
phoneHeight
|
||||
);
|
||||
|
||||
$('.iphone').css(
|
||||
'top',
|
||||
startIphonePosition +
|
||||
courseHeight +
|
||||
courseTop -
|
||||
windowScrollTop -
|
||||
phoneHeight +
|
||||
120
|
||||
);
|
||||
} else {
|
||||
$('.iphone-position').css('top', startIphonePosition);
|
||||
$('.iphone').css('top', startIphone);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ($('.scroll-locker').html()) {
|
||||
|
||||
if ($('.scroll-locker').html()) {
|
||||
common.lockTop();
|
||||
$(window).on('resize', function() {
|
||||
common.lockTop();
|
||||
});
|
||||
$(window).on('scroll', function() {
|
||||
common.lockTop();
|
||||
});
|
||||
}
|
||||
|
||||
var execInProgress = false;
|
||||
|
||||
// why is this not $???
|
||||
document
|
||||
.getElementById('scroll-locker')
|
||||
.addEventListener(
|
||||
'previewUpdateSpy',
|
||||
function(e) {
|
||||
if (execInProgress) {
|
||||
return null;
|
||||
}
|
||||
execInProgress = true;
|
||||
setTimeout(function() {
|
||||
if (
|
||||
$($('.scroll-locker').children()[0]).height() - 800 > e.detail
|
||||
) {
|
||||
$('.scroll-locker').scrollTop(e.detail);
|
||||
} else {
|
||||
var scrollTop = $($('.scroll-locker').children()[0]).height();
|
||||
|
||||
$('.scroll-locker').animate({ scrollTop: scrollTop }, 175);
|
||||
}
|
||||
execInProgress = false;
|
||||
}, 750);
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return common;
|
||||
}());
|
||||
@@ -0,0 +1,56 @@
|
||||
window.common = (function({ common = { init: [] } }) {
|
||||
common.init.push(function($) {
|
||||
$('#report-issue').on('click', function() {
|
||||
var textMessage = [
|
||||
'Challenge [',
|
||||
(common.challengeName || window.location.pathname),
|
||||
'](',
|
||||
window.location.href,
|
||||
') has an issue.\n',
|
||||
'User Agent is: <code>',
|
||||
navigator.userAgent,
|
||||
'</code>.\n',
|
||||
'Please describe how to reproduce this issue, and include ',
|
||||
'links to screenshots if possible.\n\n'
|
||||
].join('');
|
||||
|
||||
if (
|
||||
common.editor &&
|
||||
typeof common.editor.getValue === 'function' &&
|
||||
common.editor.getValue().trim()
|
||||
) {
|
||||
var type;
|
||||
switch (common.challengeType) {
|
||||
case common.challengeTypes.html:
|
||||
type = 'html';
|
||||
break;
|
||||
case common.challengeTypes.js:
|
||||
case common.challengeTypes.bonfire:
|
||||
type = 'javascript';
|
||||
break;
|
||||
default:
|
||||
type = '';
|
||||
}
|
||||
|
||||
textMessage += [
|
||||
'My code:\n```',
|
||||
type,
|
||||
'\n',
|
||||
common.editor.getValue(),
|
||||
'\n```\n\n'
|
||||
].join('');
|
||||
}
|
||||
|
||||
textMessage = encodeURIComponent(textMessage);
|
||||
|
||||
$('#issue-modal').modal('hide');
|
||||
window.open(
|
||||
'https://github.com/freecodecamp/freecodecamp/issues/new?&body=' +
|
||||
textMessage,
|
||||
'_blank'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -0,0 +1,35 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
Rx: { Observable },
|
||||
chai,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
common.runTests$ = function runTests$({ code, userTests, ...rest }) {
|
||||
|
||||
return Observable.from(userTests)
|
||||
.map(function(test) {
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
const assert = chai.assert;
|
||||
const editor = { getValue() { return code; }};
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
try {
|
||||
if (test) {
|
||||
/* eslint-disable no-eval */
|
||||
eval(common.reassembleTest(test, code));
|
||||
/* eslint-enable no-eval */
|
||||
}
|
||||
} catch (e) {
|
||||
test.err = e.message;
|
||||
}
|
||||
|
||||
return test;
|
||||
})
|
||||
.toArray()
|
||||
.map(tests => ({ ...rest, tests }));
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -1,43 +0,0 @@
|
||||
window.common = (function({ common = { init: [] }}) {
|
||||
common.runTests = function runTests(err, data) {
|
||||
var head = common.arrayToNewLineString(common.head);
|
||||
var tail = common.arrayToNewLineString(common.tail);
|
||||
var userTests = Array.isArray(userTests) ? userTests.slice() : [];
|
||||
|
||||
var editorValue = head + common.editor.getValue() + tail;
|
||||
|
||||
if (err) {
|
||||
userTests = [{
|
||||
text: 'Program Execution Failure',
|
||||
err
|
||||
}];
|
||||
return userTests;
|
||||
}
|
||||
|
||||
// Add blocks to test exploits here!
|
||||
if (editorValue.match(/if\s\(null\)\sconsole\.log\(1\);/gi)) {
|
||||
userTests = [{
|
||||
text: 'Program Execution Failure',
|
||||
err: 'Invalid if (null) console.log(1); detected'
|
||||
}];
|
||||
|
||||
return userTests;
|
||||
}
|
||||
|
||||
return userTests.map(function(test) {
|
||||
try {
|
||||
if (test) {
|
||||
/* eslint-disable no-eval, no-unused-vars */
|
||||
var output = eval(common.reassembleTest(test, data));
|
||||
/* eslint-enable no-eval, no-unused-vars */
|
||||
}
|
||||
} catch (e) {
|
||||
test.err = e.message;
|
||||
}
|
||||
|
||||
return test;
|
||||
});
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -0,0 +1,70 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
$,
|
||||
ga = (() => {}),
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
common.showCompletion = function showCompletion() {
|
||||
var time = Math.floor(Date.now()) - window.started;
|
||||
|
||||
ga(
|
||||
'send',
|
||||
'event',
|
||||
'Challenge',
|
||||
'solved',
|
||||
common.challengeName + ', Time: ' + time + ', Attempts: ' + 0
|
||||
);
|
||||
|
||||
var bonfireSolution = common.editor.getValue();
|
||||
var didCompleteWith = $('#completed-with').val() || null;
|
||||
|
||||
$('#complete-courseware-dialog').modal('show');
|
||||
$('#complete-courseware-dialog .modal-header').click();
|
||||
|
||||
$('#submit-challenge').click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$('#submit-challenge')
|
||||
.attr('disabled', 'true')
|
||||
.removeClass('btn-primary')
|
||||
.addClass('btn-warning disabled');
|
||||
|
||||
var $checkmarkContainer = $('#checkmark-container');
|
||||
$checkmarkContainer.css({ height: $checkmarkContainer.innerHeight() });
|
||||
|
||||
$('#challenge-checkmark')
|
||||
.addClass('zoomOutUp')
|
||||
// .removeClass('zoomInDown')
|
||||
.delay(1000)
|
||||
.queue(function(next) {
|
||||
$(this).replaceWith(
|
||||
'<div id="challenge-spinner" ' +
|
||||
'class="animated zoomInUp inner-circles-loader">' +
|
||||
'submitting...</div>'
|
||||
);
|
||||
next();
|
||||
});
|
||||
|
||||
$.post(
|
||||
'/completed-bonfire/', {
|
||||
challengeInfo: {
|
||||
challengeId: common.challengeId,
|
||||
challengeName: common.challengeName,
|
||||
completedWith: didCompleteWith,
|
||||
challengeType: common.challengeType,
|
||||
solution: bonfireSolution
|
||||
}
|
||||
},
|
||||
function(res) {
|
||||
if (res) {
|
||||
window.location =
|
||||
'/challenges/next-challenge?id=' + common.challengeId;
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
@@ -8,15 +8,11 @@ window.common = (function({ $, common = { init: [] }}) {
|
||||
const submitModalId = '#challenge-step-modal';
|
||||
|
||||
function getPreviousStep($challengeSteps) {
|
||||
var length = $challengeSteps.length;
|
||||
var $prevStep = false;
|
||||
var prevStepIndex = 0;
|
||||
$challengeSteps.each(function(index) {
|
||||
var $step = $(this);
|
||||
if (
|
||||
!$step.hasClass('hidden') &&
|
||||
index + 1 !== length
|
||||
) {
|
||||
if (!$step.hasClass('hidden')) {
|
||||
prevStepIndex = index - 1;
|
||||
}
|
||||
});
|
||||
@@ -50,7 +46,8 @@ window.common = (function({ $, common = { init: [] }}) {
|
||||
var prevStep = getPreviousStep($(stepClass));
|
||||
$(this)
|
||||
.parent()
|
||||
.removeClass('fadeOutLeft')
|
||||
.parent()
|
||||
.removeClass('slideInLeft slideInRight')
|
||||
.addClass('animated fadeOutRight fast-animation')
|
||||
.delay(250)
|
||||
.queue(function(prev) {
|
||||
@@ -58,7 +55,7 @@ window.common = (function({ $, common = { init: [] }}) {
|
||||
if (prevStep) {
|
||||
$(prevStep)
|
||||
.removeClass('hidden')
|
||||
.removeClass('slideInRight')
|
||||
.removeClass('fadeOutLeft fadeOutRight')
|
||||
.addClass('animated slideInLeft fast-animation')
|
||||
.delay(500)
|
||||
.queue(function(prev) {
|
||||
@@ -74,7 +71,8 @@ window.common = (function({ $, common = { init: [] }}) {
|
||||
var nextStep = getNextStep($(stepClass));
|
||||
$(this)
|
||||
.parent()
|
||||
.removeClass('fadeOutRight')
|
||||
.parent()
|
||||
.removeClass('slideInRight slideInLeft')
|
||||
.addClass('animated fadeOutLeft fast-animation')
|
||||
.delay(250)
|
||||
.queue(function(next) {
|
||||
@@ -82,7 +80,7 @@ window.common = (function({ $, common = { init: [] }}) {
|
||||
if (nextStep) {
|
||||
$(nextStep)
|
||||
.removeClass('hidden')
|
||||
.removeClass('slideInLeft')
|
||||
.removeClass('fadeOutRight fadeOutLeft')
|
||||
.addClass('animated slideInRight fast-animation')
|
||||
.delay(500)
|
||||
.queue(function(next) {
|
||||
@@ -0,0 +1,40 @@
|
||||
window.common = (function({ common = { init: [] } }) {
|
||||
var libraryIncludes = `
|
||||
<link
|
||||
rel='stylesheet'
|
||||
href='//cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.0/animate.min.css'
|
||||
/>
|
||||
<link
|
||||
rel='stylesheet'
|
||||
href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css'
|
||||
/>
|
||||
|
||||
<link
|
||||
rel='stylesheet'
|
||||
href='//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css'
|
||||
/>
|
||||
|
||||
<style>
|
||||
body { padding: 0px 3px 0px 3px; }
|
||||
</style>
|
||||
`;
|
||||
|
||||
var iFrameScript = "<script src='/js/iFrameScripts.js'></script>";
|
||||
|
||||
|
||||
common.updatePreview = function updatePreview(code = '', shouldTest = false) {
|
||||
const previewFrame = document.getElementById('preview');
|
||||
const preview = previewFrame.contentDocument ||
|
||||
previewFrame.contentWindow.document;
|
||||
if (!preview) {
|
||||
return code;
|
||||
}
|
||||
preview.open();
|
||||
preview.write(libraryIncludes + code + (shouldTest ? iFrameScript : ''));
|
||||
preview.close();
|
||||
|
||||
return code;
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
||||
Reference in New Issue
Block a user