diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 6b4cce7534..36754815f9 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -1182,6 +1182,11 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F xblock_info.update({ '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 if settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'): diff --git a/cms/envs/common.py b/cms/envs/common.py index 4a2d4172ef..c7e3731d55 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -264,7 +264,10 @@ FEATURES = { # Whether archived courses (courses with end dates in the past) should be # 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 diff --git a/cms/envs/test.py b/cms/envs/test.py index 051b10c293..cc844bfc06 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -326,6 +326,8 @@ SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine" FEATURES['ENABLE_ENROLLMENT_TRACK_USER_PARTITION'] = True +FEATURES['ENABLE_SECTION_HIGHLIGHTS'] = True + ########################## AUTHOR PERMISSION ####################### FEATURES['ENABLE_CREATOR_GROUP'] = False diff --git a/cms/static/js/models/xblock_info.js b/cms/static/js/models/xblock_info.js index e55bd2c4d9..84264750aa 100644 --- a/cms/static/js/models/xblock_info.js +++ b/cms/static/js/models/xblock_info.js @@ -159,7 +159,12 @@ function(Backbone, _, str, ModuleUtils) { * some additional fields that are not stored in the course descriptor * (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() { diff --git a/cms/static/js/views/course_outline.js b/cms/static/js/views/course_outline.js index d15609a7f6..3872aa2bca 100644 --- a/cms/static/js/views/course_outline.js +++ b/cms/static/js/views/course_outline.js @@ -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) { XBlockOutlineView.prototype.addButtonActions.apply(this, arguments); element.find('.configure-button').click(function(event) { @@ -207,6 +220,10 @@ define(['jquery', 'underscore', 'js/views/xblock_outline', 'common/js/components event.preventDefault(); this.publishXBlock(); }.bind(this)); + element.find('.highlights-button').click(function(event) { + event.preventDefault(); + this.highlightsXBlock(); + }.bind(this)); }, makeContentDraggable: function(element) { diff --git a/cms/static/js/views/modals/base_modal.js b/cms/static/js/views/modals/base_modal.js index 2974a7720a..24f703a202 100644 --- a/cms/static/js/views/modals/base_modal.js +++ b/cms/static/js/views/modals/base_modal.js @@ -5,7 +5,7 @@ * * getTitle(): * returns the title for the modal. - * getHTMLContent(): + * getContentHtml(): * returns the HTML content to be shown inside the modal. * * A modal implementation should also provide the following options: diff --git a/cms/static/js/views/modals/course_outline_modals.js b/cms/static/js/views/modals/course_outline_modals.js index 90a0b5a028..3711e0d78f 100644 --- a/cms/static/js/views/modals/course_outline_modals.js +++ b/cms/static/js/views/modals/course_outline_modals.js @@ -13,10 +13,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', $, Backbone, _, gettext, BaseView, BaseModal, date, XBlockViewUtils, DateUtils, HtmlUtils, StringUtils ) { 'use strict'; - var CourseOutlineXBlockModal, SettingsXBlockModal, PublishXBlockModal, AbstractEditor, BaseDateEditor, + var CourseOutlineXBlockModal, SettingsXBlockModal, PublishXBlockModal, HighlightsXBlockModal, + AbstractEditor, BaseDateEditor, ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor, StaffLockEditor, UnitAccessEditor, ContentVisibilityEditor, TimedExaminationPreferenceEditor, - AccessEditor, ShowCorrectnessEditor; + AccessEditor, ShowCorrectnessEditor, HighlightsEditor; CourseOutlineXBlockModal = BaseModal.extend({ events: _.extend({}, BaseModal.prototype.events, { @@ -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({ tagName: 'section', templateName: null, @@ -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 { getModal: function(type, xblockInfo, options) { if (type === 'edit') { return this.getEditModal(xblockInfo, options); } else if (type === 'publish') { 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', editors: [PublishEditor], model: xblockInfo }, options)); + }, + + getHighlightsModal: function(xblockInfo, options) { + return new HighlightsXBlockModal($.extend({ + editors: [HighlightsEditor], + model: xblockInfo + }, options)); } }; }); diff --git a/cms/static/sass/views/_outline.scss b/cms/static/sass/views/_outline.scss index 926d650f75..dcbb5b8ec6 100644 --- a/cms/static/sass/views/_outline.scss +++ b/cms/static/sass/views/_outline.scss @@ -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 .wrapper-modal-window-bulkpublish-section, .wrapper-modal-window-bulkpublish-subsection, diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index ca0a529d44..8073e7a814 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text <%block name="header_extras"> -% 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']: diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore index cb14c9d52e..30a3506920 100644 --- a/cms/templates/js/course-outline.underscore +++ b/cms/templates/js/course-outline.underscore @@ -200,6 +200,22 @@ if (is_proctored_exam) {
<% } %> + <% if (xblockInfo.get('highlights_enabled') && course.get('self_paced') && xblockInfo.isChapter()) { %> +diff --git a/cms/templates/js/highlights-editor.underscore b/cms/templates/js/highlights-editor.underscore new file mode 100644 index 0000000000..a8c2e97b88 --- /dev/null +++ b/cms/templates/js/highlights-editor.underscore @@ -0,0 +1,27 @@ +