diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 914ad1ec65d4..2d8f5444d76a 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -178,6 +178,18 @@ def is_currently_visible_to_students(xblock): return True +def has_children_visible_to_specific_content_groups(xblock): + """ + Returns True if this xblock has children that are limited to specific content groups. + Note that this method is not recursive (it does not check grandchildren. + """ + for child in xblock.children: + if modulestore().get_item(child).group_access: + return True + + return False + + def find_release_date_source(xblock): """ Finds the ancestor of xblock that set its release date. diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index ba14898e694b..e033260458c2 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -1343,7 +1343,7 @@ def group_configurations_list_handler(request, course_key_string): 'context_course': course, 'group_configuration_url': group_configuration_url, 'course_outline_url': course_outline_url, - 'configurations': configurations if should_show_group_configurations_page(course) else None, + 'configurations': configurations, }) elif "application/json" in request.META.get('HTTP_ACCEPT'): if request.method == 'POST': @@ -1422,16 +1422,6 @@ def group_configurations_detail_handler(request, course_key_string, group_config return JsonResponse(status=204) -def should_show_group_configurations_page(course): - """ - Returns true if Studio should show the "Group Configurations" page for the specified course. - """ - return ( - SPLIT_TEST_COMPONENT_TYPE in ADVANCED_COMPONENT_TYPES and - SPLIT_TEST_COMPONENT_TYPE in course.advanced_modules - ) - - def _get_course_creator_status(user): """ Helper method for returning the course creator status for a particular user, diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 28c9a5e48093..ef3ae954e81f 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -39,7 +39,7 @@ from student.auth import has_course_author_access from contentstore.utils import find_release_date_source, find_staff_lock_source, is_currently_visible_to_students, \ - ancestor_has_staff_lock + ancestor_has_staff_lock, has_children_visible_to_specific_content_groups from contentstore.views.helpers import is_unit, xblock_studio_url, xblock_primary_child_category, \ xblock_type_display_name, get_parent_xblock from contentstore.views.preview import get_preview_fragment @@ -47,6 +47,7 @@ from models.settings.course_grading import CourseGradingModel from cms.lib.xblock.runtime import handler_url, local_resource_url from opaque_keys.edx.keys import UsageKey, CourseKey +from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW __all__ = ['orphan_handler', 'xblock_handler', 'xblock_view_handler', 'xblock_outline_handler'] @@ -58,7 +59,6 @@ NEVER = lambda x: False ALWAYS = lambda x: True - # In order to allow descriptors to use a handler url, we need to # monkey-patch the x_module library. # TODO: Remove this code when Runtimes are no longer created by modulestores @@ -215,14 +215,14 @@ def xblock_view_handler(request, usage_key_string, view_name): request_token=request_token(request), )) - if view_name == STUDIO_VIEW: + if view_name in (STUDIO_VIEW, VISIBILITY_VIEW): try: - fragment = xblock.render(STUDIO_VIEW) + fragment = xblock.render(view_name) # catch exceptions indiscriminately, since after this point they escape the # dungeon and surface as uneditable, unsaveable, and undeletable # component-goblins. except Exception as exc: # pylint: disable=broad-except - log.debug("unable to render studio_view for %r", xblock, exc_info=True) + log.debug("Unable to render %s for %r", view_name, xblock, exc_info=True) fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)})) elif view_name in (PREVIEW_VIEWS + container_views): @@ -757,6 +757,7 @@ def safe_get_username(user_id): xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by) xblock_info["published_by"] = safe_get_username(xblock.published_by) xblock_info["currently_visible_to_students"] = is_currently_visible_to_students(xblock) + xblock_info["has_content_group_components"] = has_children_visible_to_specific_content_groups(xblock) if release_date: xblock_info["release_date_from"] = _get_release_date_from(xblock) if visibility_state == VisibilityState.staff_only: diff --git a/cms/envs/common.py b/cms/envs/common.py index 9243f3b5145c..56b8392c7b6e 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -36,6 +36,7 @@ from path import path from warnings import simplefilter +from cms.lib.xblock.authoring_mixin import AuthoringMixin from lms.lib.xblock.mixin import LmsBlockMixin from dealer.git import git from xmodule.modulestore.edit_info import EditInfoMixin @@ -255,7 +256,13 @@ # This should be moved into an XBlock Runtime/Application object # once the responsibility of XBlock creation is moved out of modulestore - cpennington -XBLOCK_MIXINS = (LmsBlockMixin, InheritanceMixin, XModuleMixin, EditInfoMixin) +XBLOCK_MIXINS = ( + LmsBlockMixin, + InheritanceMixin, + XModuleMixin, + EditInfoMixin, + AuthoringMixin, +) # Allow any XBlock in Studio # You should also enable the ALLOW_ALL_ADVANCED_COMPONENTS feature flag, so that diff --git a/cms/lib/xblock/authoring_mixin.py b/cms/lib/xblock/authoring_mixin.py new file mode 100644 index 000000000000..37311dc20fa7 --- /dev/null +++ b/cms/lib/xblock/authoring_mixin.py @@ -0,0 +1,50 @@ +""" +Mixin class that provides authoring capabilities for XBlocks. +""" + +import logging + +from xblock.core import XBlock +from xblock.fields import XBlockMixin +from xblock.fragment import Fragment + +logger = logging.getLogger(__name__) + +VISIBILITY_VIEW = 'visibility_view' + + +@XBlock.needs("i18n") +class AuthoringMixin(XBlockMixin): + """ + Mixin class that provides authoring capabilities for XBlocks. + """ + _services_requested = { + 'i18n': 'need', + } + + def _get_studio_resource_url(self, relative_url): + """ + Returns the Studio URL to a static resource. + """ + # TODO: is there a cleaner way to do this? + from cms.envs.common import STATIC_URL + return STATIC_URL + '/js/xblock/authoring.js' + + + def visibility_view(self, context=None): + """ + Render the view to manage an xblock's visibility settings in Studio. + Args: + context: Not actively used for this view. + Returns: + (Fragment): An HTML fragment for editing the visibility of this XBlock. + """ + fragment = Fragment() + from contentstore.utils import reverse_course_url + fragment.add_content(self.system.render_template('visibility_editor.html', { + 'xblock': self, + 'manage_groups_url': reverse_course_url('group_configurations_list_handler', self.location.course_key), + })) + fragment.add_javascript_url(self._get_studio_resource_url('/js/xblock/authoring.js')) + fragment.initialize_js('VisibilityEditorInit') + return fragment diff --git a/cms/static/js/models/xblock_info.js b/cms/static/js/models/xblock_info.js index d2324313b5de..64c1241a4dc9 100644 --- a/cms/static/js/models/xblock_info.js +++ b/cms/static/js/models/xblock_info.js @@ -120,7 +120,12 @@ function(Backbone, _, str, ModuleUtils) { /** * True iff this xblock should display a "Contains staff only content" message. */ - 'staff_only_message': null + 'staff_only_message': null, + /** + * True iff this xblock is a unit, and it has children that are only visible to certain + * content groups. Note that this is not a recursive property. + */ + 'has_content_group_components': null }, initialize: function () { diff --git a/cms/static/js/views/modals/base_modal.js b/cms/static/js/views/modals/base_modal.js index eb543295ed6c..b603da7d0aff 100644 --- a/cms/static/js/views/modals/base_modal.js +++ b/cms/static/js/views/modals/base_modal.js @@ -1,5 +1,12 @@ /** * This is a base modal implementation that provides common utilities. + * + * A modal implementation should override the following methods: + * + * getTitle(): + * returns the title for the modal. + * getHTMLContent(): + * returns the HTML content to be shown inside the modal. */ define(["jquery", "underscore", "gettext", "js/views/baseview"], function($, _, gettext, BaseView) { @@ -41,7 +48,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview"], name: this.options.modalName, type: this.options.modalType, size: this.options.modalSize, - title: this.options.title, + title: this.getTitle(), viewSpecificClasses: this.options.viewSpecificClasses })); this.addActionButtons(); @@ -49,6 +56,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview"], this.parentElement.append(this.$el); }, + getTitle: function() { + return this.options.title; + }, + renderContents: function() { var contentHtml = this.getContentHtml(); this.$('.modal-content').html(contentHtml); diff --git a/cms/static/js/views/modals/edit_xblock.js b/cms/static/js/views/modals/edit_xblock.js index 67e9de6f88e1..2a0c0ae8442c 100644 --- a/cms/static/js/views/modals/edit_xblock.js +++ b/cms/static/js/views/modals/edit_xblock.js @@ -15,7 +15,9 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", "js/vie options: $.extend({}, BaseModal.prototype.options, { modalName: 'edit-xblock', addSaveButton: true, - viewSpecificClasses: 'modal-editor confirm' + view: 'studio_view', + viewSpecificClasses: 'modal-editor confirm', + titleFormat: gettext("Editing: %(title)s") }), initialize: function() { @@ -56,7 +58,8 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", "js/vie displayXBlock: function() { this.editorView = new XBlockEditorView({ el: this.$('.xblock-editor'), - model: this.xblockInfo + model: this.xblockInfo, + view: this.options.view }); this.editorView.render({ success: _.bind(this.onDisplayXBlock, this) @@ -111,7 +114,7 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", "js/vie if (!displayName) { displayName = gettext('Component'); } - return interpolate(gettext("Editing: %(title)s"), { title: displayName }, true); + return interpolate(this.options.titleFormat, { title: displayName }, true); }, addDefaultModes: function() { diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index 7a62e535919d..c0409db392ef 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -15,6 +15,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views events: { "click .edit-button": "editXBlock", + "click .visibility-button": "editVisibilitySettings", "click .duplicate-button": "duplicateXBlock", "click .delete-button": "deleteXBlock" }, @@ -136,10 +137,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views }); }, - editXBlock: function(event) { + editXBlock: function(event, options) { var xblockElement = this.findXBlockElement(event.target), self = this, - modal = new EditXBlockModal({ }); + modal = new EditXBlockModal(options); event.preventDefault(); modal.edit(xblockElement, this.model, { @@ -149,6 +150,15 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views }); }, + editVisibilitySettings: function(event) { + this.editXBlock(event, { + view: 'visibility_view', + titleFormat: gettext("Editing visibility for: %(title)s"), + viewSpecificClasses: '', + modalSize: 'med' + }); + }, + duplicateXBlock: function(event) { event.preventDefault(); this.duplicateComponent(this.findXBlockElement(event.target)); diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js index 1957633f3bff..2af46b879633 100644 --- a/cms/static/js/views/pages/container_subviews.js +++ b/cms/static/js/views/pages/container_subviews.js @@ -120,7 +120,8 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/ releaseDate: this.model.get('release_date'), releaseDateFrom: this.model.get('release_date_from'), hasExplicitStaffLock: this.model.get('has_explicit_staff_lock'), - staffLockFrom: this.model.get('staff_lock_from') + staffLockFrom: this.model.get('staff_lock_from'), + hasContentGroupComponents: this.model.get('has_content_group_components') })); return this; diff --git a/cms/static/js/views/pages/course_outline.js b/cms/static/js/views/pages/course_outline.js index a812eb7b02fa..4e815fd08bd3 100644 --- a/cms/static/js/views/pages/course_outline.js +++ b/cms/static/js/views/pages/course_outline.js @@ -26,7 +26,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views }); this.model.on('change', this.setCollapseExpandVisibility, this); $('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () { - $('.wrapper-alert-announcement').removeClass('is-shown').addClass('is-hidden') + $('.wrapper-alert-announcement').removeClass('is-shown').addClass('is-hidden'); })); }, diff --git a/cms/static/js/xblock/authoring.js b/cms/static/js/xblock/authoring.js new file mode 100644 index 000000000000..81f7ae08c432 --- /dev/null +++ b/cms/static/js/xblock/authoring.js @@ -0,0 +1,26 @@ +/** + * Client-side logic to support XBlock authoring. + */ +var edx = edx || {}; + +(function($) { + 'use strict'; + + edx.studio = edx.studio || {}; + edx.studio.xblock = edx.studio.xblock || {}; + + function initializeVisibilityEditor(runtime, element) { + element.find('.field-visibility-level input').change(function(event) { + if ($(event.target).hasClass('visibility-level-all')) { + element.find('.field-visibility-content-group input').prop('checked', false); + } + }); + element.find('.field-visibility-content-group input').change(function(event) { + element.find('.visibility-level-all').prop('checked', true); + element.find('.visibility-level-specific').prop('checked', true); + }); + } + + // XBlock initialization functions must be global + window.VisibilityEditorInit = initializeVisibilityEditor; +})($); diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index c310f4296f1c..9542cb8cbf43 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -165,6 +165,7 @@ $color-ready: $green; $color-warning: $orange-l2; $color-error: $red-l2; $color-staff-only: $black; +$color-visibility-set: $black; $color-heading-base: $gray-d2; $color-copy-base: $gray-l1; diff --git a/cms/static/sass/elements/_forms.scss b/cms/static/sass/elements/_forms.scss index 5979c92ff740..e80944c1f66e 100644 --- a/cms/static/sass/elements/_forms.scss +++ b/cms/static/sass/elements/_forms.scss @@ -1,7 +1,24 @@ // studio - elements - forms // ==================== +// element-specific utilities +// -------------------- +// UI: checkbox/radio inputs +%input-tickable { + + ~ label { + color: $color-copy-base; + } + + // STATE: checked/selected + &:checked ~ label { + @extend %t-strong; + color: $ui-action-primary-color-focus; + } +} + // forms - general +// -------------------- input[type="text"], input[type="email"], input[type="password"], @@ -99,8 +116,18 @@ form { } } + // CASE: checkbox input + .field-checkbox .input-checkbox { + @extend %input-tickable; + } + + // CASE: radio input + .field-radio .input-radio { + @extend %input-tickable; + } + // CASE: file input - input[type=file] { + input[type="file"] { @extend %t-copy-sub1; } diff --git a/cms/static/sass/elements/_modal-window.scss b/cms/static/sass/elements/_modal-window.scss index c80ff868625e..94710de0683c 100644 --- a/cms/static/sass/elements/_modal-window.scss +++ b/cms/static/sass/elements/_modal-window.scss @@ -52,6 +52,45 @@ } } + // UI: summary messages + .summary-message { + margin-bottom: $baseline; + padding: ($baseline*0.75); + background: $gray-d3; + + .icon, .copy { + display: inline-block; + vertical-align: top; + } + + .icon { + @extend %t-icon4; + margin-right: ($baseline/2); + color: $white; + } + + .copy { + @extend %t-copy-sub1; + max-width: 85%; + color: $white; + } + } + + // CASE: Warning summary message + .summary-message-warning { + border-top: ($baseline/5) solid $color-warning; + + .icon { + color: $color-warning; + } + } + + // visual dividers + .divider-visual { + margin: ($baseline*0.75) 0; + border: ($baseline/20) solid $gray-l4; + } + // sections within a modal .modal-section { margin-bottom: ($baseline*0.75); @@ -64,11 +103,20 @@ .modal-section-title { @extend %t-title6; margin: 0 0 ($baseline/2) 0; - border-bottom: 1px solid $gray-l4; + border-bottom: ($baseline/10) solid $gray-l4; padding-bottom: ($baseline/4); color: $gray-d2; } + .modal-subsection-title { + @extend %t-title8; + @extend %t-strong; + margin-bottom: ($baseline/4); + text-transform: uppercase; + letter-spacing: 0.1rem; + color: $gray-l2; + } + .modal-section-content { .list-fields, .list-actions { @@ -234,143 +282,6 @@ } } - // outline: edit item settings - .wrapper-modal-window-bulkpublish-section, - .wrapper-modal-window-bulkpublish-subsection, - .wrapper-modal-window-bulkpublish-unit, - .course-outline-modal { - - .list-fields { - - .field { - display: inline-block; - vertical-align: top; - margin-right: ($baseline/2); - margin-bottom: ($baseline/4); - - label { - @extend %t-copy-sub1; - @extend %t-strong; - @include transition(color $tmg-f3 ease-in-out 0s); - margin: 0 0 ($baseline/4) 0; - - &.is-focused { - color: $blue; - } - } - - - input, textarea { - @extend %t-copy-base; - @include transition(all $tmg-f2 ease-in-out 0s); - height: 100%; - width: 100%; - padding: ($baseline/2); - - // CASE: long length - &.long { - width: 100%; - } - - // CASE: short length - &.short { - width: 25%; - } - } - - // CASE: specific release + due times/dates - .start-date, - .start-time, - .due-date, - .due-time { - width: ($baseline*7); - } - - .tip { - @extend %t-copy-sub1; - @include transition(color, 0.15s, ease-in-out); - display: block; - margin-top: ($baseline/4); - color: $gray-l2; - } - - .tip-warning { - color: $gray-d2; - } - } - - // CASE: type-based input - .field-text { - - // TODO: refactor the _forms.scss partial to allow for this area to inherit from it - label, input, textarea { - display: block; - } - } - - // CASE: select input - .field-select { - - .label, .input { - display: inline-block; - vertical-align: middle; - } - - .label { - margin-right: ($baseline/2); - } - - .input { - width: 100%; - } - - // CASE: checkbox input - .field-checkbox { - - .label, label { - margin-bottom: 0; - } - } - } - } - - - - // UI: grading section - .edit-settings-grading { - - .grading-type { - margin-bottom: $baseline; - } - } - - // UI: staff lock section - .edit-staff-lock { - - .checkbox-cosmetic .input-checkbox { - @extend %cont-text-sr; - - // CASE: unchecked - ~ .tip-warning { - display: block; - } - - // CASE: checked - &:checked { - - ~ .tip-warning { - display: none; - } - } - } - - // needed to override poorly scoped margin-bottom on any label element in a view (from _forms.scss) - .checkbox-cosmetic .label { - margin-bottom: 0; - } - } - } - // xblock custom actions .modal-window .editor-with-buttons { margin-bottom: ($baseline*3); @@ -390,7 +301,7 @@ } - // special overrides for video module editor/hidden tab editors + // MODAL TYPE: component - video modal (includes special overrides for xblock-related editing view) .modal-lg.modal-type-video { .modal-content { @@ -513,4 +424,264 @@ opacity: 0.5; filter: alpha(opacity=50); } + + // MODAL TYPE: component - visibility modal + .xblock-visibility_view { + + // UI: visibility summary + .visibility-summary-title { + margin-bottom: ($baseline*0.75); + padding-bottom: ($baseline*0.75); + } + + .visibility-summary-value { + display: inline-block; + vertical-align: top; + margin-left: ($baseline/4); + @extend %t-strong; + } + + .visibility-controls-secondary { + max-height: 100%; + overflow-y: scroll; + margin: ($baseline*0.75) 0 0 $baseline; + } + + .visibility-controls-group { + @extend %wipe-last-child; + margin-bottom: $baseline; + } + + + // UI: form fields + .list-fields { + + .field { + @extend %wipe-last-child; + display: block; + width: 100%; + margin-bottom: ($baseline/4); + + label { + @extend %t-copy-sub1; + @include transition(color $tmg-f3 ease-in-out 0s); + + &.is-focused { + color: $blue; + } + } + + input, textarea { + @extend %t-copy-base; + @include transition(all $tmg-f2 ease-in-out 0s); + padding: ($baseline/2); + } + + .tip { + @extend %t-copy-sub1; + @include transition(color, 0.15s, ease-in-out); + display: block; + margin-top: ($baseline/4); + color: $gray-l2; + } + + .tip-warning { + color: $gray-d2; + } + } + + // UI: radio and checkbox inputs + .field-radio, .field-checkbox { + + label { + margin-left: ($baseline/4); + } + } + } + + // CASE: content group has been removed + .field-visibility-content-group.was-removed { + + .input-checkbox:checked ~ label { + color: $color-error; + } + + .note { + @extend %t-copy-sub2; + @extend %t-regular; + display: block; + margin-top: ($baseline/4); + color: $color-error; + } + } + + // CASE: no groups configured for visibility + .is-not-configured { + @extend %no-content; + text-align: left; // reset for %no-content's default styling + + .title { + @extend %t-title6; + font-weight: 600; // needed for poorly scoped .title rule in modals + margin: 0 0 ($baseline/2) 0; // needed for poorly scoped .title rule in modals + } + + .copy { + @extend %t-copy-sub1; + + p { + @extend %wipe-last-child; + margin-bottom: $baseline; + } + } + + &.has-actions { + + .actions { + margin-top: $baseline; + } + + .action { + margin-left: 0; // reset for %no-content's default styling + } + } + } + } + + // MODAL TYPE: outline - edit item settings + .wrapper-modal-window-bulkpublish-section, + .wrapper-modal-window-bulkpublish-subsection, + .wrapper-modal-window-bulkpublish-unit, + .course-outline-modal { + + .list-fields { + + .field { + display: inline-block; + vertical-align: top; + margin-right: ($baseline/2); + margin-bottom: ($baseline/4); + + label { + @extend %t-copy-sub1; + @extend %t-strong; + @include transition(color $tmg-f3 ease-in-out 0s); + margin: 0 0 ($baseline/4) 0; + + &.is-focused { + color: $blue; + } + } + + + input, textarea { + @extend %t-copy-base; + @include transition(all $tmg-f2 ease-in-out 0s); + height: 100%; + width: 100%; + padding: ($baseline/2); + + // CASE: long length + &.long { + width: 100%; + } + + // CASE: short length + &.short { + width: 25%; + } + } + + // CASE: specific release + due times/dates + .start-date, + .start-time, + .due-date, + .due-time { + width: ($baseline*7); + } + + .tip { + @extend %t-copy-sub1; + @include transition(color, 0.15s, ease-in-out); + display: block; + margin-top: ($baseline/4); + color: $gray-l2; + } + + .tip-warning { + color: $gray-d2; + } + } + + // CASE: type-based input + .field-text { + + // TODO: refactor the _forms.scss partial to allow for this area to inherit from it + label, input, textarea { + display: block; + } + } + + // CASE: select input + .field-select { + + .label, .input { + display: inline-block; + vertical-align: middle; + } + + .label { + margin-right: ($baseline/2); + } + + .input { + width: 100%; + } + + // CASE: checkbox input + .field-checkbox { + + .label, label { + margin-bottom: 0; + } + } + } + } + + + + // UI: grading section + .edit-settings-grading { + + .grading-type { + margin-bottom: $baseline; + } + } + + // UI: staff lock section + .edit-staff-lock { + + .checkbox-cosmetic .input-checkbox { + @extend %cont-text-sr; + + // CASE: unchecked + ~ .tip-warning { + display: block; + } + + // CASE: checked + &:checked { + + ~ .tip-warning { + display: none; + } + } + } + + // needed to override poorly scoped margin-bottom on any label element in a view (from _forms.scss) + .checkbox-cosmetic .label { + margin-bottom: 0; + } + } + } } diff --git a/cms/static/sass/elements/_xblocks.scss b/cms/static/sass/elements/_xblocks.scss index d98506f6dfb2..20ef254606c1 100644 --- a/cms/static/sass/elements/_xblocks.scss +++ b/cms/static/sass/elements/_xblocks.scss @@ -119,42 +119,50 @@ // ==================== - // UI: xblocks - calls-to-action - .wrapper-xblock .header-actions { + .wrapper-xblock { - .actions-list { + // UI: xblocks - calls-to-action + .header-actions .actions-list { @extend %actions-list; } - } - // UI: xblock is collapsible - .wrapper-xblock.is-collapsible, - .wrapper-xblock.xblock-type-container { + // CASE: xblock is collapsible + &.is-collapsible, + &.xblock-type-container { - [class^="icon-"] { - font-style: normal; - } + [class^="icon-"] { + font-style: normal; + } - .expand-collapse { - @extend %expand-collapse; - margin: 0 ($baseline/4); - height: ($baseline*1.25); - width: $baseline; + .expand-collapse { + @extend %expand-collapse; + margin: 0 ($baseline/4); + height: ($baseline*1.25); + width: $baseline; - &:focus { - outline: 0; + &:focus { + outline: 0; + } } - } - .action-view { + .action-view { + + .action-button { + transition: none; + } - .action-button { - transition: none; + .action-button-text { + padding-right: ($baseline/5); + padding-left: 0; + } } + } + + // CASE: xblock has specific visibility set + &.has-visibility-set { - .action-button-text { - padding-right: ($baseline/5); - padding-left: 0; + .action-visibility .visibility-button.visibility-button { // needed to cascade in front of overscoped header-actions CSS rule + color: $color-visibility-set; } } } diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss index 7780d0485078..bed0dbe454e4 100644 --- a/cms/static/sass/views/_container.scss +++ b/cms/static/sass/views/_container.scss @@ -6,7 +6,20 @@ // ==================== +// view-specific utilities +// -------------------- +%status-value-base { + @extend %t-title7; + @extend %t-strong; +} + +%status-value-sub1 { + @extend %t-title8; + display: block; +} + // UI: container page view +// -------------------- .view-container { @extend %two-col-1; @@ -102,6 +115,7 @@ @extend %t-title8; } + // UI: publishing details/summary .bit-publishing { @extend %bar-module; @@ -159,19 +173,18 @@ .wrapper-release { .release-date { - @extend %t-strong; + @extend %status-value-base; } .release-with { - @extend %t-title8; - display: block; + @extend %status-value-sub1; } } .wrapper-visibility { .copy { - @extend %t-strong; + @extend %status-value-base; margin-bottom: ($baseline/10); } @@ -181,15 +194,23 @@ } .inherited-from { - @extend %t-title8; - display: block; + @extend %status-value-sub1; } + // UI: note about specific access + .note-visibility { + @extend %status-value-sub1; + .icon { + margin-right: ($baseline/4); + } + } } .wrapper-pub-actions { - padding: ($baseline*.75); + border-top: 1px solid $gray-l4; + margin-top: ($baseline/2); + padding: $baseline ($baseline*0.75) ($baseline*0.75) ($baseline*0.75); .action-publish { @extend %btn-primary-blue; @@ -209,7 +230,6 @@ } } } - } // versioning widget @@ -244,8 +264,7 @@ .wrapper-unit-id { .unit-id-value { - @extend %cont-text-wrap; - @extend %t-copy-sub1; + @extend %status-value-base; display: inline-block; width: 100%; } diff --git a/cms/templates/base.html b/cms/templates/base.html index 8a8a70c0ded2..5a606f730182 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -80,6 +80,9 @@
+ + <%block name="modal_placeholder"> + <%block name="jsextra">