Commit 2211ae33 by Nimisha Asthagiri Committed by GitHub

Merge pull request #16304 from edx/pacing/studio-updates

Dynamic pacing: Studio support of Section Highlights
parents 59490244 ee1a003c
...@@ -1182,6 +1182,11 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -1182,6 +1182,11 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
xblock_info.update({ xblock_info.update({
'hide_after_due': xblock.hide_after_due, 'hide_after_due': xblock.hide_after_due,
}) })
elif xblock.category == 'chapter':
xblock_info.update({
'highlights': xblock.highlights,
'highlights_enabled': settings.FEATURES.get('ENABLE_SECTION_HIGHLIGHTS', False),
})
# update xblock_info with special exam information if the feature flag is enabled # update xblock_info with special exam information if the feature flag is enabled
if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
......
...@@ -2389,7 +2389,8 @@ class TestXBlockInfo(ItemTest): ...@@ -2389,7 +2389,8 @@ class TestXBlockInfo(ItemTest):
super(TestXBlockInfo, self).setUp() super(TestXBlockInfo, self).setUp()
user_id = self.user.id user_id = self.user.id
self.chapter = ItemFactory.create( self.chapter = ItemFactory.create(
parent_location=self.course.location, category='chapter', display_name="Week 1", user_id=user_id parent_location=self.course.location, category='chapter', display_name="Week 1", user_id=user_id,
highlights=['highlight'],
) )
self.sequential = ItemFactory.create( self.sequential = ItemFactory.create(
parent_location=self.chapter.location, category='sequential', display_name="Lesson 1", user_id=user_id parent_location=self.chapter.location, category='sequential', display_name="Lesson 1", user_id=user_id
...@@ -2596,6 +2597,7 @@ class TestXBlockInfo(ItemTest): ...@@ -2596,6 +2597,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['graded'], False) self.assertEqual(xblock_info['graded'], False)
self.assertEqual(xblock_info['due'], None) self.assertEqual(xblock_info['due'], None)
self.assertEqual(xblock_info['format'], None) self.assertEqual(xblock_info['format'], None)
self.assertEqual(xblock_info['highlights'], self.chapter.highlights)
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)
......
...@@ -264,7 +264,10 @@ FEATURES = { ...@@ -264,7 +264,10 @@ FEATURES = {
# Whether archived courses (courses with end dates in the past) should be # Whether archived courses (courses with end dates in the past) should be
# shown in Studio in a separate list. # shown in Studio in a separate list.
'ENABLE_SEPARATE_ARCHIVED_COURSES': True 'ENABLE_SEPARATE_ARCHIVED_COURSES': True,
# Whether section-level highlights are enabled on the platform.
'ENABLE_SECTION_HIGHLIGHTS': False,
} }
ENABLE_JASMINE = False ENABLE_JASMINE = False
......
...@@ -326,6 +326,8 @@ SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine" ...@@ -326,6 +326,8 @@ SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine"
FEATURES['ENABLE_ENROLLMENT_TRACK_USER_PARTITION'] = True FEATURES['ENABLE_ENROLLMENT_TRACK_USER_PARTITION'] = True
FEATURES['ENABLE_SECTION_HIGHLIGHTS'] = True
########################## AUTHOR PERMISSION ####################### ########################## AUTHOR PERMISSION #######################
FEATURES['ENABLE_CREATOR_GROUP'] = False FEATURES['ENABLE_CREATOR_GROUP'] = False
......
...@@ -159,7 +159,12 @@ function(Backbone, _, str, ModuleUtils) { ...@@ -159,7 +159,12 @@ function(Backbone, _, str, ModuleUtils) {
* some additional fields that are not stored in the course descriptor * some additional fields that are not stored in the course descriptor
* (for example, which groups are selected for this particular XBlock). * (for example, which groups are selected for this particular XBlock).
*/ */
user_partitions: null user_partitions: null,
/**
* This xBlock's Highlights to message to learners.
*/
highlights: null,
highlights_enabled: null
}, },
initialize: function() { initialize: function() {
......
...@@ -197,6 +197,19 @@ define(['jquery', 'underscore', 'js/views/xblock_outline', 'common/js/components ...@@ -197,6 +197,19 @@ define(['jquery', 'underscore', 'js/views/xblock_outline', 'common/js/components
} }
}, },
highlightsXBlock: function() {
var modal = CourseOutlineModalsFactory.getModal('highlights', this.model, {
onSave: this.refresh.bind(this),
xblockType: XBlockViewUtils.getXBlockType(
this.model.get('category'), this.parentView.model, true
)
});
if (modal) {
modal.show();
}
},
addButtonActions: function(element) { addButtonActions: function(element) {
XBlockOutlineView.prototype.addButtonActions.apply(this, arguments); XBlockOutlineView.prototype.addButtonActions.apply(this, arguments);
element.find('.configure-button').click(function(event) { element.find('.configure-button').click(function(event) {
...@@ -207,6 +220,10 @@ define(['jquery', 'underscore', 'js/views/xblock_outline', 'common/js/components ...@@ -207,6 +220,10 @@ define(['jquery', 'underscore', 'js/views/xblock_outline', 'common/js/components
event.preventDefault(); event.preventDefault();
this.publishXBlock(); this.publishXBlock();
}.bind(this)); }.bind(this));
element.find('.highlights-button').click(function(event) {
event.preventDefault();
this.highlightsXBlock();
}.bind(this));
}, },
makeContentDraggable: function(element) { makeContentDraggable: function(element) {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* *
* getTitle(): * getTitle():
* returns the title for the modal. * returns the title for the modal.
* getHTMLContent(): * getContentHtml():
* returns the HTML content to be shown inside the modal. * returns the HTML content to be shown inside the modal.
* *
* A modal implementation should also provide the following options: * A modal implementation should also provide the following options:
......
...@@ -13,10 +13,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', ...@@ -13,10 +13,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
$, Backbone, _, gettext, BaseView, BaseModal, date, XBlockViewUtils, DateUtils, HtmlUtils, StringUtils $, Backbone, _, gettext, BaseView, BaseModal, date, XBlockViewUtils, DateUtils, HtmlUtils, StringUtils
) { ) {
'use strict'; 'use strict';
var CourseOutlineXBlockModal, SettingsXBlockModal, PublishXBlockModal, AbstractEditor, BaseDateEditor, var CourseOutlineXBlockModal, SettingsXBlockModal, PublishXBlockModal, HighlightsXBlockModal,
AbstractEditor, BaseDateEditor,
ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor, ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor,
StaffLockEditor, UnitAccessEditor, ContentVisibilityEditor, TimedExaminationPreferenceEditor, StaffLockEditor, UnitAccessEditor, ContentVisibilityEditor, TimedExaminationPreferenceEditor,
AccessEditor, ShowCorrectnessEditor; AccessEditor, ShowCorrectnessEditor, HighlightsEditor;
CourseOutlineXBlockModal = BaseModal.extend({ CourseOutlineXBlockModal = BaseModal.extend({
events: _.extend({}, BaseModal.prototype.events, { events: _.extend({}, BaseModal.prototype.events, {
...@@ -206,6 +207,38 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', ...@@ -206,6 +207,38 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
} }
}); });
HighlightsXBlockModal = CourseOutlineXBlockModal.extend({
initialize: function() {
CourseOutlineXBlockModal.prototype.initialize.call(this);
if (this.options.xblockType) {
this.options.modalName = 'highlights-' + this.options.xblockType;
}
},
getTitle: function() {
return StringUtils.interpolate(
gettext('Highlights for {display_name}'),
{display_name: this.model.get('display_name')}
);
},
getIntroductionMessage: function() {
return StringUtils.interpolate(
gettext(
'The highlights you provide here are messaged (i.e., emailed) to learners. Each {item}\'s ' +
'highlights are emailed at the time that we expect the learner to start working on that {item}. ' +
'At this time, we assume that each {item} will take 1 week to complete.'
),
{item: this.options.xblockType}
);
},
addActionButtons: function() {
this.addActionButton('save', gettext('Save'), true);
this.addActionButton('cancel', gettext('Cancel'));
}
});
AbstractEditor = BaseView.extend({ AbstractEditor = BaseView.extend({
tagName: 'section', tagName: 'section',
templateName: null, templateName: null,
...@@ -844,12 +877,58 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', ...@@ -844,12 +877,58 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
} }
}); });
HighlightsEditor = AbstractEditor.extend({
templateName: 'highlights-editor',
className: 'edit-show-highlights',
currentValue: function() {
var highlights = [];
$('.highlight-input-text').each(function() {
var value = $(this).val();
if (value !== '' && value !== null) {
highlights.push(value);
}
});
return highlights;
},
hasChanges: function() {
return this.model.get('highlights') !== this.currentValue();
},
getRequestData: function() {
if (this.hasChanges()) {
return {
publish: 'republish',
metadata: {
highlights: this.currentValue()
}
};
} else {
return {};
}
},
getContext: function() {
return $.extend(
{},
AbstractEditor.prototype.getContext.call(this),
{
highlights: this.model.get('highlights') || []
}
);
}
});
return { return {
getModal: function(type, xblockInfo, options) { getModal: function(type, xblockInfo, options) {
if (type === 'edit') { if (type === 'edit') {
return this.getEditModal(xblockInfo, options); return this.getEditModal(xblockInfo, options);
} else if (type === 'publish') { } else if (type === 'publish') {
return this.getPublishModal(xblockInfo, options); return this.getPublishModal(xblockInfo, options);
} else if (type === 'highlights') {
return this.getHighlightsModal(xblockInfo, options);
} else {
return null;
} }
}, },
...@@ -918,6 +997,13 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', ...@@ -918,6 +997,13 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
editors: [PublishEditor], editors: [PublishEditor],
model: xblockInfo model: xblockInfo
}, options)); }, options));
},
getHighlightsModal: function(xblockInfo, options) {
return new HighlightsXBlockModal($.extend({
editors: [HighlightsEditor],
model: xblockInfo
}, options));
} }
}; };
}); });
...@@ -634,6 +634,23 @@ ...@@ -634,6 +634,23 @@
} }
} }
// outline: highlight settings
.highlights-button {
cursor: pointer;
color: $uxpl-blue-base;
}
.highlight-input-text {
width: 100%;
margin-bottom: 5px;
margin-top: 5px;
}
.highlights-description {
font-size: 80%;
font-weight: bolder;
}
// outline: edit item settings // outline: edit item settings
.wrapper-modal-window-bulkpublish-section, .wrapper-modal-window-bulkpublish-section,
.wrapper-modal-window-bulkpublish-subsection, .wrapper-modal-window-bulkpublish-subsection,
......
...@@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text
<%block name="header_extras"> <%block name="header_extras">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor']: % for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor']:
<script type="text/template" id="${template_name}-tpl"> <script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" /> <%static:include path="js/${template_name}.underscore" />
</script> </script>
......
...@@ -200,6 +200,22 @@ if (is_proctored_exam) { ...@@ -200,6 +200,22 @@ if (is_proctored_exam) {
</p> </p>
</div> </div>
<% } %> <% } %>
<% if (xblockInfo.get('highlights_enabled') && course.get('self_paced') && xblockInfo.isChapter()) { %>
<div class="block-highlights">
<span class="sr block-highlights-label"><%- gettext('Highlights:') %></span>
<% var number_of_highlights = (xblockInfo.get('highlights') || []).length; %>
<% if (number_of_highlights > 0) { %>
<span class="block-highlights-value highlights-button action-button">
<%- edx.StringUtils.interpolate(
gettext('Section Highlights: {number_of_highlights} entered'),
{number_of_highlights: number_of_highlights}
) %>
</span>
<% } else { %>
<span class="block-highlights-value highlights-button action-button"><%- gettext('Enter Section Highlights') %></span>
<% } %>
</div>
<% } %>
<% if (xblockInfo.get('is_time_limited')) { %> <% if (xblockInfo.get('is_time_limited')) { %>
<div class="status-timed-proctored-exam"> <div class="status-timed-proctored-exam">
<p> <p>
......
<form>
<h3 class="modal-section-title" id="highlights_label"><%- gettext('Section Highlights') %></h3>
<div class="modal-section-content block-highlights">
<div role="group" class="list-fields" aria-labelledby="highlights_label">
<p class='field-message highlights-description' id='highlights_description'>
<%- gettext('Please enter 3-5 highlights to be sent as separate bullet points in the message.') %>
</p>
<%
var max_number_of_highlights = 5;
%>
<% _.each(highlights, function(highlight){ %>
<input
class="input input-text highlight-input-text"
type="text" maxlength="250" aria-describedby="highlights_description"
value="<%= _.escape(highlight) %>"
/>
<% }); %>
<% for (i = highlights.length; i < max_number_of_highlights; i++) { %>
<input
class="input input-text highlight-input-text"
type="text" maxlength="250" aria-describedby="highlights_description"
placeholder="<%- gettext('A highlight to look forward to this week.') %>"
/>
<% } %>
</div>
</div>
</form>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment