Files
edx-platform/cms/templates/js/course-outline.underscore
Rômulo Penido b42da7429f feat: Enable taxonomy/tagging feature in MFE by default (#34633)
* feat: make tagging feature enabled by default

* fix: use the correct flag for tagging enabled

* fix: make compatible with other changes from master

* fix: more compatibility fixes

* fix: show tag counts at all levels of the outline, not just units

* chore: typo

* test: fix counts in test suite now that tagging is on by default

---------

Co-authored-by: Braden MacDonald <braden@opencraft.com>
Co-authored-by: Yusuf Musleh <yusuf@opencraft.com>
2024-05-09 18:57:05 +05:30

527 lines
28 KiB
Plaintext

<%
var releasedToStudents = xblockInfo.get('released_to_students');
var visibilityState = xblockInfo.get('visibility_state');
var published = xblockInfo.get('published');
var prereq = xblockInfo.get('prereq');
var hasPartitionGroups = xblockInfo.get('has_partition_group_components');
var userPartitionInfo = xblockInfo.get('user_partition_info');
var selectedGroupsLabel = userPartitionInfo['selected_groups_label'];
var selectedPartitionIndex = userPartitionInfo['selected_partition_index'];
var xblockId = xblockInfo.get('id')
var tagsCount = (xblockInfo.get('tag_counts_by_block') || {})[xblockId] || 0;
var statusMessages = [];
var messageType;
var messageText;
var statusType = null;
var addStatusMessage = function (statusType, message) {
var statusIconClass = '';
if (statusType === 'warning') {
statusIconClass = 'fa-file-o';
} else if (statusType === 'error') {
statusIconClass = 'fa-warning';
} else if (statusType === 'staff-only' || statusType === 'gated') {
statusIconClass = 'fa-lock';
} else if(statusType === 'hide-from-toc-chapter') {
statusIconClass = 'fa-eye-slash';
}
else if(statusType === 'hide-from-toc-sequence') {
statusIconClass = 'fa-ban';
}
else if (statusType === 'partition-groups') {
statusIconClass = 'fa-eye';
}
statusMessages.push({iconClass: statusIconClass, text: message});
};
if (prereq) {
var prereqDisplayName = '';
_.each(xblockInfo.get('prereqs'), function (p) {
if (p.block_usage_key == prereq) {
prereqDisplayName = p.block_display_name;
return false;
}
});
messageType = 'gated';
messageText = interpolate(
gettext('Prerequisite: %(prereq_display_name)s'),
{prereq_display_name: prereqDisplayName},
true
);
addStatusMessage(messageType, messageText);
}
if (staffOnlyMessage) {
messageType = 'staff-only';
messageText = gettext('Contains staff only content');
addStatusMessage(messageType, messageText);
} else if (hideFromTOCMessage && !xblockInfo.isVertical()){
if (xblockInfo.isChapter()) {
messageText = gettext('Hidden in the course outline, but sections are accessible for your learners through their separate link.');
messageType = 'hide-from-toc-chapter';
} else {
messageText = gettext('Subsections are not navigable between each other, they can only be accessed through their link.');
messageType = 'hide-from-toc-sequence';
}
addStatusMessage(messageType, messageText);
} else {
if (visibilityState === 'needs_attention' && xblockInfo.isVertical()) {
messageType = 'warning';
if (published && releasedToStudents) {
messageText = gettext('Unpublished changes to live content');
} else if (!published) {
messageText = gettext('Unpublished units will not be released');
} else {
messageText = gettext('Unpublished changes to content that will release in the future');
}
addStatusMessage(messageType, messageText);
}
if (selectedPartitionIndex !== -1 && !isNaN(selectedPartitionIndex) && xblockInfo.isVertical()) {
messageType = 'partition-groups';
messageText = edx.StringUtils.interpolate(
gettext('Access to this unit is restricted to: {selectedGroupsLabel}'),
{
selectedGroupsLabel: selectedGroupsLabel
}
)
addStatusMessage(messageType, messageText);
} else if (hasPartitionGroups && xblockInfo.isVertical()) {
addStatusMessage(
'partition-groups',
gettext('Access to some content in this unit is restricted to specific groups of learners')
);
}
}
var gradingType = gettext('Ungraded');
var gradingPolicyMismatch = false;
if (xblockInfo.get('graded')) {
gradingType = xblockInfo.get('format')
if (gradingType) {
gradingPolicyMismatch = (
xblockInfo.get('course_graders').filter((cg) => cg.toLowerCase() === gradingType.toLowerCase())
).length === 0;
}
}
var is_proctored_exam = xblockInfo.get('is_proctored_exam');
var is_practice_exam = xblockInfo.get('is_practice_exam');
var is_onboarding_exam = xblockInfo.get('is_onboarding_exam');
var exam_value;
if (is_proctored_exam) {
if (is_onboarding_exam) {
exam_value = gettext('Onboarding Exam');
} else if (is_practice_exam) {
exam_value = gettext('Practice proctored Exam');
} else {
exam_value = gettext('Proctored Exam');
}
} else {
exam_value = gettext('Timed Exam');
}
%>
<% if (parentInfo) { %>
<li class="outline-item outline-<%- xblockType %> <%- visibilityClass %> is-draggable <%- includesChildren ? 'is-collapsible' : '' %> <%- isCollapsed ? 'is-collapsed' : '' %>"
data-parent="<%- parentInfo.get('id') %>" data-locator="<%- xblockInfo.get('id') %>" id="<%- xblockInfo.get('id') %>">
<span class="draggable-drop-indicator draggable-drop-indicator-before"><span class="icon fa fa-caret-right" aria-hidden="true"></span></span>
<% if (xblockInfo.isHeaderVisible()) { %>
<div class="<%- xblockType %>-header">
<% if (includesChildren) { %>
<h3 class="<%- xblockType %>-header-details expand-collapse <%- isCollapsed ? 'expand' : 'collapse' %> ui-toggle-expansion"
title="<%- interpolate(
gettext('Collapse/Expand this %(xblock_type)s'), { xblock_type: xblockTypeDisplayName }, true
) %>"
>
<span class="icon fa fa-caret-down" aria-hidden="true"></span>
<% } else { %>
<% if (xblockType == "unit") { %>
<h3 class="unit-header-details" style="width: 50%">
<% } else { %>
<h3 class="<%- xblockType %>-header-details">
<% } %>
<% } %>
<% if (xblockInfo.isVertical()) { %>
<span class="unit-title item-title">
<span class="wrapper-<%- xblockType %>-title wrapper-xblock-field incontext-editor is-editable" data-field="display_name" data-field-display-name="<%- gettext("Display Name") %>">
<a class="<%- xblockType %>-title item-title xblock-field-value incontext-editor-value" href="<%- xblockInfo.get('studio_url') %>"><%- xblockInfo.get('display_name') %></a>
</span>
</span>
<% } else { %>
<span class="wrapper-<%- xblockType %>-title wrapper-xblock-field incontext-editor is-editable" data-field="display_name" data-field-display-name="<%- gettext("Display Name") %>">
<span class="<%- xblockType %>-title item-title xblock-field-value incontext-editor-value"><%- xblockInfo.get('display_name') %></span>
</span>
<% } %>
</h3>
<% if (xblockType == "unit") {%>
<div class="unit-header-actions" style="width: 50%">
<% } else { %>
<div class="<%- xblockType %>-header-actions">
<% } %>
<ul class="actions-list nav-dd ui-right">
<% var discussion_settings = course.get('discussions_settings') %>
<% if ((xblockInfo.isVertical()) && discussion_settings && (!parentInfo.get('is_time_limited'))) { %>
<% if (xblockInfo.get('discussion_enabled') && (discussion_settings.provider_type == "openedx")) { %>
<% if (discussion_settings.enable_graded_units || (!discussion_settings.enable_graded_units && !parentInfo.get('graded'))) {%>
<li class="action-item" style="font-size: 75%; color:grey">
<%- gettext('Discussions enabled') %>
</li>
<% } %>
<% } %>
<% } %>
<% if (xblockInfo.isSequential() && xblockInfo.get('proctoring_exam_configuration_link')) { %>
<li class="action-item">
<a href="<%- xblockInfo.get('proctoring_exam_configuration_link') %>" data-tooltip="<%- gettext('Proctoring Settings') %>" class="proctoring-configuration-button" rel="noopener" target="_blank">
<span class="action-button-text"><%- gettext('Proctoring Settings') %></span>
</a>
</li>
<% } %>
<% if (xblockInfo.isPublishable()) { %>
<li class="action-item action-publish">
<a href="#" data-tooltip="<%- gettext('Publish') %>" class="publish-button action-button">
<span class="icon fa fa-upload" aria-hidden="true"></span>
<span class="sr action-button-text"><%- gettext('Publish') %></span>
</a>
</li>
<% } %>
<% if (!isTaggingFeatureDisabled) { %>
<li class="action-item tag-count" data-locator="<%- xblockId %>"></li>
<% } %>
<% if (typeof enableCopyPasteUnits !== "undefined" && enableCopyPasteUnits) { %>
<!--
If the ENABLE_COPY_PASTE_UNITS feature flag is enabled, all these actions (besides "Publish")
appear in a menu. We use .nav-dd on the parent element and .nav-item on this button to get the
same dropdown menu appearance and behavior as in Studio's various other nav bars.
-->
<li class="action-item action-actions-menu nav-item">
<button data-tooltip="<%- gettext('Actions') %>" class="btn-default show-actions-menu-button action-button">
<span class="icon fa fa-ellipsis-v" aria-hidden="true"></span>
<span class="sr"><%- gettext('Actions') %></span>
</button>
<div class="wrapper wrapper-nav-sub" style="right: -10px; top: 45px;">
<div class="nav-sub">
<ul>
<% if (xblockInfo.isEditableOnCourseOutline()) { %>
<li class="nav-item">
<a class="configure-button" href="#" role="button"><%- gettext('Configure') %></a>
</li>
<% } %>
<% if (!isTaggingFeatureDisabled) { %>
<li class="nav-item">
<a class="manage-tags-button" href="#" role="button"><%- gettext('Manage Tags') %></a>
</li>
<% } %>
<% if (xblockInfo.isVertical()) { %>
<li class="nav-item">
<a class="copy-button" href="#" role="button"><%- gettext('Copy to Clipboard') %></a>
</li>
<% } %>
<% if (xblockInfo.isDuplicable()) { %>
<li class="nav-item">
<a class="duplicate-button" href="#" role="button"><%- gettext('Duplicate') %></a>
</li>
<% } %>
<% if (xblockInfo.isDeletable()) { %>
<li class="nav-item">
<a class="delete-button" href="#" role="button"><%- gettext('Delete') %></a>
</li>
<% } %>
</ul>
</div>
</div>
</li>
<% } else { %>
<% if (xblockInfo.isEditableOnCourseOutline()) { %>
<li class="action-item action-configure">
<a href="#" data-tooltip="<%- gettext('Configure') %>" class="configure-button action-button">
<span class="icon fa fa-gear" aria-hidden="true"></span>
<span class="sr action-button-text"><%- gettext('Configure') %></span>
</a>
</li>
<% } %>
<% if (xblockInfo.isDuplicable()) { %>
<li class="action-item action-duplicate">
<a href="#" data-tooltip="<%- gettext('Duplicate') %>" class="duplicate-button action-button">
<span class="icon fa fa-copy" aria-hidden="true"></span>
<span class="sr action-button-text"><%- gettext('Duplicate') %></span>
</a>
</li>
<% } %>
<% if (xblockInfo.isDeletable()) { %>
<li class="action-item action-delete">
<a href="#" data-tooltip="<%- gettext('Delete') %>" class="delete-button action-button">
<span class="icon fa fa-trash-o" aria-hidden="true"></span>
<span class="sr action-button-text"><%- gettext('Delete') %></span>
</a>
</li>
<% } %>
<% } %>
<% if (xblockInfo.isDraggable()) { %>
<li class="action-item action-drag">
<span data-tooltip="<%- gettext('Drag to reorder') %>"
class="drag-handle <%- xblockType %>-drag-handle action">
<span class="sr"><%- gettext('Drag to reorder') %></span>
</span>
</li>
<% } %>
</ul>
</div>
</div>
<div class="<%- xblockType %>-status">
<% if (!xblockInfo.isVertical()) { %>
<% if (xblockInfo.get('explanatory_message') !=null) { %>
<div class="explanatory-message">
<span>
<%- xblockInfo.get('explanatory_message') %>
</span>
</div>
<% } else { %>
<div class="status-release">
<p>
<span class="sr status-release-label"><%- gettext('Release Status:') %></span>
<span class="status-release-value">
<% if (!course.get('self_paced')) { %>
<% if (xblockInfo.get('released_to_students')) { %>
<span class="icon fa fa-check" aria-hidden="true"></span>
<%- gettext('Released:') %>
<% } else if (xblockInfo.get('release_date')) { %>
<span class="icon fa fa-clock-o" aria-hidden="true"></span>
<%- gettext('Scheduled:') %>
<% } else { %>
<span class="icon fa fa-clock-o" aria-hidden="true"></span>
<%- gettext('Unscheduled') %>
<% } %>
<% if (xblockInfo.get('release_date')) { %>
<%- xblockInfo.get('release_date') %>
<% } %>
<% } %>
</span>
</p>
</div>
<% } %>
<% if (xblockInfo.isChapter()) { %>
<div class="block-highlights">
<% var number_of_highlights = (xblockInfo.get('highlights') || []).length; %>
<button class="block-highlights-value highlights-button action-button">
<span class="number-highlights"><%- number_of_highlights %></span>
<%- gettext('Section Highlights') %>
</button>
</div>
<% } %>
<% if (xblockInfo.get('is_time_limited')) { %>
<div class="status-timed-proctored-exam">
<p>
<span class="sr status-grading-label"> <%- gettext('Graded as:') %> </span>
<span class="icon fa fa-check" aria-hidden="true"></span>
<span class="status-grading-value"> <%- gradingType %> </span>
-
<span class="sr status-proctored-exam-label"> <%- exam_value %> </span>
<span class="status-proctored-exam-value"> <%- exam_value %> </span>
<% if (xblockInfo.get('due_date') && !course.get('self_paced')) { %>
<span class="status-grading-date"> <%- gettext('Due:') %> <%- xblockInfo.get('due_date') %> </span>
<% } %>
</p>
</div>
<% if (course.get('self_paced') && course.get('is_custom_relative_dates_active') && xblockInfo.get('relative_weeks_due')) { %>
<div class="status-grading">
<p>
<span class="icon fa fa-calendar" aria-hidden="true"></span>
<span class="status-custom-grading-date">
<%- edx.StringUtils.interpolate(
ngettext(
'Custom due date: {relativeWeeks} week from enrollment',
'Custom due date: {relativeWeeks} weeks from enrollment',
xblockInfo.get('relative_weeks_due')),
{
relativeWeeks: xblockInfo.get('relative_weeks_due')
}
)
%>
</span>
</p>
</div>
<% } %>
<% } else if ((xblockInfo.get('due_date') && !course.get('self_paced')) || xblockInfo.get('graded')) { %>
<div class="status-grading">
<p>
<span class="sr status-grading-label"> <%- gettext('Graded as:') %> </span>
<span class="icon fa fa-check" aria-hidden="true"></span>
<span class="status-grading-value"> <%- gradingType %> </span>
<% if (xblockInfo.get('due_date') && !course.get('self_paced')) { %>
<span class="status-grading-date"> <%- gettext('Due:') %> <%- xblockInfo.get('due_date') %> </span>
<% } %>
</p>
</div>
<% if (course.get('self_paced') && course.get('is_custom_relative_dates_active') && xblockInfo.get('relative_weeks_due')) { %>
<div class="status-grading">
<p>
<span class="icon fa fa-calendar" aria-hidden="true"></span>
<span class="status-custom-grading-date">
<%- edx.StringUtils.interpolate(
ngettext(
'Custom due date: {relativeWeeks} week from enrollment',
'Custom due date: {relativeWeeks} weeks from enrollment',
xblockInfo.get('relative_weeks_due')),
{
relativeWeeks: xblockInfo.get('relative_weeks_due')
}
)
%>
</span>
</p>
</div>
<% } %>
<% } else if (course.get('self_paced') && course.get('is_custom_relative_dates_active') && xblockInfo.get('relative_weeks_due')) { %>
<div class="status-grading">
<p>
<span class="sr status-grading-label"> <%- gettext('Graded as:') %> </span>
<span class="icon fa fa-check" aria-hidden="true"></span>
<span class="status-grading-value"> <%- gradingType %> </span>
</p>
</div>
<div class="status-grading">
<p>
<span class="icon fa fa-calendar" aria-hidden="true"></span>
<span class="status-custom-grading-date">
<%- edx.StringUtils.interpolate(
ngettext(
'Custom due date: {relativeWeeks} week from enrollment',
'Custom due date: {relativeWeeks} weeks from enrollment',
xblockInfo.get('relative_weeks_due')),
{
relativeWeeks: xblockInfo.get('relative_weeks_due')
}
)
%>
</span>
</p>
</div>
<% } %>
<div class="status-hide-after-due">
<p>
<% if (xblockInfo.get('hide_after_due')) { %>
<span class="icon fa fa-eye-slash" aria-hidden="true"></span>
<span class="status-hide-after-due-value">
<% if (course.get('self_paced')) { %>
<%- gettext("Subsection is hidden after course end date") %> </span>
<% } else { %>
<%- gettext("Subsection is hidden after due date") %> </span>
<% } %>
<% } %>
</p>
</div>
<% } %>
<% if (statusMessages.length > 0) { %>
<div class="status-messages">
<% for (var i=0; i<statusMessages.length; i++) { %>
<div class="status-message">
<div>
<span class="icon fa <%- statusMessages[i].iconClass %>" aria-hidden="true"></span>
<p class="status-message-copy"><%- statusMessages[i].text %></p>
</div>
<% if (enableHideFromTOC && xblockInfo.isSequential()) { %>
<button type="button" class="button btn-primary subsection-share-link-button">
<span class="icon fa fa-link icon-share-link"></span><%- gettext('Share') %>
</button>
<% } %>
</div>
<% } %>
</div>
<% } %>
<% if (gradingPolicyMismatch) { %>
<div class="status-messages">
<div class="container-message wrapper-message">
<div class="message has-warnings">
<p class="warning">
<span class="icon fa fa-warning" aria-hidden="true"></span>
<%- interpolate(
gettext("This subsection is configured as \"%(gradingType)s\", which doesn't exist in the current grading policy."),
{ gradingType: gradingType },
true
) %>
</p>
</div>
</div>
</div>
<% } %>
</div>
<% } %>
<% } %>
<% if (!parentInfo && xblockInfo.get('child_info') && xblockInfo.get('child_info').children.length === 0) { %>
<div class="no-content add-section">
<p><%- gettext("You haven't added any content to this course yet.") %>
<a href="#" class="button button-new" data-category="<%- childCategory %>"
data-parent="<%- xblockInfo.get('id') %>" data-default-name="<%- defaultNewChildName %>"
title="<%- interpolate(
gettext('Click to add a new %(xblock_type)s'), { xblock_type: defaultNewChildName }, true
) %>"
>
<span class="icon fa fa-plus" aria-hidden="true"></span><%- addChildLabel %>
</a>
</p>
</div>
<% } else if (!xblockInfo.isVertical()) { %>
<div class="outline-content <%- xblockType %>-content">
<ol class="<%- typeListClass %> is-sortable">
<li class="ui-splint ui-splint-indicator">
<span class="draggable-drop-indicator draggable-drop-indicator-initial"><span class="icon fa fa-caret-right" aria-hidden="true"></span></span>
</li>
</ol>
<% if (childType) { %>
<% if (xblockInfo.isChildAddable()) { %>
<div class="add-<%- childType %> add-item">
<a href="#" class="button button-new" data-category="<%- childCategory %>"
data-parent="<%- xblockInfo.get('id') %>" data-default-name="<%- defaultNewChildName %>"
title="<%- interpolate(
gettext('Click to add a new %(xblock_type)s'), { xblock_type: defaultNewChildName }, true
) %>"
>
<span class="icon fa fa-plus" aria-hidden="true"></span><%- addChildLabel %>
</a>
<!--
Technically this isn't pasting a "component" (leaf XBlock), but we re-use most of the same UI
elements and CSS from the Unit page's "paste component" button, so the class names are the same.
-->
<div
class="paste-component"
style="display: none;"
data-category="<%- childCategory %>"
data-parent="<%- xblockInfo.get('id') %>"
>
<button type="button" class="button paste-component-button">
<span class="icon fa fa-paste" aria-hidden="true"></span>
<%- interpolate(
gettext('Paste %(xblock_type)s'), { xblock_type: defaultNewChildName }, true
) %>
</button>
<div class="paste-component-whats-in-clipboard" tabindex="0">
<!-- These details get filled in by JavaScript code when it makes the paste button visible: -->
<a href="#" class="clipboard-details-popup" onClick="if (this.getAttribute('href') === '#') return false;" target="_blank">
<span class="fa fa-external-link" aria-hidden="true"></span>
<strong class="detail-block-name">Block Name</strong>
<span class="detail-block-type">Type</span>
<%- gettext("From:") %> <span class="detail-course-name">Course Name Goes Here</span>
</a>
<span class="icon fa fa-question-circle" aria-hidden="true"></span>
<%- gettext("What's in my clipboard?") %>
</div>
</div>
</div>
<% } %>
<% } %>
</div>
<% } %>
<% if (parentInfo) { %>
<span class="draggable-drop-indicator draggable-drop-indicator-after"><span class="icon fa fa-caret-right" aria-hidden="true"></span></span>
</li>
<% } %>