mumuki-laboratory 5.6.3 → 5.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/application/bridge.js +1 -1
  3. data/app/assets/javascripts/application/codemirror-builder.js +33 -10
  4. data/app/assets/javascripts/application/discussions.js +12 -0
  5. data/app/assets/stylesheets/application/modules/_discussion.scss +19 -0
  6. data/app/controllers/application_controller.rb +1 -0
  7. data/app/controllers/exercises_controller.rb +1 -0
  8. data/app/helpers/application_helper.rb +4 -0
  9. data/app/helpers/discussions_helper.rb +1 -2
  10. data/app/helpers/progress_helper.rb +10 -9
  11. data/app/models/assignment.rb +9 -1
  12. data/app/models/concerns/submittable/solvable.rb +1 -5
  13. data/app/models/concerns/with_assignments.rb +5 -1
  14. data/app/models/discussion.rb +4 -2
  15. data/app/models/submission/submission.rb +1 -0
  16. data/app/views/discussions/_description_message.html.erb +17 -0
  17. data/app/views/discussions/_message.html.erb +1 -1
  18. data/app/views/discussions/_new_message.html.erb +3 -1
  19. data/app/views/discussions/show.html.erb +3 -0
  20. data/app/views/exercise_solutions/_results_button.html.erb +7 -5
  21. data/app/views/exercises/show.html.erb +2 -2
  22. data/app/views/layouts/_main.html.erb +63 -0
  23. data/app/views/layouts/application.html.erb +50 -105
  24. data/app/views/layouts/embedded.html.erb +1 -0
  25. data/app/views/layouts/exercise_inputs/editors/_multiple_files.html.erb +1 -1
  26. data/lib/mumuki/laboratory.rb +1 -0
  27. data/lib/mumuki/laboratory/controllers.rb +1 -0
  28. data/lib/mumuki/laboratory/controllers/embedded_mode.rb +45 -0
  29. data/lib/mumuki/laboratory/extensions/string.rb +4 -0
  30. data/lib/mumuki/laboratory/file.rb +18 -0
  31. data/lib/mumuki/laboratory/locales/en.yml +5 -1
  32. data/lib/mumuki/laboratory/locales/es.yml +10 -0
  33. data/lib/mumuki/laboratory/locales/pt.yml +4 -0
  34. data/lib/mumuki/laboratory/status/discussion/opened.rb +6 -2
  35. data/lib/mumuki/laboratory/status/discussion/pending_review.rb +1 -1
  36. data/lib/mumuki/laboratory/status/discussion/solved.rb +1 -1
  37. data/lib/mumuki/laboratory/version.rb +1 -1
  38. data/spec/controllers/exercise_solutions_controller_spec.rb +12 -3
  39. data/spec/features/exercise_flow_spec.rb +21 -0
  40. data/spec/models/discussion_spec.rb +12 -4
  41. data/spec/models/exercise_spec.rb +15 -1
  42. metadata +9 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 304a4ed4733a6f648ce0437780519c8c3f3fe874cf3b96ce96d2eea351a5b250
4
- data.tar.gz: 295c271a9fff7056a7309fdcd9f07f7206abbc5cc8a8df44fb2cb9f9e0cc273d
3
+ metadata.gz: eebf90572caa6242083ad55e7af966691ffadd00c9ea6cf4b9ccd5ce3ef89f22
4
+ data.tar.gz: a7a348cc2799dbbf19ab01be6f9b4edace17979ce784f124ef695db12a6ba1c2
5
5
  SHA512:
6
- metadata.gz: be00363a9eb23b5797bc81ff39da5e11edb8a8d38bd0558b12b54ac03ac879c1b933f9cf990c03e427387039f16e569df9aaf3b619b28310149672a1f2d7cc19
7
- data.tar.gz: 07bef8f427e030ddbf4c636d757836dddc95184e0856be511043f2d281c0d4ddecb60e6367cb3eec2112e4b44eb1650b1c2ce70b18c4e3296499c1d18f8a2571
6
+ metadata.gz: a02f3ef36c6f80aa9b88b508b579e4d9479c4c43d04c8c120928394ca59a222192a31a7d8ec816d4ac6ccc18fd6e13d36913e819b959b10bbf26875ea95e9ebc
7
+ data.tar.gz: bc5ae60058112ffac03761ee34567349a3b34348f3a2f90c334b607fa97bd84e1e82af08886c828f607ac010d1be50eec4f0504fa83bc9ca77ce4506d5aded95
@@ -23,7 +23,7 @@ var mumuki = mumuki || {};
23
23
  var token = new mumuki.CsrfToken();
24
24
  var request = token.newRequest({
25
25
  type: 'POST',
26
- url: window.location.origin + window.location.pathname + '/solutions',
26
+ url: window.location.origin + window.location.pathname + '/solutions' + window.location.search,
27
27
  data: solution
28
28
  });
29
29
 
@@ -13,18 +13,28 @@ var mumuki = mumuki || {};
13
13
  $('.btn-submit').click();
14
14
  }
15
15
 
16
+ function submitMessage() {
17
+ $('.discussion-new-message-button').click();
18
+ }
19
+
20
+ var codeMirrorDefaults = {
21
+ autofocus: false,
22
+ tabSize: 2,
23
+ cursorHeight: 1,
24
+ matchBrackets: true,
25
+ lineWiseCopyCut: true,
26
+ autoCloseBrackets: true,
27
+ showCursorWhenSelecting: true,
28
+ lineWrapping: true
29
+ };
30
+
16
31
  CodeMirrorBuilder.prototype = {
32
+ createEditor: function (customOptions) {
33
+ return CodeMirror.fromTextArea(this.textarea, Object.assign({}, codeMirrorDefaults, customOptions));
34
+ },
17
35
  setupEditor: function () {
18
- this.editor = CodeMirror.fromTextArea(this.textarea, {
19
- autofocus: false,
20
- tabSize: 2,
36
+ this.editor = this.createEditor({
21
37
  lineNumbers: true,
22
- lineWrapping: true,
23
- cursorHeight: 1,
24
- matchBrackets: true,
25
- lineWiseCopyCut: true,
26
- autoCloseBrackets: true,
27
- showCursorWhenSelecting: true,
28
38
  extraKeys: {
29
39
  'Ctrl-Space': 'autocomplete',
30
40
  'Cmd-Enter': submit,
@@ -38,12 +48,25 @@ var mumuki = mumuki || {};
38
48
  }
39
49
  });
40
50
  },
51
+ setupSimpleEditor: function () {
52
+ this.editor = this.createEditor({
53
+ mode: 'text',
54
+ extraKeys: {
55
+ 'Cmd-Enter': submitMessage,
56
+ 'Ctrl-Enter': submitMessage,
57
+ 'Tab': function (cm) {
58
+ mumuki.editor.indentWithSpaces(cm)
59
+ }
60
+ }
61
+ });
62
+ },
41
63
  setupLanguage: function () {
42
64
  var language = this.$textarea.data('editor-language');
43
65
  if (language === 'dynamic') {
44
66
  mumuki.page.dynamicEditors.push(this.editor);
45
67
  } else {
46
- mumuki.editor.setOption('mode', language);
68
+ this.editor.setOption('mode', language);
69
+ this.editor.refresh();
47
70
  }
48
71
  },
49
72
  setupOptions: function (minLines) {
@@ -14,6 +14,18 @@ mumuki.load(function () {
14
14
  var $subscriptionSpans = $('.discussion-subscription > span');
15
15
  var $upvoteSpans = $('.discussion-upvote > span');
16
16
 
17
+ function createNewMessageEditor() {
18
+ var $textarea = $("#new-discussion-message");
19
+ var textarea = $textarea[0];
20
+ if(!textarea) return;
21
+ var builder = new mumuki.editor.CodeMirrorBuilder(textarea);
22
+ builder.setupSimpleEditor();
23
+ builder.setupOptions($textarea.data('lines'));
24
+ builder.build();
25
+ }
26
+
27
+ createNewMessageEditor();
28
+
17
29
  var Forum = {
18
30
  toggleButton: function (spans) {
19
31
  spans.toggleClass('hidden');
@@ -242,12 +242,31 @@ $moderator-star-color: #dd9900;
242
242
 
243
243
  .discussion-message-content {
244
244
  padding: 15px;
245
+ p {
246
+ margin: 0px;
247
+ white-space: pre-wrap;
248
+ }
245
249
  }
246
250
 
247
251
  .discussion-new-message-content {
248
252
  resize: none;
253
+ min-height: 150px;
249
254
  border-radius: 0;
250
255
  border-color: transparent;
256
+ overflow: hidden;
257
+ text-overflow: ellipsis;
258
+ white-space: nowrap;
259
+ .CodeMirror {
260
+ border: unset;
261
+ color: $brand-primary;
262
+ font-family: $font-family-sans-serif;
263
+ padding: 15px;
264
+ }
265
+ .CodeMirror-wrap,
266
+ .CodeMirror-scroll {
267
+ height: auto !important;
268
+ min-height: 100px !important;
269
+ }
251
270
  &:focus {
252
271
  border-color: white;
253
272
  }
@@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
7
7
  include Mumuki::Laboratory::Controllers::Authorization
8
8
  include Mumuki::Laboratory::Controllers::Notifications
9
9
  include Mumuki::Laboratory::Controllers::DynamicErrors
10
+ include Mumuki::Laboratory::Controllers::EmbeddedMode
10
11
 
11
12
  before_action :set_current_organization!
12
13
  before_action :set_locale!
@@ -10,6 +10,7 @@ class ExercisesController < ApplicationController
10
10
 
11
11
  def show
12
12
  @solution = @exercise.new_solution if current_user?
13
+ enable_embedded_rendering
13
14
  end
14
15
 
15
16
  def show_by_slug
@@ -15,6 +15,10 @@ module ApplicationHelper
15
15
  end
16
16
  end
17
17
 
18
+ def profile_picture
19
+ image_tag(@current_user.image_url, height: 40, class: 'img-circle', onError: "this.onerror = null; this.src = '#{image_url('user_shape.png')}'")
20
+ end
21
+
18
22
  def paginate(object, options={})
19
23
  "<div class=\"text-center\">#{super(object, {theme: 'twitter-bootstrap-3'}.merge(options))}</div>".html_safe
20
24
  end
@@ -120,8 +120,7 @@ module DiscussionsHelper
120
120
  %Q{
121
121
  #{status_fa_icon(status)}
122
122
  <span>
123
- #{discussions_count}
124
- #{t status}
123
+ #{t("#{status}_count", count: discussions_count)}
125
124
  </span>
126
125
  }.html_safe
127
126
  end
@@ -1,18 +1,19 @@
1
1
  module ProgressHelper
2
2
  def lesson_practice_key_for(stats)
3
- if stats.try(:started?)
4
- :continue_lesson
5
- else
6
- :start_lesson
7
- end
3
+ key_or_default stats, :started?, :continue_lesson, :start_lesson
8
4
  end
9
5
 
10
- #FIXME refactorme: similar methods
11
6
  def book_practice_key_for(student)
12
- if student.try(:last_exercise)
13
- :continue_practicing
7
+ key_or_default student, :last_exercise, :continue_practicing, :start_practicing
8
+ end
9
+
10
+ private
11
+
12
+ def key_or_default(object, property, key, default_key)
13
+ if object.try(property)
14
+ key
14
15
  else
15
- :start_practicing
16
+ default_key
16
17
  end
17
18
  end
18
19
  end
@@ -60,6 +60,10 @@ class Assignment < ApplicationRecord
60
60
  end
61
61
  end
62
62
 
63
+ def test
64
+ exercise.test_for submitter
65
+ end
66
+
63
67
  def extra
64
68
  exercise.extra_for submitter
65
69
  end
@@ -90,11 +94,15 @@ class Assignment < ApplicationRecord
90
94
  update! result: message, submission_status: :errored
91
95
  end
92
96
 
93
- %w(query try tests).each do |key|
97
+ %w(query try).each do |key|
94
98
  name = "run_#{key}!"
95
99
  define_method(name) { |params| exercise.send name, params.merge(extra: extra) }
96
100
  end
97
101
 
102
+ def run_tests!(params)
103
+ exercise.run_tests! params.merge(extra: extra, test: test)
104
+ end
105
+
98
106
  def as_platform_json
99
107
  navigable_parent = exercise.navigable_parent
100
108
  as_json(except: [:exercise_id, :submission_id, :id, :submitter_id, :solution, :created_at, :updated_at, :submission_status],
@@ -6,11 +6,7 @@ module Solvable
6
6
  end
7
7
 
8
8
  def run_tests!(params)
9
- language.run_tests!(
10
- params.merge(
11
- test: test,
12
- locale: locale,
13
- expectations: expectations))
9
+ language.run_tests!(params.merge(locale: locale, expectations: expectations))
14
10
  end
15
11
  end
16
12
 
@@ -15,7 +15,7 @@ module WithAssignments
15
15
  .directives_sections
16
16
  .split_sections(assignment_for(user)&.solution || default_content_for(user))
17
17
  .except('content')
18
- .map { |name, content| struct name: name, content: content }
18
+ .map { |name, content| Mumuki::Laboratory::File.new name, content }
19
19
  end
20
20
 
21
21
  def interpolate_for(user, field)
@@ -30,6 +30,10 @@ module WithAssignments
30
30
  interpolate_for user, extra
31
31
  end
32
32
 
33
+ def test_for(user)
34
+ test && interpolate_for(user, test)
35
+ end
36
+
33
37
  def replace_content_reference(user, interpolee)
34
38
  case interpolee
35
39
  when /previousContent|previousSolution/
@@ -13,6 +13,8 @@ class Discussion < ApplicationRecord
13
13
  before_save :capitalize_title
14
14
  validates_presence_of :title
15
15
 
16
+ markdown_on :description
17
+
16
18
  sortable :created_at, :upvotes_count, default: :created_at_desc
17
19
  filterable :status, :language
18
20
  pageable
@@ -43,7 +45,7 @@ class Discussion < ApplicationRecord
43
45
  end
44
46
 
45
47
  def commentable_by?(user)
46
- opened? && user.present?
48
+ user&.moderator? || (opened? && user.present?)
47
49
  end
48
50
 
49
51
  def subscribable?
@@ -110,7 +112,7 @@ class Discussion < ApplicationRecord
110
112
  end
111
113
 
112
114
  def has_messages?
113
- messages.exists?
115
+ messages.exists? || description.present?
114
116
  end
115
117
 
116
118
  def responses_count
@@ -29,6 +29,7 @@ class Submission
29
29
  def evaluate!(assignment)
30
30
  try_evaluate! assignment
31
31
  rescue => e
32
+ Rails.logger.error "Evaluation failed: #{e} \n#{e.backtrace.join("\n")}"
32
33
  {status: :errored, result: e.message}
33
34
  end
34
35
 
@@ -0,0 +1,17 @@
1
+ <%= render layout: 'discussions/message_container', locals: {user: discussion.initiator} do %>
2
+ <div class="discussion-message-bubble">
3
+ <div class="discussion-message-bubble-header">
4
+ <div class="discussion-message-bubble-title">
5
+ <%= discussion.initiator.name %>
6
+ <span class="message-date">
7
+ <%= t(:time_since, time: time_ago_in_words(discussion.created_at)) %>
8
+ </span>
9
+ </div>
10
+ </div>
11
+ <div class="discussion-message-bubble-content">
12
+ <div class="discussion-message-content">
13
+ <%= discussion.description_html %>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <% end %>
@@ -18,7 +18,7 @@
18
18
  </div>
19
19
  <div class="discussion-message-bubble-content">
20
20
  <div class="discussion-message-content">
21
- <%= message.content %>
21
+ <%= message.content_html %>
22
22
  </div>
23
23
  </div>
24
24
  </div>
@@ -9,7 +9,9 @@
9
9
  <div class="discussion-message-bubble-content">
10
10
  <div class="container-fluid">
11
11
  <div class="row">
12
- <div><%= f.text_area :content, class: 'form-control discussion-new-message-content', placeholder: t(:message), required: true %></div>
12
+ <div class="discussion-new-message-content">
13
+ <%= f.editor :content, '', { id: 'new-discussion-message', class: 'form-control', placeholder: t(:message) } %>
14
+ </div>
13
15
  </div>
14
16
  </div>
15
17
  </div>
@@ -56,6 +56,9 @@
56
56
 
57
57
  <% if @discussion.has_messages? %>
58
58
  <div class="discussion-messages">
59
+ <% if @discussion.description.present? %>
60
+ <%= render partial: 'discussions/description_message', locals: {discussion: @discussion} %>
61
+ <% end %>
59
62
  <% @discussion.messages.each do |message| %>
60
63
  <%= render partial: 'discussions/message', locals: {user: message.sender_user, message: message} %>
61
64
  <% end %>
@@ -1,7 +1,9 @@
1
- <div class="row">
2
- <div class="col-md-12">
3
- <div class="actions">
4
- <%= next_exercise_button(@exercise) %>
1
+ <% if standalone_mode? %>
2
+ <div class="row">
3
+ <div class="col-md-12">
4
+ <div class="actions">
5
+ <%= next_exercise_button(@exercise) %>
6
+ </div>
5
7
  </div>
6
8
  </div>
7
- </div>
9
+ <% end %>
@@ -1,7 +1,7 @@
1
1
  <%= render_runner_assets @exercise.language, :layout %>
2
2
 
3
3
  <%= content_for :breadcrumbs do %>
4
- <%= breadcrumbs @exercise %>
4
+ <%= breadcrumbs @exercise %>
5
5
  <% end %>
6
6
 
7
7
  <%= render partial: 'layouts/authoring', locals: {guide: @guide} %>
@@ -28,7 +28,7 @@
28
28
  <% end %>
29
29
 
30
30
  <div>
31
- <% if @stats %>
31
+ <% if @stats && standalone_mode? %>
32
32
  <%= render partial: 'layouts/progress_bar', locals: {actual: @exercise, guide: @exercise.guide, stats: @stats} %>
33
33
  <% end %>
34
34
  </div>
@@ -0,0 +1,63 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= Organization.current.locale %>">
3
+ <head>
4
+ <title><%= page_title subject %></title>
5
+ <% unless input_kids? %>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
+ <% end %>
8
+
9
+ <link rel="icon" type="image/x-icon" href="<%= Organization.current.favicon_url %>" data-turbolinks-track="reload">
10
+ <link rel="stylesheet" type="text/css" href="<%= theme_stylesheet_url %>" data-turbolinks-track="reload">
11
+
12
+ <meta property="og:site_name" content="<%= Organization.current.site_name %>" />
13
+ <meta property="og:title" content="<%= page_title subject %>"/>
14
+ <meta property="og:description" content="<%= Organization.current.central? ? t(:mumuki_short_description) : Organization.current.description %>"/>
15
+ <meta property="og:type" content="website"/>
16
+ <meta property="og:image" content="<%= Organization.current.open_graph_image_url %>"/>
17
+ <meta property="og:url" content="<%= request.original_url %>"/>
18
+
19
+ <meta name="turbolinks-cache-control" content="no-cache">
20
+
21
+ <meta name="description" content="<%= t :mumuki_short_description %>"/>
22
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
23
+ <%= csrf_meta_tags %>
24
+ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
25
+ <script type="text/javascript" src="<%= extension_javascript_url %>" defer data-turbolinks-track="reload"></script>
26
+ <script type="text/javascript">
27
+ window.mumukiLocale = <%= raw Organization.current.locale_json %>;
28
+ mumuki.locale = '<%= Organization.current.locale %>';
29
+ moment.locale('<%= Organization.current.locale %>');
30
+ </script>
31
+ <%= login_form.header_html %>
32
+ </head>
33
+ <body>
34
+ <% if should_choose_organization? %>
35
+ <%= render partial: 'layouts/organization_chooser' %>
36
+ <% end %>
37
+
38
+ <%= yield :navbar %>
39
+
40
+ <div class="<%= exercise_container_type %><%= @kids ? '-fluid' : '' %>" id="wrap">
41
+ <div id="toast-container" class="toast-bottom-left">
42
+ <% if notice %>
43
+ <div class="alert toast toast-success" role="alert">
44
+ <%= notice.html_safe %>
45
+ <button type="button" class="close" data-dismiss="alert">&times;</button>
46
+ </div>
47
+ <% elsif alert %>
48
+ <div class="alert toast toast-error" role="alert">
49
+ <%= alert.html_safe %>
50
+ <button type="button" class="close" data-dismiss="alert">&times;</button>
51
+ </div>
52
+ <% end %>
53
+ </div>
54
+
55
+ <%= yield %>
56
+ </div>
57
+
58
+ <%= yield :no_container %>
59
+
60
+ <%= yield :footer %>
61
+
62
+ </body>
63
+ </html>
@@ -1,59 +1,23 @@
1
- <!DOCTYPE html>
2
- <html lang="<%= Organization.current.locale %>">
3
- <head>
4
- <title><%= page_title subject %></title>
5
- <% unless input_kids? %>
6
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
- <% end %>
8
-
9
- <link rel="icon" type="image/x-icon" href="<%= Organization.current.favicon_url %>" data-turbolinks-track="reload">
10
- <link rel="stylesheet" type="text/css" href="<%= theme_stylesheet_url %>" data-turbolinks-track="reload">
11
-
12
- <meta property="og:site_name" content="<%= Organization.current.site_name %>" />
13
- <meta property="og:title" content="<%= page_title subject %>"/>
14
- <meta property="og:description" content="<%= Organization.current.central? ? t(:mumuki_short_description) : Organization.current.description %>"/>
15
- <meta property="og:type" content="website"/>
16
- <meta property="og:image" content="<%= Organization.current.open_graph_image_url %>"/>
17
- <meta property="og:url" content="<%= request.original_url %>"/>
18
-
19
- <meta name="turbolinks-cache-control" content="no-cache">
20
-
21
- <meta name="description" content="<%= t :mumuki_short_description %>"/>
22
- <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
23
- <%= csrf_meta_tags %>
24
- <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
25
- <script type="text/javascript" src="<%= extension_javascript_url %>" defer data-turbolinks-track="reload"></script>
26
- <script type="text/javascript">
27
- window.mumukiLocale = <%= raw Organization.current.locale_json %>;
28
- mumuki.locale = '<%= Organization.current.locale %>';
29
- moment.locale('<%= Organization.current.locale %>');
30
- </script>
31
- <%= login_form.header_html %>
32
- </head>
33
- <body>
34
- <% if should_choose_organization? %>
35
- <%= render partial: 'layouts/organization_chooser' %>
36
- <% end %>
37
-
38
- <div class="<%= exercise_container_type %>">
39
- <nav class="mu-navbar">
40
- <div class="mu-navbar-breadcrumb hidden-xs">
41
- <ul class="mu-breadcrumb-list">
42
- <%= yield :breadcrumbs %>
43
- </ul>
44
- </div>
45
- <div class="dropdown hamburguer-breadcrumb hidden-sm hidden-md hidden-lg">
46
- <% if content_for? :breadcrumbs %>
47
- <span class="hamburguer" id="profileDropdown" data-toggle="dropdown">
48
- <i class="fa fa-bars"></i>
49
- </span>
1
+ <%= content_for :navbar do %>
2
+ <div class="<%= exercise_container_type %>">
3
+ <nav class="mu-navbar">
4
+ <div class="mu-navbar-breadcrumb hidden-xs">
5
+ <ul class="mu-breadcrumb-list">
6
+ <%= yield :breadcrumbs %>
7
+ </ul>
8
+ </div>
9
+ <div class="dropdown hamburguer-breadcrumb hidden-sm hidden-md hidden-lg">
10
+ <% if content_for? :breadcrumbs %>
11
+ <span class="hamburguer" id="profileDropdown" data-toggle="dropdown">
12
+ <i class="fa fa-bars"></i>
13
+ </span>
50
14
  <ul class="dropdown-menu dropdown-menu-left" aria-labelledby="profileDropdown">
51
15
  <%= yield :breadcrumbs %>
52
16
  </ul>
53
- <% end %>
54
- </div>
55
- <div class="mu-navbar-avatar">
56
- <% if current_user? %>
17
+ <% end %>
18
+ </div>
19
+ <div class="mu-navbar-avatar">
20
+ <% if current_user? %>
57
21
  <div class="dropdown">
58
22
  <span>
59
23
  <a class="notifications-box <%= has_notifications? ? '' : 'notifications-box-empty' %>" href=<%= "#{user_notifications_path}" %>>
@@ -64,7 +28,7 @@
64
28
  </div>
65
29
  <div class="dropdown">
66
30
  <span id="profileDropdown" data-toggle="dropdown" aria-label="<%= t(:user) %>" role="menu" tabindex="0">
67
- <%= image_tag(current_user.image_url, height: 40, class: 'img-circle') %>
31
+ <%= profile_picture %>
68
32
  </span>
69
33
  <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="profileDropdown">
70
34
  <li>
@@ -77,64 +41,45 @@
77
41
  <li><%= link_to(t(:sign_out), logout_path(origin: url_for), role: 'menuitem') %></li>
78
42
  </ul>
79
43
  </div>
80
- <% else %>
44
+ <% else %>
81
45
  <%= login_button %>
82
- <% end %>
83
- </div>
84
- </nav>
85
- </div>
86
-
87
- <div class="<%= exercise_container_type %><%= @kids ? '-fluid' : '' %>" id="wrap">
88
- <div id="toast-container" class="toast-bottom-left">
89
- <% if notice %>
90
- <div class="alert toast toast-success" role="alert">
91
- <%= notice.html_safe %>
92
- <button type="button" class="close" data-dismiss="alert">&times;</button>
93
- </div>
94
- <% elsif alert %>
95
- <div class="alert toast toast-error" role="alert">
96
- <%= alert.html_safe %>
97
- <button type="button" class="close" data-dismiss="alert">&times;</button>
98
- </div>
99
- <% end %>
46
+ <% end %>
47
+ </div>
48
+ </nav>
100
49
  </div>
50
+ <% end %>
101
51
 
102
- <%= yield %>
103
-
104
- </div>
105
-
106
- <%= yield :no_container %>
107
-
108
- <footer class="footer">
109
- <div class="<%= exercise_container_type %>">
110
- <hr>
52
+ <% content_for :footer do %>
53
+ <footer class="footer">
54
+ <div class="<%= exercise_container_type %>">
55
+ <hr>
111
56
 
112
- <div class="row">
113
- <div class="col-md-12">
114
- <%= yield :authoring %>
57
+ <div class="row">
58
+ <div class="col-md-12">
59
+ <%= yield :authoring %>
60
+ </div>
115
61
  </div>
116
- </div>
117
62
 
118
- <div id="footer-copyright" class="row">
119
- <div class="col-md-4 text-left">
120
- <p>&copy; Copyright 2015-<%= DateTime.now.year %>
121
- <a href="http://mumuki.org/" class="mu-org-link"><span class="da da-mumuki-circle"></span> Mumuki Project</a>
122
- </p>
123
- </div>
63
+ <div id="footer-copyright" class="row">
64
+ <div class="col-md-4 text-left">
65
+ <p>&copy; Copyright 2015-<%= DateTime.now.year %>
66
+ <a href="http://mumuki.org/" class="mu-org-link"><span class="da da-mumuki-circle"></span> Mumuki Project</a>
67
+ </p>
68
+ </div>
124
69
 
125
- <div class="col-md-4 text-center">
126
- <%= login_form.footer_html %>
127
- </div>
70
+ <div class="col-md-4 text-center">
71
+ <%= login_form.footer_html %>
72
+ </div>
128
73
 
129
- <div id="footer-social" class="col-md-4 text-right" lang="en">
130
- <a class="fa fa-facebook social-icon" aria-label="Facebook" href="https://www.facebook.com/MumukiProject" target="_blank"></a>
131
- <a class="fa fa-twitter social-icon" aria-label="Twitter" href="https://twitter.com/MumukiProject" target="_blank"></a>
132
- <a class="fa fa-github social-icon" aria-label="Github" href="https://github.com/mumuki" target="_blank"></a>
133
- <a class="fa fa-linkedin social-icon" aria-label="LinkedIn" href="https://www.linkedin.com/company/mumuki-project" target="_blank"></a>
74
+ <div id="footer-social" class="col-md-4 text-right" lang="en">
75
+ <a class="fa fa-facebook social-icon" aria-label="Facebook" href="https://www.facebook.com/MumukiProject" target="_blank"></a>
76
+ <a class="fa fa-twitter social-icon" aria-label="Twitter" href="https://twitter.com/MumukiProject" target="_blank"></a>
77
+ <a class="fa fa-github social-icon" aria-label="Github" href="https://github.com/mumuki" target="_blank"></a>
78
+ <a class="fa fa-linkedin social-icon" aria-label="LinkedIn" href="https://www.linkedin.com/company/mumuki-project" target="_blank"></a>
79
+ </div>
134
80
  </div>
135
-
136
81
  </div>
137
- </div>
138
- </footer>
139
- </body>
140
- </html>
82
+ </footer>
83
+ <% end %>
84
+
85
+ <%= render partial: 'layouts/main' %>
@@ -0,0 +1 @@
1
+ <%= render partial: 'layouts/main' %>
@@ -13,7 +13,7 @@
13
13
  <div class="tab-content">
14
14
  <% @files.each_with_index do |file, index| %>
15
15
  <div role="tabpanel" class="tab-pane mu-input-panel <%= 'fade in active' if index == 0 %>" id="editor-file-<%= index %>">
16
- <%= form.editor "content[#{file.name}]", 'dynamic',
16
+ <%= form.editor "content[#{file.name}]", file.highlight_mode,
17
17
  placeholder: t(:editor_placeholder),
18
18
  class: 'form-control editor',
19
19
  value: file.content,
@@ -28,3 +28,4 @@ require_relative './laboratory/exceptions'
28
28
  require_relative './laboratory/status'
29
29
  require_relative './laboratory/evaluation'
30
30
  require_relative './laboratory/controllers'
31
+ require_relative './laboratory/file'
@@ -5,6 +5,7 @@ require_relative './controllers/authorization'
5
5
  require_relative './controllers/current_organization'
6
6
  require_relative './controllers/dynamic_errors'
7
7
  require_relative './controllers/notifications'
8
+ require_relative './controllers/embedded_mode'
8
9
  require_relative './controllers/nested_in_exercise'
9
10
  require_relative './controllers/results_rendering'
10
11
  require_relative './controllers/exercise_seed'
@@ -0,0 +1,45 @@
1
+ # This mixin provides support for implementing the
2
+ # embedded mode - as opposed to the standalone mode -in which
3
+ # the mumuki pages
4
+ #
5
+ # * are displayed used a simplified layout, called `embedded.html.erb`
6
+ # * are served using `X-Frame-Options` that allow them to be used within
7
+ # an iframe
8
+ #
9
+ # Not all organizations can be emedded - only those that have the `embeddable?`
10
+ # setting set.
11
+ #
12
+ # This mixin provides two sets of methods:
13
+ #
14
+ # * `embedded_mode?` / `standalone_mode?`, which are helpers aimed to be used by views
15
+ # and change very specific rendering details in one or the other mode
16
+ # * `enable_embedded_rendering`, which is designed to be called from main-views controller-methods that
17
+ # actually support embedded mode.
18
+ module Mumuki::Laboratory::Controllers::EmbeddedMode
19
+ extend ActiveSupport::Concern
20
+
21
+ included do
22
+ helper_method :embedded_mode?,
23
+ :standalone_mode?
24
+ end
25
+
26
+ def embedded_mode?
27
+ @embedded_mode ||= params[:embed] == 'true' && Organization.current.embeddable?
28
+ end
29
+
30
+ def standalone_mode?
31
+ !embedded_mode?
32
+ end
33
+
34
+ def enable_embedded_rendering
35
+ return unless embedded_mode?
36
+ allow_parent_iframe!
37
+ render layout: 'embedded'
38
+ end
39
+
40
+ private
41
+
42
+ def allow_parent_iframe!
43
+ response.set_header 'X-Frame-Options', 'ALLOWALL'
44
+ end
45
+ end
@@ -22,4 +22,8 @@ class String
22
22
  def normalize_whitespaces
23
23
  gsub(/([^[:ascii:]])/) { $1.blank? ? ' ' : $1 }
24
24
  end
25
+
26
+ def get_file_extension
27
+ File.extname(self).delete '.'
28
+ end
25
29
  end
@@ -0,0 +1,18 @@
1
+ module Mumuki::Laboratory
2
+ class File
3
+ attr_reader :name, :content
4
+
5
+ def initialize(name, content)
6
+ @name = name
7
+ @content = content
8
+ end
9
+
10
+ def highlight_mode
11
+ Language.find_by(extension: extension)&.highlight_mode || extension
12
+ end
13
+
14
+ def extension
15
+ name.get_file_extension
16
+ end
17
+ end
18
+ end
@@ -27,6 +27,7 @@ en:
27
27
  classroom: Classroom
28
28
  clear_console: Clear console
29
29
  closed: Closed
30
+ closed_count: '%{count} closed'
30
31
  created_at_asc: Oldest
31
32
  created_at_desc: Newest
32
33
  comment: Comment
@@ -138,6 +139,7 @@ en:
138
139
  notify_problem_with_exercise: Report a bug
139
140
  office: Office
140
141
  opened: Open
142
+ opened_count: '%{count} opened'
141
143
  output: Output
142
144
  overview: Overview
143
145
  passed: Everything is in order! Your solution passed all our tests!
@@ -145,7 +147,8 @@ en:
145
147
  pending: pending
146
148
  pending_messages: You have unread messages
147
149
  pending_messages_explanation: You can't send new solutions until you read pending messages
148
- Pending_review: Pending review
150
+ pending_review: Pending review
151
+ pending_review_count: '%{count} for review'
149
152
  permissions: Permissions
150
153
  please_fill_profile_data: Please complete your profile data to continue!
151
154
  please_validate: 'Please validate your data before continue:'
@@ -172,6 +175,7 @@ en:
172
175
  sign_up_date: User since
173
176
  solution: Solution
174
177
  solved: Solved
178
+ solved_count: '%{count} solved'
175
179
  solve_doubts: Solve other's doubts
176
180
  solve_your_doubts: Solve your doubts
177
181
  solve_your_doubts_teaser: Do you have any doubts?
@@ -28,6 +28,9 @@ es:
28
28
  classroom: Aula
29
29
  clear_console: Reiniciar consola
30
30
  closed: Cerrada
31
+ closed_count:
32
+ one: 1 cerrada
33
+ other: '%{count} cerradas'
31
34
  created_at_asc: Antiguas
32
35
  created_at_desc: Nuevas
33
36
  comment: Comentar
@@ -151,6 +154,9 @@ es:
151
154
  notify_problem_with_exercise: Reportá un bug
152
155
  office: Secretaría
153
156
  opened: Abierta
157
+ opened_count:
158
+ one: '1 abierta'
159
+ other: '%{count} abiertas'
154
160
  organizations: Organizaciones
155
161
  output: Salida
156
162
  overview: Vista general
@@ -158,6 +164,7 @@ es:
158
164
  passed_with_warnings: Tu solución funcionó, pero hay cosas que mejorar
159
165
  pending: pendiente
160
166
  pending_review: En revisión
167
+ pending_review_count: '%{count} en revisión'
161
168
  pending_messages: Tenés mensajes sin leer
162
169
  pending_messages_explanation: "¡Tu docente te dejó un mensaje! Revisalo antes de seguir enviando soluciones."
163
170
  permissions: Permisos
@@ -188,6 +195,9 @@ es:
188
195
  sign_up_date: Usuario desde
189
196
  solution: Solución
190
197
  solved: Resuelta
198
+ solved_count:
199
+ one: 1 resuelta
200
+ other: '%{count} resueltas'
191
201
  solved_exercises: Ejercicios Resueltos
192
202
  solve_doubts: Resolvé consultas
193
203
  solve_your_doubts: Consultá tus dudas
@@ -27,6 +27,7 @@ pt:
27
27
  classroom: Sala de aula
28
28
  clear_console: Reiniciar o console
29
29
  closed: Fechado
30
+ closed_count: '%{count} fechado'
30
31
  comment: Comentar
31
32
  confirm: Confirme
32
33
  confirm_restart: Você está prestes a apagar o seu progresso neste guia. Você quer continuar?
@@ -148,6 +149,7 @@ pt:
148
149
  notify_problem_with_exercise: Relatar um erro
149
150
  office: Secretariado
150
151
  opened: Aberto
152
+ opened_count: '%{count} aberto'
151
153
  organizations: Organizações
152
154
  output: Sair
153
155
  overview: Visão global
@@ -157,6 +159,7 @@ pt:
157
159
  pending_messages: Você tem mensagens não lidas
158
160
  pending_messages_explanation: Seu professor deixou uma mensagem para você! Verifique antes de continuar a enviar soluções.
159
161
  pending_review: Sendo revisto
162
+ pending_review_count: '%{count} sendo revisto'
160
163
  permissions: Permissões
161
164
  please_fill_profile_data: Preencha suas informações pessoais para continuar!
162
165
  please_validate: Você está prestes a entrar no curso. Para oferecer uma experiência melhor, valide que seus dados abaixo sejam reais e corretos.
@@ -185,6 +188,7 @@ pt:
185
188
  sign_up_date: Usuário de
186
189
  solution: Solução
187
190
  solved: Resolvido
191
+ solved_count: '%{count} resolvido'
188
192
  solved_exercises: Exercícios resolvidos
189
193
  solve_doubts: Resolvi dúvidas
190
194
  solve_your_doubts: Consulte suas dúvidas
@@ -13,8 +13,12 @@ module Mumuki::Laboratory::Status::Discussion::Opened
13
13
  end
14
14
  end
15
15
 
16
- def self.reachable_statuses_for_moderator(_)
17
- [Mumuki::Laboratory::Status::Discussion::Closed, Mumuki::Laboratory::Status::Discussion::Solved]
16
+ def self.reachable_statuses_for_moderator(discussion)
17
+ if discussion.has_responses?
18
+ [Mumuki::Laboratory::Status::Discussion::Closed, Mumuki::Laboratory::Status::Discussion::Solved]
19
+ else
20
+ [Mumuki::Laboratory::Status::Discussion::Closed]
21
+ end
18
22
  end
19
23
 
20
24
  def self.iconize
@@ -6,7 +6,7 @@ module Mumuki::Laboratory::Status::Discussion::PendingReview
6
6
  end
7
7
 
8
8
  def self.reachable_statuses_for_moderator(*)
9
- [Mumuki::Laboratory::Status::Discussion::Closed, Mumuki::Laboratory::Status::Discussion::Solved]
9
+ [Mumuki::Laboratory::Status::Discussion::Opened, Mumuki::Laboratory::Status::Discussion::Closed, Mumuki::Laboratory::Status::Discussion::Solved]
10
10
  end
11
11
 
12
12
  def self.iconize
@@ -6,7 +6,7 @@ module Mumuki::Laboratory::Status::Discussion::Solved
6
6
  end
7
7
 
8
8
  def self.reachable_statuses_for_moderator(*)
9
- [Mumuki::Laboratory::Status::Discussion::Closed]
9
+ [Mumuki::Laboratory::Status::Discussion::Opened, Mumuki::Laboratory::Status::Discussion::Closed]
10
10
  end
11
11
 
12
12
  def self.iconize
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Laboratory
3
- VERSION = '5.6.3'
3
+ VERSION = '5.7.0'
4
4
  end
5
5
  end
@@ -20,10 +20,19 @@ describe ExerciseSolutionsController, organization_workspace: :test do
20
20
  end
21
21
 
22
22
  context 'when multifile content is sent' do
23
- before { post :create, params: { exercise_id: problem.id, solution: { content: { a_file: 'a content' } } } }
23
+ before { create(:language, extension: 'js', highlight_mode: 'javascript') }
24
+ before { post :create, params: { exercise_id: problem.id, solution: { content: {
25
+ 'a_file.css' => 'a css content',
26
+ 'a_file.js' => 'a js content'
27
+ } } } }
28
+ let(:files) { problem.files_for(user) }
24
29
 
25
30
  it { expect(response.status).to eq 200 }
26
- it { expect(Assignment.last.solution).to eq("/*<a_file#*/a content/*#a_file>*/\n/*<content#*//*...a_file...*//*#content>*/") }
27
- it { expect(problem.files_for(user)).to eq [struct(name: 'a_file', content: 'a content')] }
31
+ it { expect(Assignment.last.solution).to eq("/*<a_file.css#*/a css content/*#a_file.css>*/\n/*<a_file.js#*/a js content/*#a_file.js>*/\n/*<content#*//*...a_file.css...*/\n/*...a_file.js...*//*#content>*/") }
32
+ it { expect(files.count).to eq 2 }
33
+ it { expect(files[0]).to have_attributes(name: 'a_file.css', content: 'a css content') }
34
+ it { expect(files[0].highlight_mode).to eq 'css' }
35
+ it { expect(files[1]).to have_attributes(name: 'a_file.js', content: 'a js content') }
36
+ it { expect(files[1].highlight_mode).to eq 'javascript' }
28
37
  end
29
38
  end
@@ -91,6 +91,27 @@ feature 'Exercise Flow', organization_workspace: :test do
91
91
  expect(page).to have_text('Description of Succ1')
92
92
  end
93
93
 
94
+ describe 'embedded mode' do
95
+ scenario 'visit exercise by id, standalone mode' do
96
+ visit "/exercises/#{problem_1.id}"
97
+ expect(page).to have_text('Functional Programming 1')
98
+ expect(page).to have_text('Profile')
99
+ end
100
+ scenario 'visit exercise by id, embedded mode in non embeddable organization' do
101
+ visit "/exercises/#{problem_1.id}?embed=true"
102
+ expect(page).to have_text('Functional Programming 1')
103
+ expect(page).to have_text('Profile')
104
+ end
105
+ scenario 'visit exercise by id, embedded mode in embeddable organization' do
106
+ Organization.current.tap { |it| it.embeddable = true }.save!
107
+
108
+ visit "/exercises/#{problem_1.id}?embed=true"
109
+ expect(page).to_not have_text('Functional Programming 1')
110
+ expect(page).to_not have_text('Profile')
111
+ end
112
+ end
113
+
114
+
94
115
  scenario 'visit exercise by id, editor right layout' do
95
116
  visit "/exercises/#{problem_1.id}"
96
117
 
@@ -18,8 +18,10 @@ describe Discussion, organization_workspace: :test do
18
18
  it { expect(initiator.subscribed_to? discussion).to be true }
19
19
  it { expect(discussion.status).to eq :opened }
20
20
  it { expect(discussion.reachable_statuses_for initiator).to eq [:closed] }
21
- it { expect(discussion.reachable_statuses_for moderator).to eq [:closed, :solved] }
21
+ it { expect(discussion.reachable_statuses_for moderator).to eq [:closed] }
22
22
  it { expect(discussion.reachable_statuses_for student).to eq [] }
23
+ it { expect(discussion.commentable_by? student).to be true }
24
+ it { expect(discussion.commentable_by? moderator).to be true }
23
25
 
24
26
  describe 'initiator sends a message' do
25
27
  before { discussion.submit_message!({content: 'I forgot to say this'}, initiator) }
@@ -28,7 +30,7 @@ describe Discussion, organization_workspace: :test do
28
30
  it { expect(discussion.messages.first.content).to eq 'I forgot to say this' }
29
31
  it { expect(initiator.unread_discussions).to eq [] }
30
32
  it { expect(discussion.reachable_statuses_for initiator).to eq [:closed] }
31
- it { expect(discussion.reachable_statuses_for moderator).to eq [:closed, :solved] }
33
+ it { expect(discussion.reachable_statuses_for moderator).to eq [:closed] }
32
34
  it { expect(discussion.reachable_statuses_for student).to eq [] }
33
35
 
34
36
  describe 'and closes the discussion' do
@@ -38,6 +40,8 @@ describe Discussion, organization_workspace: :test do
38
40
  it { expect(discussion.reachable_statuses_for initiator).to eq [] }
39
41
  it { expect(discussion.reachable_statuses_for moderator).to eq [:opened, :solved] }
40
42
  it { expect(discussion.reachable_statuses_for student).to eq [] }
43
+ it { expect(discussion.commentable_by? student).to be false }
44
+ it { expect(discussion.commentable_by? moderator).to be true }
41
45
  end
42
46
  end
43
47
 
@@ -56,8 +60,10 @@ describe Discussion, organization_workspace: :test do
56
60
 
57
61
  it { expect(discussion.status).to eq :pending_review }
58
62
  it { expect(discussion.reachable_statuses_for initiator).to eq [] }
59
- it { expect(discussion.reachable_statuses_for moderator).to eq [:closed, :solved] }
63
+ it { expect(discussion.reachable_statuses_for moderator).to eq [:opened, :closed, :solved] }
60
64
  it { expect(discussion.reachable_statuses_for student).to eq [] }
65
+ it { expect(discussion.commentable_by? student).to be false }
66
+ it { expect(discussion.commentable_by? moderator).to be true }
61
67
  end
62
68
 
63
69
  describe 'initiator tries to solve it' do
@@ -69,8 +75,10 @@ describe Discussion, organization_workspace: :test do
69
75
 
70
76
  it { expect(discussion.status).to eq :solved }
71
77
  it { expect(discussion.reachable_statuses_for initiator).to eq [] }
72
- it { expect(discussion.reachable_statuses_for moderator).to eq [:closed] }
78
+ it { expect(discussion.reachable_statuses_for moderator).to eq [:opened, :closed] }
73
79
  it { expect(discussion.reachable_statuses_for student).to eq [] }
80
+ it { expect(discussion.commentable_by? student).to be false }
81
+ it { expect(discussion.commentable_by? moderator).to be true }
74
82
  end
75
83
  end
76
84
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Exercise, organization_workspace: :test do
4
4
  let(:exercise) { create(:exercise) }
5
- let(:user) { create(:user) }
5
+ let(:user) { create(:user, first_name: 'Orlo') }
6
6
 
7
7
  describe '#slug' do
8
8
  let(:guide) { create(:guide, slug: 'foo/bar') }
@@ -274,6 +274,20 @@ describe Exercise, organization_workspace: :test do
274
274
  end
275
275
  end
276
276
  end
277
+ context 'when interpolation is in test' do
278
+ context 'using user_first_name' do
279
+ let(:exercise) { create(:exercise, test: "<div>Hola #{interpolation}</div>") }
280
+ let(:interpolation) { '/*...user_first_name...*/' }
281
+
282
+ it { expect(exercise.test_for(user)).to eq "<div>Hola Orlo</div>" }
283
+ end
284
+
285
+ context 'and test is nil' do
286
+ let(:exercise) { create(:exercise, test: nil, expectations: [{}]) }
287
+
288
+ it { expect(exercise.test_for(user)).to eq nil }
289
+ end
290
+ end
277
291
  context 'when interpolation is in extra' do
278
292
  describe 'right previous content' do
279
293
  let(:exercise) { create(:exercise, extra: interpolation) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mumuki-laboratory
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.3
4
+ version: 5.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Franco Bulgarelli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-27 00:00:00.000000000 Z
11
+ date: 2018-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '2.3'
117
+ version: '2.4'
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '2.3'
124
+ version: '2.4'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: mumukit-login
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -460,6 +460,7 @@ files:
460
460
  - app/views/book_discussions/index.html.erb
461
461
  - app/views/chapters/show.html.erb
462
462
  - app/views/complements/show.html.erb
463
+ - app/views/discussions/_description_message.html.erb
463
464
  - app/views/discussions/_message.html.erb
464
465
  - app/views/discussions/_message_container.html.erb
465
466
  - app/views/discussions/_new_message.html.erb
@@ -488,6 +489,7 @@ files:
488
489
  - app/views/layouts/_error.html.erb
489
490
  - app/views/layouts/_guide.html.erb
490
491
  - app/views/layouts/_kids.html.erb
492
+ - app/views/layouts/_main.html.erb
491
493
  - app/views/layouts/_messages.html.erb
492
494
  - app/views/layouts/_organization_chooser.html.erb
493
495
  - app/views/layouts/_organizations_listing.html.erb
@@ -500,6 +502,7 @@ files:
500
502
  - app/views/layouts/_test_results.html.erb
501
503
  - app/views/layouts/_timer.html.erb
502
504
  - app/views/layouts/application.html.erb
505
+ - app/views/layouts/embedded.html.erb
503
506
  - app/views/layouts/exercise_inputs/editors/_code.html.erb
504
507
  - app/views/layouts/exercise_inputs/editors/_custom.html.erb
505
508
  - app/views/layouts/exercise_inputs/editors/_hidden.html.erb
@@ -796,6 +799,7 @@ files:
796
799
  - lib/mumuki/laboratory/controllers/content.rb
797
800
  - lib/mumuki/laboratory/controllers/current_organization.rb
798
801
  - lib/mumuki/laboratory/controllers/dynamic_errors.rb
802
+ - lib/mumuki/laboratory/controllers/embedded_mode.rb
799
803
  - lib/mumuki/laboratory/controllers/exercise_seed.rb
800
804
  - lib/mumuki/laboratory/controllers/nested_in_exercise.rb
801
805
  - lib/mumuki/laboratory/controllers/notifications.rb
@@ -813,6 +817,7 @@ files:
813
817
  - lib/mumuki/laboratory/extensions/array.rb
814
818
  - lib/mumuki/laboratory/extensions/request.rb
815
819
  - lib/mumuki/laboratory/extensions/string.rb
820
+ - lib/mumuki/laboratory/file.rb
816
821
  - lib/mumuki/laboratory/locales/activerecord.en.yml
817
822
  - lib/mumuki/laboratory/locales/activerecord.es.yml
818
823
  - lib/mumuki/laboratory/locales/datetime.es.yml