feat: make hide from TOC a visibility section setting (#33952)

Exposes the hide_from_toc xblock attribute so course authors can configure it as a section visibility option in Studio. Before this change, the Hide from TOC functionality was mainly used by OLX components. Hence, it wasn't available for configuration through the Studio UI. Still, its implementation existed in the platform and could be used by setting the attribute: hide_from_toc=true as part of the OLX definition.
Ref: https://openedx.atlassian.net/wiki/spaces/OEPM/pages/3853975595/Feature+Enhancement+Proposal+Hide+Sections+from+course+outline
This commit is contained in:
Maria Grimaldi
2024-02-29 11:13:33 -04:00
committed by GitHub
parent 547f7febcc
commit f544a4825d
15 changed files with 205 additions and 18 deletions

View File

@@ -177,6 +177,14 @@ define(
* List of tags of the unit. This list is managed by the content_tagging module.
*/
tags: null,
/**
* True if the xblock is not visible to students only via links.
*/
hide_from_toc: null,
/**
* True iff this xblock should display a "Contains staff only content" message.
*/
hide_from_toc_message: null,
},
initialize: function() {

View File

@@ -42,6 +42,8 @@ describe('CourseOutlinePage', function() {
user_partition_info: {},
highlights_enabled: true,
highlights_enabled_for_messaging: false,
hide_from_toc: false,
enable_hide_from_toc_ui: true
}, options, {child_info: {children: children}});
};
@@ -68,7 +70,9 @@ describe('CourseOutlinePage', function() {
show_review_rules: true,
user_partition_info: {},
highlights_enabled: true,
highlights_enabled_for_messaging: false
highlights_enabled_for_messaging: false,
hide_from_toc: false,
enable_hide_from_toc_ui: true
}, options, {child_info: {children: children}});
};
@@ -93,7 +97,9 @@ describe('CourseOutlinePage', function() {
group_access: {},
user_partition_info: {},
highlights: [],
highlights_enabled: true
highlights_enabled: true,
hide_from_toc: false,
enable_hide_from_toc_ui: true
}, options, {child_info: {children: children}});
};
@@ -123,7 +129,9 @@ describe('CourseOutlinePage', function() {
},
user_partitions: [],
group_access: {},
user_partition_info: {}
user_partition_info: {},
hide_from_toc: false,
enable_hide_from_toc_ui: true
}, options, {child_info: {children: children}});
};
@@ -141,7 +149,9 @@ describe('CourseOutlinePage', function() {
edited_by: 'MockUser',
user_partitions: [],
group_access: {},
user_partition_info: {}
user_partition_info: {},
hide_from_toc: false,
enable_hide_from_toc_ui: true
}, options);
};
@@ -1214,7 +1224,9 @@ describe('CourseOutlinePage', function() {
is_practice_exam: false,
is_proctored_exam: false,
default_time_limit_minutes: 150,
hide_after_due: true
hide_after_due: true,
hide_from_toc: false,
enable_hide_from_toc_ui: true,
}, [
createMockVerticalJSON({
has_changes: true,
@@ -1397,6 +1409,7 @@ describe('CourseOutlinePage', function() {
default_time_limit_minutes: 150,
hide_after_due: true,
is_onboarding_exam: false,
hide_from_toc: null,
}
});
expect(requests[0].requestHeaders['X-HTTP-Method-Override']).toBe('PATCH');
@@ -2240,6 +2253,8 @@ describe('CourseOutlinePage', function() {
is_practice_exam: false,
is_proctored_exam: false,
default_time_limit_minutes: null,
hide_from_toc: false,
enable_hide_from_toc_ui: true,
}, [
createMockVerticalJSON({
has_changes: true,
@@ -2521,6 +2536,7 @@ describe('CourseOutlinePage', function() {
publish: 'republish',
metadata: {
visible_to_staff_only: null,
hide_from_toc: null
}
});
})

View File

@@ -314,6 +314,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
var isProctoredExam = xblockInfo.get('is_proctored_exam');
var isPracticeExam = xblockInfo.get('is_practice_exam');
var isOnboardingExam = xblockInfo.get('is_onboarding_exam');
var enableHideFromTOCUI = xblockInfo.get('enable_hide_from_toc_ui');
var hideFromTOC = xblockInfo.get('hide_from_toc');
var html = this.template($.extend({}, {
xblockInfo: xblockInfo,
xblockType: this.options.xblockType,
@@ -323,6 +325,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
isProctoredExam: isProctoredExam,
isPracticeExam: isPracticeExam,
isOnboardingExam: isOnboardingExam,
enableHideFromTOCUI: enableHideFromTOCUI,
hideFromTOC: hideFromTOC,
isTimedExam: isTimeLimited && !(
isProctoredExam || isPracticeExam || isOnboardingExam
),
@@ -798,6 +802,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
return this.model.get('ancestor_has_staff_lock');
},
isModelHiddenFromTOC: function() {
return this.model.get('hide_from_toc');
},
getContext: function() {
return {
hasExplicitStaffLock: this.isModelLocked(),
@@ -812,6 +820,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
afterRender: function() {
AbstractVisibilityEditor.prototype.afterRender.call(this);
this.setLock(this.isModelLocked());
this.setHideFromTOC(this.isModelHiddenFromTOC());
this.setVisibleToLearners(this.isVisibleToLearners());
},
setLock: function(value) {
@@ -822,8 +832,24 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
return this.$('#staff_lock').is(':checked');
},
setHideFromTOC: function(value) {
this.$('#hide_from_toc').prop('checked', value);
},
setVisibleToLearners: function(value) {
this.$('#visible_to_learners').prop('checked', value);
},
isVisibleToLearners: function() {
return this.$('#staff_lock').is(':not(:checked)') && this.$('#hide_from_toc').is(':not(:checked)');
},
isHiddenFromTOC: function() {
return this.$('#hide_from_toc').is(':checked');
},
hasChanges: function() {
return this.isModelLocked() !== this.isLocked();
return this.isModelLocked() !== this.isLocked() || this.isModelHiddenFromTOC() !== this.isHiddenFromTOC();
},
getRequestData: function() {
@@ -831,7 +857,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
return {
publish: 'republish',
metadata: {
visible_to_staff_only: this.isLocked() ? true : null
visible_to_staff_only: this.isLocked() || null,
hide_from_toc: this.isHiddenFromTOC() || null
}
};
} else {
@@ -1055,12 +1082,20 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
if (this.currentVisibility() === 'staff_only') {
metadata.visible_to_staff_only = true;
metadata.hide_after_due = null;
metadata.hide_from_toc = null;
} else if (this.currentVisibility() === 'hide_after_due') {
metadata.visible_to_staff_only = null;
metadata.hide_after_due = true;
} else {
metadata.hide_from_toc = null;
} else if (this.currentVisibility() === 'hide_from_toc'){
metadata.visible_to_staff_only = null;
metadata.hide_after_due = null;
metadata.hide_from_toc = true;
}
else {
metadata.visible_to_staff_only = null;
metadata.hide_after_due = null;
metadata.hide_from_toc = null;
}
return {

View File

@@ -149,6 +149,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
releaseDate: this.model.get('release_date'),
releaseDateFrom: this.model.get('release_date_from'),
hasExplicitStaffLock: this.model.get('has_explicit_staff_lock'),
hideFromTOC: this.model.get('hide_from_toc'),
staffLockFrom: this.model.get('staff_lock_from'),
enableCopyUnit: this.model.get('enable_copy_paste_units'),
course: window.course,

View File

@@ -29,6 +29,8 @@ function($, _, gettext, ViewUtils, ModuleUtils, XBlockInfo, StringUtils) {
*
* staffOnly - all of the block's content is to be shown to staff only
* Note: staff only items do not affect their parent's state.
*
* hideFromTOC - all of the block's content is to be hidden from the table of contents.
*/
VisibilityState = {
live: 'live',
@@ -36,7 +38,8 @@ function($, _, gettext, ViewUtils, ModuleUtils, XBlockInfo, StringUtils) {
unscheduled: 'unscheduled',
needsAttention: 'needs_attention',
staffOnly: 'staff_only',
gated: 'gated'
gated: 'gated',
hideFromTOC: 'hide_from_toc'
};
/**
@@ -310,6 +313,9 @@ function($, _, gettext, ViewUtils, ModuleUtils, XBlockInfo, StringUtils) {
if (visibilityState === VisibilityState.staffOnly) {
return 'is-staff-only';
}
if (visibilityState === VisibilityState.hideFromTOC) {
return 'is-hidden-from-toc';
}
if (visibilityState === VisibilityState.gated) {
return 'is-gated';
}

View File

@@ -112,6 +112,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, XBlockStringFieldE
includesChildren: this.shouldRenderChildren(),
hasExplicitStaffLock: this.model.get('has_explicit_staff_lock'),
staffOnlyMessage: this.model.get('staff_only_message'),
hideFromTOCMessage: this.model.get('hide_from_toc_message'),
course: course,
enableCopyPasteUnits: this.model.get("enable_copy_paste_units"), // ENABLE_COPY_PASTE_UNITS waffle flag
useTaggingTaxonomyListPage: this.model.get("use_tagging_taxonomy_list_page"), // ENABLE_TAGGING_TAXONOMY_LIST_PAGE waffle flag

View File

@@ -507,6 +507,18 @@ $outline-indent-width: $baseline;
}
}
// CASE: is hidden from TOC
&.is-hidden-from-toc {
// needed to make sure direct children only
> .section-status,
> .subsection-status,
> .unit-status {
.status-message .icon {
color: $color-hide-from-toc;
}
}
}
// CASE: has gated content
&.is-gated {
@@ -603,6 +615,11 @@ $outline-indent-width: $baseline;
border-left-color: $color-staff-only;
}
// CASE: is hidden from TOC
&.is-hidden-from-toc {
border-left-color: $color-hide-from-toc;
}
// CASE: has gated content
&.is-gated {
border-left-color: $color-gated;
@@ -698,6 +715,11 @@ $outline-indent-width: $baseline;
border-left-color: $color-staff-only;
}
// CASE: is hidden from TOC
&.is-hidden-from-toc {
border-left-color: $color-hide-from-toc;
}
// CASE: is presented for gated
&.is-gated {
border-left-color: $color-gated;

View File

@@ -218,6 +218,7 @@ $color-ready: $green !default;
$color-warning: $orange-l2 !default;
$color-error: $red-l2 !default;
$color-staff-only: $black !default;
$color-hide-from-toc: $black !default;
$color-gated: $black !default;
$color-heading-base: $gray-d2 !default;

View File

@@ -155,6 +155,15 @@
}
}
// CASE: is hidden from TOC
&.is-hidden-from-toc{
@extend %bar-module-black;
&.is-scheduled .wrapper-release .copy {
text-decoration: line-through;
}
}
// CASE: content is gated
&.is-gated {
@extend %bar-module-black;