mumuki-laboratory 7.6.1 → 7.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +193 -2
- data/Rakefile +3 -0
- data/app/assets/javascripts/mumuki_laboratory/application.js +0 -1
- data/app/assets/javascripts/mumuki_laboratory/application/assets-loader.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +36 -10
- data/app/assets/javascripts/mumuki_laboratory/application/button.js +90 -1
- data/app/assets/javascripts/mumuki_laboratory/application/codemirror.js +1 -0
- data/app/assets/javascripts/mumuki_laboratory/application/custom-editor.js +46 -4
- data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +14 -13
- data/app/assets/javascripts/mumuki_laboratory/application/kids.js +73 -36
- data/app/assets/javascripts/mumuki_laboratory/application/progress.js +3 -0
- data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +51 -0
- data/app/assets/javascripts/mumuki_laboratory/application/submission.js +184 -35
- data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +1 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_discussion.scss +43 -5
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kids.scss +3 -3
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_kindergarten.scss +55 -0
- data/app/controllers/assets_controller.rb +2 -0
- data/app/controllers/concerns/with_authorization.rb +4 -0
- data/app/controllers/concerns/with_user_discussion_validation.rb +14 -0
- data/app/controllers/discussions_controller.rb +6 -14
- data/app/controllers/discussions_messages_controller.rb +10 -1
- data/app/controllers/exercise_solutions_controller.rb +4 -2
- data/app/helpers/application_helper.rb +9 -5
- data/app/helpers/discussions_helper.rb +37 -23
- data/app/helpers/exercise_input_helper.rb +1 -1
- data/app/helpers/icons_helper.rb +3 -3
- data/app/views/book_discussions/index.html.erb +3 -3
- data/app/views/discussions/_message.html.erb +20 -8
- data/app/views/discussions/index.html.erb +0 -1
- data/app/views/discussions/new.html.erb +33 -0
- data/app/views/discussions/show.html.erb +18 -46
- data/app/views/exercise_solutions/_contextualization_results_container.html.erb +1 -1
- data/app/views/exercise_solutions/_results_title.html.erb +2 -2
- data/app/views/exercises/_read_only.html.erb +33 -6
- data/app/views/layouts/_copyright.html.erb +1 -1
- data/app/views/layouts/_discussions.html.erb +21 -3
- data/app/views/layouts/_social_media.html.erb +3 -3
- data/app/views/layouts/_test_results.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/editors/_custom.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/layouts/_input_bottom.html.erb +1 -1
- data/app/views/layouts/exercise_inputs/layouts/_input_kindergarten.html.erb +40 -0
- data/app/views/layouts/exercise_inputs/layouts/{_input_kids.html.erb → _input_primary.html.erb} +1 -1
- data/app/views/layouts/exercise_inputs/layouts/_input_right.html.erb +1 -1
- data/app/views/layouts/modals/_kids_context.html.erb +1 -8
- data/app/views/user_mailer/1st_reminder.html.erb +3 -3
- data/app/views/user_mailer/1st_reminder.text.erb +1 -1
- data/app/views/user_mailer/2nd_reminder.html.erb +3 -3
- data/app/views/user_mailer/2nd_reminder.text.erb +1 -1
- data/app/views/user_mailer/3rd_reminder.html.erb +3 -3
- data/app/views/user_mailer/3rd_reminder.text.erb +1 -1
- data/app/views/user_mailer/no_submissions_reminder.html.erb +3 -3
- data/app/views/user_mailer/no_submissions_reminder.text.erb +1 -1
- data/config/routes.rb +2 -1
- data/lib/mumuki/laboratory/controllers/results_rendering.rb +1 -2
- data/lib/mumuki/laboratory/locales/en.yml +8 -2
- data/lib/mumuki/laboratory/locales/es.yml +7 -1
- data/lib/mumuki/laboratory/locales/pt.yml +8 -4
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/spec/controllers/confirmations_controller_spec.rb +1 -1
- data/spec/controllers/discussions_messages_controller_spec.rb +73 -0
- data/spec/controllers/exercise_solutions_controller_spec.rb +41 -6
- data/spec/dummy/db/schema.rb +12 -1
- data/spec/features/discussion_flow_spec.rb +190 -0
- data/spec/features/exercise_flow_spec.rb +1 -1
- data/spec/features/menu_bar_spec.rb +88 -7
- data/spec/helpers/breadcrumbs_helper_spec.rb +1 -1
- data/spec/javascripts/bridge-spec.js +5 -0
- data/spec/javascripts/csrf-token-spec.js +7 -0
- data/spec/javascripts/elipsis-spec.js +25 -0
- data/spec/javascripts/results-renderers-spec.js +17 -0
- data/spec/javascripts/spec-helper.js +30 -0
- data/spec/javascripts/speech-bubble-renderer-spec.js +11 -0
- data/spec/javascripts/timeout-spec.js +5 -0
- data/spec/javascripts/timer-spec.js +5 -0
- data/spec/teaspoon_env.rb +187 -0
- metadata +33 -9
- data/app/views/layouts/modals/_new_discussion.html.erb +0 -27
- data/vendor/assets/javascripts/hotjar.js +0 -8
@@ -201,10 +201,6 @@ $moderator-star-color: #dd9900;
|
|
201
201
|
}
|
202
202
|
}
|
203
203
|
|
204
|
-
.discussion-create {
|
205
|
-
cursor: pointer;
|
206
|
-
}
|
207
|
-
|
208
204
|
.discussion-context {
|
209
205
|
margin-top: 40px;
|
210
206
|
}
|
@@ -331,9 +327,9 @@ summary.discussion-summary {
|
|
331
327
|
float: right;
|
332
328
|
a {
|
333
329
|
margin-left: 5px;
|
330
|
+
cursor: pointer;
|
334
331
|
}
|
335
332
|
.discussion-message-approved {
|
336
|
-
cursor: pointer;
|
337
333
|
text-decoration: none;
|
338
334
|
&.selected {
|
339
335
|
i {
|
@@ -341,6 +337,29 @@ summary.discussion-summary {
|
|
341
337
|
}
|
342
338
|
}
|
343
339
|
}
|
340
|
+
.discussion-message-not-actually-a-question {
|
341
|
+
text-decoration: none;
|
342
|
+
i {
|
343
|
+
position: relative;
|
344
|
+
&:after {
|
345
|
+
position: absolute;
|
346
|
+
left: 6px;
|
347
|
+
content: ' ';
|
348
|
+
height: 19px;
|
349
|
+
width: 2px;
|
350
|
+
transform: rotate(-45deg);
|
351
|
+
background-color: #aaaaaa;
|
352
|
+
}
|
353
|
+
}
|
354
|
+
&.selected {
|
355
|
+
i {
|
356
|
+
color: $brand-primary;
|
357
|
+
&:after {
|
358
|
+
background-color: $brand-primary;
|
359
|
+
}
|
360
|
+
}
|
361
|
+
}
|
362
|
+
}
|
344
363
|
i {
|
345
364
|
color: #aaaaaa
|
346
365
|
}
|
@@ -383,4 +402,23 @@ $statuses: (
|
|
383
402
|
}
|
384
403
|
}
|
385
404
|
|
405
|
+
.no-margin {
|
406
|
+
margin: 0 !important;
|
407
|
+
}
|
408
|
+
|
409
|
+
.discussion-requires-attention {
|
410
|
+
margin-right: 20px;
|
411
|
+
label {
|
412
|
+
font-weight: normal;
|
413
|
+
}
|
414
|
+
}
|
386
415
|
|
416
|
+
.discussion-moderator-access {
|
417
|
+
margin-right: 20px;
|
418
|
+
display: flex;
|
419
|
+
flex-direction: column;
|
420
|
+
align-items: center;
|
421
|
+
.moderator-initials {
|
422
|
+
font-size: 14px;
|
423
|
+
}
|
424
|
+
}
|
@@ -182,17 +182,17 @@ $kids-speech-tabs-width: 40px;
|
|
182
182
|
cursor: pointer;
|
183
183
|
}
|
184
184
|
}
|
185
|
-
|
185
|
+
.mu-kids-character-speech-bubble-normal {
|
186
186
|
width: 100%;
|
187
187
|
height: 100%;
|
188
188
|
overflow: hidden;
|
189
189
|
}
|
190
|
-
|
190
|
+
.mu-kids-character-speech-bubble-failed {
|
191
191
|
width: 100%;
|
192
192
|
height: 100%;
|
193
193
|
overflow: hidden;
|
194
194
|
}
|
195
|
-
|
195
|
+
.mu-kids-character-speech-bubble-tabs {
|
196
196
|
display: flex;
|
197
197
|
flex-direction: column;
|
198
198
|
justify-content: center;
|
@@ -0,0 +1,55 @@
|
|
1
|
+
.mu-kids-exercise.mu-kindergarten {
|
2
|
+
|
3
|
+
// layout
|
4
|
+
|
5
|
+
display: flex;
|
6
|
+
flex-flow: column;
|
7
|
+
|
8
|
+
.mu-kids-exercise-workspace {
|
9
|
+
display: flex;
|
10
|
+
flex-flow: row;
|
11
|
+
height: 100%;
|
12
|
+
width: 100%;
|
13
|
+
}
|
14
|
+
|
15
|
+
.mu-kids-single-state,
|
16
|
+
.mu-kids-blocks {
|
17
|
+
width: 50%;
|
18
|
+
height: 100%
|
19
|
+
}
|
20
|
+
|
21
|
+
.mu-kids-blocks {
|
22
|
+
margin-top: 0;
|
23
|
+
}
|
24
|
+
|
25
|
+
// no board numbers,
|
26
|
+
// but a thicker border instead
|
27
|
+
|
28
|
+
table.gbs_board.style-scope.gs-board {
|
29
|
+
padding: 3px;
|
30
|
+
}
|
31
|
+
|
32
|
+
.gbs_lv.gbs_lvl.style-scope.gs-board,
|
33
|
+
.gbs_lv.gbs_lvr.style-scope.gs-board,
|
34
|
+
tr.style-scope.gs-board:first-child,
|
35
|
+
tr.style-scope.gs-board:last-child {
|
36
|
+
display: none
|
37
|
+
}
|
38
|
+
|
39
|
+
// no final state,
|
40
|
+
// initial state fills its area
|
41
|
+
|
42
|
+
.mu-kids-state.mu-state-initial {
|
43
|
+
height: 100%;
|
44
|
+
}
|
45
|
+
|
46
|
+
.mu-kids-state.mu-state-final {
|
47
|
+
display: none;
|
48
|
+
}
|
49
|
+
|
50
|
+
.mu-kids-compass-rose {
|
51
|
+
display: none;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
|
@@ -2,6 +2,8 @@ class AssetsController < ApplicationController
|
|
2
2
|
|
3
3
|
protect_from_forgery except: [:theme_stylesheet, :extension_javascript]
|
4
4
|
skip_before_action :authorize_if_private!
|
5
|
+
skip_before_action :validate_user_profile!
|
6
|
+
skip_before_action :validate_active_organization!
|
5
7
|
|
6
8
|
def theme_stylesheet
|
7
9
|
render inline: Organization.current.theme_stylesheet.to_s, content_type: 'text/css'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module WithUserDiscussionValidation
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
# discussions are not enabled for all organizations nor all users
|
6
|
+
before_action :validate_user_can_discuss!
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def validate_user_can_discuss!
|
12
|
+
raise Mumuki::Domain::NotFoundError unless current_user&.can_discuss_here?
|
13
|
+
end
|
14
|
+
end
|
@@ -1,11 +1,6 @@
|
|
1
1
|
class DiscussionsController < ApplicationController
|
2
2
|
include Mumuki::Laboratory::Controllers::Content
|
3
|
-
|
4
|
-
# discussions are not enabled for all organitions nor all users
|
5
|
-
# there is no need to validate forum existance when creating; next validation is stronger
|
6
|
-
before_action :validate_forum_enabled!, except: :create
|
7
|
-
before_action :validate_can_create_discusssion!, only: :create
|
8
|
-
|
3
|
+
include WithUserDiscussionValidation
|
9
4
|
# users are not allowed to access discussions during exams
|
10
5
|
before_action :validate_not_in_exam!
|
11
6
|
|
@@ -21,7 +16,12 @@ class DiscussionsController < ApplicationController
|
|
21
16
|
@filtered_discussions = @discussions.scoped_query_by(discussion_filter_params)
|
22
17
|
end
|
23
18
|
|
19
|
+
def new
|
20
|
+
@discussion = @debatable.new_discussion_for current_user
|
21
|
+
end
|
22
|
+
|
24
23
|
def show
|
24
|
+
@discussion.update_last_moderator_access! current_user
|
25
25
|
end
|
26
26
|
|
27
27
|
def update
|
@@ -71,14 +71,6 @@ class DiscussionsController < ApplicationController
|
|
71
71
|
@filter_params ||= params.permit(Discussion.permitted_query_params)
|
72
72
|
end
|
73
73
|
|
74
|
-
def validate_forum_enabled!
|
75
|
-
raise Mumuki::Domain::NotFoundError unless Organization.current.forum_enabled?
|
76
|
-
end
|
77
|
-
|
78
|
-
def validate_can_create_discusssion!
|
79
|
-
raise Mumuki::Domain::NotFoundError unless Organization.current.can_create_discussions?(current_user)
|
80
|
-
end
|
81
|
-
|
82
74
|
def validate_not_in_exam!
|
83
75
|
raise Mumuki::Domain::BlockedForumError if current_user&.currently_in_exam?
|
84
76
|
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
class DiscussionsMessagesController < AjaxController
|
2
|
+
include WithAuthorization
|
3
|
+
include WithUserDiscussionValidation
|
4
|
+
|
2
5
|
before_action :set_discussion!, only: [:create, :destroy]
|
3
|
-
before_action :authorize_user!, only: [:destroy
|
6
|
+
before_action :authorize_user!, only: [:destroy]
|
7
|
+
before_action :authorize_moderator!, only: [:question, :approve]
|
4
8
|
|
5
9
|
def create
|
6
10
|
@discussion.submit_message! message_params, current_user
|
@@ -17,6 +21,11 @@ class DiscussionsMessagesController < AjaxController
|
|
17
21
|
head :ok
|
18
22
|
end
|
19
23
|
|
24
|
+
def question
|
25
|
+
current_message.toggle_not_actually_a_question!
|
26
|
+
head :ok
|
27
|
+
end
|
28
|
+
|
20
29
|
private
|
21
30
|
|
22
31
|
def set_discussion!
|
@@ -22,7 +22,9 @@ class ExerciseSolutionsController < AjaxController
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def solution_params
|
25
|
-
|
26
|
-
|
25
|
+
{
|
26
|
+
content: params.require(:solution).permit!.to_h[:content],
|
27
|
+
client_result: params[:client_result].try { |it| it.permit(:status, test_results: [:title, :status, :result, :summary]).to_h }
|
28
|
+
}
|
27
29
|
end
|
28
30
|
end
|
@@ -7,10 +7,14 @@ module ApplicationHelper
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def profile_picture
|
10
|
-
|
10
|
+
profile_picture_for current_user
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def profile_picture_for(user, height = 40)
|
14
|
+
image_tag(user.profile_picture, height: height, class: 'img-circle', onError: "this.onerror = null; this.src = '#{image_url('user_shape.png')}'")
|
15
|
+
end
|
16
|
+
|
17
|
+
def paginate(object, options = {})
|
14
18
|
"<div class=\"text-center\">#{super(object, {theme: 'twitter-bootstrap-3'}.merge(options))}</div>".html_safe
|
15
19
|
end
|
16
20
|
|
@@ -32,10 +36,10 @@ module ApplicationHelper
|
|
32
36
|
t :chapter_finished_html, chapter: link_to_path_element(chapter) if chapter
|
33
37
|
end
|
34
38
|
|
35
|
-
def span_toggle(hidden_text, active_text, active)
|
39
|
+
def span_toggle(hidden_text, active_text, active, **options)
|
36
40
|
%Q{
|
37
|
-
<span class="#{'hidden' if active}">#{hidden_text}</span>
|
38
|
-
<span class="#{'hidden' unless active}">#{active_text}</span>
|
41
|
+
<span class="#{'hidden' if active} #{options[:class]}">#{hidden_text}</span>
|
42
|
+
<span class="#{'hidden' unless active} #{options[:class]}">#{active_text}</span>
|
39
43
|
}.html_safe
|
40
44
|
end
|
41
45
|
end
|
@@ -23,31 +23,32 @@ module DiscussionsHelper
|
|
23
23
|
fixed_fa_icon 'comment', text: text
|
24
24
|
end
|
25
25
|
|
26
|
-
def discussions_link(item, path, html_options=nil
|
27
|
-
|
26
|
+
def discussions_link(item, path, html_options = nil)
|
27
|
+
return unless current_user&.can_discuss_here?
|
28
|
+
link_to item, path, html_options
|
28
29
|
end
|
29
30
|
|
30
|
-
def item_discussion_path(discussion, params={})
|
31
|
+
def item_discussion_path(discussion, params = {})
|
31
32
|
polymorphic_path([discussion.item, discussion], params)
|
32
33
|
end
|
33
34
|
|
34
|
-
def item_discussions_path(item, params={})
|
35
|
+
def item_discussions_path(item, params = {})
|
35
36
|
polymorphic_path([item, :discussions], params)
|
36
37
|
end
|
37
38
|
|
38
39
|
def solve_discussion_params_for(user)
|
39
40
|
if user&.moderator_here?
|
40
|
-
{
|
41
|
+
{status: :pending_review, sort: :responses_count_asc, requires_moderator_response: true}
|
41
42
|
else
|
42
|
-
{
|
43
|
+
{status: :opened, sort: :responses_count_desc}
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
46
47
|
def default_discussions_params
|
47
|
-
{
|
48
|
+
{status: :solved, sort: :upvotes_count_desc}
|
48
49
|
end
|
49
50
|
|
50
|
-
def user_avatar(user, image_class='')
|
51
|
+
def user_avatar(user, image_class = '')
|
51
52
|
image_tag user.profile_picture, height: 40, class: "img-circle #{image_class}"
|
52
53
|
end
|
53
54
|
|
@@ -67,7 +68,7 @@ module DiscussionsHelper
|
|
67
68
|
%Q{
|
68
69
|
<span class="discussion-icon fa-stack fa-xs">
|
69
70
|
<i class="fa fa-comment-o fa-stack-2x"></i>
|
70
|
-
<i class="fa fa-stack-1x">#{discussion.
|
71
|
+
<i class="fa fa-stack-1x">#{discussion.validated_messages_count}</i>
|
71
72
|
</span>
|
72
73
|
}.html_safe
|
73
74
|
end
|
@@ -91,22 +92,16 @@ module DiscussionsHelper
|
|
91
92
|
end
|
92
93
|
|
93
94
|
def new_discussion_link(teaser_text, link_text)
|
94
|
-
return '' unless Organization.current.can_create_discussions?(current_user)
|
95
|
-
|
96
95
|
%Q{
|
97
96
|
<h4>
|
98
97
|
<span>#{t(teaser_text)}</span>
|
99
|
-
|
100
|
-
<span class="discussion-create">
|
101
|
-
#{t(link_text)}
|
102
|
-
</span>
|
103
|
-
</a>
|
98
|
+
#{link_to t(link_text), new_exercise_discussion_path(@debatable, anchor: 'new-discussion-description-container') }
|
104
99
|
</h4>
|
105
100
|
}.html_safe
|
106
101
|
end
|
107
102
|
|
108
103
|
def discussion_count_for_status(status, discussions)
|
109
|
-
discussions.scoped_query_by(discussion_filter_params, :status).by_status(status).count
|
104
|
+
discussions.scoped_query_by(discussion_filter_params, excluded_params: [:status], excluded_methods: [:page]).by_status(status).count
|
110
105
|
end
|
111
106
|
|
112
107
|
def discussions_reset_query_link
|
@@ -117,8 +112,10 @@ module DiscussionsHelper
|
|
117
112
|
Mumuki::Domain::Status::Discussion::STATUSES
|
118
113
|
end
|
119
114
|
|
115
|
+
#TODO: this one uses a long method chain in order to take advantage of eager load
|
116
|
+
# Delegate it once again when polymorphic association is removed
|
120
117
|
def discussions_languages(discussions)
|
121
|
-
discussions.map { |it| it.language.name }.uniq
|
118
|
+
@languages ||= discussions.map { |it| it.exercise.language.name }.uniq
|
122
119
|
end
|
123
120
|
|
124
121
|
def discussion_status_filter_link(status, discussions)
|
@@ -132,14 +129,14 @@ module DiscussionsHelper
|
|
132
129
|
|
133
130
|
def discussion_status_filter(status, discussions_count)
|
134
131
|
%Q{
|
135
|
-
|
132
|
+
#{discussion_status_fa_icon(status)}
|
136
133
|
<span>
|
137
134
|
#{t("#{status}_count", count: discussions_count)}
|
138
135
|
</span>
|
139
136
|
}.html_safe
|
140
137
|
end
|
141
138
|
|
142
|
-
def discussion_dropdown_filter(label, filters, &block)
|
139
|
+
def discussion_dropdown_filter(label, filters, can_select_all = false, &block)
|
143
140
|
if filters.present?
|
144
141
|
%Q{
|
145
142
|
<div class="dropdown discussions-toolbar-filter">
|
@@ -147,6 +144,7 @@ module DiscussionsHelper
|
|
147
144
|
#{t label} #{fa_icon :'caret-down', class: 'fa-xs'}
|
148
145
|
</a>
|
149
146
|
<ul class="dropdown-menu" aria-labelledby="dropdown-#{label}">
|
147
|
+
#{discussion_filter_unselect_item(label, can_select_all)}
|
150
148
|
#{discussion_filter_list(label, filters, &block)}
|
151
149
|
</ul>
|
152
150
|
</div>
|
@@ -159,7 +157,15 @@ module DiscussionsHelper
|
|
159
157
|
end
|
160
158
|
|
161
159
|
def discussion_filter_item(label, filter, &block)
|
162
|
-
content_tag(:li, discussion_filter_link(label, filter, &block), class:
|
160
|
+
content_tag(:li, discussion_filter_link(label, filter, &block), class: ('selected' if discussion_filter_selected?(label, filter)))
|
161
|
+
end
|
162
|
+
|
163
|
+
def discussion_filter_unselect_item(label, can_select_all)
|
164
|
+
if can_select_all
|
165
|
+
content_tag(:li,
|
166
|
+
link_to(t(:all), discussion_filter_params_without_page.except(label)),
|
167
|
+
class: ('selected' unless discussion_filter_params.include?(label)))
|
168
|
+
end
|
163
169
|
end
|
164
170
|
|
165
171
|
def discussion_filter_selected?(label, filter)
|
@@ -167,10 +173,18 @@ module DiscussionsHelper
|
|
167
173
|
end
|
168
174
|
|
169
175
|
def discussion_filter_link(label, filter, &block)
|
170
|
-
link_to capture(filter, &block),
|
176
|
+
link_to capture(filter, &block), discussion_filter_params_without_page.merge(Hash[label, filter])
|
171
177
|
end
|
172
178
|
|
173
179
|
def discussion_info(discussion)
|
174
|
-
|
180
|
+
"#{t(:time_since, time: time_ago_in_words(discussion.created_at))} · #{t(:message_count, count: discussion.messages.size)}"
|
181
|
+
end
|
182
|
+
|
183
|
+
def discussion_filter_params_without_page
|
184
|
+
discussion_filter_params.except(:page)
|
185
|
+
end
|
186
|
+
|
187
|
+
def should_show_approved_for?(user, message)
|
188
|
+
!user&.moderator_here? && message.approved? && !message.from_moderator?
|
175
189
|
end
|
176
190
|
end
|