mumuki-laboratory 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 %>
|