From 19bc5c802c4b74a42f2c21f7cb6ff4dfa2987ece Mon Sep 17 00:00:00 2001 From: Bryann Valderrama <64033729+BryanttV@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:12:17 -0500 Subject: [PATCH] feat: add share link button when hide from toc is enabled in sections (#34043) * feat: add share link modal when hide from toc is enabled Adds a new button in the child subsections of sections with Hide From TOC enabled. This button displays a new modal with two tabs. The first tab displays a button that allows you to copy the link of that subsection to the clipboard. The second tab displays a button that allows you to copy the embedded link of the same subsection to the clipboard. Ref: https://openedx.atlassian.net/wiki/spaces/OEPM/pages/3853975595/Feature+Enhancement+Proposal+Hide+Sections+from+course+outline --- cms/djangoapps/contentstore/helpers.py | 30 +++++ .../contentstore/views/tests/test_helpers.py | 42 ++++++- .../xblock_storage_handlers/view_handlers.py | 4 + cms/static/images/subsection-embed.svg | 85 +++++++++++++ cms/static/images/subsection-full-page.svg | 60 ++++++++++ .../spec/views/pages/course_outline_spec.js | 3 +- cms/static/js/utils/copy_to_clipboard.js | 31 +++++ cms/static/js/views/course_outline.js | 17 +++ .../js/views/modals/course_outline_modals.js | 112 +++++++++++++++++- cms/static/js/views/xblock_outline.js | 1 + cms/static/sass/elements/_modal-window.scss | 56 +++++++++ cms/static/sass/elements/_modules.scss | 23 ++++ cms/templates/course_outline.html | 2 +- cms/templates/js/course-outline.underscore | 11 +- .../embed-link-share-link-editor.underscore | 20 ++++ .../js/full-page-share-link-editor.underscore | 20 ++++ ...ubsection-share-link-modal-tabs.underscore | 10 ++ 17 files changed, 516 insertions(+), 11 deletions(-) create mode 100644 cms/static/images/subsection-embed.svg create mode 100644 cms/static/images/subsection-full-page.svg create mode 100644 cms/static/js/utils/copy_to_clipboard.js create mode 100644 cms/templates/js/embed-link-share-link-editor.underscore create mode 100644 cms/templates/js/full-page-share-link-editor.underscore create mode 100644 cms/templates/js/subsection-share-link-modal-tabs.underscore diff --git a/cms/djangoapps/contentstore/helpers.py b/cms/djangoapps/contentstore/helpers.py index 9fd09193b5..9d847c6786 100644 --- a/cms/djangoapps/contentstore/helpers.py +++ b/cms/djangoapps/contentstore/helpers.py @@ -8,6 +8,7 @@ from lxml import etree from mimetypes import guess_type from attrs import frozen, Factory +from django.conf import settings from django.utils.translation import gettext as _ from opaque_keys.edx.keys import AssetKey, CourseKey, UsageKey from opaque_keys.edx.locator import DefinitionLocator, LocalId @@ -22,6 +23,7 @@ from xmodule.modulestore.django import modulestore from xmodule.xml_block import XmlMixin from cms.djangoapps.models.settings.course_grading import CourseGradingModel +from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers import openedx.core.djangoapps.content_staging.api as content_staging_api from .utils import reverse_course_url, reverse_library_url, reverse_usage_url @@ -125,6 +127,34 @@ def xblock_studio_url(xblock, parent_xblock=None, find_parent=False): return reverse_usage_url('container_handler', xblock.location) +def xblock_lms_url(xblock) -> str: + """ + Returns the LMS URL for the specified xblock. + + Args: + xblock: The xblock to get the LMS URL for. + + Returns: + str: The LMS URL for the specified xblock. + """ + lms_root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL) + return f"{lms_root_url}/courses/{xblock.location.course_key}/jump_to/{xblock.location}" + + +def xblock_embed_lms_url(xblock) -> str: + """ + Returns the LMS URL for the specified xblock in embed mode. + + Args: + xblock: The xblock to get the LMS URL for. + + Returns: + str: The LMS URL for the specified xblock in embed mode. + """ + lms_root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL) + return f"{lms_root_url}/xblock/{xblock.location}" + + def xblock_type_display_name(xblock, default_display_name=None): """ Returns the display name for the specified type of xblock. Note that an instance can be passed in diff --git a/cms/djangoapps/contentstore/views/tests/test_helpers.py b/cms/djangoapps/contentstore/views/tests/test_helpers.py index 17ed8ae2e6..e64cbaf463 100644 --- a/cms/djangoapps/contentstore/views/tests/test_helpers.py +++ b/cms/djangoapps/contentstore/views/tests/test_helpers.py @@ -2,13 +2,13 @@ Unit tests for helpers.py. """ - +from unittest.mock import patch, Mock from urllib.parse import quote from cms.djangoapps.contentstore.tests.utils import CourseTestCase from xmodule.modulestore.tests.factories import BlockFactory, LibraryFactory # lint-amnesty, pylint: disable=wrong-import-order -from ...helpers import xblock_studio_url, xblock_type_display_name +from ...helpers import xblock_embed_lms_url, xblock_lms_url, xblock_studio_url, xblock_type_display_name class HelpersTestCase(CourseTestCase): @@ -60,6 +60,44 @@ class HelpersTestCase(CourseTestCase): expected_url = f'/library/{str(library.location.library_key)}' self.assertEqual(xblock_studio_url(library), expected_url) + @patch('cms.djangoapps.contentstore.helpers.configuration_helpers.get_value') + def test_xblock_lms_url(self, mock_get_value: Mock): + mock_get_value.return_value = 'lms.example.com' + + # Verify chapter URL + chapter = BlockFactory.create( + parent_location=self.course.location, category='chapter', display_name="Week 1" + ) + self.assertEqual( + xblock_lms_url(chapter), + f"lms.example.com/courses/{chapter.location.course_key}/jump_to/{chapter.location}" + ) + + # Verify sequential URL + sequential = BlockFactory.create( + parent_location=chapter.location, category='sequential', display_name="Lesson 1" + ) + self.assertEqual( + xblock_lms_url(sequential), + f"lms.example.com/courses/{sequential.location.course_key}/jump_to/{sequential.location}" + ) + + @patch('cms.djangoapps.contentstore.helpers.configuration_helpers.get_value') + def test_xblock_embed_lms_url(self, mock_get_value: Mock): + mock_get_value.return_value = 'lms.example.com' + + # Verify chapter URL + chapter = BlockFactory.create( + parent_location=self.course.location, category='chapter', display_name="Week 1" + ) + self.assertEqual(xblock_embed_lms_url(chapter), f"lms.example.com/xblock/{chapter.location}") + + # Verify sequential URL + sequential = BlockFactory.create( + parent_location=chapter.location, category='sequential', display_name="Lesson 1" + ) + self.assertEqual(xblock_embed_lms_url(sequential), f"lms.example.com/xblock/{sequential.location}") + def test_xblock_type_display_name(self): # Verify chapter type display name diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py index 92f991b470..535e930cc0 100644 --- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py +++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py @@ -78,6 +78,8 @@ from ..helpers import ( get_parent_xblock, import_staged_content_from_user_clipboard, is_unit, + xblock_embed_lms_url, + xblock_lms_url, xblock_primary_child_category, xblock_studio_url, xblock_type_display_name, @@ -1070,6 +1072,8 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements "published": published, "published_on": published_on, "studio_url": xblock_studio_url(xblock, parent_xblock), + "lms_url": xblock_lms_url(xblock), + "embed_lms_url": xblock_embed_lms_url(xblock), "released_to_students": datetime.now(UTC) > xblock.start, "release_date": release_date, "visibility_state": visibility_state, diff --git a/cms/static/images/subsection-embed.svg b/cms/static/images/subsection-embed.svg new file mode 100644 index 0000000000..326f88d6fd --- /dev/null +++ b/cms/static/images/subsection-embed.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cms/static/images/subsection-full-page.svg b/cms/static/images/subsection-full-page.svg new file mode 100644 index 0000000000..37785193ec --- /dev/null +++ b/cms/static/images/subsection-full-page.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cms/static/js/spec/views/pages/course_outline_spec.js b/cms/static/js/spec/views/pages/course_outline_spec.js index 7253938b31..cda8888aee 100644 --- a/cms/static/js/spec/views/pages/course_outline_spec.js +++ b/cms/static/js/spec/views/pages/course_outline_spec.js @@ -323,7 +323,8 @@ describe('CourseOutlinePage', function() { 'staff-lock-editor', 'unit-access-editor', 'discussion-editor', 'content-visibility-editor', 'settings-modal-tabs', 'timed-examination-preference-editor', 'access-editor', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', - 'course-highlights-enable', 'course-video-sharing-enable', 'summary-configuration-editor' + 'course-highlights-enable', 'course-video-sharing-enable', 'summary-configuration-editor', + 'subsection-share-link-modal-tabs', 'full-page-share-link-editor', 'embed-link-share-link-editor', ]); appendSetFixtures(mockOutlinePage); mockCourseJSON = createMockCourseJSON({}, [ diff --git a/cms/static/js/utils/copy_to_clipboard.js b/cms/static/js/utils/copy_to_clipboard.js new file mode 100644 index 0000000000..8c4c22c471 --- /dev/null +++ b/cms/static/js/utils/copy_to_clipboard.js @@ -0,0 +1,31 @@ +define(["jquery"], function ($) { + "use strict"; + function copyToClipboard(id, textToCopy) { + if (navigator.clipboard) { + navigator.clipboard.writeText(textToCopy); + changeButtonText(id); + return; + } + const textArea = document.createElement("textarea"); + textArea.value = textToCopy; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand("copy"); + document.body.removeChild(textArea); + changeButtonText(id); + } + + function changeButtonText(id, delay = 2000) { + const buttonId = `#${id}`; + const textClass = ".copy-link-button-text"; + + const previewShareLinkText = $(buttonId).find(textClass).html(); + const shareLinkCopiedText = gettext("Copied"); + $(buttonId).find(textClass).text(shareLinkCopiedText); + + setTimeout(() => { + $(buttonId).find(textClass).text(previewShareLinkText); + }, delay); + } + return { copyToClipboard }; +}); diff --git a/cms/static/js/views/course_outline.js b/cms/static/js/views/course_outline.js index 04dc985130..8319e42eb3 100644 --- a/cms/static/js/views/course_outline.js +++ b/cms/static/js/views/course_outline.js @@ -453,6 +453,19 @@ function( } }, + subsectionShareLinkXBlock: function() { + var modal = CourseOutlineModalsFactory.getModal('subsection_share_link', this.model, { + onSave: this.refresh.bind(this), + xblockType: XBlockViewUtils.getXBlockType( + this.model.get('category'), this.parentView.model, true + ) + }); + + if (modal) { + modal.show(); + } + }, + /** * If the new "Actions" menu is enabled, most actions like Configure, * Duplicate, Move, Delete, etc. are moved into this menu. For this @@ -501,6 +514,10 @@ function( this.highlightsXBlock(); } }.bind(this)); + element.find('.subsection-share-link-button').click(function(event) { + event.preventDefault(); + this.subsectionShareLinkXBlock(); + }.bind(this)); element.find('.copy-button').click((event) => { event.preventDefault(); this.copyXBlock(); diff --git a/cms/static/js/views/modals/course_outline_modals.js b/cms/static/js/views/modals/course_outline_modals.js index f52340dea8..532e2ad10c 100644 --- a/cms/static/js/views/modals/course_outline_modals.js +++ b/cms/static/js/views/modals/course_outline_modals.js @@ -7,10 +7,10 @@ */ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', 'js/views/modals/base_modal', 'date', 'js/views/utils/xblock_utils', - 'js/utils/date_utils', 'edx-ui-toolkit/js/utils/html-utils', + 'js/utils/date_utils', 'js/utils/copy_to_clipboard', 'edx-ui-toolkit/js/utils/html-utils', 'edx-ui-toolkit/js/utils/string-utils' ], function( - $, Backbone, _, gettext, BaseView, BaseModal, date, XBlockViewUtils, DateUtils, HtmlUtils, StringUtils + $, Backbone, _, gettext, BaseView, BaseModal, date, XBlockViewUtils, DateUtils, ClipboardUtils, HtmlUtils, StringUtils ) { 'use strict'; @@ -19,7 +19,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', ReleaseDateEditor, DueDateEditor, SelfPacedDueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor, StaffLockEditor, UnitAccessEditor, ContentVisibilityEditor, TimedExaminationPreferenceEditor, AccessEditor, ShowCorrectnessEditor, HighlightsEditor, HighlightsEnableXBlockModal, HighlightsEnableEditor, - DiscussionEditor, SummaryConfigurationEditor; + DiscussionEditor, SummaryConfigurationEditor, SubsectionShareLinkXBlockModal, FullPageShareLinkEditor, + EmbedLinkShareLinkEditor; CourseOutlineXBlockModal = BaseModal.extend({ events: _.extend({}, BaseModal.prototype.events, { @@ -298,6 +299,71 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', } }); + SubsectionShareLinkXBlockModal = CourseOutlineXBlockModal.extend({ + + initializeEditors: function() { + var tabsTemplate; + var tabs = this.options.tabs; + if (tabs && tabs.length > 0) { + tabsTemplate = this.loadTemplate('subsection-share-link-modal-tabs'); + HtmlUtils.setHtml(this.$('.modal-section'), HtmlUtils.HTML(tabsTemplate({tabs: tabs}))); + _.each(this.options.tabs, function(tab) { + // eslint-disable-next-line prefer-spread + this.options.editors.push.apply( + this.options.editors, + _.map(tab.editors, function(Editor) { + return new Editor({ + parent: this, + parentElement: this.$(`.modal-section .${tab.name}`), + model: this.model, + xblockType: this.options.xblockType, + }); + }, this) + ); + }, this); + this.showTab(tabs[0].name); + } else { + CourseOutlineXBlockModal.prototype.initializeEditors.call(this); + } + }, + + events: _.extend({}, CourseOutlineXBlockModal.prototype.events, { + 'click .subsection-share-link-tab-button': 'handleShowTab', + 'click #full-page-link-button': 'copyToClipboard', + 'click #embed-link-button': 'copyToClipboard', + }), + + getTitle: function() { + return StringUtils.interpolate( + gettext('Share {display_name} subsection'), + {display_name: this.model.get('display_name')} + ); + }, + + copyToClipboard: function(event) { + return ClipboardUtils.copyToClipboard( + $(event.currentTarget).attr('id'), + $(event.currentTarget).data('clipboard-text') + ); + }, + + addActionButtons: function() { + this.addActionButton('cancel', gettext('Close')); + }, + + handleShowTab: function(event) { + event.preventDefault(); + this.showTab($(event.target).data('tab')); + }, + + showTab: function(tab) { + this.$('.modal-section .subsection-share-link-tab-button').removeClass('active'); + this.$(`.modal-section .subsection-share-link-tab-button[data-tab="${tab}"]`).addClass('active'); + this.$('.modal-section .subsection-share-link-tab').hide(); + this.$(`.modal-section .${tab}`).show(); + } + }); + AbstractEditor = BaseView.extend({ tagName: 'section', templateName: null, @@ -307,7 +373,6 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', this.parentElement = this.options.parentElement; this.render(); }, - render: function() { var xblockInfo = this.model; var isTimeLimited = xblockInfo.get('is_time_limited'); @@ -316,6 +381,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', var isOnboardingExam = xblockInfo.get('is_onboarding_exam'); var enableHideFromTOCUI = xblockInfo.get('enable_hide_from_toc_ui'); var hideFromTOC = xblockInfo.get('hide_from_toc'); + var lmsUrl = xblockInfo.get('lms_url'); + var embedLmsUrl = xblockInfo.get('embed_lms_url'); var html = this.template($.extend({}, { xblockInfo: xblockInfo, xblockType: this.options.xblockType, @@ -327,6 +394,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', isOnboardingExam: isOnboardingExam, enableHideFromTOCUI: enableHideFromTOCUI, hideFromTOC: hideFromTOC, + lmsUrl: lmsUrl, + embedLmsUrl: embedLmsUrl, isTimedExam: isTimeLimited && !( isProctoredExam || isPracticeExam || isOnboardingExam ), @@ -509,6 +578,16 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', } }); + FullPageShareLinkEditor = AbstractEditor.extend({ + templateName: 'full-page-share-link-editor', + className: 'edit-settings-full-page-share-link', + }); + + EmbedLinkShareLinkEditor = AbstractEditor.extend({ + templateName: 'embed-link-share-link-editor', + className: 'edit-settings-embed-link-share-link', + }); + TimedExaminationPreferenceEditor = AbstractEditor.extend({ templateName: 'timed-examination-preference-editor', className: 'edit-settings-timed-examination', @@ -1289,7 +1368,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', return this.getHighlightsModal(xblockInfo, options); } else if (type === 'highlights_enable') { return this.getHighlightsEnableModal(xblockInfo, options); - } else { + } else if (type === 'subsection_share_link') { + return this.getSubsectionShareLinkModal(xblockInfo, options); + } + else { return null; } }, @@ -1395,6 +1477,26 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview', editors: [HighlightsEnableEditor], model: xblockInfo }, options)); + }, + + getSubsectionShareLinkModal: function(xblockInfo, options) { + let tabs = [ + { + name: 'full_page', + displayName: gettext('Full Page'), + editors: [FullPageShareLinkEditor], + }, + { + name: 'embed_link', + displayName: gettext('Embed Link'), + editors: [EmbedLinkShareLinkEditor], + } + ] + return new SubsectionShareLinkXBlockModal($.extend({ + tabs: tabs, + editors: [FullPageShareLinkEditor, EmbedLinkShareLinkEditor], + model: xblockInfo + }, options)); } }; }); diff --git a/cms/static/js/views/xblock_outline.js b/cms/static/js/views/xblock_outline.js index d5a12e6f0c..79dc80334b 100644 --- a/cms/static/js/views/xblock_outline.js +++ b/cms/static/js/views/xblock_outline.js @@ -113,6 +113,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, XBlockStringFieldE hasExplicitStaffLock: this.model.get('has_explicit_staff_lock'), staffOnlyMessage: this.model.get('staff_only_message'), hideFromTOCMessage: this.model.get('hide_from_toc_message'), + enableHideFromTOC: this.model.get('hide_from_toc'), 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 diff --git a/cms/static/sass/elements/_modal-window.scss b/cms/static/sass/elements/_modal-window.scss index defcf49efe..c43daf825c 100644 --- a/cms/static/sass/elements/_modal-window.scss +++ b/cms/static/sass/elements/_modal-window.scss @@ -143,6 +143,40 @@ } } } + + .subsection-share-link-tabs-header { + margin-bottom: $baseline; + border-bottom: 1px solid $gray-l3; + + li.subsection-share-link-tab-buttons { + display: inline-block; + margin-right: $baseline; + + .subsection-share-link-tab-button { + @extend %t-copy-sub1; + @extend %t-regular; + + background-image: none; + background-color: $white; + color: $gray-d1; + border-radius: 0; + box-shadow: none; + border: 0; + padding: ($baseline/4) ($baseline/2); + text-transform: uppercase; + + &:hover { + background-color: theme-color("inverse"); + color: theme-color("primary"); + } + + &.active { + border-bottom: 4px solid theme-color("primary"); + color: theme-color("primary"); + } + } + } + } } .modal-section-title { @@ -183,6 +217,28 @@ @extend %t-icon4; } } + + .subsection-share-link-container { + display: flex; + align-items: center; + + .copy-link-button { + border-radius: ($baseline/4); + font-size: 1.5rem; + font-weight: 600; + padding: 0.7rem 1rem; + + .icon-copy-clipboard { + color: $white; + } + } + + .share-link-animated-svg { + border-radius: 0.5rem; + margin-left: 1rem; + box-shadow: 0 0 7px $shadow-d1; + } + } } // TODO: need to sync up (alongside general editing mode) with xblocks.scss UI diff --git a/cms/static/sass/elements/_modules.scss b/cms/static/sass/elements/_modules.scss index 29a2989c1a..7812a221b4 100644 --- a/cms/static/sass/elements/_modules.scss +++ b/cms/static/sass/elements/_modules.scss @@ -666,6 +666,29 @@ $outline-indent-width: $baseline; .icon { @include margin-right($baseline/4); } + + .status-message { + display: flex; + flex-direction: row; + justify-content: space-between; + + @media (max-width: 768px) { + flex-direction: column; + } + } + + .subsection-share-link-button { + border-radius: ($baseline/4); + font-size: 1.2rem; + font-weight: 600; + padding: 0.5rem 1rem; + margin: 0 1rem; + + .icon-share-link { + color: $white !important; + transform: rotate(90deg); + } + } } .status-message-copy { diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index b3eca807e0..f1e426212b 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -29,7 +29,7 @@ from django.urls import reverse <%block name="header_extras"> -% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'self-paced-due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'discussion-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable', 'course-video-sharing-enable', 'summary-configuration-editor', 'tag-count']: +% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'self-paced-due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'discussion-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable', 'course-video-sharing-enable', 'summary-configuration-editor', 'tag-count', 'subsection-share-link-modal-tabs', 'full-page-share-link-editor', 'embed-link-share-link-editor']: diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore index 69b33f05ec..6525c4dc21 100644 --- a/cms/templates/js/course-outline.underscore +++ b/cms/templates/js/course-outline.underscore @@ -419,8 +419,15 @@ if (is_proctored_exam) {
<% for (var i=0; i
- -

<%- statusMessages[i].text %>

+
+ +

<%- statusMessages[i].text %>

+
+ <% if (enableHideFromTOC && xblockInfo.isSequential()) { %> + + <% } %>
<% } %>
diff --git a/cms/templates/js/embed-link-share-link-editor.underscore b/cms/templates/js/embed-link-share-link-editor.underscore new file mode 100644 index 0000000000..e5789c1507 --- /dev/null +++ b/cms/templates/js/embed-link-share-link-editor.underscore @@ -0,0 +1,20 @@ + diff --git a/cms/templates/js/full-page-share-link-editor.underscore b/cms/templates/js/full-page-share-link-editor.underscore new file mode 100644 index 0000000000..99e69c4641 --- /dev/null +++ b/cms/templates/js/full-page-share-link-editor.underscore @@ -0,0 +1,20 @@ + diff --git a/cms/templates/js/subsection-share-link-modal-tabs.underscore b/cms/templates/js/subsection-share-link-modal-tabs.underscore new file mode 100644 index 0000000000..c170f278e7 --- /dev/null +++ b/cms/templates/js/subsection-share-link-modal-tabs.underscore @@ -0,0 +1,10 @@ + +<% _.each(tabs, function(tab) { %> + +<% }); %>