Progress towards refactoring
This commit is contained in:
@ -25,7 +25,7 @@
|
||||
"font-awesome": "~4.3.0",
|
||||
"moment": "~2.10.2",
|
||||
"angular-bootstrap": "~0.13.0",
|
||||
"jshint": "~2.7.0",
|
||||
"jshint": "~2.9.0",
|
||||
"lightbox2": "~2.8.1",
|
||||
"rxjs": "~4.0.6",
|
||||
"CodeMirror": "~5.8.0",
|
||||
|
File diff suppressed because it is too large
Load Diff
82
client/commonFramework/codeStorage.js
Normal file
82
client/commonFramework/codeStorage.js
Normal file
@ -0,0 +1,82 @@
|
||||
// depends on: codeUri
|
||||
window.common = (function(global ) {
|
||||
const {
|
||||
$,
|
||||
localStorage,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
const {
|
||||
codeUri
|
||||
} = common;
|
||||
|
||||
var CodeStorageProps = {
|
||||
version: 0.01,
|
||||
keyVersion: 'saveVersion',
|
||||
keyValue: null,
|
||||
updateWait: 2000,
|
||||
updateTimeoutId: null
|
||||
};
|
||||
|
||||
var CodeStorage = {
|
||||
hasSaved: function() {
|
||||
return this.updateTimeoutId === null;
|
||||
},
|
||||
|
||||
onSave: function(func) {
|
||||
this.eventArray.push(func);
|
||||
},
|
||||
|
||||
setSaveKey: function(key) {
|
||||
this.keyValue = key + 'Val';
|
||||
},
|
||||
|
||||
getStoredValue: function() {
|
||||
return '' + localStorage.getItem(this.keyValue);
|
||||
},
|
||||
|
||||
setEditor: function(editor) {
|
||||
this.editor = editor;
|
||||
},
|
||||
|
||||
isAlive: function() {
|
||||
var val = this.getStoredValue();
|
||||
return val !== 'null' &&
|
||||
val !== 'undefined' &&
|
||||
(val && val.length > 0);
|
||||
},
|
||||
|
||||
updateStorage: function() {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
var value = this.editor.getValue();
|
||||
// store in localStorage
|
||||
localStorage.setItem(this.keyValue, value);
|
||||
// also store code in URL
|
||||
codeUri.querify(value);
|
||||
} else {
|
||||
console.log('no web storage');
|
||||
}
|
||||
this.updateTimeoutId = null;
|
||||
}
|
||||
};
|
||||
|
||||
function codeStorageFactory(editor, challengeName) {
|
||||
var codeStorage = Object.create(CodeStorage);
|
||||
$.extend(codeStorage, CodeStorageProps);
|
||||
codeStorage.setEditor(editor);
|
||||
codeStorage.setSaveKey(challengeName);
|
||||
return codeStorage;
|
||||
}
|
||||
|
||||
var savedVersion = localStorage.getItem(CodeStorageProps.keyVersion);
|
||||
if (savedVersion === null) {
|
||||
localStorage.setItem(
|
||||
CodeStorageProps.keyVersion,
|
||||
CodeStorageProps.version
|
||||
);
|
||||
}
|
||||
|
||||
common.codeStorageFactory = codeStorageFactory;
|
||||
|
||||
return common;
|
||||
}(window, window.common));
|
118
client/commonFramework/codeUri.js
Normal file
118
client/commonFramework/codeUri.js
Normal file
@ -0,0 +1,118 @@
|
||||
// store code in the URL
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
encodeURIComponent: encode,
|
||||
decodeURIComponent: decode,
|
||||
location,
|
||||
history,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
const {
|
||||
replaceScriptTags,
|
||||
replaceSafeTags,
|
||||
replaceFormActionAttr,
|
||||
replaceFccfaaAttr
|
||||
} = common;
|
||||
|
||||
function encodeFcc(val) {
|
||||
return replaceScriptTags(replaceFormActionAttr(val));
|
||||
}
|
||||
|
||||
function decodeFcc(val) {
|
||||
return replaceSafeTags(replaceFccfaaAttr(val));
|
||||
}
|
||||
|
||||
var codeUri = {
|
||||
encode: function(code) {
|
||||
return encode(code);
|
||||
},
|
||||
decode: function(code) {
|
||||
try {
|
||||
return decode(code);
|
||||
} catch (ignore) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
isInQuery: function(query) {
|
||||
var decoded = codeUri.decode(query);
|
||||
if (!decoded || typeof decoded.split !== 'function') {
|
||||
return false;
|
||||
}
|
||||
return decoded
|
||||
.split('?')
|
||||
.splice(1)
|
||||
.reduce(function(found, param) {
|
||||
var key = param.split('=')[0];
|
||||
if (key === 'solution') {
|
||||
return true;
|
||||
}
|
||||
return found;
|
||||
}, false);
|
||||
},
|
||||
isAlive: function() {
|
||||
return codeUri.enabled &&
|
||||
codeUri.isInQuery(location.search) ||
|
||||
codeUri.isInQuery(location.hash);
|
||||
},
|
||||
parse: function() {
|
||||
if (!codeUri.enabled) {
|
||||
return null;
|
||||
}
|
||||
var query;
|
||||
if (location.search && codeUri.isInQuery(location.search)) {
|
||||
query = location.search.replace(/^\?/, '');
|
||||
if (history && typeof history.replaceState === 'function') {
|
||||
history.replaceState(
|
||||
history.state,
|
||||
null,
|
||||
location.href.split('?')[0]
|
||||
);
|
||||
location.hash = '#?' + encodeFcc(query);
|
||||
}
|
||||
} else {
|
||||
query = location.hash.replace(/^\#\?/, '');
|
||||
}
|
||||
if (!query) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return query
|
||||
.split('&')
|
||||
.reduce(function(solution, param) {
|
||||
var key = param.split('=')[0];
|
||||
var value = param.split('=')[1];
|
||||
if (key === 'solution') {
|
||||
return decodeFcc(codeUri.decode(value || ''));
|
||||
}
|
||||
return solution;
|
||||
}, null);
|
||||
},
|
||||
querify: function(solution) {
|
||||
if (!codeUri.enabled) {
|
||||
return null;
|
||||
}
|
||||
if (history && typeof history.replaceState === 'function') {
|
||||
history.replaceState(
|
||||
history.state,
|
||||
null,
|
||||
'?solution=' + codeUri.encode(encodeFcc(solution))
|
||||
);
|
||||
} else {
|
||||
location.hash = '?solution=' +
|
||||
codeUri.encode(encodeFcc(solution));
|
||||
}
|
||||
|
||||
return solution;
|
||||
},
|
||||
enabled: true
|
||||
};
|
||||
|
||||
common.init.push(function() {
|
||||
codeUri.parse();
|
||||
});
|
||||
|
||||
common.codeUri = codeUri;
|
||||
|
||||
return common;
|
||||
}(window));
|
113
client/commonFramework/createEditor.js
Normal file
113
client/commonFramework/createEditor.js
Normal file
@ -0,0 +1,113 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
CodeMirror,
|
||||
emmetCodeMirror,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
if (!CodeMirror) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var delay;
|
||||
var codeStorageFactory = common.codeStorageFactory;
|
||||
|
||||
var editor = CodeMirror.fromTextArea(document.getElementById('codeEditor'), {
|
||||
lint: true,
|
||||
lineNumbers: true,
|
||||
mode: 'javascript',
|
||||
theme: 'monokai',
|
||||
runnable: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
scrollbarStyle: 'null',
|
||||
lineWrapping: true,
|
||||
gutters: ['CodeMirror-lint-markers']
|
||||
});
|
||||
|
||||
editor.setSize('100%', 'auto');
|
||||
|
||||
var codeStorage = common.codeStorage =
|
||||
codeStorageFactory(editor, common.challengeName);
|
||||
|
||||
editor.on('keyup', function() {
|
||||
clearTimeout(codeStorage.updateTimeoutId);
|
||||
codeStorage.updateTimeoutId = setTimeout(
|
||||
codeStorage.updateStorage.bind(codeStorage),
|
||||
codeStorage.updateWait
|
||||
);
|
||||
});
|
||||
|
||||
// Initialize CodeMirror editor with a nice html5 canvas demo.
|
||||
editor.on('keyup', function() {
|
||||
clearTimeout(delay);
|
||||
delay = setTimeout(common.updatePreview, 300);
|
||||
});
|
||||
|
||||
editor.setOption('extraKeys', {
|
||||
Tab: function(cm) {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add');
|
||||
} else {
|
||||
var spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
||||
cm.replaceSelection(spaces);
|
||||
}
|
||||
},
|
||||
'Shift-Tab': function(cm) {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('subtract');
|
||||
} else {
|
||||
var spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
||||
cm.replaceSelection(spaces);
|
||||
}
|
||||
},
|
||||
'Ctrl-Enter': function() {
|
||||
common.executeChallenge(true);
|
||||
return false;
|
||||
},
|
||||
'Cmd-Enter': function() {
|
||||
common.executeChallenge(true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var info = editor.getScrollInfo();
|
||||
|
||||
var after = editor.charCoords({
|
||||
line: editor.getCursor().line + 1,
|
||||
ch: 0
|
||||
}, 'local').top;
|
||||
|
||||
if (info.top + info.clientHeight < after) {
|
||||
editor.scrollTo(null, after - info.clientHeight + 3);
|
||||
}
|
||||
|
||||
if (emmetCodeMirror) {
|
||||
emmetCodeMirror(
|
||||
editor,
|
||||
{
|
||||
'Cmd-E': 'emmet.expand_abbreviation',
|
||||
Tab: 'emmet.expand_abbreviation_with_tab',
|
||||
Enter: 'emmet.insert_formatted_line_break_only'
|
||||
}
|
||||
);
|
||||
}
|
||||
common.init.push(function() {
|
||||
var editorValue;
|
||||
if (common.codeUri.isAlive()) {
|
||||
editorValue = common.codeUri.parse();
|
||||
} else {
|
||||
editorValue = codeStorage.isAlive() ?
|
||||
codeStorage.getStoredValue() :
|
||||
common.seed;
|
||||
}
|
||||
|
||||
editor.setValue(common.replaceSafeTags(editorValue));
|
||||
editor.refresh();
|
||||
});
|
||||
|
||||
common.editor = editor;
|
||||
|
||||
return common;
|
||||
}(window));
|
57
client/commonFramework/detectLoops.js
Normal file
57
client/commonFramework/detectLoops.js
Normal file
@ -0,0 +1,57 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
jailed,
|
||||
document: doc,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
if (!jailed) {
|
||||
return (code, cb) => cb(new Error('Could not load jailed plugin'));
|
||||
}
|
||||
|
||||
// obtaining absolute path of this script
|
||||
var scripts = doc.getElementsByTagName('script');
|
||||
var path = scripts[scripts.length - 1].src
|
||||
.split('?')[0]
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.join('/') + '/';
|
||||
|
||||
var sandbox = {
|
||||
timeoutId: null,
|
||||
|
||||
startTimeout() {
|
||||
this.timeoutId = setTimeout(() => {
|
||||
this.disconnect();
|
||||
}, 3000);
|
||||
},
|
||||
endTimeout() {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
},
|
||||
createPlugin() {
|
||||
this.plugin = new jailed.Plugin(path + 'plugin.js');
|
||||
this.plugin.whenDisconnected(() => {
|
||||
this.endTimeout();
|
||||
});
|
||||
},
|
||||
destroyPlugin() {
|
||||
this.plugin.disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// sends the input to the plugin for evaluation
|
||||
common.detectLoops = function detectLoops(code, callback) {
|
||||
sandbox.createPlugin();
|
||||
sandbox.plugin.whenConnected(() => {
|
||||
this.endTimeout();
|
||||
|
||||
sandbox.plugin.remote.run(code, callback);
|
||||
});
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
28
client/commonFramework/displayTestResults.js
Normal file
28
client/commonFramework/displayTestResults.js
Normal file
@ -0,0 +1,28 @@
|
||||
window.common = (function({ $, common = { init: [] }}) {
|
||||
|
||||
common.displayTestResults = function displayTestResults(data = []) {
|
||||
data.forEach(({ err = false, text = '' }) => {
|
||||
var iconClass = err ?
|
||||
'"ion-checkmark-circled big-success-icon"' :
|
||||
'"ion-close-circled big-error-icon"';
|
||||
|
||||
$('#testSuite').children().remove();
|
||||
$('<div></div>').html(`
|
||||
<div class='row'>
|
||||
<div class='col-xs-2 text-center'>
|
||||
<i class=${iconClass}></i>
|
||||
</div>
|
||||
<div class='col-xs-10 test-output wrappable'>
|
||||
${text.split('message: ').pop().replace(/\'\);/g, '')}
|
||||
</div>
|
||||
<div class='ten-pixel-break'/>
|
||||
</div>
|
||||
`)
|
||||
.appendTo($('#testSuite'));
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
94
client/commonFramework/executeChallenge.js
Normal file
94
client/commonFramework/executeChallenge.js
Normal file
@ -0,0 +1,94 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
ga,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
let attempts = 0;
|
||||
common.executeChallenge = function executeChallenge(shouldTest) {
|
||||
const editorValue = common.editor.getValue();
|
||||
const head = common.arrayToNewLineString(common.head);
|
||||
const tail = common.arrayToNewLineString(common.tail);
|
||||
const codeOutput = common.codeOutput;
|
||||
|
||||
attempts++;
|
||||
ga('send', 'event', 'Challenge', 'ran-code', common.challengeName);
|
||||
$('#testSuite').empty();
|
||||
|
||||
const openingComments = editorValue.match(/\/\*/gi);
|
||||
|
||||
// checks if the number of opening comments(/*) matches the number of
|
||||
// closing comments(*/)
|
||||
if (
|
||||
editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi) &&
|
||||
openingComments &&
|
||||
openingComments.length > editorValue.match(/\*\//gi).length
|
||||
) {
|
||||
|
||||
common.editor.setValue(common.editor.getValue() + '*/');
|
||||
codeOutput.setValue('Unfinished multi-line comment');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (common.challengeType !== '0') {
|
||||
let userJavaScript = head + common.editor.getValue() + tail;
|
||||
|
||||
userJavaScript = common.removeComments(userJavaScript);
|
||||
userJavaScript = common.addTests(userJavaScript);
|
||||
// simple fix in case the user forgets to invoke their function
|
||||
|
||||
if (userJavaScript.match(/function/gi)) {
|
||||
if (userJavaScript.match(/function\s*?\(|function\s+\w+\s*?\(/gi)) {
|
||||
common.runInSand(userJavaScript, function(err, message) {
|
||||
if (err) {
|
||||
codeOutput.setValue(err);
|
||||
if (shouldTest) {
|
||||
common.runTests('Error', null);
|
||||
}
|
||||
} else {
|
||||
codeOutput.setValue(message.output);
|
||||
codeOutput.setValue(codeOutput.getValue().replace(/\\\"/gi, ''));
|
||||
message.input = common.removeLogs(message.input);
|
||||
if (shouldTest) {
|
||||
common.runTests(null, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
codeOutput.setValue('Unsafe or unfinished function declaration');
|
||||
}
|
||||
} else {
|
||||
common.runInSand(userJavaScript, function(cls, message) {
|
||||
|
||||
if (cls) {
|
||||
codeOutput.setValue(message.error);
|
||||
if (shouldTest) {
|
||||
common.runTests('Error', null);
|
||||
}
|
||||
} else {
|
||||
codeOutput.setValue(message.output);
|
||||
codeOutput.setValue(codeOutput.getValue().replace(/\\\"/gi, ''));
|
||||
message.input = common.removeLogs(message.input);
|
||||
|
||||
if (shouldTest) {
|
||||
common.runTests(null, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
!editorValue.match(/\$\s*?\(\s*?\$\s*?\)/gi) &&
|
||||
common.challengeType === '0'
|
||||
) {
|
||||
common.safeHTMLRun(shouldTest);
|
||||
} else {
|
||||
common.workerError('Unsafe $($)');
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
var $marginFix = $('.innerMarginFix');
|
||||
$marginFix.css('min-height', $marginFix.height());
|
||||
}, 1000);
|
||||
};
|
||||
return common;
|
||||
}(window));
|
44
client/commonFramework/outputDisplay.js
Normal file
44
client/commonFramework/outputDisplay.js
Normal file
@ -0,0 +1,44 @@
|
||||
window.common = (function(global) {
|
||||
const {
|
||||
CodeMirror,
|
||||
document: doc,
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
const { challengeType = '0' } = common;
|
||||
|
||||
if (!CodeMirror) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (
|
||||
challengeType === '0' ||
|
||||
challengeType === '7'
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
common.codeOutput = CodeMirror.fromTextArea(
|
||||
doc.getElementById('codeOutput'),
|
||||
{
|
||||
lineNumbers: false,
|
||||
mode: 'text',
|
||||
theme: 'monokai',
|
||||
readOnly: 'nocursor',
|
||||
lineWrapping: true
|
||||
}
|
||||
);
|
||||
|
||||
common.codeOutput.setValue(`
|
||||
/**
|
||||
* Your output will go here.
|
||||
* Console.log() -type statements
|
||||
* will appear in your browser\'s
|
||||
* DevTools JavaScript console.
|
||||
*/'
|
||||
`);
|
||||
|
||||
common.codeOutput.setSize('100%', '100%');
|
||||
|
||||
return common;
|
||||
}(window));
|
43
client/commonFramework/runTests.js
Normal file
43
client/commonFramework/runTests.js
Normal file
@ -0,0 +1,43 @@
|
||||
window.common = (function({ common = { init: [] }}) {
|
||||
common.runTests = function runTests(err, data) {
|
||||
var head = common.arrayToNewLineString(common.head);
|
||||
var tail = common.arrayToNewLineString(common.tail);
|
||||
var userTests = Array.isArray(userTests) ? userTests.slice() : [];
|
||||
|
||||
var editorValue = head + common.editor.getValue() + tail;
|
||||
|
||||
if (err) {
|
||||
userTests = [{
|
||||
text: 'Program Execution Failure',
|
||||
err
|
||||
}];
|
||||
return userTests;
|
||||
}
|
||||
|
||||
// Add blocks to test exploits here!
|
||||
if (editorValue.match(/if\s\(null\)\sconsole\.log\(1\);/gi)) {
|
||||
userTests = [{
|
||||
text: 'Program Execution Failure',
|
||||
err: 'Invalid if (null) console.log(1); detected'
|
||||
}];
|
||||
|
||||
return userTests;
|
||||
}
|
||||
|
||||
return userTests.map(function(test) {
|
||||
try {
|
||||
if (test) {
|
||||
/* eslint-disable no-eval, no-unused-vars */
|
||||
var output = eval(common.reassembleTest(test, data));
|
||||
/* eslint-enable no-eval, no-unused-vars */
|
||||
}
|
||||
} catch (e) {
|
||||
test.err = e.message;
|
||||
}
|
||||
|
||||
return test;
|
||||
});
|
||||
};
|
||||
|
||||
return common;
|
||||
}(window));
|
202
client/commonFramework/stepChallenge.js
Normal file
202
client/commonFramework/stepChallenge.js
Normal file
@ -0,0 +1,202 @@
|
||||
window.common = (function({ $, common = { init: [] }}) {
|
||||
const stepClass = '.challenge-step';
|
||||
const prevBtnClass = '.challenge-step-btn-prev';
|
||||
const nextBtnClass = '.challenge-step-btn-next';
|
||||
const actionBtnClass = '.challenge-step-btn-action';
|
||||
const finishBtnClass = '.challenge-step-btn-finish';
|
||||
const submitBtnId = '#challenge-step-btn-submit';
|
||||
const submitModalId = '#challenge-step-modal';
|
||||
|
||||
function getPreviousStep($challengeSteps) {
|
||||
var length = $challengeSteps.length;
|
||||
var $prevStep = false;
|
||||
var prevStepIndex = 0;
|
||||
$challengeSteps.each(function(index) {
|
||||
var $step = $(this);
|
||||
if (
|
||||
!$step.hasClass('hidden') &&
|
||||
index + 1 !== length
|
||||
) {
|
||||
prevStepIndex = index - 1;
|
||||
}
|
||||
});
|
||||
|
||||
$prevStep = $challengeSteps[prevStepIndex];
|
||||
|
||||
return $prevStep;
|
||||
}
|
||||
|
||||
function getNextStep($challengeSteps) {
|
||||
var length = $challengeSteps.length;
|
||||
var $nextStep = false;
|
||||
var nextStepIndex = 0;
|
||||
$challengeSteps.each(function(index) {
|
||||
var $step = $(this);
|
||||
if (
|
||||
!$step.hasClass('hidden') &&
|
||||
index + 1 !== length
|
||||
) {
|
||||
nextStepIndex = index + 1;
|
||||
}
|
||||
});
|
||||
|
||||
$nextStep = $challengeSteps[nextStepIndex];
|
||||
|
||||
return $nextStep;
|
||||
}
|
||||
|
||||
function handlePrevStepClick(e) {
|
||||
e.preventDefault();
|
||||
var prevStep = getPreviousStep($(stepClass));
|
||||
$(this)
|
||||
.parent()
|
||||
.removeClass('fadeOutLeft')
|
||||
.addClass('animated fadeOutRight fast-animation')
|
||||
.delay(250)
|
||||
.queue(function(prev) {
|
||||
$(this).addClass('hidden');
|
||||
if (prevStep) {
|
||||
$(prevStep)
|
||||
.removeClass('hidden')
|
||||
.removeClass('slideInRight')
|
||||
.addClass('animated slideInLeft fast-animation')
|
||||
.delay(500)
|
||||
.queue(function(prev) {
|
||||
prev();
|
||||
});
|
||||
}
|
||||
prev();
|
||||
});
|
||||
}
|
||||
|
||||
function handleNextStepClick(e) {
|
||||
e.preventDefault();
|
||||
var nextStep = getNextStep($(stepClass));
|
||||
$(this)
|
||||
.parent()
|
||||
.removeClass('fadeOutRight')
|
||||
.addClass('animated fadeOutLeft fast-animation')
|
||||
.delay(250)
|
||||
.queue(function(next) {
|
||||
$(this).addClass('hidden');
|
||||
if (nextStep) {
|
||||
$(nextStep)
|
||||
.removeClass('hidden')
|
||||
.removeClass('slideInLeft')
|
||||
.addClass('animated slideInRight fast-animation')
|
||||
.delay(500)
|
||||
.queue(function(next) {
|
||||
next();
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
function handleActionClick(e) {
|
||||
var props = common.challengeSeed[0] ||
|
||||
{ stepIndex: [] };
|
||||
|
||||
var $el = $(this);
|
||||
var index = +$el.attr('id');
|
||||
var propIndex = props.stepIndex.indexOf(index);
|
||||
|
||||
if (propIndex === -1) {
|
||||
return $el
|
||||
.parent()
|
||||
.find('.disabled')
|
||||
.removeClass('disabled');
|
||||
}
|
||||
|
||||
// an API action
|
||||
// prevent link from opening
|
||||
e.preventDefault();
|
||||
var prop = props.properties[propIndex];
|
||||
var api = props.apis[propIndex];
|
||||
if (common[prop]) {
|
||||
return $el
|
||||
.parent()
|
||||
.find('.disabled')
|
||||
.removeClass('disabled');
|
||||
}
|
||||
$
|
||||
.post(api)
|
||||
.done(function(data) {
|
||||
// assume a boolean indicates passing
|
||||
if (typeof data === 'boolean') {
|
||||
return $el
|
||||
.parent()
|
||||
.find('.disabled')
|
||||
.removeClass('disabled');
|
||||
}
|
||||
// assume api returns string when fails
|
||||
$el
|
||||
.parent()
|
||||
.find('.disabled')
|
||||
.replaceWith('<p>' + data + '</p>');
|
||||
})
|
||||
.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(
|
||||
'<div id="challenge-spinner" ' +
|
||||
'class="animated zoomInUp inner-circles-loader">' +
|
||||
'submitting...</div>'
|
||||
);
|
||||
next();
|
||||
});
|
||||
|
||||
$.post(
|
||||
'/completed-bonfire/', {
|
||||
challengeInfo: {
|
||||
challengeId: common.challengeId,
|
||||
challengeName: common.challengeName,
|
||||
challengeType: common.challengeType
|
||||
}
|
||||
},
|
||||
function(res) {
|
||||
if (res) {
|
||||
window.location =
|
||||
'/challenges/next-challenge?id=' + common.challengeId;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
common.init.push(function($) {
|
||||
if (common.challengeType === '7') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$(prevBtnClass).click(handlePrevStepClick);
|
||||
$(nextBtnClass).click(handleNextStepClick);
|
||||
$(actionBtnClass).click(handleActionClick);
|
||||
$(finishBtnClass).click(handleFinishClick);
|
||||
});
|
||||
|
||||
return common;
|
||||
}(window));
|
79
client/commonFramework/utils.js
Normal file
79
client/commonFramework/utils.js
Normal file
@ -0,0 +1,79 @@
|
||||
window.common = (function(global) {
|
||||
// common namespace
|
||||
// all classes should be stored here
|
||||
// called at the beginning of dom ready
|
||||
const {
|
||||
common = { init: [] }
|
||||
} = global;
|
||||
|
||||
common.head = common.head || [];
|
||||
common.tail = common.tail || [];
|
||||
common.salt = Math.random();
|
||||
|
||||
common.arrayToNewLineString = function arrayToNewLineString(seedData) {
|
||||
seedData = Array.isArray(seedData) ? seedData : [seedData];
|
||||
return seedData.reduce(function(seed, line) {
|
||||
return '' + seed + line + '\n';
|
||||
}, '');
|
||||
};
|
||||
|
||||
common.seed = common.arrayToNewLineString(common.challengeSeed);
|
||||
|
||||
common.replaceScriptTags = function replaceScriptTags(value) {
|
||||
return value
|
||||
.replace(/<script>/gi, 'fccss')
|
||||
.replace(/<\/script>/gi, 'fcces');
|
||||
};
|
||||
|
||||
common.replaceSafeTags = function replaceSafeTags(value) {
|
||||
return value
|
||||
.replace(/fccss/gi, '<script>')
|
||||
.replace(/fcces/gi, '</script>');
|
||||
};
|
||||
|
||||
common.replaceFormActionAttr = function replaceFormAction(value) {
|
||||
return value.replace(/<form[^>]*>/, function(val) {
|
||||
return val.replace(/action(\s*?)=/, 'fccfaa$1=');
|
||||
});
|
||||
};
|
||||
|
||||
common.replaceFccfaaAttr = function replaceFccfaaAttr(value) {
|
||||
return value.replace(/<form[^>]*>/, function(val) {
|
||||
return val.replace(/fccfaa(\s*?)=/, 'action$1=');
|
||||
});
|
||||
};
|
||||
|
||||
common.scopejQuery = function scopejQuery(str) {
|
||||
return str
|
||||
.replace(/\$/gi, 'j$')
|
||||
.replace(/document/gi, 'jdocument')
|
||||
.replace(/jQuery/gi, 'jjQuery');
|
||||
};
|
||||
|
||||
common.unScopeJQuery = function unScopeJQuery(str) {
|
||||
return str
|
||||
.replace(/j\$/gi, '$')
|
||||
.replace(/jdocument/gi, 'document')
|
||||
.replace(/jjQuery/gi, 'jQuery');
|
||||
};
|
||||
|
||||
const commentRegex = /(\/\*[^(\*\/)]*\*\/)|([ \n]\/\/[^\n]*)/g;
|
||||
common.removeLogs = function removeComments(str) {
|
||||
return str.replace(commentRegex, '');
|
||||
};
|
||||
|
||||
const logRegex = /(console\.[\w]+\s*\(.*\;)/g;
|
||||
common.removeLogs = function removeLogs(str) {
|
||||
return str.replace(logRegex, '');
|
||||
};
|
||||
|
||||
common.reassembleTest = function reassembleTest(test, data) {
|
||||
var lineNum = test.line;
|
||||
var regexp = new RegExp('\/\/' + lineNum + common.salt);
|
||||
return data.input.replace(regexp, test.text);
|
||||
};
|
||||
|
||||
|
||||
return common;
|
||||
})();
|
||||
|
@ -1,26 +1,26 @@
|
||||
/* eslint-disable no-undef, no-unused-vars, no-native-reassign */
|
||||
(function() {
|
||||
window.$ = parent.$;
|
||||
window.$(function() {
|
||||
var _ = parent._;
|
||||
var chai = parent.chai;
|
||||
var expect = chai.expect;
|
||||
var tests = parent.tests;
|
||||
var editor = parent.editorValueForIFrame;
|
||||
var common = parent.common;
|
||||
var editorValue = common.editor.getValue();
|
||||
|
||||
setTimeout(function() {
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
var thisTest = true;
|
||||
try {
|
||||
/* eslint-disable no-eval */
|
||||
eval(parent.tests[i]);
|
||||
/* eslint-enable no-eval */
|
||||
} catch (err) {
|
||||
allTestsGood = false;
|
||||
thisTest = false;
|
||||
parent.postError(JSON.stringify(err.message.split(':').shift()));
|
||||
} finally {
|
||||
if (thisTest) {
|
||||
parent.postSuccess(JSON.stringify(tests[i].split(',').pop().replace(
|
||||
/\'/g, '').replace(/\)/, '')));
|
||||
}
|
||||
}
|
||||
common.tests.forEach(test => {
|
||||
try {
|
||||
/* eslint-disable no-eval */
|
||||
eval(test);
|
||||
/* eslint-enable no-eval */
|
||||
} catch (e) {
|
||||
parent.postError(JSON.stringify(e.message.split(':').shift()));
|
||||
} finally {
|
||||
parent.postSuccess(
|
||||
JSON.stringify(
|
||||
test.split(',').pop().replace(/\'/g, '').replace(/\)/, '')
|
||||
)
|
||||
);
|
||||
}
|
||||
}, 10);
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
@ -11,11 +11,11 @@ function importScript(url, error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
function run(code) {
|
||||
function run(code, cb) {
|
||||
var err = null;
|
||||
var result = {
|
||||
input: code,
|
||||
output: null,
|
||||
error: null,
|
||||
type: null
|
||||
};
|
||||
|
||||
@ -24,10 +24,15 @@ function run(code) {
|
||||
result.type = typeof codeExec;
|
||||
result.output = stringify(codeExec);
|
||||
} catch (e) {
|
||||
result.error = e.message;
|
||||
err = e;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
cb(err.message, null);
|
||||
} else {
|
||||
cb(null, result);
|
||||
}
|
||||
|
||||
application.remote.output(result);
|
||||
self.close();
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ var Rx = require('rx'),
|
||||
concat = require('gulp-concat'),
|
||||
uglify = require('gulp-uglify'),
|
||||
merge = require('merge-stream'),
|
||||
babel = require('gulp-babel'),
|
||||
|
||||
// react app
|
||||
webpack = require('webpack-stream'),
|
||||
@ -375,6 +376,7 @@ gulp.task('js', function() {
|
||||
|
||||
gulp.src(paths.js)
|
||||
.pipe(plumber({ errorHandler: errorHandler }))
|
||||
.pipe(babel())
|
||||
.pipe(__DEV__ ? gutil.noop() : uglify())
|
||||
);
|
||||
|
||||
@ -395,7 +397,6 @@ gulp.task('js', function() {
|
||||
});
|
||||
|
||||
// commonFramework depend on iFrameScripts
|
||||
// sandbox depends on plugin
|
||||
gulp.task('dependents', ['js'], function() {
|
||||
var manifestName = 'dependents-manifest.json';
|
||||
var dest = paths.publicJs;
|
||||
@ -406,6 +407,7 @@ gulp.task('dependents', ['js'], function() {
|
||||
|
||||
return gulp.src(paths.dependents)
|
||||
.pipe(plumber({ errorHandler: errorHandler }))
|
||||
.pipe(babel())
|
||||
.pipe(__DEV__ ? gutil.noop() : uglify())
|
||||
.pipe(revReplace({ manifest: manifest }))
|
||||
.pipe(gulp.dest(dest))
|
||||
|
@ -57,6 +57,7 @@
|
||||
"forever": "~0.15.1",
|
||||
"frameguard": "~0.2.2",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-babel": "^5.3.0",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-eslint": "^1.1.0",
|
||||
"gulp-inject": "^3.0.0",
|
||||
|
263
public/js/fauxJQuery.js
Normal file
263
public/js/fauxJQuery.js
Normal file
@ -0,0 +1,263 @@
|
||||
function $() {
|
||||
if (!(this instanceof $)) {
|
||||
return new $();
|
||||
}
|
||||
}
|
||||
function returnThis() { return this; }
|
||||
function return$() { return $; }
|
||||
var methods = [
|
||||
'add',
|
||||
'addBack',
|
||||
'addClass',
|
||||
'after',
|
||||
'ajaxComplete',
|
||||
'ajaxError',
|
||||
'ajaxSend',
|
||||
'ajaxStart',
|
||||
'ajaxStop',
|
||||
'ajaxSuccess',
|
||||
'andSelf',
|
||||
'animate',
|
||||
'append',
|
||||
'appendTo',
|
||||
'attr',
|
||||
'before',
|
||||
'bind',
|
||||
'blur',
|
||||
'callbacksadd',
|
||||
'callbacksdisable',
|
||||
'callbacksdisabled',
|
||||
'callbacksempty',
|
||||
'callbacksfire',
|
||||
'callbacksfired',
|
||||
'callbacksfireWith',
|
||||
'callbackshas',
|
||||
'callbackslock',
|
||||
'callbackslocked',
|
||||
'callbacksremove',
|
||||
'change',
|
||||
'children',
|
||||
'clearQueue',
|
||||
'click',
|
||||
'clone',
|
||||
'closest',
|
||||
'contents',
|
||||
'context',
|
||||
'css',
|
||||
'data',
|
||||
'dblclick',
|
||||
'delay',
|
||||
'delegate',
|
||||
'dequeue',
|
||||
'detach',
|
||||
'die',
|
||||
'each',
|
||||
'empty',
|
||||
'end',
|
||||
'eq',
|
||||
'error',
|
||||
'fadeIn',
|
||||
'fadeOut',
|
||||
'fadeTo',
|
||||
'fadeToggle',
|
||||
'filter',
|
||||
'find',
|
||||
'finish',
|
||||
'first',
|
||||
'focus',
|
||||
'focusin',
|
||||
'focusout',
|
||||
'get',
|
||||
'has',
|
||||
'hasClass',
|
||||
'height',
|
||||
'hide',
|
||||
'hover',
|
||||
'html',
|
||||
'index',
|
||||
'innerHeight',
|
||||
'innerWidth',
|
||||
'insertAfter',
|
||||
'insertBefore',
|
||||
'is',
|
||||
'jQuery',
|
||||
'jquery',
|
||||
'keydown',
|
||||
'keypress',
|
||||
'keyup',
|
||||
'last',
|
||||
'length',
|
||||
'live',
|
||||
'load',
|
||||
'load',
|
||||
'map',
|
||||
'mousedown',
|
||||
'mouseenter',
|
||||
'mouseleave',
|
||||
'mousemove',
|
||||
'mouseout',
|
||||
'mouseover',
|
||||
'mouseup',
|
||||
'next',
|
||||
'nextAll',
|
||||
'nextUntil',
|
||||
'not',
|
||||
'off',
|
||||
'offset',
|
||||
'offsetParent',
|
||||
'on',
|
||||
'one',
|
||||
'outerHeight',
|
||||
'outerWidth',
|
||||
'parent',
|
||||
'parents',
|
||||
'parentsUntil',
|
||||
'position',
|
||||
'prepend',
|
||||
'prependTo',
|
||||
'prev',
|
||||
'prevAll',
|
||||
'prevUntil',
|
||||
'promise',
|
||||
'prop',
|
||||
'pushStack',
|
||||
'queue',
|
||||
'ready',
|
||||
'remove',
|
||||
'removeAttr',
|
||||
'removeClass',
|
||||
'removeData',
|
||||
'removeProp',
|
||||
'replaceAll',
|
||||
'replaceWith',
|
||||
'resize',
|
||||
'scroll',
|
||||
'scrollLeft',
|
||||
'scrollTop',
|
||||
'select',
|
||||
'selector',
|
||||
'serialize',
|
||||
'serializeArray',
|
||||
'show',
|
||||
'siblings',
|
||||
'size',
|
||||
'slice',
|
||||
'slideDown',
|
||||
'slideToggle',
|
||||
'slideUp',
|
||||
'stop',
|
||||
'submit',
|
||||
'text',
|
||||
'toArray',
|
||||
'toggle',
|
||||
'toggle',
|
||||
'toggleClass',
|
||||
'trigger',
|
||||
'triggerHandler',
|
||||
'unbind',
|
||||
'undelegate',
|
||||
'unload',
|
||||
'unwrap',
|
||||
'val',
|
||||
'width',
|
||||
'wrap',
|
||||
'wrapAll',
|
||||
'wrapInner'
|
||||
];
|
||||
|
||||
var statics = [
|
||||
'ajax',
|
||||
'ajaxPrefilter',
|
||||
'ajaxSetup',
|
||||
'ajaxTransport',
|
||||
'boxModel',
|
||||
'browser',
|
||||
'Callbacks',
|
||||
'contains',
|
||||
'cssHooks',
|
||||
'cssNumber',
|
||||
'data',
|
||||
'Deferred',
|
||||
'dequeue',
|
||||
'each',
|
||||
'error',
|
||||
'extend',
|
||||
'fnextend',
|
||||
'fxinterval',
|
||||
'fxoff',
|
||||
'get',
|
||||
'getJSON',
|
||||
'getScript',
|
||||
'globalEval',
|
||||
'grep',
|
||||
'hasData',
|
||||
'holdReady',
|
||||
'inArray',
|
||||
'isArray',
|
||||
'isEmptyObject',
|
||||
'isFunction',
|
||||
'isNumeric',
|
||||
'isPlainObject',
|
||||
'isWindow',
|
||||
'isXMLDoc',
|
||||
'makeArray',
|
||||
'map',
|
||||
'merge',
|
||||
'noConflict',
|
||||
'noop',
|
||||
'now',
|
||||
'param',
|
||||
'parseHTML',
|
||||
'parseJSON',
|
||||
'parseXML',
|
||||
'post',
|
||||
'proxy',
|
||||
'queue',
|
||||
'removeData',
|
||||
'sub',
|
||||
'support',
|
||||
'trim',
|
||||
'type',
|
||||
'unique',
|
||||
'when',
|
||||
'always',
|
||||
'done',
|
||||
'fail',
|
||||
'isRejected',
|
||||
'isResolved',
|
||||
'notify',
|
||||
'notifyWith',
|
||||
'pipe',
|
||||
'progress',
|
||||
'promise',
|
||||
'reject',
|
||||
'rejectWith',
|
||||
'resolve',
|
||||
'resolveWith',
|
||||
'state',
|
||||
'then',
|
||||
'currentTarget',
|
||||
'data',
|
||||
'delegateTarget',
|
||||
'isDefaultPrevented',
|
||||
'isImmediatePropagationStopped',
|
||||
'isPropagationStopped',
|
||||
'metaKey',
|
||||
'namespace',
|
||||
'pageX',
|
||||
'pageY',
|
||||
'preventDefault',
|
||||
'relatedTarget',
|
||||
'result',
|
||||
'stopImmediatePropagation',
|
||||
'stopPropagation',
|
||||
'target',
|
||||
'timeStamp',
|
||||
'type',
|
||||
'which'
|
||||
];
|
||||
|
||||
var $Proto = {};
|
||||
methods.forEach(method => $Proto[method] = returnThis);
|
||||
statics.forEach(staticMeth => $[staticMeth] = return$);
|
||||
$.prototype = Object.create($);
|
@ -1,8 +1,8 @@
|
||||
extends ../layout-wide
|
||||
block content
|
||||
link(rel='stylesheet', href='/js/lib/codemirror/lib/codemirror.css')
|
||||
link(rel='stylesheet', href='/js/lib/codemirror/addon/lint/lint.css')
|
||||
link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css')
|
||||
link(rel='stylesheet', href='/bower_components/CodeMirror/lib/codemirror.css')
|
||||
link(rel='stylesheet', href='/bower_components/CodeMirror/addon/lint/lint.css')
|
||||
link(rel='stylesheet', href='/bower_components/CodeMirror/theme/monokai.css')
|
||||
link(rel='stylesheet', href='/css/ubuntu.css')
|
||||
|
||||
.row(ng-controller="pairedWithController")
|
||||
|
Reference in New Issue
Block a user