mumuki-laboratory 7.7.6 → 7.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -3
- data/Rakefile +7 -1
- data/app/assets/javascripts/mumuki_laboratory/application/alias-modes.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +66 -57
- data/app/assets/javascripts/mumuki_laboratory/application/codemirror-builder.js +28 -25
- data/app/assets/javascripts/mumuki_laboratory/application/codemirror.js +8 -10
- data/app/assets/javascripts/mumuki_laboratory/application/confirmation.js +2 -2
- data/app/assets/javascripts/mumuki_laboratory/application/console.js +41 -43
- data/app/assets/javascripts/mumuki_laboratory/application/csrf-token.js +9 -12
- data/app/assets/javascripts/mumuki_laboratory/application/custom-editor.js +11 -15
- data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +1 -3
- data/app/assets/javascripts/mumuki_laboratory/application/editors.js +104 -0
- data/app/assets/javascripts/mumuki_laboratory/application/elipsis.js +5 -4
- data/app/assets/javascripts/mumuki_laboratory/application/exercise.js +32 -0
- data/app/assets/javascripts/mumuki_laboratory/application/inputs.js +4 -2
- data/app/assets/javascripts/mumuki_laboratory/application/interval.js +2 -4
- data/app/assets/javascripts/mumuki_laboratory/application/kids.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/load-analytics.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/load-error-svg.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/messages.js +2 -2
- data/app/assets/javascripts/mumuki_laboratory/application/multiple-choice.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/multiple-scenarios.js +3 -6
- data/app/assets/javascripts/mumuki_laboratory/application/pin.js +3 -5
- data/app/assets/javascripts/mumuki_laboratory/application/progress.js +24 -6
- data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +20 -11
- data/app/assets/javascripts/mumuki_laboratory/application/speech-bubble-renderer.js +12 -5
- data/app/assets/javascripts/mumuki_laboratory/application/submission.js +19 -101
- data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +93 -0
- data/app/assets/javascripts/mumuki_laboratory/application/sync-mode.js +75 -0
- data/app/assets/javascripts/mumuki_laboratory/application/timer.js +5 -6
- data/app/assets/javascripts/mumuki_laboratory/application/tooltip.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/upload.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/user.js +1 -1
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_gs-board.scss +3 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kids.scss +0 -1
- data/app/controllers/application_controller.rb +1 -0
- data/app/helpers/{locale_helper.rb → globals_helper.rb} +6 -2
- data/app/mailers/user_mailer.rb +24 -11
- data/app/views/book/show.html.erb +1 -1
- data/app/views/exercises/show.html.erb +2 -0
- data/app/views/layouts/_main.html.erb +1 -2
- data/app/views/layouts/_progress.html.erb +1 -1
- data/app/views/layouts/_progress_bar.html.erb +7 -1
- data/app/views/layouts/application.html.erb +1 -1
- data/lib/mumuki/laboratory/controllers.rb +1 -0
- data/lib/mumuki/laboratory/controllers/incognito_mode.rb +28 -0
- data/lib/mumuki/laboratory/locales/en.yml +6 -4
- data/lib/mumuki/laboratory/locales/es.yml +6 -4
- data/lib/mumuki/laboratory/locales/pt.yml +4 -2
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/spec/dummy/db/schema.rb +2 -1
- data/spec/features/chapter_spec.rb +17 -0
- data/spec/features/exercise_flow_spec.rb +47 -2
- data/spec/features/home_public_flow_spec.rb +16 -0
- data/spec/javascripts/editors-spec.js +54 -0
- data/spec/javascripts/exercise-spec.js +22 -0
- data/spec/javascripts/global-spec.js +6 -0
- data/spec/javascripts/spec-helper.js +4 -0
- data/spec/javascripts/submissions-store-spec.js +44 -0
- data/spec/javascripts/sync-mode-spec.js +15 -0
- data/spec/mailers/user_mailer_spec.rb +18 -3
- data/spec/teaspoon_env.rb +8 -2
- data/vendor/assets/javascripts/codemirror-modes/gobstones.js +2 -3
- metadata +19 -5
- data/app/helpers/version_helper.rb +0 -5
@@ -1,18 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
CsrfToken.prototype = {
|
8
|
-
newRequest: function (data) {
|
1
|
+
mumuki.CsrfToken = (() => {
|
2
|
+
class CsrfToken {
|
3
|
+
constructor() {
|
4
|
+
this.value = $('meta[name="csrf-token"]').attr('content');
|
5
|
+
}
|
6
|
+
newRequest(data) {
|
9
7
|
var self = this;
|
10
8
|
data.beforeSend = function (xhr) {
|
11
9
|
xhr.setRequestHeader('X-CSRF-Token', self.value);
|
12
10
|
};
|
13
11
|
return data;
|
14
12
|
}
|
15
|
-
}
|
16
|
-
|
17
|
-
|
18
|
-
}(mumuki));
|
13
|
+
}
|
14
|
+
return CsrfToken;
|
15
|
+
})();
|
@@ -6,11 +6,16 @@
|
|
6
6
|
* @typedef {{getContent: () => EditorProperty}} CustomEditorSource
|
7
7
|
*/
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
/**
|
10
|
+
* This module allows custom editors to register
|
11
|
+
* content sources that can not me mapped to standard selectors {@code mu-custom-editor-value},
|
12
|
+
* {@code mu-custom-editor-extra} and {@code mu-custom-editor-test}
|
13
|
+
*
|
14
|
+
* CustomEditor sources are cleared after page reload even when using turbolinks
|
15
|
+
*/
|
16
|
+
mumuki.CustomEditor = (() => {
|
12
17
|
|
13
|
-
|
18
|
+
const CustomEditor = {
|
14
19
|
/**
|
15
20
|
* @type {CustomEditorSource[]}
|
16
21
|
*/
|
@@ -50,14 +55,5 @@ var mumuki = mumuki || {};
|
|
50
55
|
mumuki.CustomEditor.clearSources();
|
51
56
|
});
|
52
57
|
|
53
|
-
|
54
|
-
|
55
|
-
* content sources that can not me mapped to standard selectors {@code mu-custom-editor-value},
|
56
|
-
* {@code mu-custom-editor-extra} and {@code mu-custom-editor-test}
|
57
|
-
*
|
58
|
-
* CustomEditor sources are cleared after page reload even when using turbolinks
|
59
|
-
*
|
60
|
-
* @module mumuki.CustomEditor
|
61
|
-
*/
|
62
|
-
mumuki.CustomEditor = CustomEditor;
|
63
|
-
}(mumuki));
|
58
|
+
return CustomEditor;
|
59
|
+
})();
|
@@ -0,0 +1,104 @@
|
|
1
|
+
/**
|
2
|
+
* This module allows to read and write the current editor's contents
|
3
|
+
* regardless if it is an standard editor or a custom editor
|
4
|
+
*/
|
5
|
+
mumuki.editors = {
|
6
|
+
/**
|
7
|
+
* @type {() => void}
|
8
|
+
*/
|
9
|
+
_contentSyncer: null,
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Updates the current editor's content
|
13
|
+
*
|
14
|
+
* @param {string} content
|
15
|
+
*/
|
16
|
+
setContent(content) {
|
17
|
+
const $customEditor = $('#mu-custom-editor-value');
|
18
|
+
if ($customEditor.length) {
|
19
|
+
$customEditor.val(content);
|
20
|
+
} else {
|
21
|
+
mumuki.editor.setContent(content);
|
22
|
+
}
|
23
|
+
},
|
24
|
+
|
25
|
+
|
26
|
+
/**
|
27
|
+
* @returns {EditorProperty[]}
|
28
|
+
*/
|
29
|
+
getContents() {
|
30
|
+
return mumuki.CustomEditor.hasSources ?
|
31
|
+
mumuki.CustomEditor.getContents() :
|
32
|
+
this.getStandardEditorContents();
|
33
|
+
},
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Syncs and returns the content objects of the standard editor form
|
37
|
+
*
|
38
|
+
* This content object may include keys like {@code content},
|
39
|
+
* {@code content_extra} and {@code content_test}
|
40
|
+
*
|
41
|
+
* @returns {EditorProperty[]}
|
42
|
+
*/
|
43
|
+
getStandardEditorContents() {
|
44
|
+
this._syncContent();
|
45
|
+
return $('.new_solution').serializeArray();
|
46
|
+
},
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Answers a submission object with a key for each of the current
|
50
|
+
* editor sources.
|
51
|
+
*
|
52
|
+
* This method will use CustomEditor's sources if availble, or
|
53
|
+
* standard editor's content sources otherwise
|
54
|
+
*
|
55
|
+
* @returns {Submission}
|
56
|
+
*/
|
57
|
+
getSubmission() {
|
58
|
+
let content = {};
|
59
|
+
let contents = this.getContents();
|
60
|
+
contents.forEach((it) => {
|
61
|
+
content[it.name] = it.value;
|
62
|
+
});
|
63
|
+
return content;
|
64
|
+
},
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Copies current solution from it native rendering components
|
68
|
+
* to the appropriate submission form elements.
|
69
|
+
*
|
70
|
+
* Both editors and runners with a custom editor that don't register a source should
|
71
|
+
* register its own syncer function in order to {@link syncContent} work properly.
|
72
|
+
*
|
73
|
+
* @see registerContentSyncer
|
74
|
+
* @see CustomEditor#addSource
|
75
|
+
*/
|
76
|
+
_syncContent() {
|
77
|
+
if (this._contentSyncer) {
|
78
|
+
this._contentSyncer();
|
79
|
+
}
|
80
|
+
},
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Sets a content syncer, that will be used by {@link _syncContent}
|
84
|
+
* in ordet to dump solution into the submission form fields.
|
85
|
+
*
|
86
|
+
* Each editor should have its own syncer registered - otherwise previous or none may be used
|
87
|
+
* causing unpredicatble behaviours - or cleared by passing {@code null}.
|
88
|
+
*
|
89
|
+
* As a particular case, runners with custom editors that don't add sources using {@link CustomEditor#addSource}
|
90
|
+
* should set the {@code #mu-custom-editor-value} value within its syncer.
|
91
|
+
*
|
92
|
+
* @param {() => void} [syncer] the syncer, or null, if no sync'ing is needed
|
93
|
+
*/
|
94
|
+
registerContentSyncer(syncer = null) {
|
95
|
+
this._contentSyncer = syncer;
|
96
|
+
},
|
97
|
+
|
98
|
+
/**
|
99
|
+
* @param {CustomEditorSource} source
|
100
|
+
*/
|
101
|
+
addCustomSource(source) {
|
102
|
+
mumuki.CustomEditor.addSource(source)
|
103
|
+
}
|
104
|
+
};
|
@@ -7,7 +7,7 @@
|
|
7
7
|
//
|
8
8
|
//
|
9
9
|
// This module assumes the strings are already markdown-like escaped code strings, not plain code
|
10
|
-
|
10
|
+
mumuki.elipsis = (() => {
|
11
11
|
|
12
12
|
function elipsis(code) {
|
13
13
|
return code
|
@@ -16,8 +16,7 @@
|
|
16
16
|
.replace(/<description-for-student\[([^\]]*)\]@[\s\S]*?@description-for-student>/g, ' ... $1 ... ');
|
17
17
|
}
|
18
18
|
|
19
|
-
|
20
|
-
mumuki.elipsis.replaceHtml = () => {
|
19
|
+
elipsis.replaceHtml = () => {
|
21
20
|
let $elipsis = $('.mu-elipsis');
|
22
21
|
$elipsis.each((it, e) => {
|
23
22
|
let $e = $(e);
|
@@ -28,4 +27,6 @@
|
|
28
27
|
mumuki.load(() => {
|
29
28
|
mumuki.elipsis.replaceHtml();
|
30
29
|
});
|
31
|
-
|
30
|
+
|
31
|
+
return elipsis;
|
32
|
+
})();
|
@@ -0,0 +1,32 @@
|
|
1
|
+
mumuki.exercise = {
|
2
|
+
/**
|
3
|
+
* The current exercise's id
|
4
|
+
*
|
5
|
+
* @type {number}
|
6
|
+
* */
|
7
|
+
id: null,
|
8
|
+
|
9
|
+
/**
|
10
|
+
* The current exercise's layout
|
11
|
+
*
|
12
|
+
* @type {"input_right" | "input_bottom" | "input_primary" | "input_kindergarten"}
|
13
|
+
* */
|
14
|
+
layout: null,
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Set global current exercise information
|
18
|
+
*/
|
19
|
+
load() {
|
20
|
+
const $muExerciseId = $('#mu-exercise-id');
|
21
|
+
if ($muExerciseId.length) {
|
22
|
+
this.id = Number($muExerciseId.val());
|
23
|
+
// @ts-ignore
|
24
|
+
this.layout = $('#mu-exercise-layout').val();
|
25
|
+
} else {
|
26
|
+
this.id = null;
|
27
|
+
this.layout = null;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
mumuki.load(() => mumuki.exercise.load())
|
@@ -1,12 +1,14 @@
|
|
1
|
-
(() => {
|
1
|
+
mumuki.onInputsReady = (() => {
|
2
2
|
// Declares a `document.ready` handler that will be
|
3
3
|
// activated only when there is at least one element that match
|
4
4
|
// the given selector
|
5
|
-
|
5
|
+
function onInputsReady(inputsSelector, callback) {
|
6
6
|
$(document).ready((event) => {
|
7
7
|
if ($(inputsSelector).length === 0) return;
|
8
8
|
|
9
9
|
callback(event);
|
10
10
|
})
|
11
11
|
}
|
12
|
+
|
13
|
+
return onInputsReady;
|
12
14
|
})();
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
(function (mumuki) {
|
1
|
+
(function () {
|
4
2
|
// When using Turbolinks, intervals loaded inside <body> aren't destroyed on page changes
|
5
3
|
// Use this function instead if you want the behaviour of a regular setInterval
|
6
4
|
mumuki.setInterval = function (intervalFunction, milliseconds) {
|
@@ -14,4 +12,4 @@ var mumuki = mumuki || {};
|
|
14
12
|
return interval;
|
15
13
|
}.bind(this);
|
16
14
|
|
17
|
-
}(
|
15
|
+
}());
|
@@ -1,4 +1,4 @@
|
|
1
|
-
mumuki.load(
|
1
|
+
mumuki.load(() => {
|
2
2
|
var Chat = {
|
3
3
|
$body: function () {
|
4
4
|
return $('body')
|
@@ -61,7 +61,7 @@ mumuki.load(function () {
|
|
61
61
|
Chat.readMessages(readUrl);
|
62
62
|
}
|
63
63
|
|
64
|
-
function error(
|
64
|
+
function error(_xhr) {
|
65
65
|
Chat.tokenRequest({
|
66
66
|
url: errorUrl,
|
67
67
|
success: renderHTML,
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
(function (mumuki) {
|
1
|
+
mumuki.MultipleScenarios = (() => {
|
4
2
|
|
5
3
|
const setControlVisibility = function ($control, visible) {
|
6
4
|
visible ? $control.show() : $control.hide();
|
@@ -147,6 +145,5 @@ var mumuki = mumuki || {};
|
|
147
145
|
}
|
148
146
|
}
|
149
147
|
|
150
|
-
|
151
|
-
|
152
|
-
}(mumuki));
|
148
|
+
return MultipleScenarios;
|
149
|
+
})();
|
@@ -1,12 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
(function (mumuki) {
|
1
|
+
mumuki.pin = (() => {
|
4
2
|
function smoothScrollToElement(domElement) {
|
5
3
|
var SPEED = 1000;
|
6
4
|
$('html, body').animate({scrollTop: domElement.offset().top}, SPEED);
|
7
5
|
}
|
8
6
|
|
9
|
-
|
7
|
+
return {
|
10
8
|
scroll: function () {
|
11
9
|
var scrollPin = $('.scroll-pin');
|
12
10
|
if (scrollPin.length) {
|
@@ -14,4 +12,4 @@ var mumuki = mumuki || {};
|
|
14
12
|
}
|
15
13
|
}
|
16
14
|
}
|
17
|
-
})(
|
15
|
+
})();
|
@@ -1,13 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
(function (mumuki) {
|
4
|
-
|
1
|
+
mumuki.progress = (() => {
|
5
2
|
/**
|
6
3
|
* Updates the current exercise progress indicator
|
4
|
+
*
|
5
|
+
* @param {SubmissionResult} data
|
7
6
|
* */
|
8
|
-
|
7
|
+
function updateProgressBarAndShowModal(data) {
|
9
8
|
$('.progress-list-item.active').attr('class', data.class_for_progress_list_item);
|
10
9
|
if(data.guide_finished_by_solution) $('#guide-done').modal();
|
10
|
+
}
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Update all links in the progress bar with the given function
|
14
|
+
*
|
15
|
+
* @param {(anchor: JQuery) => string} f
|
16
|
+
*/
|
17
|
+
function updateWholeProgressBar(f) {
|
18
|
+
$('.progress-list-item').each((_, it) => {
|
19
|
+
const $anchor = $(it);
|
20
|
+
$anchor.attr('class', f($anchor))
|
21
|
+
});
|
22
|
+
}
|
23
|
+
|
24
|
+
return {
|
25
|
+
updateProgressBarAndShowModal,
|
26
|
+
updateWholeProgressBar
|
11
27
|
};
|
28
|
+
})();
|
12
29
|
|
13
|
-
}
|
30
|
+
/** @deprecated use {@code mumuki.progress.updateProgressBarAndShowModal} instead */
|
31
|
+
mumuki.updateProgressBarAndShowModal = mumuki.progress.updateProgressBarAndShowModal;
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
mumuki.renderers = mumuki.renderers || {};
|
2
|
+
mumuki.renderers.results = (() => {
|
3
|
+
|
2
4
|
|
3
5
|
// ==========================
|
4
6
|
// View function for building
|
@@ -6,7 +8,7 @@
|
|
6
8
|
// ==========================
|
7
9
|
|
8
10
|
/**
|
9
|
-
* @param {
|
11
|
+
* @param {SubmissionStatus} status
|
10
12
|
* @returns {string}
|
11
13
|
*/
|
12
14
|
function iconForStatus(status) {
|
@@ -20,8 +22,7 @@
|
|
20
22
|
}
|
21
23
|
|
22
24
|
/**
|
23
|
-
*
|
24
|
-
* @param {string} status
|
25
|
+
* @param {SubmissionStatus} status
|
25
26
|
* @returns {string}
|
26
27
|
*/
|
27
28
|
function classForStatus(status) {
|
@@ -32,20 +33,28 @@
|
|
32
33
|
case "passed": return "success";
|
33
34
|
case "pending": return "muted";
|
34
35
|
}
|
35
|
-
}
|
36
|
+
}
|
36
37
|
|
37
38
|
|
38
39
|
/**
|
39
|
-
* @param {
|
40
|
+
* @param {SubmissionStatus} status
|
40
41
|
* @param {boolean} [active]
|
41
42
|
* @returns {string}
|
42
43
|
*/
|
43
44
|
function progressListItemClassForStatus(status, active = false) {
|
44
45
|
return `progress-list-item text-center ${classForStatus(status)} ${active ? 'active' : ''}`;
|
45
|
-
}
|
46
|
+
}
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
return {
|
49
|
+
classForStatus,
|
50
|
+
iconForStatus,
|
51
|
+
progressListItemClassForStatus
|
52
|
+
}
|
51
53
|
})();
|
54
|
+
|
55
|
+
/** @deprecated use {@code mumuki.renderers.results.classForStatus} instead */
|
56
|
+
mumuki.renderers.classForStatus = mumuki.renderers.results.classForStatus;
|
57
|
+
/** @deprecated use {@code mumuki.renderers.results.iconForStatus} instead */
|
58
|
+
mumuki.renderers.iconForStatus = mumuki.renderers.results.iconForStatus;
|
59
|
+
/** @deprecated use {@code mumuki.renderers.results.progressListItemClassForStatus} instead */
|
60
|
+
mumuki.renderers.progressListItemClassForStatus = mumuki.renderers.results.progressListItemClassForStatus;
|