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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -3
  3. data/Rakefile +7 -1
  4. data/app/assets/javascripts/mumuki_laboratory/application/alias-modes.js +1 -1
  5. data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +66 -57
  6. data/app/assets/javascripts/mumuki_laboratory/application/codemirror-builder.js +28 -25
  7. data/app/assets/javascripts/mumuki_laboratory/application/codemirror.js +8 -10
  8. data/app/assets/javascripts/mumuki_laboratory/application/confirmation.js +2 -2
  9. data/app/assets/javascripts/mumuki_laboratory/application/console.js +41 -43
  10. data/app/assets/javascripts/mumuki_laboratory/application/csrf-token.js +9 -12
  11. data/app/assets/javascripts/mumuki_laboratory/application/custom-editor.js +11 -15
  12. data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +1 -3
  13. data/app/assets/javascripts/mumuki_laboratory/application/editors.js +104 -0
  14. data/app/assets/javascripts/mumuki_laboratory/application/elipsis.js +5 -4
  15. data/app/assets/javascripts/mumuki_laboratory/application/exercise.js +32 -0
  16. data/app/assets/javascripts/mumuki_laboratory/application/inputs.js +4 -2
  17. data/app/assets/javascripts/mumuki_laboratory/application/interval.js +2 -4
  18. data/app/assets/javascripts/mumuki_laboratory/application/kids.js +1 -1
  19. data/app/assets/javascripts/mumuki_laboratory/application/load-analytics.js +1 -1
  20. data/app/assets/javascripts/mumuki_laboratory/application/load-error-svg.js +1 -1
  21. data/app/assets/javascripts/mumuki_laboratory/application/messages.js +2 -2
  22. data/app/assets/javascripts/mumuki_laboratory/application/multiple-choice.js +1 -1
  23. data/app/assets/javascripts/mumuki_laboratory/application/multiple-scenarios.js +3 -6
  24. data/app/assets/javascripts/mumuki_laboratory/application/pin.js +3 -5
  25. data/app/assets/javascripts/mumuki_laboratory/application/progress.js +24 -6
  26. data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +20 -11
  27. data/app/assets/javascripts/mumuki_laboratory/application/speech-bubble-renderer.js +12 -5
  28. data/app/assets/javascripts/mumuki_laboratory/application/submission.js +19 -101
  29. data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +93 -0
  30. data/app/assets/javascripts/mumuki_laboratory/application/sync-mode.js +75 -0
  31. data/app/assets/javascripts/mumuki_laboratory/application/timer.js +5 -6
  32. data/app/assets/javascripts/mumuki_laboratory/application/tooltip.js +1 -1
  33. data/app/assets/javascripts/mumuki_laboratory/application/upload.js +1 -1
  34. data/app/assets/javascripts/mumuki_laboratory/application/user.js +1 -1
  35. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_gs-board.scss +3 -0
  36. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kids.scss +0 -1
  37. data/app/controllers/application_controller.rb +1 -0
  38. data/app/helpers/{locale_helper.rb → globals_helper.rb} +6 -2
  39. data/app/mailers/user_mailer.rb +24 -11
  40. data/app/views/book/show.html.erb +1 -1
  41. data/app/views/exercises/show.html.erb +2 -0
  42. data/app/views/layouts/_main.html.erb +1 -2
  43. data/app/views/layouts/_progress.html.erb +1 -1
  44. data/app/views/layouts/_progress_bar.html.erb +7 -1
  45. data/app/views/layouts/application.html.erb +1 -1
  46. data/lib/mumuki/laboratory/controllers.rb +1 -0
  47. data/lib/mumuki/laboratory/controllers/incognito_mode.rb +28 -0
  48. data/lib/mumuki/laboratory/locales/en.yml +6 -4
  49. data/lib/mumuki/laboratory/locales/es.yml +6 -4
  50. data/lib/mumuki/laboratory/locales/pt.yml +4 -2
  51. data/lib/mumuki/laboratory/version.rb +1 -1
  52. data/spec/dummy/db/schema.rb +2 -1
  53. data/spec/features/chapter_spec.rb +17 -0
  54. data/spec/features/exercise_flow_spec.rb +47 -2
  55. data/spec/features/home_public_flow_spec.rb +16 -0
  56. data/spec/javascripts/editors-spec.js +54 -0
  57. data/spec/javascripts/exercise-spec.js +22 -0
  58. data/spec/javascripts/global-spec.js +6 -0
  59. data/spec/javascripts/spec-helper.js +4 -0
  60. data/spec/javascripts/submissions-store-spec.js +44 -0
  61. data/spec/javascripts/sync-mode-spec.js +15 -0
  62. data/spec/mailers/user_mailer_spec.rb +18 -3
  63. data/spec/teaspoon_env.rb +8 -2
  64. data/vendor/assets/javascripts/codemirror-modes/gobstones.js +2 -3
  65. metadata +19 -5
  66. data/app/helpers/version_helper.rb +0 -5
@@ -1,18 +1,15 @@
1
- var mumuki = mumuki || {};
2
- (function (mumuki) {
3
- function CsrfToken() {
4
- this.value = $('meta[name="csrf-token"]').attr('content');
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
- mumuki.CsrfToken = CsrfToken;
18
- }(mumuki));
13
+ }
14
+ return CsrfToken;
15
+ })();
@@ -6,11 +6,16 @@
6
6
  * @typedef {{getContent: () => EditorProperty}} CustomEditorSource
7
7
  */
8
8
 
9
- var mumuki = mumuki || {};
10
-
11
- (function (mumuki) {
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
- var CustomEditor = {
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
- * This module allows custom editors to register
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
+ })();
@@ -1,6 +1,4 @@
1
- var mumuki = mumuki || {};
2
-
3
- mumuki.load(function () {
1
+ mumuki.load(() => {
4
2
  var $subscriptionSpans = $('.discussion-subscription > span');
5
3
  var $upvoteSpans = $('.discussion-upvote > span');
6
4
 
@@ -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
- (function (mumuki) {
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
- mumuki.elipsis = elipsis;
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
- })(mumuki);
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
- mumuki.onInputsReady = (inputsSelector, callback) => {
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
- var mumuki = mumuki || {};
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
- }(mumuki));
15
+ }());
@@ -1,4 +1,4 @@
1
- mumuki.load(function () {
1
+ mumuki.load(() => {
2
2
  let $bubble = $('.mu-kids-character-speech-bubble').children('.mu-kids-character-speech-bubble-normal');
3
3
 
4
4
  let availableTabs = ['.description', '.hint'];
@@ -1,4 +1,4 @@
1
- mumuki.load(function(){
1
+ mumuki.load(() => {
2
2
  ga('create', 'UA-58353823-1', 'auto');
3
3
  ga('send', 'pageview');
4
4
  });
@@ -1,4 +1,4 @@
1
- mumuki.load(function () {
1
+ mumuki.load(() => {
2
2
  var error_svgs = ['403', '404', '500', 'timeout_1', 'timeout_2', 'timeout_3'];
3
3
 
4
4
  mumuki.errors = mumuki.errors || {};
@@ -1,4 +1,4 @@
1
- mumuki.load(function () {
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(xhr) {
64
+ function error(_xhr) {
65
65
  Chat.tokenRequest({
66
66
  url: errorUrl,
67
67
  success: renderHTML,
@@ -1,5 +1,5 @@
1
1
  mumuki.load(() => {
2
- function dumpChoices(evt) {
2
+ function dumpChoices(_evt) {
3
3
  var indexes = $('.solution-choice:checked').map(function () {
4
4
  return $(this).data('index')
5
5
  }).get().join(':');
@@ -1,6 +1,4 @@
1
- var mumuki = mumuki || {};
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
- mumuki.MultipleScenarios = MultipleScenarios;
151
-
152
- }(mumuki));
148
+ return MultipleScenarios;
149
+ })();
@@ -1,12 +1,10 @@
1
- var mumuki = mumuki || {};
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
- mumuki.pin = {
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
- })(mumuki);
15
+ })();
@@ -1,13 +1,31 @@
1
- var mumuki = mumuki || {};
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
- mumuki.updateProgressBarAndShowModal = function (data) {
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
- })(mumuki);
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 {string} status
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 {string} status
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
- mumuki.renderers = mumuki.renderers || {};
48
- mumuki.renderers.classForStatus = classForStatus;
49
- mumuki.renderers.iconForStatus = iconForStatus;
50
- mumuki.renderers.progressListItemClassForStatus = progressListItemClassForStatus;
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;