mumuki-laboratory 5.3.0 → 5.4.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 +6 -1
- data/app/assets/javascripts/application/kids.js +47 -17
- data/app/assets/stylesheets/application/modules/_kids.scss +20 -0
- data/app/assets/stylesheets/application.scss +1 -1
- data/app/controllers/invitations_controller.rb +2 -2
- data/app/helpers/application_helper.rb +12 -2
- data/app/mailers/user_mailer.rb +12 -3
- data/app/models/assignment.rb +8 -0
- data/app/models/concerns/assistable.rb +17 -0
- data/app/models/concerns/with_expectations.rb +1 -5
- data/app/models/concerns/with_reminders.rb +53 -9
- data/app/models/concerns/with_slug.rb +1 -1
- data/app/models/concerns/with_status.rb +1 -1
- data/app/models/exercise.rb +2 -2
- data/app/models/organization.rb +8 -0
- data/app/models/submission/submission.rb +3 -1
- data/app/models/user.rb +4 -0
- data/app/views/book/show.html.erb +1 -1
- data/app/views/exercise_solutions/_expectations.html.erb +7 -0
- data/app/views/exercise_solutions/_results.html.erb +6 -3
- data/app/views/layouts/_kids.html.erb +3 -3
- data/app/views/layouts/application.html.erb +4 -5
- data/app/views/user_mailer/no_submissions_reminder.html.erb +349 -0
- data/app/views/user_mailer/no_submissions_reminder.text.erb +13 -0
- data/db/migrate/20180307150148_add_failed_submissions_count_to_assignments.rb +5 -0
- data/db/migrate/20180526141344_add_tips_rules_to_exercise.rb +5 -0
- data/lib/mumuki/laboratory/controllers/dynamic_errors.rb +1 -1
- data/lib/mumuki/laboratory/controllers/results_rendering.rb +7 -1
- data/lib/mumuki/laboratory/locales/en.yml +2 -0
- data/lib/mumuki/laboratory/locales/es.yml +2 -0
- data/lib/mumuki/laboratory/locales/narrator.en.yml +14 -0
- data/lib/mumuki/laboratory/locales/narrator.es.yml +14 -0
- data/lib/mumuki/laboratory/locales/narrator.pt.yml +14 -0
- data/lib/mumuki/laboratory/seed.rb +9 -29
- data/lib/mumuki/laboratory/status/base.rb +3 -1
- data/lib/mumuki/laboratory/status/failed.rb +0 -4
- data/lib/mumuki/laboratory/status/passed_with_warnings.rb +0 -4
- data/lib/mumuki/laboratory/status/unknown.rb +0 -4
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/lib/mumuki/laboratory.rb +1 -0
- data/lib/tasks/users.rake +5 -4
- data/spec/dummy/db/schema.rb +3 -1
- data/spec/features/profile_flow_spec.rb +1 -1
- data/spec/features/progressive_tips_spec.rb +48 -0
- data/spec/helpers/application_helper_spec.rb +3 -5
- data/spec/helpers/email_helper_spec.rb +0 -2
- data/spec/helpers/with_navigation_spec.rb +0 -2
- data/spec/mailers/user_mailer_spec.rb +104 -10
- data/spec/models/event_generation_spec.rb +3 -0
- data/spec/models/exercise_spec.rb +0 -2
- data/spec/models/guide_import_spec.rb +7 -0
- data/spec/models/organization_spec.rb +0 -1
- data/spec/models/playground_spec.rb +0 -2
- data/spec/models/user_spec.rb +8 -0
- data/spec/spec_helper.rb +2 -0
- metadata +29 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2a576c10df838214910df546c5ba3aca918fc08275df1b39be0191c419a1d28
|
4
|
+
data.tar.gz: e87e69c8d7a253d16b0694413db2618c37bf6de828bc72e9270fc371b4560631
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8a8b571044afdd28d6a59957b0bd51ec6b92197429cd5c36e2221842d65cd65ef44c3e7b8727dafd66c8437fde9e774d3fc39e8e130b31a44ff92910224a472
|
7
|
+
data.tar.gz: 936bf126f56fcdc664c28ec9a52ea6d7cf426b347e15e1c31d32e9b07809454f9da5692dbb88a4a8dcd7b2b1b632b2c55a0eb19f76ed041227c9dbf186784b44
|
data/README.md
CHANGED
@@ -56,11 +56,16 @@ cd mumuki-laboratory
|
|
56
56
|
> We need to create a PostgreSQL role - AKA a user - who will be used by Laboratory to create and access the database
|
57
57
|
|
58
58
|
```bash
|
59
|
-
# create db user
|
59
|
+
# create db user for linux users
|
60
60
|
sudo -u postgres psql <<EOF
|
61
61
|
create role mumuki with createdb login password 'mumuki';
|
62
62
|
EOF
|
63
63
|
|
64
|
+
# create db user for mac users
|
65
|
+
psql postgres
|
66
|
+
#once inside postgres server
|
67
|
+
create role mumuki with createdb login password 'mumuki';
|
68
|
+
|
64
69
|
# create schema and initial development data
|
65
70
|
./devinit
|
66
71
|
```
|
@@ -40,19 +40,18 @@ mumuki.load(function () {
|
|
40
40
|
|
41
41
|
var $speechParagraphs;
|
42
42
|
var currentParagraphIndex = 0;
|
43
|
-
var $prevSpeech = $('.mu-kids-character-speech-bubble > .mu-kids-prev-speech').hide();
|
44
|
-
var $nextSpeech = $('.mu-kids-character-speech-bubble > .mu-kids-next-speech');
|
43
|
+
var $prevSpeech = $('.mu-kids-character-speech-bubble-normal > .mu-kids-prev-speech').hide();
|
44
|
+
var $nextSpeech = $('.mu-kids-character-speech-bubble-normal > .mu-kids-next-speech');
|
45
|
+
var $speechTabs = $('.mu-kids-character-speech-bubble-tabs > li:not(.separator)');
|
46
|
+
var $bubble = $('.mu-kids-character-speech-bubble').children('.mu-kids-character-speech-bubble-normal');
|
47
|
+
var $texts = $bubble.children('.description, .hint');
|
45
48
|
|
46
49
|
updateSpeechParagraphs();
|
47
|
-
|
48
50
|
function updateSpeechParagraphs() {
|
49
|
-
$speechParagraphs = $('.mu-kids-character-speech-bubble > p');
|
51
|
+
$speechParagraphs = $('.mu-kids-character-speech-bubble > .mu-kids-character-speech-bubble-normal > div.' + getSelectedTabName() + ' > p');
|
52
|
+
showParagraph(0);
|
50
53
|
}
|
51
54
|
|
52
|
-
var $speechTabs = $('.mu-kids-character-speech-bubble-tabs > li:not(.separator)');
|
53
|
-
var $bubble = $('.mu-kids-character-speech-bubble').children('.mu-kids-character-speech-bubble-normal');
|
54
|
-
var $texts = $bubble.children('.description, .hint');
|
55
|
-
|
56
55
|
$speechTabs.each(function (i) {
|
57
56
|
var $tab = $($speechTabs[i]);
|
58
57
|
$tab.click(function () {
|
@@ -60,12 +59,12 @@ mumuki.load(function () {
|
|
60
59
|
$tab.addClass('active');
|
61
60
|
$texts.hide();
|
62
61
|
$bubble.children('.' + $tab.data('target')).show();
|
62
|
+
$bubble.scroll(0);
|
63
|
+
hideCurrentParagraph();
|
63
64
|
updateSpeechParagraphs();
|
64
65
|
})
|
65
66
|
});
|
66
67
|
|
67
|
-
if ($speechParagraphs.length <= 1) $nextSpeech.hide();
|
68
|
-
|
69
68
|
$nextSpeech.click(function () {
|
70
69
|
hideCurrentParagraph();
|
71
70
|
showNextParagraph();
|
@@ -75,22 +74,50 @@ mumuki.load(function () {
|
|
75
74
|
showPrevParagraph();
|
76
75
|
});
|
77
76
|
|
77
|
+
function getSelectedTabName() {
|
78
|
+
return $speechTabs.filter(".active").data('target');
|
79
|
+
}
|
80
|
+
|
78
81
|
function hideCurrentParagraph() {
|
79
82
|
$($speechParagraphs[currentParagraphIndex]).hide();
|
80
83
|
}
|
81
84
|
|
82
85
|
function showPrevParagraph() {
|
83
|
-
|
84
|
-
$($speechParagraphs[--currentParagraphIndex]).show();
|
85
|
-
if (currentParagraphIndex === 0) $prevSpeech.hide();
|
86
|
+
showParagraph(currentParagraphIndex - 1);
|
86
87
|
}
|
87
88
|
|
88
89
|
function showNextParagraph() {
|
89
|
-
|
90
|
-
$($speechParagraphs[++currentParagraphIndex]).show();
|
91
|
-
if ($speechParagraphs.length - 1 === currentParagraphIndex) $nextSpeech.hide();
|
90
|
+
showParagraph(currentParagraphIndex + 1);
|
92
91
|
}
|
93
92
|
|
93
|
+
function showParagraph(index) {
|
94
|
+
$($speechParagraphs[index]).show();
|
95
|
+
setVisibility($prevSpeech, index !== 0);
|
96
|
+
setVisibility($nextSpeech, index !== $speechParagraphs.length - 1);
|
97
|
+
|
98
|
+
currentParagraphIndex = index;
|
99
|
+
}
|
100
|
+
|
101
|
+
function setVisibility(element, isVisible) {
|
102
|
+
if (isVisible) element.show(); else element.hide();
|
103
|
+
}
|
104
|
+
|
105
|
+
function autoScrollBubble(toBottom) {
|
106
|
+
var SCROLL_TIME = 1000;
|
107
|
+
var PAUSE_TIME = 2000;
|
108
|
+
|
109
|
+
setTimeout(function () {
|
110
|
+
$bubble.animate({scrollTop: toBottom ? $bubble.height() : 0}, {
|
111
|
+
duration: SCROLL_TIME,
|
112
|
+
complete: function () {
|
113
|
+
autoScrollBubble(!toBottom);
|
114
|
+
}
|
115
|
+
});
|
116
|
+
}, PAUSE_TIME);
|
117
|
+
}
|
118
|
+
|
119
|
+
autoScrollBubble();
|
120
|
+
|
94
121
|
mumuki.kids = {
|
95
122
|
|
96
123
|
getResultsModal: function () {
|
@@ -145,6 +172,9 @@ mumuki.load(function () {
|
|
145
172
|
$bubble.find('.mu-kids-character-speech-bubble-normal').hide();
|
146
173
|
$bubble.find('.mu-kids-character-speech-bubble-failed').show().html(data.title_html);
|
147
174
|
$bubble.addClass(data.status);
|
175
|
+
if (data.status === 'passed_with_warnings') {
|
176
|
+
$bubble.find('.mu-kids-character-speech-bubble-failed').append(data.expectations_html);
|
177
|
+
}
|
148
178
|
mumuki.kids.getOverlay().show();
|
149
179
|
},
|
150
180
|
|
@@ -213,7 +243,7 @@ mumuki.load(function () {
|
|
213
243
|
};
|
214
244
|
|
215
245
|
mumuki.kids.resultAction.passed = mumuki.kids._showOnSuccessPopup;
|
216
|
-
mumuki.kids.resultAction.passed_with_warnings = mumuki.kids.
|
246
|
+
mumuki.kids.resultAction.passed_with_warnings = mumuki.kids._showOnCharacterBubble;
|
217
247
|
|
218
248
|
mumuki.kids.resultAction.aborted = mumuki.kids._showOnFailurePopup;
|
219
249
|
|
@@ -104,6 +104,16 @@ $kids-speech-tabs-width: 40px;
|
|
104
104
|
height: 100%;
|
105
105
|
}
|
106
106
|
.mu-kids-character-speech-bubble {
|
107
|
+
|
108
|
+
&.passed_with_warnings {
|
109
|
+
padding-top: 5px;
|
110
|
+
padding-bottom: 5px;
|
111
|
+
|
112
|
+
.results-list {
|
113
|
+
margin-top: -10px;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
107
117
|
ul {
|
108
118
|
margin: 0;
|
109
119
|
}
|
@@ -117,6 +127,16 @@ $kids-speech-tabs-width: 40px;
|
|
117
127
|
cursor: pointer;
|
118
128
|
}
|
119
129
|
}
|
130
|
+
div.mu-kids-character-speech-bubble-normal {
|
131
|
+
width: 100%;
|
132
|
+
height: 100%;
|
133
|
+
overflow: hidden;
|
134
|
+
}
|
135
|
+
div.mu-kids-character-speech-bubble-failed {
|
136
|
+
width: 100%;
|
137
|
+
height: 100%;
|
138
|
+
overflow: hidden;
|
139
|
+
}
|
120
140
|
ul.mu-kids-character-speech-bubble-tabs {
|
121
141
|
display: flex;
|
122
142
|
flex-direction: column;
|
@@ -2,7 +2,7 @@ $icon-font-path: '../assets/bootstrap/';
|
|
2
2
|
$fa-font-path: '../assets';
|
3
3
|
$da-font-path: '../assets';
|
4
4
|
|
5
|
-
@import "mumuki-styles
|
5
|
+
@import "scss/mumuki-styles";
|
6
6
|
@import "codemirror/codemirror.min";
|
7
7
|
@import "codemirror/show-hint.min";
|
8
8
|
@import "application/vendor";
|
@@ -7,7 +7,7 @@ class InvitationsController < ApplicationController
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def join
|
10
|
-
current_user.
|
10
|
+
current_user.accept_invitation! @invitation
|
11
11
|
current_user.update! user_params
|
12
12
|
current_user.notify!
|
13
13
|
redirect_to_organization!
|
@@ -20,7 +20,7 @@ class InvitationsController < ApplicationController
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def redirect_to_organization!
|
23
|
-
redirect_to Mumukit::Platform.laboratory.organic_url
|
23
|
+
redirect_to Mumukit::Platform.laboratory.organic_url(@organization)
|
24
24
|
end
|
25
25
|
|
26
26
|
def user_params
|
@@ -6,10 +6,12 @@ module ApplicationHelper
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def page_title(subject)
|
9
|
+
name = "Mumuki#{Organization.current.title_suffix}"
|
10
|
+
|
9
11
|
if subject && !subject.new_record?
|
10
|
-
"#{subject.friendly} -
|
12
|
+
"#{subject.friendly} - #{name}"
|
11
13
|
else
|
12
|
-
"
|
14
|
+
"#{name} - #{t :mumuki_catchphrase}"
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
@@ -27,6 +29,14 @@ module ApplicationHelper
|
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
32
|
+
def assistance_box(assignment)
|
33
|
+
if assignment.tips.present?
|
34
|
+
%Q{<div class="mu-tips-box">
|
35
|
+
#{Mumukit::Assistant::Narrator.random.compose_explanation_html assignment.tips}
|
36
|
+
</div>}.html_safe
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
30
40
|
def chapter_finished(chapter)
|
31
41
|
t :chapter_finished_html, chapter: link_to_path_element(chapter) if chapter
|
32
42
|
end
|
data/app/mailers/user_mailer.rb
CHANGED
@@ -1,15 +1,24 @@
|
|
1
1
|
class UserMailer < ApplicationMailer
|
2
2
|
|
3
3
|
def we_miss_you_reminder(user, cycles)
|
4
|
+
send_reminder! user, t(:we_miss_you), "#{cycles.ordinalize}_reminder"
|
5
|
+
end
|
6
|
+
|
7
|
+
def no_submissions_reminder(user)
|
8
|
+
send_reminder! user, t(:start_using_mumuki), "no_submissions_reminder"
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def send_reminder!(user, subject, template_name)
|
4
14
|
@user = user
|
5
15
|
@unsubscribe_code = User.unsubscription_verifier.generate(user.id)
|
6
16
|
@organization = user.last_organization
|
7
17
|
|
8
18
|
I18n.with_locale(@organization.locale) do
|
9
19
|
mail to: user.email,
|
10
|
-
subject:
|
11
|
-
template_name:
|
20
|
+
subject: subject,
|
21
|
+
template_name: template_name
|
12
22
|
end
|
13
23
|
end
|
14
|
-
|
15
24
|
end
|
data/app/models/assignment.rb
CHANGED
@@ -140,6 +140,14 @@ class Assignment < ApplicationRecord
|
|
140
140
|
}})
|
141
141
|
end
|
142
142
|
|
143
|
+
def tips
|
144
|
+
@tips ||= exercise.assist_with(self)
|
145
|
+
end
|
146
|
+
|
147
|
+
def increment_attemps!
|
148
|
+
self.attemps_count += 1 unless passed?
|
149
|
+
end
|
150
|
+
|
143
151
|
private
|
144
152
|
|
145
153
|
def update_submissions_count!
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Assistable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
serialize :assistance_rules, Array
|
6
|
+
end
|
7
|
+
|
8
|
+
def assistant
|
9
|
+
Mumukit::Assistant.parse(assistance_rules)
|
10
|
+
end
|
11
|
+
|
12
|
+
def assist_with(assignment)
|
13
|
+
# not strictly necessary, but avoid going through
|
14
|
+
# all the assistence process when there are no rules
|
15
|
+
assistance_rules.blank? ? [] : assistant.assist_with(assignment)
|
16
|
+
end
|
17
|
+
end
|
@@ -2,7 +2,7 @@ module WithExpectations
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
-
serialize :expectations
|
5
|
+
serialize :expectations, Array
|
6
6
|
end
|
7
7
|
|
8
8
|
def expectations_yaml
|
@@ -16,8 +16,4 @@ module WithExpectations
|
|
16
16
|
def expectations=(expectations)
|
17
17
|
self[:expectations] = expectations.map(&:stringify_keys)
|
18
18
|
end
|
19
|
-
|
20
|
-
def expectations
|
21
|
-
self[:expectations] || []
|
22
|
-
end
|
23
19
|
end
|
@@ -1,28 +1,72 @@
|
|
1
1
|
module WithReminders
|
2
|
+
extend ActiveSupport::Concern
|
2
3
|
|
3
4
|
def build_reminder
|
4
|
-
UserMailer.new
|
5
|
+
mailer = UserMailer.new
|
6
|
+
last_submission_date.nil? ?
|
7
|
+
mailer.no_submissions_reminder(self) :
|
8
|
+
mailer.we_miss_you_reminder(self, cycles_since(last_submission_date))
|
5
9
|
end
|
6
10
|
|
7
|
-
def
|
11
|
+
def remind!
|
8
12
|
build_reminder.deliver
|
9
|
-
update!
|
13
|
+
update! last_reminded_date: Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
def should_remind?
|
17
|
+
reminder_due? && (has_no_submissions? || has_no_recent_submission?)
|
10
18
|
end
|
11
19
|
|
20
|
+
# Try to send a reminder, by acquiring a database lock for update
|
21
|
+
# the aproppriate record. This object can't be updated as long as
|
22
|
+
# the reminder is being sent.
|
23
|
+
#
|
24
|
+
# This method is aimed to be sent across multiple servers or processed concurrently
|
25
|
+
# and still not send duplicate mails
|
26
|
+
def try_remind_with_lock!
|
27
|
+
# Some notes:
|
28
|
+
#
|
29
|
+
# * nowait is a postgre specific option and may not work with other databases
|
30
|
+
# * nowait will raise an exception if the lock can not be acquired
|
31
|
+
# * we are using a double check lock pattern to reduce lock acquisition
|
32
|
+
with_lock('for update nowait') do
|
33
|
+
reload
|
34
|
+
remind! if should_remind?
|
35
|
+
end if should_remind?
|
36
|
+
rescue
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
12
42
|
def cycles_since(time)
|
13
|
-
((Date.current - time.to_date) /
|
43
|
+
((Date.current - time.to_date) / self.class.reminder_frequency).to_i
|
14
44
|
end
|
15
45
|
|
16
|
-
def
|
46
|
+
def reminder_due?
|
17
47
|
last_reminded_date.nil? || cycles_since(last_reminded_date) >= 1
|
18
48
|
end
|
19
49
|
|
20
|
-
def
|
21
|
-
|
50
|
+
def can_still_remind?(date)
|
51
|
+
cycles_since(date).between?(1, 3)
|
22
52
|
end
|
23
53
|
|
24
|
-
def
|
25
|
-
|
54
|
+
def has_no_submissions?
|
55
|
+
last_submission_date.nil? && can_still_remind?(created_at)
|
56
|
+
end
|
57
|
+
|
58
|
+
def has_no_recent_submission?
|
59
|
+
!last_submission_date.nil? && can_still_remind?(last_submission_date)
|
26
60
|
end
|
27
61
|
|
62
|
+
module ClassMethods
|
63
|
+
def remindable
|
64
|
+
where('accepts_reminders and (last_submission_date < ? or last_submission_date is null)', reminder_frequency.days.ago)
|
65
|
+
end
|
66
|
+
|
67
|
+
# The frequency of reminders, expressed in days
|
68
|
+
def reminder_frequency
|
69
|
+
Rails.configuration.reminder_frequency
|
70
|
+
end
|
71
|
+
end
|
28
72
|
end
|
@@ -8,7 +8,7 @@ module WithSlug
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def import!
|
11
|
-
import_from_json! Mumukit::
|
11
|
+
import_from_json! Mumukit::Platform.bibliotheca_bridge.send(self.class.name.underscore, slug)
|
12
12
|
end
|
13
13
|
|
14
14
|
def slug_parts
|
data/app/models/exercise.rb
CHANGED
@@ -4,7 +4,8 @@ class Exercise < ApplicationRecord
|
|
4
4
|
include WithNumber,
|
5
5
|
WithAssignments,
|
6
6
|
FriendlyName,
|
7
|
-
WithLanguage
|
7
|
+
WithLanguage,
|
8
|
+
Assistable
|
8
9
|
|
9
10
|
include Submittable,
|
10
11
|
Questionable
|
@@ -13,7 +14,6 @@ class Exercise < ApplicationRecord
|
|
13
14
|
ParentNavigation
|
14
15
|
|
15
16
|
belongs_to :guide
|
16
|
-
|
17
17
|
defaults { self.submissions_count = 0 }
|
18
18
|
|
19
19
|
validates_presence_of :submissions_count,
|
data/app/models/organization.rb
CHANGED
@@ -84,6 +84,14 @@ class Organization < ApplicationRecord
|
|
84
84
|
all.select { |it| it.public? || user.has_permission?(role, it.slug) }
|
85
85
|
end
|
86
86
|
|
87
|
+
def title_suffix
|
88
|
+
central? ? '' : " - #{book.name}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def site_name
|
92
|
+
central? ? 'mumuki' : name
|
93
|
+
end
|
94
|
+
|
87
95
|
private
|
88
96
|
|
89
97
|
def ensure_consistent_public_login
|
data/app/models/user.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
<div class="col-md-offset-2 col-md-8">
|
2
2
|
<div class="book-header <%= current_user? ? '' : 'logged' %>">
|
3
|
-
<h1>ム mumuki
|
3
|
+
<h1>ム mumuki<%= Organization.current.title_suffix %></h1>
|
4
4
|
|
5
5
|
<img src="<%= Organization.current.banner_url %>" alt="<%= Organization.current.name %> - Mumuki">
|
6
6
|
<h2><%= @book.name %></h2>
|
@@ -25,7 +25,6 @@
|
|
25
25
|
<% if render_feedback?(assignment) %>
|
26
26
|
<div class="results-item">
|
27
27
|
<strong><%= t :feedback %>:</strong>
|
28
|
-
|
29
28
|
<div>
|
30
29
|
<%= assignment.feedback_html %>
|
31
30
|
</div>
|
@@ -50,6 +49,7 @@
|
|
50
49
|
<%= mail_to contact_email,
|
51
50
|
fa_icon(:bug, text: t(:notify_problem_with_exercise), class: 'fa-fw'),
|
52
51
|
subject: t(:problem_with_exercise, title: @exercise.name),
|
52
|
+
body: assignment_help_email_body(assignment),
|
53
53
|
class: 'warning' %>
|
54
54
|
</li>
|
55
55
|
<li>
|
@@ -63,6 +63,9 @@
|
|
63
63
|
<%= solution_download_link assignment %>
|
64
64
|
</div>
|
65
65
|
|
66
|
-
|
67
|
-
|
66
|
+
<% if assignment.should_retry? %>
|
67
|
+
<%= assistance_box assignment %>
|
68
|
+
<% else %>
|
69
|
+
<%= corollary_box @exercise %>
|
70
|
+
<% end %>
|
68
71
|
<%= render partial: 'exercise_solutions/results_button', locals: {assignment: assignment} %>
|
@@ -1,7 +1,5 @@
|
|
1
1
|
<img src="/anim_amarillo.svg" alt="">
|
2
2
|
<div class="mu-kids-character-speech-bubble">
|
3
|
-
<i class="mu-kids-prev-speech fa fa-fw fa-caret-up"></i>
|
4
|
-
<i class="mu-kids-next-speech fa fa-fw fa-caret-down"></i>
|
5
3
|
<% if @exercise.hint? %>
|
6
4
|
<ul class="mu-kids-character-speech-bubble-tabs">
|
7
5
|
<li class="active" data-target="description" title="<%= t :task %>"><i class="fa fa-fw fa-file-text-o"></i>
|
@@ -11,8 +9,10 @@
|
|
11
9
|
</ul>
|
12
10
|
<% end %>
|
13
11
|
<div class="mu-kids-character-speech-bubble-normal">
|
12
|
+
<i class="mu-kids-prev-speech fa fa-fw fa-caret-up"></i>
|
13
|
+
<i class="mu-kids-next-speech fa fa-fw fa-caret-down"></i>
|
14
14
|
<div class="description"><%= @exercise.description_task %></div>
|
15
15
|
<div class="hint" style="display: none"><%= @exercise.hint_html %></div>
|
16
16
|
</div>
|
17
|
-
<div class="mu-kids-character-speech-bubble-failed" style="display: none"></div>
|
17
|
+
<div class="mu-kids-character-speech-bubble-failed <%= @assignment&.status %>" style="display: none"></div>
|
18
18
|
</div>
|
@@ -9,9 +9,9 @@
|
|
9
9
|
<link rel="icon" type="image/x-icon" href="<%= Organization.current.favicon_url %>" data-turbolinks-track="reload">
|
10
10
|
<link rel="stylesheet" type="text/css" href="<%= theme_stylesheet_url %>" data-turbolinks-track="reload">
|
11
11
|
|
12
|
-
<meta property="og:site_name" content="
|
12
|
+
<meta property="og:site_name" content="<%= Organization.current.site_name %>" />
|
13
13
|
<meta property="og:title" content="<%= page_title subject %>"/>
|
14
|
-
<meta property="og:description" content="<%= t
|
14
|
+
<meta property="og:description" content="<%= Organization.current.central? ? t(:mumuki_short_description) : Organization.current.description %>"/>
|
15
15
|
<meta property="og:type" content="website"/>
|
16
16
|
<meta property="og:image" content="<%= Organization.current.open_graph_image_url %>"/>
|
17
17
|
<meta property="og:url" content="<%= request.original_url %>"/>
|
@@ -21,12 +21,11 @@
|
|
21
21
|
<meta name="description" content="<%= t :mumuki_short_description %>"/>
|
22
22
|
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
23
23
|
<%= csrf_meta_tags %>
|
24
|
-
<script type="text/javascript">
|
25
|
-
window.mumukiLocale = <%= raw Organization.current.locale_json %>;
|
26
|
-
</script>
|
27
24
|
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
|
28
25
|
<script type="text/javascript" src="<%= extension_javascript_url %>" defer data-turbolinks-track="reload"></script>
|
29
26
|
<script type="text/javascript">
|
27
|
+
window.mumukiLocale = <%= raw Organization.current.locale_json %>;
|
28
|
+
mumuki.locale = '<%= Organization.current.locale %>';
|
30
29
|
moment.locale('<%= Organization.current.locale %>');
|
31
30
|
</script>
|
32
31
|
<%= login_form.header_html %>
|