mumuki-laboratory 9.15.0 → 9.18.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/mumuki_laboratory/application/csrf-token.js +5 -5
  3. data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +1 -0
  4. data/app/assets/javascripts/mumuki_laboratory/application/exercise.js +17 -2
  5. data/app/controllers/ajax_controller.rb +1 -1
  6. data/app/controllers/application_controller.rb +3 -3
  7. data/app/controllers/exam_authorization_requests_controller.rb +22 -17
  8. data/app/controllers/exam_registrations_controller.rb +1 -1
  9. data/app/controllers/exercises_controller.rb +0 -1
  10. data/app/helpers/discussions_helper.rb +4 -1
  11. data/app/helpers/editor_helper.rb +1 -1
  12. data/app/helpers/exams_helper.rb +33 -0
  13. data/app/helpers/exercise_input_helper.rb +0 -4
  14. data/app/helpers/menu_bar_helper.rb +5 -4
  15. data/app/helpers/notifications_helper.rb +1 -1
  16. data/app/helpers/time_helper.rb +12 -0
  17. data/app/helpers/time_zone_helper.rb +5 -1
  18. data/app/helpers/user_discussions_helper.rb +7 -7
  19. data/app/views/book/show.html.erb +4 -2
  20. data/app/views/discussions/_actions.html.erb +1 -0
  21. data/app/views/discussions/_basic_actions.html.erb +20 -0
  22. data/app/views/discussions/_description_message.html.erb +1 -1
  23. data/app/views/discussions/_message.html.erb +6 -2
  24. data/app/views/discussions/new.html.erb +7 -1
  25. data/app/views/discussions/show.html.erb +1 -18
  26. data/app/views/exam_registrations/show.html.erb +1 -1
  27. data/app/views/exercises/_read_only.html.erb +54 -60
  28. data/app/views/exercises/show.html.erb +1 -0
  29. data/app/views/layouts/_discussions_list.html.erb +1 -1
  30. data/app/views/layouts/_messages.html.erb +1 -1
  31. data/app/views/users/_user_form.html.erb +1 -1
  32. data/app/views/users/messages.html.erb +1 -1
  33. data/app/views/users/notifications.html.erb +1 -1
  34. data/lib/mumuki/laboratory/controllers/validate_access_mode.rb +5 -0
  35. data/lib/mumuki/laboratory/locales/en.yml +8 -2
  36. data/lib/mumuki/laboratory/locales/es-CL.yml +8 -2
  37. data/lib/mumuki/laboratory/locales/es.yml +8 -2
  38. data/lib/mumuki/laboratory/locales/pt.yml +8 -2
  39. data/lib/mumuki/laboratory/version.rb +1 -1
  40. data/spec/controllers/exam_authorization_requests_controller_spec.rb +16 -2
  41. data/spec/controllers/exercises_controller_spec.rb +32 -0
  42. data/spec/dummy/db/schema.rb +2 -1
  43. data/spec/dummy/db/seeds.rb +16 -8
  44. data/spec/features/not_found_private_flow_spec.rb +1 -1
  45. metadata +123 -117
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67dd086ce64c96505cea38bf9fdf857f304acd8322d91e4c139e0a847e89ce46
4
- data.tar.gz: 48b771a27f10a159b4b10e38e88cee08f4f032969fbe7a533d188dd4f50cc0b5
3
+ metadata.gz: 1ae29f9665d934e9d1a3ef71a67074980155c36eabd6dc0ee5d66ddcc7c6cea3
4
+ data.tar.gz: 5a9158cd4bec4155c1890241f2dab4d87889c87d0b9143d572bc63cc4e925f99
5
5
  SHA512:
6
- metadata.gz: a28dd46787bfb3b9fb50ab7ae7b6d1421e8abb5ec9b0f4f9c20bc4af62da0a5343c6a0973ebad6073af95f66178c49d0f4cccac36f11e1ce6245a23e4527143c
7
- data.tar.gz: 3ba41f5eee2cdd908ecedbe5694de3738715f99a2c3e6c6e17d0049d2224f74be08fbbcf72b4910a4e4a7c440e05e2b87130466055919ba1b0b704d15ed29688
6
+ metadata.gz: 9cafbd64b3e29e4005bb51c3da4a3e45a7532295a34e4e7727185af580544ca9ff614f0c7b15496f9a043e6dd9c8d3becc82282a9f4deb6cbfec6c3e09fa78ed
7
+ data.tar.gz: ecf6beb36af7772043cb3775523373b38db0cdbc58ebe316b9fd4239decf8610c9ea7200b1fc4954be83765a2f71a1d97e75621814c19dcccb5984a1ac4f17ec
@@ -1,12 +1,12 @@
1
1
  mumuki.CsrfToken = (() => {
2
2
  class CsrfToken {
3
- constructor() {
4
- this.value = $('meta[name="csrf-token"]').attr('content');
3
+ get token() {
4
+ return $('meta[name="csrf-token"]').attr('content');
5
5
  }
6
+
6
7
  newRequest(data) {
7
- var self = this;
8
- data.beforeSend = function (xhr) {
9
- xhr.setRequestHeader('X-CSRF-Token', self.value);
8
+ data.beforeSend = (xhr) => {
9
+ xhr.setRequestHeader('X-CSRF-Token', this.token);
10
10
  };
11
11
  return data;
12
12
  }
@@ -70,6 +70,7 @@ mumuki.load(() => {
70
70
  discussionMessageToggleApprove: function (url, elem) {
71
71
  Forum.discussionPost(url).done(function () {
72
72
  elem.toggleClass("selected");
73
+ elem.attr('data-bs-original-title', '');
73
74
  });
74
75
  },
75
76
  discussionMessageToggleNotActuallyAQuestion: function (url, elem) {
@@ -1,12 +1,20 @@
1
1
  (() => {
2
+ function shouldSkipChangesCheck() {
3
+ return mumuki.isKidsExercise() || mumuki.exercise.isReadOnly;
4
+ }
5
+
2
6
  function solutionChangedSinceLastSubmission() {
3
7
  return mumuki.exercise.id &&
4
8
  mumuki.SubmissionsStore.getLastSubmissionAndResult(mumuki.exercise.id) &&
5
9
  !mumuki.SubmissionsStore.getSubmissionResultFor(mumuki.exercise.id, mumuki.editors.getSubmission());
6
10
  }
7
11
 
12
+ function shouldWarnOfChanges() {
13
+ return !shouldSkipChangesCheck() && solutionChangedSinceLastSubmission();
14
+ }
15
+
8
16
  window.addEventListener("beforeunload", (event) => {
9
- if (solutionChangedSinceLastSubmission()) {
17
+ if (shouldWarnOfChanges()) {
10
18
  event.returnValue = 'unsaved_progress';
11
19
  } else {
12
20
  delete event['returnValue'];
@@ -14,7 +22,7 @@
14
22
  });
15
23
 
16
24
  window.addEventListener("turbolinks:before-visit", (event) => {
17
- if (solutionChangedSinceLastSubmission() && !confirm(mumuki.I18n.t('unsaved_progress'))) event.preventDefault();
25
+ if (shouldWarnOfChanges() && !confirm(mumuki.I18n.t('unsaved_progress'))) event.preventDefault();
18
26
  });
19
27
  })();
20
28
 
@@ -66,6 +74,13 @@ mumuki.exercise = {
66
74
  return this._current;
67
75
  },
68
76
 
77
+ /**
78
+ * @type {Boolean?}
79
+ */
80
+ get isReadOnly() {
81
+ return $('#mu-exercise-read-only').val() === 'true';
82
+ },
83
+
69
84
  /**
70
85
  * Set global current exercise information
71
86
  */
@@ -9,6 +9,6 @@ class AjaxController < ApplicationController
9
9
  end
10
10
 
11
11
  def validate_organization_enabled!
12
- Organization.current.validate_enabled!
12
+ Organization.current.validate_enabled! unless current_user&.teacher_here?
13
13
  end
14
14
  end
@@ -19,11 +19,11 @@ class ApplicationController < ActionController::Base
19
19
  before_action :set_time_zone!
20
20
 
21
21
  before_action :ensure_user_enabled!, if: :current_user?
22
-
23
22
  before_action :redirect_to_proper_context!, if: :immersive_context_wrong?
24
- before_action :validate_active_organization!
25
23
 
26
24
  before_action :authorize_if_private!
25
+ before_action :validate_active_organization!
26
+
27
27
  before_action :validate_user_profile!, if: :current_user?
28
28
  before_action :validate_accepted_role_terms!, if: :current_user?
29
29
  before_action :ensure_restore_progress!, if: :current_user?
@@ -68,7 +68,7 @@ class ApplicationController < ActionController::Base
68
68
  # ensures contents are accessible to current user
69
69
  def validate_accessible!
70
70
  return if current_user&.teacher_here?
71
- accessible_subject.validate_accessible_for! current_user
71
+ accessible_subject&.validate_accessible_for! current_user
72
72
  end
73
73
 
74
74
  def validate_active_organization!
@@ -1,36 +1,41 @@
1
1
  class ExamAuthorizationRequestsController < ApplicationController
2
2
 
3
- before_action :verify_registration_opened!, on: [:create, :update]
3
+ before_action :set_registration!
4
+ before_action :set_exam!
5
+ before_action :verify_registration_opened!
4
6
 
5
7
  def create
6
- authorization_request = ExamAuthorizationRequest.find_or_create_by! create_authorization_request_params do |it|
7
- it.assign_attributes authorization_request_params
8
- end
9
- current_user.read_notification! authorization_request.exam_registration
10
- flash.notice = I18n.t :exam_authorization_request_created
11
- redirect_to root_path
8
+ authorization_request = @registration.request_authorization! current_user, @exam
9
+ current_user.read_notification! @registration
10
+ flash.notice = friendly_flash_notice(:exam_authorization_request_created)
11
+ redirect_to exam_authorizations_user_path
12
12
  end
13
13
 
14
14
  def update
15
- ExamAuthorizationRequest.update params[:id], authorization_request_params
16
- flash.notice = I18n.t :exam_authorization_request_saved
17
- redirect_to root_path
15
+ @registration.update_authorization_request_by_id! params[:id], @exam
16
+ flash.notice = friendly_flash_notice(:exam_authorization_request_saved)
17
+ redirect_to exam_authorizations_user_path
18
18
  end
19
19
 
20
20
  private
21
21
 
22
- def create_authorization_request_params
23
- authorization_request_params.slice :exam_registration_id, :user, :organization
22
+ def friendly_flash_notice(key)
23
+ I18n.t key, friendly_date: helpers.local_time(@exam.start_time)
24
24
  end
25
25
 
26
26
  def authorization_request_params
27
- params
28
- .require(:exam_authorization_request).permit(:exam_id, :exam_registration_id)
29
- .merge(user: current_user, organization: Organization.current)
27
+ params.require(:exam_authorization_request).permit(:exam_id, :exam_registration_id)
28
+ end
29
+
30
+ def set_registration!
31
+ @registration = Organization.current.exam_registrations.find(authorization_request_params[:exam_registration_id])
32
+ end
33
+
34
+ def set_exam!
35
+ @exam = @registration.exams.find(authorization_request_params[:exam_id])
30
36
  end
31
37
 
32
38
  def verify_registration_opened!
33
- exam_registration = ExamRegistration.find(authorization_request_params[:exam_registration_id])
34
- raise Mumuki::Domain::GoneError if exam_registration.ended?
39
+ raise Mumuki::Domain::GoneError if @registration.ended?
35
40
  end
36
41
  end
@@ -1,6 +1,6 @@
1
1
  class ExamRegistrationsController < ApplicationController
2
2
  def show
3
- @registration = ExamRegistration.find(params[:id])
3
+ @registration = Organization.current.exam_registrations.find(params[:id])
4
4
  @authorization_request = @registration.authorization_request_for(current_user)
5
5
  end
6
6
  end
@@ -6,7 +6,6 @@ class ExercisesController < ApplicationController
6
6
 
7
7
  before_action :set_guide!, only: :show
8
8
  before_action :set_assignment!, only: :show, if: :current_user?
9
- before_action :validate_accessible!, only: :show
10
9
  before_action :start!, only: :show
11
10
 
12
11
  def show
@@ -184,7 +184,10 @@ module DiscussionsHelper
184
184
  end
185
185
 
186
186
  def discussion_info(discussion)
187
- "#{t(:time_since, time: time_ago_in_words(discussion.created_at))} · #{t(:reply_count, count: discussion.visible_messages.size)}"
187
+ <<~HTML.html_safe
188
+ <span>#{friendly_time(discussion.created_at, :time_since)}</span>
189
+ <span> · #{t(:reply_count, count: discussion.visible_messages.size)}</span>
190
+ HTML
188
191
  end
189
192
 
190
193
  def discussion_filter_params_without_page
@@ -8,7 +8,7 @@ module EditorHelper
8
8
 
9
9
  def read_only_editor(content, language, options = {})
10
10
  editor_options = editor_defaults(language, options.deep_merge(data: { readonly: true }), 'editor')
11
- text_area_tag :solution_content, content, editor_options
11
+ text_area_tag 'solution[content]', content, editor_options
12
12
  end
13
13
 
14
14
  def spell_checked_editor(name, options = {})
@@ -0,0 +1,33 @@
1
+ module ExamsHelper
2
+ def exam_information_for(user, exam)
3
+ %Q{
4
+ #{course_information_for(user, exam)}
5
+ #{date_information_for(exam)}
6
+ #{duration_information_for(exam)}
7
+ }.html_safe
8
+ end
9
+
10
+ private
11
+
12
+ def course_information_for(user, exam)
13
+ if user.teacher_here?
14
+ "#{fa_icon('graduation-cap', class: 'fa-fw',
15
+ text: "<strong>#{t :course}:</strong> #{exam.course.canonical_code}".html_safe)}
16
+ <br>"
17
+ end
18
+ end
19
+
20
+ def date_information_for(exam)
21
+ "#{fa_icon('calendar-alt', class: 'fa-fw',
22
+ text: "<strong>#{t :date_and_time}:</strong> #{local_time_without_time_zone(exam.start_time)} - #{local_time(exam.end_time)}".html_safe)}
23
+ <br>"
24
+ end
25
+
26
+ def duration_information_for(exam)
27
+ if exam.duration?
28
+ "#{fa_icon(:stopwatch, class: 'fa-fw',
29
+ text: "<strong>#{t :available_time}:</strong> #{t :time_in_minutes, time: exam.duration}".html_safe)}
30
+ <br>"
31
+ end
32
+ end
33
+ end
@@ -35,10 +35,6 @@ module ExerciseInputHelper
35
35
  should_render_exercise_tabs?(exercise) { exercise.has_messages_for? user }
36
36
  end
37
37
 
38
- def should_render_read_only_exercise_tabs?(discussion)
39
- should_render_exercise_tabs?(discussion.exercise) { discussion.has_submission? }
40
- end
41
-
42
38
  def should_render_message_input?(exercise, organization = Organization.current)
43
39
  exercise.is_a?(Problem) && !exercise.hidden? && organization.raise_hand_enabled?
44
40
  end
@@ -38,12 +38,13 @@ module MenuBarHelper
38
38
  li_tag menu_item('sign-out-alt', :sign_out, logout_path(origin: url_for, organization: Organization.current))
39
39
  end
40
40
 
41
- def menu_item(icon, name, url, css_class = nil, **translation_params)
42
- menu_text_item(icon, t(name, translation_params), url, css_class)
41
+ def menu_item(icon, name, url, css_class = nil, translation_params = {}, options = {})
42
+ menu_text_item(icon, t(name, translation_params), url, css_class, **options)
43
43
  end
44
44
 
45
- def menu_text_item(icon, text, url, css_class = nil)
46
- link_to fixed_fa_icon(icon, text: text), url, role: 'menuitem', tabindex: '-1', class: "dropdown-item #{css_class}"
45
+ def menu_text_item(icon, text, url, css_class = nil, **options)
46
+ link_options = { role: 'menuitem', tabindex: '-1', class: "dropdown-item #{css_class}" }.merge(options)
47
+ link_to fixed_fa_icon(icon, text: text), url, link_options
47
48
  end
48
49
 
49
50
  def any_menu_bar_links?
@@ -4,7 +4,7 @@ module NotificationsHelper
4
4
  end
5
5
 
6
6
  def notification_preview_item(icon, name, url, **translation_params)
7
- menu_item icon, name, url, 'mu-notification-preview', **translation_params
7
+ menu_item icon, name, url, 'mu-notification-preview', translation_params
8
8
  end
9
9
 
10
10
  def notification_text_preview_item(icon, text, url)
@@ -0,0 +1,12 @@
1
+ module TimeHelper
2
+ def friendly_time(time, t_key = nil)
3
+ friendly_time = time_ago_in_words time
4
+ friendly_time_t = t_key ? t(t_key, time: friendly_time) : friendly_time
5
+
6
+ <<~HTML.html_safe
7
+ <time title="#{time}">
8
+ #{friendly_time_t}
9
+ </time>
10
+ HTML
11
+ end
12
+ end
@@ -1,5 +1,9 @@
1
1
  module TimeZoneHelper
2
2
  def local_time(time, time_zone = Time.zone.name)
3
- "#{l(time.in_time_zone(time_zone), format: :long)} (#{time_zone})"
3
+ "#{local_time_without_time_zone(time, time_zone)} (#{time_zone})"
4
+ end
5
+
6
+ def local_time_without_time_zone(time, time_zone = Time.zone.name)
7
+ l(time.in_time_zone(time_zone), format: :long)
4
8
  end
5
9
  end
@@ -1,6 +1,6 @@
1
1
  module UserDiscussionsHelper
2
2
  def user_discussions_table_title(discussion, user, last_read)
3
- %Q{
3
+ <<~HTML.html_safe
4
4
  <tr></tr>
5
5
  <thead>
6
6
  <tr>
@@ -9,30 +9,30 @@ module UserDiscussionsHelper
9
9
  </td>
10
10
  </tr>
11
11
  </thead>
12
- }.html_safe
12
+ HTML
13
13
  end
14
14
 
15
15
  def user_discussions_table_header
16
- %Q{
16
+ <<~HTML.html_safe
17
17
  <tr class="fw-bold">
18
18
  <td></td>
19
19
  <td>#{t(:exercise)}</td>
20
20
  <td>#{t(:discussion_created_by)}</td>
21
21
  <td>#{t(:last_message)}</td>
22
22
  </tr>
23
- }.html_safe
23
+ HTML
24
24
  end
25
25
 
26
26
  def user_discussions_table_item(discussion, user)
27
- %Q{
27
+ <<~HTML.html_safe
28
28
  <tr>
29
29
  <td class="text-center">
30
30
  #{icon_for_read(discussion.read_by?(user))}
31
31
  </td>
32
32
  <td>#{link_to discussion.item.name, item_discussion_path(discussion)}</td>
33
33
  <td>#{discussion_user_name discussion.initiator}</td>
34
- <td>#{t(:time_since, time: time_ago_in_words(discussion.last_message_date))}</td>
34
+ <td>#{friendly_time(discussion.last_message_date, :time_since)}</td>
35
35
  </tr>
36
- }.html_safe
36
+ HTML
37
37
  end
38
38
  end
@@ -45,8 +45,10 @@
45
45
  <h2><%= t(:exams) %></h2>
46
46
  <% @exams.each_with_index do |it, index| %>
47
47
  <div class="chapter">
48
- <h3> <%= index + 1 %>. <%= link_to_path_element it, mode: :plain %></h3>
49
-
48
+ <h3><%= link_to_path_element it, mode: :plain %></h3>
49
+ <div class="fs-7">
50
+ <%= exam_information_for(current_user, it) %>
51
+ </div>
50
52
  <div class="text-box">
51
53
  <%= it.guide.description_teaser_html %>
52
54
  </div>
@@ -0,0 +1 @@
1
+ <%= render partial: 'discussions/basic_actions' %>
@@ -0,0 +1,20 @@
1
+ <% if @discussion.can_toggle_responsible?(current_user) %>
2
+ <div class="discussion-responsible me-1">
3
+ <%= btn_toggle responsible_icon, not_responsible_icon, @discussion.any_responsible?, class: 'btn-sm',
4
+ onclick: "mumuki.Forum.discussionResponsible('#{responsible_discussion_url(@discussion)}')" %>
5
+ </div>
6
+ <% end %>
7
+
8
+ <% if @discussion.subscribable? %>
9
+ <div class="discussion-subscription me-1">
10
+ <%= btn_toggle subscription_icon, unsubscription_icon, current_user.subscribed_to?(@discussion), class: 'btn-sm',
11
+ onclick: "mumuki.Forum.discussionSubscription('#{subscription_discussion_url(@discussion)}')" %>
12
+ </div>
13
+ <% end %>
14
+
15
+ <% if @discussion.solved? %>
16
+ <div class="discussion-upvote">
17
+ <%= btn_toggle upvote_icon, undo_upvote_icon, current_user.upvoted?(@discussion), class: 'btn-sm',
18
+ onclick: "mumuki.Forum.discussionUpvote('#{upvote_discussion_url(@discussion)}')" %>
19
+ </div>
20
+ <% end %>
@@ -4,7 +4,7 @@
4
4
  <div class="discussion-message-bubble-title">
5
5
  <%= linked_discussion_user_name(discussion.initiator) %>
6
6
  <span class="message-date">
7
- <%= t(:time_since, time: time_ago_in_words(discussion.created_at)) %>
7
+ <%= friendly_time(discussion.created_at, :time_since) %>
8
8
  </span>
9
9
  </div>
10
10
  </div>
@@ -7,12 +7,16 @@
7
7
  <span class="moderator-badge"><%= t(:moderation) %></span>
8
8
  <% end %>
9
9
  <span class="message-date">
10
- <%= t(:time_since, time: time_ago_in_words(message.created_at)) %>
10
+ <%= friendly_time(message.created_at, :time_since) %>
11
11
  </span>
12
12
  <span class="actions">
13
13
  <% if message.authorized?(current_user) && !message.deleted? %>
14
14
  <% if current_user&.moderator_here? %>
15
15
  <a class="discussion-message-approved <%= 'selected' if message.approved? %>"
16
+ <% if message.approved? && current_user&.forum_supervisor_here? %>
17
+ data-bs-toggle="tooltip" data-bs-placement="left"
18
+ title="<%= t :approved_by, validator: message.approved_by.name, date: local_time(message.approved_at) %>"
19
+ <% end %>
16
20
  onclick="mumuki.Forum.discussionMessageToggleApprove('<%= approve_discussion_message_url(@discussion, message) %>', $(this))">
17
21
  <%= fa_icon(:check, class: 'fa-lg') %>
18
22
  </a>
@@ -47,7 +51,7 @@
47
51
  <% if current_user&.moderator_here? %>
48
52
  <hr>
49
53
  <%= t :deleted_by, deleter: message.deleted_by.name %>
50
- <%= t(:time_since, time: time_ago_in_words(message.deleted_at)) %>.
54
+ <%= friendly_time(message.deleted_at, :time_since) %>
51
55
  <a href='<%= "#deletedMessage#{message.id}" %>' data-bs-toggle="collapse">
52
56
  <%= t :show_message %>
53
57
  </a>
@@ -22,12 +22,18 @@
22
22
  <div class="container-fluid">
23
23
  <div class="row">
24
24
  <div class="discussion-new-message-content">
25
- <%= f.editor :description, '', {id: 'discussion-new-message', class: 'form-control', placeholder: t(:discussion_description_placeholder)} %>
25
+ <%= spell_checked_editor 'discussion[description]',
26
+ { id: 'discussion-new-message', placeholder: t(:discussion_description_placeholder) } %>
26
27
  </div>
27
28
  </div>
28
29
  </div>
29
30
  </div>
30
31
  </div>
32
+ <% if faqs_enabled_here? %>
33
+ <div class="fs-7">
34
+ <%= fa_icon('exclamation-triangle', text: t(:only_forum_questions_on_forum, link: link_to_faqs).html_safe) %>
35
+ </div>
36
+ <% end %>
31
37
  <%= f.submit t(:publish_discussion), class: 'btn btn-complementary w-100 discussion-new-message-button' %>
32
38
  <% end %>
33
39
  <% end %>
@@ -15,24 +15,7 @@
15
15
  <h3 class="flex-grow-1 me-3"><%= t :messages %></h3>
16
16
  <% if current_user && @discussion.persisted? && current_access_mode.show_discussion_element? %>
17
17
  <span class="d-flex">
18
- <% if @discussion.can_toggle_responsible?(current_user) %>
19
- <div class="discussion-responsible me-1">
20
- <%= btn_toggle responsible_icon, not_responsible_icon, @discussion.any_responsible?, class: 'btn-sm',
21
- onclick: "mumuki.Forum.discussionResponsible('#{responsible_discussion_url(@discussion)}')" %>
22
- </div>
23
- <% end %>
24
- <% if @discussion.subscribable? %>
25
- <div class="discussion-subscription me-1">
26
- <%= btn_toggle subscription_icon, unsubscription_icon, current_user.subscribed_to?(@discussion), class: 'btn-sm',
27
- onclick: "mumuki.Forum.discussionSubscription('#{subscription_discussion_url(@discussion)}')" %>
28
- </div>
29
- <% end %>
30
- <% if @discussion.solved? %>
31
- <div class="discussion-upvote">
32
- <%= btn_toggle upvote_icon, undo_upvote_icon, current_user.upvoted?(@discussion), class: 'btn-sm',
33
- onclick: "mumuki.Forum.discussionUpvote('#{upvote_discussion_url(@discussion)}')" %>
34
- </div>
35
- <% end %>
18
+ <%= render partial: 'discussions/actions' %>
36
19
  </span>
37
20
  <% end %>
38
21
  </div>
@@ -25,7 +25,7 @@
25
25
  <%= f.hidden_field :exam_registration_id, value: @registration.id %>
26
26
  <div class="mb-4">
27
27
  <%= f.label :exam_id, t(:exam_registration_choose_exam) %>
28
- <% @registration.exams.each do |exam| %>
28
+ <% @registration.available_exams.each do |exam| %>
29
29
  <div class="form-check">
30
30
  <%= f.radio_button(:exam_id, exam.id, id: exam.id, required: true, class: 'form-check-input mu-read-only-input',
31
31
  checked: @authorization_request.exam_id == exam.id) %>
@@ -45,82 +45,76 @@
45
45
  <div class="accordion-body accordion-collapse collapse show" id="mu-discussion-accordion-body"
46
46
  aria-labelledby="#mu-discussion-accordion-header" data-bs-parent="#mu-discussion-accordion">
47
47
 
48
- <% if should_render_read_only_exercise_tabs?(@discussion) %>
49
- <ul class="nav nav-tabs discussion-tabs" role="tablist">
50
- <li role="presentation">
51
- <a class="editor-tab nav-link active" data-bs-target="#solution" aria-controls="solution" role="tab" data-bs-toggle="tab">
52
- <%= t :solution %>
53
- </a>
54
- </li>
55
- <li role="presentation">
56
- <a class="editor-tab nav-link" data-bs-target="#results" aria-controls="results" role="tab" data-bs-toggle="tab">
57
- <%= t :results %>
58
- </a>
59
- </li>
60
- <li role="presentation">
61
- <a class="editor-tab nav-link" data-bs-target="#content" aria-controls="content" role="tab" data-bs-toggle="tab">
62
- <%= t :description %>
63
- </a>
64
- </li>
48
+ <ul class="nav nav-tabs discussion-tabs" role="tablist">
49
+ <li role="presentation">
50
+ <a class="editor-tab nav-link active" data-bs-target="#solution" aria-controls="solution" role="tab" data-bs-toggle="tab">
51
+ <%= t :solution %>
52
+ </a>
53
+ </li>
54
+ <li role="presentation">
55
+ <a class="editor-tab nav-link" data-bs-target="#results" aria-controls="results" role="tab" data-bs-toggle="tab">
56
+ <%= t :results %>
57
+ </a>
58
+ </li>
59
+ <li role="presentation">
60
+ <a class="editor-tab nav-link" data-bs-target="#content" aria-controls="content" role="tab" data-bs-toggle="tab">
61
+ <%= t :description %>
62
+ </a>
63
+ </li>
65
64
 
66
- <% if exercise.extra_visible? %>
67
- <%= extra_code_tab %>
68
- <% end %>
65
+ <% if exercise.extra_visible? %>
66
+ <%= extra_code_tab %>
67
+ <% end %>
69
68
 
70
- <% if exercise.queriable? %>
71
- <%= console_tab %>
72
- <% end %>
73
- </ul>
69
+ <% if exercise.queriable? %>
70
+ <%= console_tab %>
71
+ <% end %>
72
+ </ul>
74
73
 
75
- <div class="tab-content">
76
- <div role="tabpanel" class="tab-pane mu-input-panel fade show active" id="solution">
77
- <div class="mu-tab-body">
78
- <div class="mu-read-only-editor">
79
- <%= render_exercise_read_only_editor exercise, @discussion.solution %>
80
- </div>
74
+ <div class="tab-content">
75
+ <div role="tabpanel" class="tab-pane mu-input-panel fade show active" id="solution">
76
+ <div class="mu-tab-body">
77
+ <div class="mu-read-only-editor">
78
+ <%= render_exercise_read_only_editor exercise, @discussion.solution %>
81
79
  </div>
82
80
  </div>
81
+ </div>
83
82
 
84
- <div role="tabpanel" class="tab-pane fade" id="results">
85
- <div class="mu-tab-body">
86
- <%= render layout: 'exercise_solutions/contextualization_results_container', locals: { contextualization: @discussion } do %>
87
- <div class="row">
88
- <div class="col-md-12 submission-results">
89
- <%= render partial: 'exercise_solutions/contextualization_results_body',
90
- locals: { contextualization: @discussion, guide_finished_by_solution: false } %>
91
- </div>
83
+ <div role="tabpanel" class="tab-pane fade" id="results">
84
+ <div class="mu-tab-body">
85
+ <%= render layout: 'exercise_solutions/contextualization_results_container', locals: { contextualization: @discussion } do %>
86
+ <div class="row">
87
+ <div class="col-md-12 submission-results">
88
+ <%= render partial: 'exercise_solutions/contextualization_results_body',
89
+ locals: { contextualization: @discussion, guide_finished_by_solution: false } %>
92
90
  </div>
93
- <% end %>
94
- </div>
95
- </div>
96
-
97
- <div role="tabpanel" class="tab-pane fade" id="content">
98
- <div class="mu-tab-body">
99
- <div class="exercise-assignment">
100
- <%= render partial: 'exercises/exercise_assignment', locals: { exercise: exercise } %>
101
91
  </div>
102
- </div>
92
+ <% end %>
103
93
  </div>
94
+ </div>
104
95
 
105
- <div role="tabpanel" class="tab-pane mu-input-panel fade" id="console">
106
- <div class="mu-overlapped-container">
107
- <div class="console">
108
- </div>
109
- <div class="mu-overlapped">
110
- <%= restart_icon %>
111
- </div>
96
+ <div role="tabpanel" class="tab-pane fade" id="content">
97
+ <div class="mu-tab-body">
98
+ <div class="exercise-assignment">
99
+ <%= render partial: 'exercises/exercise_assignment', locals: { exercise: exercise } %>
112
100
  </div>
113
101
  </div>
102
+ </div>
114
103
 
115
- <div role="tabpanel" class="tab-pane mu-input-panel fade mu-elipsis" id="visible-extra">
116
- <%= @discussion.extra_preview_html %>
104
+ <div role="tabpanel" class="tab-pane mu-input-panel fade" id="console">
105
+ <div class="mu-overlapped-container">
106
+ <div class="console">
107
+ </div>
108
+ <div class="mu-overlapped">
109
+ <%= restart_icon %>
110
+ </div>
117
111
  </div>
118
112
  </div>
119
- <% else %>
120
- <div class="exercise-assignment">
121
- <%= render partial: 'exercises/exercise_assignment', locals: { exercise: exercise } %>
113
+
114
+ <div role="tabpanel" class="tab-pane mu-input-panel fade mu-elipsis" id="visible-extra">
115
+ <%= @discussion.extra_preview_html %>
122
116
  </div>
123
- <% end %>
117
+ </div>
124
118
  </div>
125
119
  </div>
126
120
  </div>